Git 中的过程是不同的,但你应该能够在任何一个系统中实现你想要的。
TL;DR 是您必须编写自己的合并工具(或 Git 中的合并驱动程序)。此合并工具应该比较三个输入文件并执行您想要的任何合并,然后使用一组新的基本文件和输入文件运行正常的低级合并驱动程序。
长
首先,请注意 Mercurial 对“合并前”有自己的定义。有关更完整的说明,请参阅 https://www.mercurial-scm.org/wiki/MergeToolConfiguration。我将您的问题解释为根本不涉及这种预合并;相反,您想编写 Mercurial 所指的 合并工具。 (Git 将此称为 合并驱动程序。)
让我们有一些定义,以便我们都同意术语。当您运行hg merge 或git merge 时,您选择两个特定的提交进行合并。一个是您当前的提交,我们将其称为 local,因为 Mercurial 使用该名称。出于同样的原因,我们将调用另一个提交 other。 (Mercurial 有时将第二个称为 remote,但大多仅在内部使用。Git 可变地称为这些 --ours 和 --theirs,或 local 和 remote em> 或 local 和 other:Git 一点也不擅长保持一致性。)
您检查本地提交并运行 hg merge other 或 git merge other,Mercurial 或 Git 将找到 merge base 提交,它们都称为合并基础或只是基础。
在这两个系统中,所有三个有趣的提交都表示为快照:以下是基本提交的文件。这是与本地提交相同的(可能还有一些新的、一些已删除的、一些重命名的)文件。这是与其他提交相同的文件。 嘿,手鼓人版本系统先生,给我做个合并吧。
高级合并
VCS 必须做的第一件事是将每个基本提交文件与每个本地提交文件和每个其他提交文件配对。整个文件可能有两个这样的操作(创建、重命名或删除)。特别是,如果一个文件在一个或两个提交中被重命名,VCS 必须处理这个问题。如果文件在一次或两次提交中被删除,VCS 必须处理该问题。如果在两次提交中都创建了基础中不存在的文件,则 VCS 也必须处理该问题。其中一些很简单:如果文件 F 在两次提交中的 一个 中被重命名,那么我们只是在最终结果中重命名它,否则像往常一样组合更改.但是其他更改相互冲突:如果两个提交都重命名了文件,VCS 应该使用哪个名称?我将这些冲突称为高级冲突。1
在这里,与 Mercurial 相比,Git 具有某种优势。 Mercurial 让您立即为每个高级冲突选择一个解决方案,以便它知道每个文件的最终命运。2 Git 让您推迟这个决定(尽管底层实现存在问题,在吉特)。稍后我会多提一点。这部分,你不能在 Mercurial 中很好地自动化(或者至少我上次尝试的时候不能)。幸运的是,对于这两个 VCS,这类冲突往往很少见。
低级合并
既然我们知道了所有文件的命运(或者在 Git 中推迟了这个决定),Mercurial 尤其会使用您选择的工具(通过--tool 或$HGMERGE)来合并每个文件。我将此称为低级合并,以将其与配对文件和确定其名称的高级过程区分开来。这个低级合并过程在this answer 到How does Mercurial merge internally? 中进行了概述
请记住,我们有三个输入:base、local 和 other。最终合并将有 两个 父级:本地和其他。与基础相比,我们可以将每个低级文件视为在任一父级中“更改”。或者,该文件可能与父母一方或双方的文件相同。如果该文件根本没有被触及——如果它在所有三个提交中完全相同——则没有什么可做的:最终文件应该与基本文件匹配。如果文件仅在 一个 父级中被修改 - 在本地或其他地方,就基础中的内容而言 - 那么 仍然 无事可做,真的;我们可以使用修改后的。
如果文件在两个父级中以完全相同的方式进行了更改,那么我们使用哪个父级并不重要。但请注意,this wiki page 表示此过程适用:
对于父母双方更改的每个文件...
这里有一些微妙之处,值得看看 Git 和 Mercurial 之间的另一个区别。
在 Git 中,当我们拥有来自 B(基础)、L(本地)和 O(其他)的三个文件时,我们真正拥有的是三个 散列 ID。哈希 ID 唯一标识内容,因此我们可以立即判断哪些文件匹配,哪些不匹配。如果 L = O,则父母双方都有相同版本的文件,我们只取其中一个,而不管 B 是什么(两者都进行了相同的更改,或者没有人进行任何更改)。否则,如果 B = L 或 B = O,我们将采用它不匹配的那个,因为那是发生变化的父级。否则(B ≠ L,B ≠ O,L ≠ O)我们必须进行真正的合并。
Mercurial 不按哈希 ID 存储文件。相反,它知道文件是否在从 B 到 L 的提交以及从 B 到 O 的提交中的某处发生了更改。因此它只是查看自 B 以来父母双方的提交序列是否修改了文件。
所有这一切的结果是,在 Git 中,只有当所有三个输入都不同时,您的合并驱动程序才会运行。在 Mercurial 中,如果父母双方都接触了文件,您的合并工具可以运行,但两个甚至所有三个输入可能匹配。在大多数情况下,这没有区别,但请记住特殊情况。 Mercurial 内置的预合并(您不是在谈论的那个)会为您处理这种特殊情况,因此除非您禁用预合并,否则您实际上不会看到它。
当您的合并驱动程序运行时,您将三个输入和输出文件的名称传递给它,如我第一个指向 Mercurial wiki 链接的示例:
mymergetool.args = $local $other $base -o $output
(这来自您的.hgrc 或同等学历)。在 Git 中,它是类似的,只是你在 .gitconfig 或类似的文件中定义了一个合并驱动程序:
driver = filfre %O %A %B
然后从.gitattributes文件中引用这个驱动,三个输入文件之一也是输出文件(详见the gitattributes documentation)。
您的合并工具/合并驱动程序必须读取三个输入文件并使用它来计算和写入正确的输出文件——一步正如版本控制系统所见。您可以根据需要在内部使用尽可能多的步骤。完成后,如果输出文件是完全合并的正确结果,则应以状态 0 退出,如果合并需要手动编辑或进一步工作,则应退出非零(通常只有 1)。
在您的情况下,您将分析差异,自己组合一些更改,创建三个 new 输入文件,并在文件上运行一些其他文件合并工具。 Mercurial 似乎没有很好的方法来运行自己的内部低级文件合并; this wiki page 建议改用 GNU diff3 来完成这项工作,并包含一个运行 diff3 的脚本,如果它表明存在冲突,请在生成的文件冲突上运行 vi 或其他一些编辑器。
Git 包含git merge-file 命令,它对任意三个输入文件进行三路合并(实际上您可以直接从 Mercurial 使用 git merge-file)。请注意,如果git merge-file 和diff3 能够成功合并文件,则它们都已经退出 0,如果不能,则非零。
1Mercurial 允许在此处选择不同的合并算法,并且提供了多种产品:请参阅 Consensus Merge 和 Bid Merge。 Git 也允许不同的合并算法,它称为 strategies,它的默认值是它称为 recursive 的策略。在某些情况下,选择要为低级合并过程配对的文件,或者在 Git 的递归合并的情况下,构造文件是由高级合并阶段决定的。
2源代码建议 Mercurial 可以将其推迟到以后,将高级冲突保存在合并状态。不过,我并没有继续研究它,但无法找到一种方法来做到这一点。