Git 如何管理自动解决的合并?

How Git manages an automatic solved merge?

我研究了什么是 git merge 操作以及当它发现无法自动解决的冲突时它会做什么。
如果我可以手动解决冲突,我可以选择要保存的内容和要更改的内容。

另一方面,如果一个分支是另一个分支的直接祖先,我们有快进合并,另一方面不快进合并但自动解决。
在这里,我发现很难理解 Git 如何处理这两种情况:我看到它会自动选择要更改的内容,但我怎么知道它是否按照我的意愿进行操作?

例如,在 test 分支上,我使用 file.txt,而在 master 分支上,我有另一个版本的 file.txt.
这两个分支有一个共同的祖先。
我执行 git checkout master 然后我想与 test.
合并 为此,我数字 git merge test。那会发生什么?

  1. master内容完全不同
  2. master 包含 file.txt
  3. test 版本中不存在的文本
  4. masterfile.txt 里面的 test
  5. 有更少的文本片段

我的问题涉及一个通用案例:我如何才能事先了解 运行 git merge test,Git 将如何处理这些合并?
也许这取决于我开始时所在的分支 git merge?

How can I understand, beforehand run git merge test, how Git will treats these merges?

事先你无法理解它,但你可以在不实际进行合并提交的情况下进行合并,因此你可以研究结果。就说

git --no-ff --no-commit merge test

然后环顾四周

How can I understand, beforehand run git merge test, how Git will treats these merges?

在一些罕见的(并且基本上不会无意中产生的)情况下,在基本操作的基础上会有额外的准备工作,或者整个事情可以在琐碎的“快进”情况下被回避,但是:看看git merge test 将使用什么,比如

git diff ...test    # diffs on test since histories diverged
git diff test...    # diffs in checkout since test history diverged

这是两组差异 Git 将合并。

Git 有一个默认的自动合并,它应用所有不重叠或邻接另一端的任何不同更改的大块,但重叠或邻接的大块不能可靠地自动合并,没有人曾经想过了解如何为这些结果得出“正确”的一般结果,因此您必须解决这个问题。

让我们看看我是否可以简短地介绍所有内容 post:

  • Git 有多个 合并策略 。当您 运行 git merge 时,您可以选择一个策略,例如 git merge -s resolve othergit merge -s octopus br1 br2 br3。标准策略是 oursrecursiveresolvesubtreeoctopus,现在是新的 ort.

  • 几乎所有的实际工作都是由策略完成的。因此,在您决定合并将如何运作之前,您必须知道您将使用哪种策略。

  • 大多数合并的默认策略 recursive 并且可能很快变成 ort。这两个主要是为了工作相同,除了 ort 应该更快并且更好地处理一些棘手的情况。 (注意:这是 goal 状态,而不是 current 状态,这就是为什么它还不是默认状态的原因。)但是,如果你给多个“heads”(真正地提交)到 git merge,默认值为 octopus

除了 ours 策略(它不需要合并基础,我认为不需要计算一个)和 octopus 策略(它使用替代的合并基础计算),这些合并必须找到(单数)merge base 提交。要找到该提交,Git 使用 Lowest Common Ancestor algorithm as extended to DAGs。您可以 运行 手动执行此操作:

git merge-base --all HEAD otherbranch

例如。但正如“全部”选项的存在所暗示的那样,并且维基百科 link 清楚地表明,该算法的输出 可能 不止一次提交 .

如果只有一个合并基地,一切都很好。如果不是,每个策略都必须对此做些什么。 (章鱼策略会做任何它会做的事情,因为它首先没有使用这个算法;我从来没有深入研究过 that 问题的底部,因为我对 bugs Balrogs。)resolve 策略使用了一个简单但糟糕的答案:它(显然)随机选择一个并使用它。然而,默认的 recursive 策略只是简单地合并合并基(不使用章鱼算法,而是使用 ort 试图改进的稍微充满 Balrog 的递归方法;我等着看结果。 ..).

跳过一些递归合并细节(但注意这是“递归”合并驱动程序条目的内容),我们继续:subtree策略是实际上只是伪装的 recursive 算法,所以它处理这些与 -s recursive 相同。 ours 策略忽略所有其他输入:它的最终提交只是 HEAD 提交的内容,带有额外的父项,因此合并基础问题变得无关紧要。如前所述,章鱼首先不使用 git merge-base --all 。因此,如果我们需要递归合并,执行它的策略合并合并基础并提交结果(包括任何合并冲突,这是Balrog 对你的任务造成严重破坏的主要地点)。这个合并的 结果 是合并操作的新合并基础。

所以,这让我们得到了一个单一的合并基础,通过丢弃额外的(-s resolve)或合并它们(除了 -s ours-s octopus 之外的所有东西甚至不去这里)。我们现在正好有三个提交要考虑合并:B,合并基础; L,“本地”或 --ours 提交;和 R,“远程”或“其他”或 --theirs 提交。可以假定这些提交具有某种 precedes/follows 关系,1 但这不再重要:各种双头合并算法现在已准备好考虑三种可能的情况: 2

  1. B=R。如果合并基础提交 “他们的”提交,则无事可做。 Git 说 Already up to date. 什么都不做。
  2. B=L。如果合并基础“我们的”(HEAD)提交,则快进是可能的。如果允许或需要,Git 将执行此操作。这种情况下不可能发生冲突;见下文。
  3. B ≼ L, B ≺ R。需要“真正的合并”。

要执行真正的合并,Git 执行以下内部化变体:

  • 运行 git diff --find-renames <em>B L</em>:这就是“我们改变了什么” ;
  • 运行 git diff --find-renames <em>B R</em>:这就是“他们改变了什么” ;
  • 合并这些更改。

合并更改 步骤是可能发生合并冲突 的地方。它们 在以下情况下发生:

  • diff #1 中受影响的行与 diff #2 中受影响的行重叠,但对这些行的更改不相同,或者
  • 两个差异中受影响的行相邻(如 )。

重叠 当且仅当两个差异对这些行进行 相同的更改 时才允许。

如果我们在允许快进的情况下强制执行“真正的合并”(参见#2),这意味着 B = L,因此来自 [=93= 的差异]B 到 L 为空。一个空差异永远不会与另一个空差异冲突,也不与任何非空差异冲突:合并的结果是采用它们的所有更改。

如果我们有冲突,-X ours-X theirs标志,如果指定,现在开始发挥作用:这些通过支持我们的来解决冲突或他们的。对于这些情况,合并不会停止。

如果我们启用了 rerere 并且现在存在已记录解决方案的冲突,则 Git 将采用记录的解决方案。但是,对于这些情况,合并 停止:您必须自己检查结果。据推测,这因此发生在 -X 个案例之后,但我还没有测试过。

如果有未解决的冲突,合并就此停止,未完成。清理遗留的任何混乱是你的工作(在你的工作树 and/or Git 的索引中)。否则,除非指定 --squash and/or --no-commit,否则 Git 会继续进行新的合并提交。

如果合并停止,其他头(或头)的哈希 ID 将写入 MERGE_HEAD 伪引用,除非指定了 --squash。这确保下一个 git commit 将正确结束合并。


1如果他们没有,我们必须提供--allow-unrelated-histories,在这种情况下合并基础是在两个分支提示提交之前的虚拟空提交。相同的代码用于 cherry-pick 和 revert,其中某些 precedes/follows 关系可能有意不成立,因此它不检查;此描述仅用于 git merge 目的。

2可以预先检查 R ≼ L,但我不认为 Git 实际上做。效果应该是一样的。