Git合并冲突的不同场景

4

我试图了解在git合并后哪些情况会发生冲突,以及如何避免它们。

我创建了一个git仓库并添加了一个文本文件。

  • 我向文本文件中添加了“1”并将其提交到主分支。
  • 我从主分支创建了一个新的分支(branch2),并向文本文件追加了一行内容为“2”的新行。
  • 我从主分支创建了一个新的分支(branch3),并向文本文件追加了一行内容为“3”的新行。

之后,我做了以下操作:

  • 我将branch2合并到主分支中。没有冲突,这是正确的,也是我预期的。
  • 我将主分支合并到branch3中。由于文本文件的第二行具有不同的内容,我遇到了冲突。我通过保留“3”而不是“2”来解决了冲突。
  • 我想将branch3合并到主分支中。现在我的问题是:1. 在执行此合并时是否有可能发生冲突?如果是,为什么?如果不是,为什么? 2. 如果不应该有冲突,但我仍然遇到了冲突,可能的原因是什么?
2个回答

15
合并冲突发生在合并期间,而不是之后。当我们的更改(从合并基础提交到我们的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

在这里,当合并 branch1branch2 ——也就是提交 JL —— 共同的起点显然是提交 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,你会发现它连接到两个提交JL。在许多合并中,我们会稍微不同地绘制它:

                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并找到冲突即可。


1
一个注意事项--如果更改重叠或相邻,它们将发生冲突。为了避免冲突,在两个更改块之间必须至少有一行未更改的代码。 - Chris Dodd
@ChrisDodd:糟糕,对的,我通常使用“重叠或接壤”这个词组。 - torek
有一份很好的文档描述了所有导致合并冲突的情况:https://github.com/mndrix/merge-this,“相邻行的更改”是其中所描述的情况之一。 - Aedvald Tseh

0

当你将branch3合并到master时,你不应该有任何冲突,如果你之前没有将master合并到branch3中,那么你就会有冲突。原因很简单,因为你已经在branch3中添加了一个提交来解决它和master之间的冲突。所以现在合并到master可以快进。


谢谢您的回答,但第二个问题呢? - Sam Stewart
我并没有看到在你描述的场景中会有任何冲突的理由。你能否包含冲突信息?这可能有助于确定问题的原因。 - Adam Nierzad

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接