术语解释
当有多个合并基础(merge bases),且将这些合并基础进行合并产生合并冲突时,就会出现这种情况。合并基础候选是您选择合并的提交的LCA,在由提交构成的子图中:
定义3.1:设G = (V;E)为DAG,而x、y ∈ V。令Gx; y为G中由x和y的所有共同祖先组成的子图。定义SLCA(x; y)为Gx; y中出度为0的节点(叶子)的集合。x和y的最低公共祖先是SLCA(x; y)的元素。
(参见https://www3.cs.stonybrook.edu/~bender/pub/JALG05-daglca.pdf)。这里,G是您的存储库中提交形成的有向无环图,而x和y是您选择合并的两个提交。实际上,我更喜欢定义3.2,虽然它使用了poset,可能更加术语化:但它更容易理解(实际上也用于家谱研究,这正是Git在做的事情)。
“递归”合并策略(-s recursive
)使用所有合并基础,合并每个合并基础——并提交结果,同时包括合并冲突,并将此临时提交用作新的合并基础。所以这就是对第一个问题的回答(“什么可能导致基文件...有合并冲突”)。
为了避免这种情况,您有几个选择,但首先让我们更加易懂地描述一下问题。
长但更有用的回答
您提到使用了git merge-base
。默认情况下,git merge-base
选择了一个最佳合并基础提交,并打印该提交的哈希值。如果您在两个分支末端提交上运行git merge-base --all
,则会发现存在多个最佳合并基础提交候选项。
在典型、简单的分支和合并模式中,我们有:
o--o--o <-- branch-A
/
...--o--o--*
\
o--o--o <-- branch-B
根据引用论文中3.1或3.2的方法找到的共同合并基础(使用3.2即可从两个分支尖端向后遍历,直至找到在两个分支上都存在的提交)当然是提交*
,Git只需要将*
与分支A和分支B的两个尖端提交进行差异比较。
但不是所有的图形都那么整齐简单。获得两个合并基础的最简单方式是在历史记录链中有一个十字形合并,如下所示:
...--o--o--*---o--o--o <-- branch-C
\ /
X
/ \
...--o--o--*---o--o--o <-- branch-D
注意,两个带星标的提交都在两个分支上,这两个提交与两个分支的末尾同样接近。运行git merge-base --all branch-C branch-D
将打印出这两个提交的哈希ID。Git应该使用哪个提交作为合并基础?
Git的默认答案是:让我们全部使用它们!实际上,Git会运行:
git merge <hash-of-first-base> <hash-of-second-base>
作为一个递归合并的(内部)合并。此合并可能存在冲突!
如果合并存在冲突,Git 不会停下来向您这个用户寻求帮助。它只会提交存在冲突的结果。这将成为外部 git 合并的输入,也就是您直接请求的那个。
...--o--o--*---o--o--o <-- branch-C
\ /
M <-- temporarily-committed merge result
/ \
...--o--o--*---o--o--o <-- branch-D
临时提交实际上不在图表中,但是为了外部合并的目的,它可以视为在图表中。
如何避免问题
现在我们看到问题出现的原因后,避免它的方法变得更加清晰,虽然不是完全清晰,但至少比之前清晰:
方法1:避免交叉合并。
通过避免创建多个合并基础的任何操作,您就不会首先遇到这种情况。这种交叉合并是通过在某一时刻将 branch-C
和 branch-D
处于以下状态而创建的:
o--o--o <-- branch-C
/
...--o
\
o--o--o <-- branch-D
某人,比如C人,运行了git checkout branch-C; git merge branch-D
来获取:
o--o--o---M1 <-- branch-C
/ /
...--o /
\ /
o--o--o <-- branch-D
之后,可能是其他人(没有新提交的他们自己的分支-C
),让我们称其为D,运行git checkout branch-D; git merge <commit that was the previous tip of branch-C before the new merge was just added>
,以获取:
o--o--o <-- branch-C
/ \
...--o \
\ \
o--o--o---M2 <-- branch-D
当C和D两人分享了他们的新提交后,结果如下:
o--o--o---M1 <-- branch-C
/ \ /
...--o X
\ / \
o--o--o---M2 <-- branch-D
时间炸弹已经设置。 这只有在您之后尝试将提交合并到线路中时才会生效,但这是设置的原因。
方法2:如果进行此类合并,则要小心处理它们。 合并通常是对称的。 人C和D运行了两个git merge命令,并且采用了相同的提交,形成相同的图形。 他们从相同的合并基础开始,具有相同的两个分支末端提交。 与每个分支提示相比较的两个差异是相同的差异。 因此,他们必须解决一些冲突,您可能已经在自己的合并合并基础中看到了相同的冲突。
方法3(简单但可能错误):使用不同的合并策略。 递归合并策略(-s recursive)找到此图形,找到所有合并基础提交候选项,如果有多个,则合并它们并将结果用作输入。您的合并。 有一个名为resolve(-s resolve)的策略,与递归策略共享几乎所有代码。
git mergetool <hash-of-first-base> <hash-of-second-base>
来绕过git的git merge <hash-of-first-base> <hash-of-second-base>
。发现的冲突是因为我们使用的工具对时间戳进行更改,而不是因为重叠的编码,所以应该很简单。 - Baldrickk