Q

首先,简单描述一下我们组的工作流。仓库里的分支主要分为三种:release, master, develop。开发通常只往develop分支合入代码,develop通过脚本定期向master同步代码,master定期分叉出release分支,用于编译交付。

release             *--
                   /
master  -----*----*----
            /    /
develop ---*----*------

最近,我正在研究改进主干同步(develop -> master)的脚本,这个脚本通过git diff master develop来识别是否需要进行代码合入,检测到有差异时,checkout mastermerge develop进行合并。

事发当时,diff步骤显示有差异,但使用merge后,发现生成的是空的MR。空MR在我们内部repo是禁止的,于是我们的脚本就产生了异常。值得我摸鱼深入研究一番。

~

diff的输出为线索,blame到两笔差异对应的两笔commit id。向当事人了解情况,相关commit主要是用来清多处代码告警的,但是有几处改的不合理,最后整笔回退了。develop的回退中,有一处没有生效。master是完全回退了。理论上来说,develop与同步过去的master也应该是相同的(应该都是回退的状态)。标记master的为m状态,develop为d状态。

使用git bisect在当前节点与回退节点之间进行二分排查,定位到最先的一笔d状态MR,改动是把已经前面被回退那一处,又单独加回去了。这就合理了。

但还是不能解释,为什么主干同步不会把这个同步到主干里去,主干应该也是d状态,而不是m状态。

           * merged node (m)
           |\
           | * warning fix (d)
           | |
revert (m) * * revert (m)
           |/
           * warning fix (LCA) (d)

当时他们提交MR的电脑时间被重置了,导致在git log里显示的时间很混乱,必须使用--graph才能看清楚顺序。但对于git来说,时间不重要,合并拓扑关系是不会根据时间顺序而改变的(子commit可能比父commit要早)。

问题总归还是出在了合并的步骤上,需要重新理解一下git是怎么合并的。(git历史是一棵DAG,输入量为两个分支A B)

找到A与B的LCAdiff LCA A > patch.adiff LCA B > patch.b手动解冲突patch.a 与 patch.b 是否会发生冲突?合并

这个步骤应该是3way,另外还有一种2way,不在本文考虑范围内。

A

如果get到diff的特点,就能解出谜题了。LCA为d状态,master的diff为d -> m,develop的diff为无(d -> d)。所以git当然会选择改变的那一种,而且也不会有任何冲突。

真相就是这样。学过的东西不用就是容易忘。里面关于ab分别合并的区别,我补充一下,还是有区别的,当HEAD指向合并完的点时,合并顺序决定了HEAD^怎么走。但是,一个分支如果合并完后,不应该继续使用,要直接删除。我们的用法很可能会有坑。