记录一次无聊的(经历了Nodejs -> Shell -> C)的探索问题过程

简介: 提出问题在运行项目的服务器的git是1.8.3.1版本的时候,pm2 deploy 项目,服务器fetch不到最新的一次commit。对于这个问题,在pm2的github也有issues讨论。然后开issues的人表示 pm2-deploy is garbage  并且觉得  I find it...

提出问题

在运行项目的服务器的git是1.8.3.1版本的时候,pm2 deploy 项目,服务器fetch不到最新的一次commit。

对于这个问题,在pm2的github也有issues讨论。然后开issues的人表示 pm2-deploy is garbage  并且觉得  I find it funny that it is easier for the authors to blame the problem on git or anything else rather than change one single line of code to make it work... excelent pm2 deploy couldn't be any more wonderfull.  em…   呵呵,表示不服,其实这个问题真的有一半原因得归咎于git …

解决方案(针对服务器上git版本 < 1.9.0)

1. 升级服务器git版本 >= 1.9.0

2. 回退pm2版本至1.x,如果该项目用不到pm2 后期版本的一些功能的话...

由于文章中的测试验证过程略显无聊(一步一步的挖掘问题所在),且以上已给出解决方案,有遇到类似问题的小伙伴可参考方案解决问题,有兴趣交流下debugger过程的同学可继续往下看。

git都出了2.14.1了,为什么还在用1.8.3.1的老版本呢?

我们用的是阿里云的服务器,使用yum安装的git包,然而yum源里的git包是1.8.3.1的,对于像我这种的新手来说,要不是遇到问题,我的态度是 "还有这操作? (。◕ˇ∀ˇ◕)"

进入探索流程

问题由pm2出发,首先我们找到pm2是怎么处理deploy命令的:

打开 pm2 地址:https://github.com/Unitech/pm2  ,进入 lib/API.js ,我们在文件中找到:

require('./API/Deploy.js')(API);

于是打开该Deploy.js:

var Deploy = require('pm2-deploy');

    /*.其他代码.*/

module.exports = function (CLI) {
    CLI.prototype.deploy = function (file, commands, cb) {
        Deploy.deployForEnv(json_conf.deploy, env, args, function (err, data) {

            /*.其他代码.*/

            if (err) {
                Common.printError('Deploy failed');
                return cb ? cb(err) : that.exitCli(cst.ERROR_EXIT);
            }
            Common.printOut('--> Success');
            return cb ? cb(null, data) : that.exitCli(cst.SUCCESS_EXIT);
        });
    }
}

由此,我们又将查找对象指向了pm2-deploy模块的Deploy对象

打开 pm2-deploy 地址:https://github.com/Unitech/pm2-deploy ,找到deploy.js 并且打开:

// 在里面找到 deployForEnv 方法 并发现在进行一堆参数处理后走到了spawn方法,纵观spawn方法,执行了个shell脚本

function spawn(hostJSON, args, cb) {
    var shellSyntaxCommand = "echo '" + hostJSON + "' | \"" + __dirname.replace(/\\/g, '/') + "/deploy\" " + args.join(' ');
    var proc = childProcess.spawn('sh', ['-c', shellSyntaxCommand], {
        stdio: 'inherit'
    });

    proc.on('error', function (e) {
        return cb(e.stack || e);
    });

    proc.on('close', function (code) {
        if (code == 0) return cb(null, args);
        else return cb(code);
    });
}

好了,现在到了shell了...

打开deploy的sh文件 => 找到deploy方法(所幸作者打了闪闪发光的注释,很简单的我们就找到哪里是重点):

deploy() {
    # pre-deploy hook

    # fetch source
    log fetching updates
    run "cd $path/source && git fetch --depth=5 --all --tags"
    test $? -eq 0 || abort fetch failed

    # latest tags & reset HEAD & link current & deploy log 等操作
}

在这里看到,最新版本的pm2-deploy执行了 git fetch --depth=5 --all --tags  来fetch最新一次的提交,那么这个fetch是不是存在问题呢?因为git-1.8.3.1对应使用pm2-1.x版本可行,那么我们看看pm2-deploy早期的deploy里的fetch是怎么写的,打开早期版本: https://github.com/Unitech/pm2-deploy/blob/0.2.0/deploy

deploy() {
    # pre-deploy hook

    # fetch source
    log fetching updates
    run "cd $path/source && git fetch --all"
    test $? -eq 0 || abort fetch failed
      
    # latest tags & reset HEAD & link current & deploy log 等操作
}

