合并中没有“优先级”之类的东西。好吧,可以有,但是你分配它;你马上就会明白我的意思。
合并是关于合并的工作。然而,为了结合工作,你和他们,无论他们是谁,都必须有一个共同的起点。由于 Git 是关于 commits 的——文件只是嵌入到提交中——这个共同的起点是 共享提交。
例如,尝试使用git log --graph --decorate --online branch-update branch-jon。您将看到的可能非常复杂;我要画一些简单的东西。 Git 将最近的提交绘制在顶部,将较早的提交绘制在下方;由于篇幅原因,我倾向于水平绘制图表。
请记住,每个提交都是所有文件的完整快照,被及时冻结——不可变——并包含其前身或父提交的哈希ID。这意味着从最近的提交开始,由某个分支名称标识,我们或 Git 可以向后工作,一次提交一个:
I--J <-- branch-update (HEAD)
/
...--F--G--H
\
K--L <-- branch-jon
在这个(高度简化,到了不切实际的简单)示例中,您和 Jon 从共同的起点开始分别进行了两次提交。实际的共同起点是提交 H(H 确实是一些大而丑陋的哈希 ID,git log --oneline 将打印缩写)。通过从您的最新提交 J 开始并向后工作到 I 然后 H,并从 Jon 的最新提交 L 开始并向后工作到 K 然后 H,Git 将自动 找到这个 best-shared-common-commit。 Git 将此最佳共享提交称为合并操作的合并基础。 (请注意,G 和 F 等提交也是共享的;它们只是不是最佳。)
(Git 可能会将此图绘制为:
* abcdef0 (HEAD -> branch-update) your commit subject
* c9f1233 your previous commit subject
| * 5bf3149 (branch-jon) his commit subject
| * 78983fc his previous commit subject
|/
* 371820d shared commit subject line
[snip]
尽管方向发生了变化,但这两张图代表的是同一张图。)
您的实际图表会更加复杂。可能不清楚合并基地在哪里。你可以让 Git 告诉你哪个提交是(或者,可能但不太可能,提交是)合并基础:
git merge-base --all branch-update branch-jon
如果这会产生多个合并基,情况会稍微复杂一些,但通常您只会看到一个提交哈希 ID 作为输出。
在任何情况下,在找到合并基础之后——我们就叫它H,就像我的绘图一样——Git 现在必须将H(合并基础)中的快照与 您的 进行比较最新快照J,以了解您所做的更改。它还必须将H 中的快照与另一个最新快照进行比较,以查看 Jon 发生了什么变化。所以它运行两个git diff 命令:
-
git diff --find-renames <em>hash-of-H</em> <em>hash-of-J</em>:你改变了什么
-
git diff --find-renames <em>hash-of-H</em> <em>hash-of-L</em>: Jon 改变了什么
现在 Git 实际上组合这些更改。它首先从提交H 中提取所有文件——从共同的起点开始。对于这些文件,它会应用您的更改。它还应用了 Jon 的更改。在某些情况下,申请很容易:例如,如果您更改了文件 F1 和 F2 而 Jon 没有接触这些文件,结果是 your F1 和 your F2。如果 Jon 触摸了F3 而你没有触摸,那么结果就是他的F3。 H 中的许多其他文件可能与 J 和 L 中的完全相同,因此您甚至无法判断 Git 使用的是 H's、J's 还是 L's .但是对于一些文件,例如F4,both 你和 Jon 做了一些更改。
这是git merge 真正需要努力的地方。在所有其他情况下,git merge 只需从H、J 或L 获取一些文件即可。对于这些文件——你两个都接触过的文件——Git 确实必须合并这些更改。
如果您更改了 F4 的第 10 到 15 行,Git 会将 您的 更改为这些行。如果 Jon 更改了第 20 到 25 行,Git 会将 他的 更改为这些行。优先级没有需要:Git 只需要两个 更改。
如果您都更改了第 30 到 35 行怎么办?好吧,如果你们都对这些行进行了相同的更改,Git 只会获取这些更改的一个副本,并将该副本应用到来自H 的文件中。 p>
但是,如果你们俩都更改了第 30 到 35 行,并且你做了不同的更改,那么,现在有一个问题。 这是不存在的优先级不进来的地方。默认情况下,Git 只是声明一个合并冲突并放弃完成合并。它给您留下一团糟:在您的工作树中,您有一个文件,其中包含围绕冲突的更改集的冲突标记。 Git 还会在索引中为您留下所有三个 输入文件。作为操作 Git 机器的人,你的工作就是收拾烂摊子。
这也是你可以选择的“优先级”的来源。如果你使用标准的recursive合并策略,你可以使用-X ours或-X theirs参数——我称之为 eXtended arguments,尽管 Git 称它们为 strategy arguments——告诉 Git:如果发生冲突,请选择我的更改 (-X ours) 或他的更改 (-X theirs)。
总结
大多数人在想到git merge 时首先犯的错误是认为只有两个输入,然后运行:
git diff branch-update branch-jon
告诉你会发生什么。那是完全错误的!每个合并有三个输入:当前 (HEAD) 提交及其文件、您提供的另一个提交 (branch-jon) 和 合并基础。 Git 从 提交图 中自行找到第三个输入(在重要的意义上它实际上是 第一个 输入)。图表在这里非常重要。该图决定了合并基数,而合并基数与两个分支提示进行比较时,决定了合并的结果。
合并过程本身——Git 到达将进入新合并的内容的路径——是对称的。哪个git diff 先运行并不重要。但是,当您说 -X ours 或 -X theirs 时,就会破坏对称性:当冲突发生时,您选择一方占优势。
最后,当 Git 从合并的文件中进行提交时,这也是不对称的。新提交的 first 父级基于HEAD:无论HEAD 附加到哪个分支,该分支的提示提交都是新合并提交的第一个父级。新提交的 second 父级是另一个提交。当然,新提交的哈希 ID 通过 HEAD 进入当前分支,因此当我们获得新的合并提交 M 时,当前 分支是获取它的分支:
I--J
/ \
...--F--G--H M <-- branch-update (HEAD)
\ /
K--L <-- branch-jon
请注意,此合并提交及其两个父项的存在会在未来的合并中更改图形结构。现在branch-update 和branch-jon 之间最好的共享common commit 是commit L:
I--J
/ \
...--F--G--H M--N--O <-- branch-update (HEAD)
\ /
K--L--------P <-- branch-jon
如果您现在运行 git merge branch-jon,Git 将比较 L 与 O 以查找我们更改的内容,并通过 L 与 P 查找他们更改的内容。