这些 Git 合并标记的简单解释是什么?

What is a simple explanation of these Git merge markers?

结合代码段1、2、3解释下面Git合并标记的含义

/* Code from beginning of file */

<<<<<<< HEAD
      /* code segment 1 */
||||||| merged common ancestors
      /* code segment 2 */
=======
      /* code segment 3 */
>>>>>>> master

/* code to end of file */

这个问题旨在引出一个简单的解释,而不参考其他问题中发现的复杂因素。

这些chevron-ish 标记(<<<<<<< 等)是冲突标记。这个包含 ||||||| 标记的特定集合来自 diff3 冲突样式。默认 merge 冲突样式省略了中间部分。

第一部分中的部分 <<<<<<<||||||| 来自 当前提交 。第二部分中的部分 |||||||======= 来自 merge base(我们将在稍后定义)。第三部分 =======>>>>>>> 的部分来自 other 提交。

更长但更有用

要理解这些含义,请记住任何 git merge 操作都没有两个,而是 三个 输入。三个输入之一是您当前的结帐,也就是 HEAD@ 提交。通常,这是您签出的 b运行ch 中的最新提交。第二个输入基于 git merge 命令,你 运行.1 例如,如果你 运行:

git merge theirbranch

那么第二个输入就是b运行ch theirbranch顶端的提交;如果你 运行:

git merge origin/master

那么第二个输入就是你的 origin/master 指向的提交。无论哪种方式,这两个都是提交——充满文件的快照,其中 HEAD 提交中的文件与其提交中的文件相同或不同。你的文件和它们的文件之间的差异并不直接相关:关键提交是 third 提交,称为 merge base.

Git 会自动为您找到合并基础提交。实际上,合并基础是 最佳共享提交 ,它位于其他两个之前。请记住,合并的目标是合并工作,为此,Git需要找出您更改了什么 他们改变了什么 。但是每个提交都是一个快照,而不是一组更改——因此 Git 必须从您的提交和他们的提交向后工作,以找到你们在开始时共享的提交。那是合并基础。

找到合并基础后,Git 现在执行两次 比较。将合并基础与当前提交进行比较:这就是 you 更改的内容。第二个 diff 将合并基础与 他们的 提交进行比较:这就是 他们 改变的地方。然后 Git 组合两组变化。合并后的更改将应用​​于合并基础,以给出最终的合并结果。

当您更改一个文件而他们根本没有触及该文件时,将您的更改与他们的任何内容结合起来意味着结果就是您的更改。将这些应用于基本文件会生成您的文件。同样,当他们更改文件而您没有更改时,将您的任何内容与他们的更改相结合意味着结果是他们的更改,并将这些更改应用于基本文件会生成他们的文件。所以这些都非常简单。

当您和他们都更改了同一个 文件时,困难的部分就出现了。现在 Git 确实必须结合不同的变化。如果您更改的是他们未触及的行,而他们的更改是您未触及的行,则 Git 可以合并这些:它只需要两个更改。如果您更改了某行并且他们进行了 完全相同的 更改,Git 也可以将其合并:它只需要一份更改。 really 导致合并冲突的困难部分发生在您更改某些行时,它们以不同的方式更改了 same 行.

对于这种情况,Git 将冲突的更改写入文件的 work-tree 副本,并由这些冲突标记包围。冲突标记区域上方的部分已成功合并——或者至少,Git认为合并成功——标记区域下方的部分也是如此。介于两者之间的部分是 Git 无法决定是在第一段中更喜欢您的更改,还是在最后一段中更喜欢他们的更改。中间部分,仅在您选择 diff3 样式时显示,是原始线条的样子。


1注意,如果你运行git pull走到这一步,git pull运行git merge 为你。所以你可能没有直接 运行 git merge ,但你确实调用了 git merge.

cherry-pick 和恢复命令也使用 Git 合并机制。 git stashgit applygit am 的某些情况也是如此。所以你也可以看到这些命令的这些合并冲突。但是,这些操作的 merge base 的定义不同,因此更难看出冲突是如何发生的。

diff3 与 merge 的另一个副作用

发生冲突时,如果您选择了 diff3 样式,Git 必须向您显示基本版本——发生冲突的整个部分。但是当您选择 merge 样式时,Git 可以省略基本版本,只显示 --ours--theirs 版本。这个表示如果它可以 部分 合并冲突区域,它会这样做,只留下被标记包围的 未合并 区域。例如:

<<<<<<< HEAD
please fix a spelling error
and ok, I changed this
||||||| merged common ancestors
please fix a speeling error
and change this
=======
please fix a spelling error
and change this to something different
>>>>>>> theirs

在这里,我们和他们以同样的方式修复了拼写错误(将speeling替换为spelling),但我们更改了第二行不同。使用 diff3 样式,您会看到基本版本和两个 end-point 版本。

如果我们选择 merge 样式,Git 会看到我们和他们以相同的方式修复了拼写错误,而我们会看到:

please fix a spelling error
<<<<<<< HEAD
and ok, I changed this
=======
and change this to something different
>>>>>>> theirs

通常,这很好——但有时这意味着您根本无法分辨 原始基础是什么,因为冲突发生在某些东西和 什么都没有,即,我们或他们中的一个人删除了一行,而我们或他们中的另一个人更改了它。我发现 diff3 样式通常更容易阅读,但 merge 样式是默认样式。

我正在寻找的初学者级别的答案是这样的:

段 1 是存在于您当前签出的分支中的代码 - 它位于 <<<<<<< 指出的 "HEAD" 位置 - 当前与段 3 冲突。

段 3 是存在于您尝试合并的另一个分支中的代码 - 如 >>>>>>> 所述的 "master" 分支 - 与段 1 冲突。

段2是两个待合并分支的共同祖先中存在的代码。由Git展示,帮助您更轻松地决定如何解决段1和段3之间的合并。

您可以通过多种方式来解决段 1 和段 3 之间的合并冲突。可能的方法有:

  1. 删除段1,保留段3;

  2. 删除段3,保留段1;

  3. 手动合并段 1 和 3 以包含两者的更改。

  4. 从共同祖先恢复到段 2,丢弃 第 1 和第 3 部分;

  5. 删除所有段并编写全新代码

最终,您需要根据自己的判断来决定如何解决冲突。请务必在提交合并之前从代码中删除所有合并标记。