好了,看出区别了, git fetch --all  和  git fetch --depth=5 --all --tags 

于是上git官网查这2个参数,然后失望而归,官网文档最老版本仅能找到1.9.0的,那么怎么办?

我们上这个网站:https://www.kernel.org 在其/pub/software/scm/git/文件夹下可以看到各个版本的git源码压缩包,下载git-1.8.3.1.tar.gz => 解压 => vscode中打开文件夹 ,git每个版本包自带文档,打开看看,Documentation/fetch-options.txt 1.9.0 对比 1.8.3.1:

/*
1.8.3.1
-t::
--tags::
    This is a short-hand for giving "refs/tags/*:refs/tags/*"
    refspec from the command line, to ask all tags to be fetched
    and stored locally.  Because this acts as an explicit
    refspec, the default refspecs (configured with the
    remote.$name.fetch variable) are overridden and not used.

*/

/*
1.9.0
-t
--tags
  Fetch all tags from the remote (i.e., fetch remote tags refs/tags/*
  into local tags with the same name), in addition to whatever else
  would otherwise be fetched. Using this option alone does not subject
  tags to pruning, even if --prune is used (though tags may be pruned
  anyway if they are also the destination of an explicit refspec; see 
  --prune).
*/

大家来找茬 + 推测 --tags 参数的描述可能是导致问题的关键 :refspec(Reference Specification/参考规范,这里个人觉得理解为本地和远程的对应关系更适合),1.8.3.1 的说这个操作是基于明确的映射关系滴,所以默认的映射关系将被覆盖并且不被使用。

那么到底是不是refspecs存在问题呢? 还是明明是 remotes tags 存在问题导致不能fetch到?先测试看看结果,不同版本、不同参数的 git fetch 测试:

初始化

创建文件 test.txt 内容为 test > 3

push 到 test 分支

服务器 pull 代码,确保test.txt文件存在且内容值为 test > 3

如图:

1.8.3.1版本测试:

修改test.txt 为 test > 4

服务器上执行 git fetch --all --tags --depth=5

效果如图:

输出 test > 3

接下来去掉--tags 参数试试

服务器上执行 git fetch --all --depth=5

效果如图:

输出 test > 4

结果正确(但由于没有--tags参数,其实并没有拉取到所有tags)

人为指定映射关系,验证是不是因为 --tags 影响refspec而导致问题

到这里,对于问题而言我们确定了是 --tags 导致fetch不到最新代码,但不能确定是refspec的问题,那么接着下一步的验证:

修改test.txt 为 test > 5

push 代码

服务器上执行 git fetch origin test:refs/remotes/origin/test --depth=5 --all --tags

效果如图:

英文版报错:fetch --all does not take a repository argument

那么暂时去掉 --all ,接下去验证猜想

服务器上执行 git fetch origin test:refs/remotes/origin/test --depth=5 --tags

效果如图:

输出 test > 5

结果正确

2.7.4版本测试 

修改test.txt 为 test > 6

服务器上执行 git fetch --depth=5 --all --tags

效果如图:

输出 test > 6

结果正确

结论:1.8.3.1 版本 git fetch --depth=5 --all --tags  的时候由于加了--tags 导致refspec出现问题

1.8.3.1版本里的 fetch 做了什么导致refspec不正确呢?

打开 git项目 里的 builtin/fetch.c => 找到 get_ref_map

static struct ref *get_ref_map(struct transport *transport,
                   struct refspec *refs, int ref_count, int tags,
                   int *autotags)
{
    int i;
    struct ref *rm;
    struct ref *ref_map = NULL;
    struct ref **tail = &ref_map;

    const struct ref *remote_refs = transport_get_remote_refs(transport);
    int *num_i = &ref_count;    /* ++ */
    int *num_tags = &tags;    /* ++ */
    printf("ref_count ->  %d\n",*num_i);    /* ++  打印ref_count*/ 
    printf("tags -> %d\n",*num_tags);    /* ++  打印num_tags*/ 
    if (ref_count || tags == TAGS_SET) {
        for (i = 0; i < ref_count; i++) {
            get_fetch_map(remote_refs, &refs[i], &tail, 0);
            if (refs[i].dst && refs[i].dst[0])
                *autotags = 1;
        printf("autotags -> %d\n",*autotags);    /* ++  打印autotags*/ 
        }
        /* Merge everything on the command line, but not --tags */
        for (rm = ref_map; rm; rm = rm->next)
            rm->merge = 1;
        if (tags == TAGS_SET)
            get_fetch_map(remote_refs, tag_refspec, &tail, 0);
    } else {
    …
    }
    if (tags == TAGS_DEFAULT && *autotags)
        find_non_local_tags(transport, &ref_map, &tail);
    ref_remove_duplicates(ref_map);

    return ref_map;
}

