提出问题

在运行项目的服务器的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 = ; i < ref_count; i++) {
            get_fetch_map(remote_refs, &refs[i], &tail, );
            if (refs[i].dst && refs[i].dst[])
                *autotags = ;
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 = ;
        if (tags == TAGS_SET)
            get_fetch_map(remote_refs, tag_refspec, &tail, );
    } 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 = ; i < refspec_count; i++) {
            get_fetch_map(remote_refs, &refspecs[i], &tail, );
            if (refspecs[i].dst && refspecs[i].dst[])
                *autotags = ;
        }
        /* 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 = ; i < transport->remote->fetch_refspec_nr; i++)
            get_fetch_map(ref_map, &transport->remote->fetch[i],
                 &oref_tail, );         if (tags == TAGS_SET)
            get_fetch_map(remote_refs, tag_refspec, &tail, );
    } else {
       …
    }     if (tags == TAGS_SET)
        /* also fetch all tags */
        get_fetch_map(remote_refs, tag_refspec, &tail, );
    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过程…   各位客官看看即可   有深入了解的大神也给小弟多分享下,非常感谢~ 不然只能以后自己功力深了再来解释了

记录一次无聊的(经历了Nodejs -> Shell -> C)的探索问题过程的更多相关文章

  1. 记录一次node中台转发表单上传文件到后台过程

    首发掘金 记录一次node中台转发表单上传文件到后台过程 本篇跟掘金为同一个作者leung   公司几个项目都是三层架构模式即前台,中台(中间层),后台.前台微信端公众号使用vue框架,后台管理前端使 ...

  2. 记录一次OOM排查经历(一)

    一.经历概要 程序里有个跑数据的job,这个job的主要功能是往数据库写假数据. 既需要跑历史数据(传给job的日期是过去的时间),也需要能够上线后,实时跑(十秒钟触发一次,传入触发时的当前时间). ...

  3. 记录一次ceph recovery经历

    一次ceph recovery经历 背景 这是一个測试环境. 该环境中是cephfs 一共12个节点, 2个client.2个mds.8个osd mds: 2颗CPU,每一个4核.一共是8核. 128 ...

  4. 记录一次gdb debug经历

    目录 问题描述 查看core文件 使用gdb查看core文件 总结 问题描述 今天在写代码时,运行时奔溃了.segment fault,而且是在程序退出main()函数后,才报的. 唯一的信息是:Se ...

  5. 记录一次OOM排查经历

    我是用了netty搭建了一个UDP接收日志,堆启动配置 Xmx256  Xms256 ,项目刚启动的时候,系统进程占用内存很正常,在250M左右. 长时间运行之后发现,进程占用内存不断增长,远远超过了 ...

  6. 完整记录一则Oracle 11.2.0.4单实例打PSU补丁的过程

    本文记录了打PSU的全过程,意在体会数据库打PSU补丁的整个过程. 1.OPatch替换为最新版本2.数据库软件应用19121551补丁程序3.数据库应用补丁4.验证PSU补丁是否应用成功 1.OPa ...

  7. 记录清除wnTKYg挖矿工木马(守护进程ddg.xxxx)的过程

    起因,阿里云多次提醒我的一台服务器有恶意发包行为,且给出了一些解决办法.之前也没太在意,就按照解决办法处理了一下.然后过一段时间,还是提示有此行为. 猜肯定是中了木马了,开始以为是被肉鸡了拿来做DDo ...

  8. CentOS7下安装Mysql失败经历--CentOS7使用yum安装和卸载Mysql过程

    起因 自己租用的BandwagonVPS上安装了个CentOS7,然后开始安装各种软件,结果yum安装MySQL发现MySQL在yum源中的Mysql不对劲,于是自己百度搜索安装方法. 终于我搜到了这 ...

  9. 记录一下在WinXP上搭建Apache的httpd+PHP+MySQL+Wordpress的过程

    实验室有台旧电脑,想用它一台服务器. 不知为何,U盘启动盘死活不能启动,所以放弃了安装Linux的念头,直接在原来的XP上弄一个服务器,毕竟用的人也不多,也就局域网的这几个人, 本来主要是搭建一个FT ...

随机推荐

  1. redis集群搭建及注意事项

    上一篇:redis的安装及注意事项 这里,在一个Linux虚拟机上搭建6个节点的redis伪集群,思路很简单,一台虚拟机上开启6个redis实例,每个redis实例有自己的端口.这样的话,相当于模拟出 ...

  2. DNSmasq服务搭建

    .c { background: #FEFEF2; padding: 30px } hr { border: 1px dotted #70C4EF } DNSmasq介绍 DNSmasq是一个小巧且方 ...

  3. window 使用git 非ssh key 面密码登录

    Windows下使用git bash时,总是提示输入用户名密码,严重影响了开发效率,经搜索查找找到了如下有效的解决方案,屡试不爽! 1.先创建存储用户名密码的文件 ,在home文件夹,一般是 C:\D ...

  4. fixed定位兼容性

    不过从ios5.1以来,fixed定位就已经支持了,但很遗憾,ios现在对它还只是半支持. 但是在某些情况下,会出现一些比较奇葩的问题,比如fixed元素中存在输入框子元素,这个时候就会跪了. 可以看 ...

  5. 使用nfs作为根文件系统启动,(3)

    通过设置u-boot的bootargs来更改开机自动进入nfs远端服务器,不需要mount指令,实现虚拟机编译程序后直接通过u-boot烧写程序 1  使用nfs作为根文件系统启动 1.1    pr ...

  6. SourceTree使用方法介绍

    SourceTree比命令行更容易操作,能更直观看到发生了什么.但是没有哪一家git图形化软件能完成git的所有操作,封装后的使用也隐藏了git的一些细节,在图形化工具出现一些非常罕见的情况时,还是需 ...

  7. none,hidden和opacity="0"设置元素不可见的区别

    none,hidden和opacity="0"均可设置元素为不可见的状态.其中none属于display的属性值,hidden属于visibility的属性值.使用none时元素不 ...

  8. JS中event.keyCode用法及keyCode对…

    js中event.keyCode用法及keyCode对照表 标签: javascriptJavaScriptJavascriptjavaScript 2012-12-11 15:11 HTML Jav ...

  9. Git 指南 -- 什么应该被纳入管理?

    Git 指南 -- 什么应该被纳入管理? 如果还不了解Git是什么,可以先阅读这篇博文:http://www.cnblogs.com/schaepher/p/5561193.html 是作品,而不是产 ...

  10. 团队作业八——第二次团队冲刺(Beta版本)第3天

    一.每个人的工作 (1) 昨天已完成的工作 对界面进行完善,并增加简单界面(包含简单界面内含的界面),简单模式与复杂模式的选择界面. (2) 今天计划完成的工作 做一下用户注册的功能和登录功能. (3 ...