记录一次无聊的(经历了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月前
|
存储 安全 Unix
七、Linux Shell 与脚本基础
别再一遍遍地敲重复的命令了,把它们写进Shell脚本,就能一键搞定。脚本本质上就是个存着一堆命令的文本文件,但要让它“活”起来,有几个关键点:文件开头最好用#!/usr/bin/env bash来指定解释器,并用chmod +x给它执行权限。执行时也有讲究:./script.sh是在一个新“房间”(子Shell)里跑,不影响你;而source script.sh是在当前“房间”里跑,适合用来加载环境变量和配置文件。
414 9
|
2月前
|
存储 Shell Linux
八、Linux Shell 脚本:变量与字符串
Shell脚本里的变量就像一个个贴着标签的“箱子”。装东西(赋值)时,=两边千万不能有空格。用单引号''装进去的东西会原封不动,用双引号""则会让里面的$变量先“变身”再装箱。默认箱子只能在当前“房间”(Shell进程)用,想让隔壁房间(子进程)也能看到,就得给箱子盖个export的“出口”戳。此外,Shell还自带了$?(上条命令的成绩单)和$1(别人递进来的第一个包裹)等许多特殊箱子,非常有用。
302 2
|
5月前
|
Shell
Shell脚本循环控制:shift、continue、break、exit指令
使用这些命令可以让你的Shell脚本像有生命一样动起来。正确使用它们,你的脚本就能像一场精心编排的舞蹈剧目,既有旋律的起伏,也有节奏的跳跃,最终以一场惊艳的表演结束。每一个动作、每一个转折点,都准确、优雅地完成所需要表达的逻辑。如此,你的脚本不只是冰冷的代码,它透过终端的界面,跳着有节奏的舞蹈,走进观众——使用者的心中。
264 60
|
2月前
|
数据采集 监控 Shell
无需Python:Shell脚本如何成为你的自动化爬虫引擎?
Shell脚本利用curl/wget发起请求,结合文本处理工具构建轻量级爬虫,支持并行加速、定时任务、增量抓取及分布式部署。通过随机UA、异常重试等优化提升稳定性,适用于日志监控、价格追踪等场景。相比Python,具备启动快、资源占用低的优势,适合嵌入式或老旧服务器环境,复杂任务可结合Python实现混合编程。
|
8月前
|
关系型数据库 MySQL Shell
MySQL 备份 Shell 脚本:支持远程同步与阿里云 OSS 备份
一款自动化 MySQL 备份 Shell 脚本,支持本地存储、远程服务器同步(SSH+rsync)、阿里云 OSS 备份,并自动清理过期备份。适用于数据库管理员和开发者,帮助确保数据安全。
|
4月前
|
Web App开发 缓存 安全
Linux一键清理系统垃圾:释放30GB空间的Shell脚本实战​
这篇博客介绍了一个实用的Linux系统盘清理脚本,主要功能包括: 安全权限检查和旧内核清理,保留当前使用内核 7天以上日志文件清理和系统日志压缩 浏览器缓存(Chrome/Firefox)、APT缓存、临时文件清理 智能清理Snap旧版本和Docker无用数据 提供磁盘空间使用前后对比和大文件查找功能 脚本采用交互式设计确保安全性,适合定期维护开发环境、服务器和个人电脑。文章详细解析了脚本的关键功能代码,并给出了使用建议。完整脚本已开源,用户可根据需求自定义调整清理策略。
538 1
|
6月前
|
存储 Unix Shell
确定Shell脚本在操作系统中的具体位置方法。
这对于掌握Linux的文件系统组织结构和路径方面的理解很有帮助,是我们日常工作和学习中都可能使用到的知识。以上讲解详细清晰,应用简便,是每一个想要精通操作系统的计算机爱好者必备的实用技能。
179 17
|
6月前
|
Linux Shell
Centos或Linux编写一键式Shell脚本删除用户、组指导手册
Centos或Linux编写一键式Shell脚本删除用户、组指导手册
195 4
|
6月前
|
Linux Shell 数据安全/隐私保护
Centos或Linux编写一键式Shell脚本创建用户、组、目录分配权限指导手册
Centos或Linux编写一键式Shell脚本创建用户、组、目录分配权限指导手册
397 3
|
7月前
|
Linux Shell
在Linux、CentOS7中设置shell脚本开机自启动服务
以上就是在CentOS 7中设置shell脚本开机自启动服务的全部步骤。希望这个指南能帮助你更好地管理你的Linux系统。
618 25