git合并冲突的不同场景

Sam*_*art 3 git git-merge merge-conflict-resolution git-merge-conflict

我试图了解在 git 合并后可能发生 git 冲突的情况以及如何避免它们。

我创建了一个 git 存储库并向其中添加了一个文本文件。

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

在此之后,我做了以下工作:

  • 我已将 branch2 合并到 master 中。没有冲突,这是正确的,我已经预料到了。
  • 我已将 master 合并到 branch3 中。我有冲突,因为文本文件的第二行有不同的内容。我通过保留“3”而不是“2”来解决冲突。
  • 我想将 branch3 合并到 master 中。现在我的问题是: 1. 进行合并时是否有可能发生冲突?如果是,为什么?如果没有,为什么?2.如果不应该有冲突,但我仍然有冲突,可能是什么原因?

tor*_*rek 12

合并冲突发生合并期间,而不是之后

冲突部分真的很简单:在文件F 中发生冲突时:

  • “我们的”更改——从合并基础提交到我们的HEAD提交的差异——对文件F 进行了更改,并且
  • “他们的”更改——从相同的合并基础提交到他们的提示提交的不同——也对文件F也有更改,并且
  • 我们的变化和他们的变化重叠,但不完全相同。

要理解这一点,您需要:

  1. 理解输出git diff;和
  2. 了解什么是合并基础

的输出git diff是非常简单的,真的,但它需要记住的是,每一个提交持有快照所有文件中。这意味着我们必须提供git diff 两个快照:旧的和新的。这是文件在两个时间点的样子的两张“图片”。然后 Git 玩了一个Spot the Difference游戏:它告诉你要从左侧快照转到右侧快照,你必须对一些文件集进行一些更改。这些更改可能涉及重命名某些文件;它们可能涉及添加新文件;它们可能涉及删除文件;它们可能涉及从某些文件中删除某些特定行,并在某些特定位置将某些行添加到某些文件中。

的输出git diff不一定是任何所做的任何事情。这只是一组更改,如果应用于左侧快照,则会为您提供右侧快照。此处的“左侧”是左参数,此处git diff的“右侧”是右参数,当您使用时:

git diff <hash1> <hash2>
Run Code Online (Sandbox Code Playgroud)

其中两个哈希是提交的哈希 ID。(git merge实际上,这就是这样做的,尽管它在内部完成所有这些。)差异引擎旨在产生产生正确效果的最小更改集。事实证明,这通常是某人实际所做的……但并非总是如此;因此,它通常是正确的,但并非总是如此。

最后,但可能是最棘手的,理解的部分git merge合并基础的概念。从技术上讲,合并基础是从算法中出现的(单个)提交,该算法找到从有向无环图 (DAG) 中选择的节点的最低公共祖先 (LCA)。并非所有 DAG 节点对(或集合)都有 LCA:有些没有,有些有多个。不过,您的Git 提交图在这里有一个 LCA是很常见的,并且git merge有一些方法可以处理多个 LCA。(当没有LCA 时,现代git merge拒绝默认运行,告诉你这两个分支有不相关的历史。旧的 Git 无论如何都会运行合并,你可以让现代 Git 进行合并;在这种情况下,Git 使用没有文件的合成提交作为合并基础。)

这里的重要部分是对合并基础有一个概念上的“感觉”。 对于某些图形,这很容易。例如,考虑一个 Git 提交图的情况,其中您的两个分支只是从散列 ID 为的共同祖先提交中分叉出来H

          I--J   <-- branch1 (HEAD)
         /
...--G--H
         \
          K--L   <-- branch2
Run Code Online (Sandbox Code Playgroud)

在这里,当合并branch1branch2- 这意味着提交JL- 共同的起点显然是 commit 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
Run Code Online (Sandbox Code Playgroud)

Git 现在将结合这两个git diff命令产生的一组更改。它们重叠但不进行相同更改的地方就是您将遇到合并冲突的地方。

Git 会将合并的更改应用于H. 将您的更改应用于此快照会导致提交J;在 commit 中应用他们的更改L;应用组合的更改会导致组合。

如果没有冲突,Git 将能够自行组合更改。应用合并的更改后,Git 将自己提交结果,作为新的合并提交 M

          I--J
         /    \
...--G--H      M   <-- branch1 (HEAD)
         \    /
          K--L   <-- branch2
Run Code Online (Sandbox Code Playgroud)

这将是您的合并结果。

如果合并失败,Git 会在合并过程中停止。您现在的工作是完成合并(自己合并更改),然后告诉 Git 您已经完成合并提交。如果这太混乱了,你可以告诉 Git:完全中止合并,它会退出所有的合并尝试,让你回到 commit 状态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
Run Code Online (Sandbox Code Playgroud)

在这里,小功能合并到主线中,大功能仍在进行中。小功能的合并基础是 commit D。大特性的合并基础将是 commit B。(其余的提交并不是很有趣。)不过,在某些情况下,我们会得到一个更复杂的图:

                  o--o---o   <-- offshoot-feature
                 /      / \
                o--o---o---o--o   <-- medium-feature
               /    \ /
...--o--o--o--o--o---o----o   <-- mainline
Run Code Online (Sandbox Code Playgroud)

这个图并没有那么复杂,但是现在真的很难看到合并基础在哪里,因为所有从各种特征到主线和彼此的交叉合并。

Git找到合并基础。您可以使用git merge-base --all. 你可以画图,也可以让 Git 用 来画git log --graph,然后试着用眼球来寻找合并基数。找到合并基础后,无论您如何操作,您都可以运行将运行的两个git diff命令git merge。这将告诉您冲突会在哪里。但通常,没有意义:只需运行git merge并找到冲突即可。