上面是加了打印测试的代码,并未修改其逻辑,然后编译 => 配置 => 运行试试

测试ref_count和tags的打印结果

修改test.txt 为 test > 7

执行命令 git fetch --all --tags --depth=5

效果如图:

得出结果 ref_count = 0,并且都 Already up-to-date 了,拉没拉到最新提交,心里也有点B数了...

然后执行指定映射关系的命令 git fetch origin test:refs/remotes/origin/test --tags --depth=5

效果如图:

由于指定了映射关系,git知道该fetch哪些代码,于是获取到了最新的提交。

然后再看1.9.0的 fetch 代码

static struct ref *get_ref_map(struct transport *transport,
                   struct refspec *refspecs, int refspec_count,
                   int tags, int *autotags)
{
    int i;
    struct ref *rm;
    struct ref *ref_map = NULL;
    struct ref **tail = &ref_map;

    /* opportunistically-updated references: */
    struct ref *orefs = NULL, **oref_tail = &orefs;

    const struct ref *remote_refs = transport_get_remote_refs(transport);

    if (refspec_count) {
        for (i = 0; i < refspec_count; i++) {
            get_fetch_map(remote_refs, &refspecs[i], &tail, 0);
            if (refspecs[i].dst && refspecs[i].dst[0])
                *autotags = 1;
        }
        /* Merge everything on the command line (but not --tags) */
        for (rm = ref_map; rm; rm = rm->next)
            rm->fetch_head_status = FETCH_HEAD_MERGE;

        /*
         * For any refs that we happen to be fetching via
         * command-line arguments, the destination ref might
         * have been missing or have been different than the
         * remote-tracking ref that would be derived from the
         * configured refspec.  In these cases, we want to
         * take the opportunity to update their configured
         * remote-tracking reference.  However, we do not want
         * to mention these entries in FETCH_HEAD at all, as
         * they would simply be duplicates of existing
         * entries, so we set them FETCH_HEAD_IGNORE below.
         *
         * We compute these entries now, based only on the
         * refspecs specified on the command line.  But we add
         * them to the list following the refspecs resulting
         * from the tags option so that one of the latter,
         * which has FETCH_HEAD_NOT_FOR_MERGE, is not removed
         * by ref_remove_duplicates() in favor of one of these
         * opportunistic entries with FETCH_HEAD_IGNORE.
         */
        for (i = 0; i < transport->remote->fetch_refspec_nr; i++)
            get_fetch_map(ref_map, &transport->remote->fetch[i],
                      &oref_tail, 1);

        if (tags == TAGS_SET)
            get_fetch_map(remote_refs, tag_refspec, &tail, 0);
    } else {
       …
    }

    if (tags == TAGS_SET)
        /* also fetch all tags */
        get_fetch_map(remote_refs, tag_refspec, &tail, 0);
    else if (tags == TAGS_DEFAULT && *autotags)
        find_non_local_tags(transport, &ref_map, &tail);

    /* Now append any refs to be updated opportunistically: */
    *tail = orefs;
    for (rm = orefs; rm; rm = rm->next) {
        rm->fetch_head_status = FETCH_HEAD_IGNORE;
        tail = &rm->next;
    }

    return ref_remove_duplicates(ref_map);
}

tags == TAGS_SET 和 refspec_count 单独判断,在 refspec_count = 0  的时候使用默认的refspec,这样get到的ref_map便是正确的,git之后的版本里把在 refspec_count 判断里的tags == TAGS_SET 判断和get_fetch_map移除了。但其实1.8.3.1官方文档说的覆盖和不使用默认的refspec,在上面代码里我还是没能看出是在哪里操作的(实在汗颜),猜测是在  Merge everything on the command line  这步,同时也求大神解释...  之前没接触过 C …

其实在这过程中,也产生了个问题,就是 refspec 关系的操作是怎么处理的,这个也值得探究探究额,决定再刷刷书熟悉下git核心那块,然后再根据源码探一探 

git fetch 的参考文档

官网文档 : https://git-scm.com/docs/git-fetch

stackoverflow 大佬的回答:https://stackoverflow.com/questions/1204190/does-git-fetch-tags-include-git-fetch

以及各个版本源码:https://github.com/git/git/ 

 

