合并冲突发生在合并期间,而不是之后。当我们的更改(从合并基础提交到我们的HEAD提交的差异)对文件F进行更改,同时他们的更改(从相同合并基础提交到其顶部提交的差异)也对文件F进行更改,并且我们的更改和他们的更改重叠(编辑:或相邻-请参见评论),但不相同时,在文件F中会发生冲突。要理解这一点,您需要了解git diff的输出;并且了解什么是合并基础。git diff的输出非常简单明了,但需要记住每个提交都保存了所有文件的快照。这意味着我们必须给git diff两个快照:一个旧的,一个新的。这是两个文件在时间上的“图片”。Git然后玩
找不同游戏:它告诉您从左侧快照到右侧快照,您必须对某些文件进行某些更改。这些更改可能涉及重命名某些文件;它们可能涉及添加新文件;它们可能涉及删除文件;它们可能涉及从某些文件中删除某些特定行,并在某些特定位置向某些文件添加某些行。git diff的输出不一定是任何人做的。它只是一组更改,如果应用于左侧快照,则可以获得右侧快照。这里的“左侧”是使用git diff时的左参数,“右侧”是右参数。
git diff <hash1> <hash2>
这里的两个哈希值是提交的哈希ID。(实际上,这就是git merge
所做的事情,尽管它在内部执行所有操作。)差异引擎旨在生成产生正确效果的最小变更集。结果通常就是某人实际进行的操作...但并非总是如此;因此,通常是正确的,但并非总是如此。
理解git merge
可能最棘手的一部分是合并基础(merge base)的概念。从技术上讲,合并基础是通过从有向无环图(DAG)中选择节点并找到最低公共祖先(LCA)的算法得出的(单个)提交。并非所有DAG节点对(或集合)都有LCA:有些没有,有些有多个。尽管如此,在你的Git提交图中,这里通常只有一个LCA,并且git merge
有一些处理多个LCAs的方法。(当没有LCA时,现代的git merge
默认会拒绝运行,并告诉你这两个分支具有不相关的历史记录。旧版本的Git仍然会运行合并,你也可以让现代的Git运行合并;在这种情况下,Git使用一个没有文件的合成提交作为合并基础。)
这里重要的是对合并基础有一个概念上的“感觉”。对于某些图形,这很容易。例如,考虑Git提交图中你的两个分支仅从哈希ID为H
的共同祖先提交中分叉的情况:
I--J <-- branch1 (HEAD)
/
...--G--H
\
K--L <-- branch2
在这里,当合并 branch1
和 branch2
——也就是提交 J
和 L
—— 共同的起点显然是提交 H
。因此,git merge
将运行两个 git diff
命令,并且每个命令中的合并基础都是 H
:
git diff --find-renames <hash-of-H> <hash-of-J> # what we changed on branch1
git diff --find-renames <hash-of-H> <hash-of-L> # what they changed on branch2
Git现在将
合并这两个 git diff 命令生成的更改集。 重叠处,但没有进行相同更改的地方就是您会遇到合并冲突的地方。
Git将应用于H中快照的组合更改。 将更改应用于此快照会导致提交J; 将其更改应用于其更改会导致提交L; 应用组合更改会产生组合结果。
如果没有冲突,Git将能够自行合并更改。 在应用组合更改之后,Git将自己提交结果,作为新的合并提交 M:
I--J
/ \
...--G--H M <-- branch1 (HEAD)
\ /
K--L <-- branch2
这将是你的合并结果。
如果合并失败,Git会在合并过程中停止。现在你的任务是完成合并(手动将更改合并),然后告诉Git你已经完成了合并并提交合并请求。如果这太麻烦了,你可以告诉Git:完全中止合并,它将撤销所有尝试合并的操作,并将你带回到提交J
,就像你根本没有运行git merge
一样。
最后一个棘手的问题是:当你通过Git自动或手动完成合并时,生成的合并提交记录了两个父提交。也就是说,如果你看一下上面的合并M
,你会发现它连接到两个提交J
和L
。在许多合并中,我们会稍微不同地绘制它:
o--o <-- small-feature
/ \
...--o--B--o--D--o---o--o <-- mainline
\
o--o--o--o--o--o <-- big-feature
这里小特性已经合并到主线,而大特性仍在进行中。小特性的合并基础是提交D
。大特性的合并基础将是提交B
。(其余提交并不是很有趣。)但在某些情况下,我们会得到一个更加复杂的图形:
o--o---o <-- offshoot-feature
/ / \
o--o---o---o--o <-- medium-feature
/ \ /
...--o--o--o--o--o---o----o <-- mainline
这张图并不是很复杂,但现在却很难看出合并基,因为各种功能从彼此以及主干交叉合并。
Git会找到合并基。您可以使用git merge-base --all
自行查找合并基。您可以绘制该图或者让Git使用git log --graph
绘制,然后试着通过肉眼找到合并基。发现合并基后,无论如何都可以运行两个git diff
命令执行git merge
将运行的命令。这将告诉您冲突会在哪里发生。但通常是没有必要的:只需运行git merge
并找到冲突即可。