说完git fetch的锅,然后回到之前说的 “有一半原因得归咎于git” ,另一半锅还是得pm2-deploy背,pm2-deploy在fetch的时候理应做个兼容,哪怕这个兼容并不是个很好的实践(因为pm2新版本有对git仓库的管理做了更严谨的把控)
比如在检测机器上git版本< 1.9.0,则走原先的 git fetch --all

如下代码:

version=`git --version | awk '{print $3}' | tr "." " "`
f=`echo $version | awk '{print $1}'`
s=`echo $version | awk '{print $2}'`

if [[ $f -le 1 ]] && [[ $s -le 8 ]]
then
    echo "version < 1.9.0"
else
    echo "version >= 1.9.0"
fi

 

<(▰˘◡˘▰)>  完!  就这么一段无聊的debugger过程…   各位客官看看即可   有深入了解的大神也给小弟多分享下,非常感谢~ 不然只能以后自己功力深了再来解释了

 

相关文章
|
2月前
|
Shell
一个用于添加/删除定时任务的shell脚本
一个用于添加/删除定时任务的shell脚本
106 1
|
1月前
|
Shell Linux 测试技术
6种方法打造出色的Shell脚本
6种方法打造出色的Shell脚本
59 2
6种方法打造出色的Shell脚本
|
25天前
|
XML JSON 监控
Shell脚本要点和难点以及具体应用和优缺点介绍
Shell脚本在系统管理和自动化任务中扮演着重要角色。尽管存在调试困难、可读性差等问题,但其简洁高效、易于学习和强大的功能使其在许多场景中不可或缺。通过掌握Shell脚本的基本语法、常用命令和函数,并了解其优缺点,开发者可以编写出高效的脚本来完成各种任务,提高工作效率。希望本文能为您在Shell脚本编写和应用中提供有价值的参考和指导。
52 1
|
29天前
|
Ubuntu Shell 开发工具
ubuntu/debian shell 脚本自动配置 gitea git 仓库
这是一个自动配置 Gitea Git 仓库的 Shell 脚本,支持 Ubuntu 20+ 和 Debian 12+ 系统。脚本会创建必要的目录、下载并安装 Gitea,创建 Gitea 用户和服务,确保 Gitea 在系统启动时自动运行。用户可以选择从官方或小绿叶技术博客下载安装包。
45 2
|
2月前
|
监控 网络协议 Shell
ip和ip网段攻击拦截系统-绿叶结界防火墙系统shell脚本
这是一个名为“小绿叶技术博客扫段攻击拦截系统”的Bash脚本,用于监控和拦截TCP攻击。通过抓取网络数据包监控可疑IP,并利用iptables和firewalld防火墙规则对这些IP进行拦截。同时,该系统能够查询数据库中的白名单,确保合法IP不受影响。此外,它还具备日志记录功能,以便于后续分析和审计。
51 6
|
1月前
|
运维 监控 Shell
深入理解Linux系统下的Shell脚本编程
【10月更文挑战第24天】本文将深入浅出地介绍Linux系统中Shell脚本的基础知识和实用技巧,帮助读者从零开始学习编写Shell脚本。通过本文的学习,你将能够掌握Shell脚本的基本语法、变量使用、流程控制以及函数定义等核心概念,并学会如何将这些知识应用于实际问题解决中。文章还将展示几个实用的Shell脚本例子,以加深对知识点的理解和应用。无论你是运维人员还是软件开发者,这篇文章都将为你提供强大的Linux自动化工具。
|
2月前
|
监控 Unix Shell
shell脚本编程学习
【10月更文挑战第1天】shell脚本编程
79 12
|
2月前
|
存储 运维 监控
自动化运维:使用Shell脚本简化日常任务
【9月更文挑战第35天】在IT运维的日常工作中,重复性的任务往往消耗大量的时间。本文将介绍如何通过编写简单的Shell脚本来自动化这些日常任务,从而提升效率。我们将一起探索Shell脚本的基础语法,并通过实际案例展示如何应用这些知识来创建有用的自动化工具。无论你是新手还是有一定经验的运维人员,这篇文章都会为你提供新的视角和技巧,让你的工作更加轻松。
69 2
|
3月前
|
Shell
shell脚本变量 $name ${name}啥区别
shell脚本变量 $name ${name}啥区别
|
3月前
|
人工智能 监控 Shell
常用的 55 个 Linux Shell 脚本(包括基础案例、文件操作、实用工具、图形化、sed、gawk)
这篇文章提供了55个常用的Linux Shell脚本实例,涵盖基础案例、文件操作、实用工具、图形化界面及sed、gawk的使用。
687 2