当SVN无法合并而Mercurial成功时

19

在此之前,我已经查看了几个有关这个主题的线程,包括以下内容:

我正在考虑将我们的开发组从Subversion转换到Mercurial。在这样做之前,我正在列出通常的利弊清单。

其中一个"优点"是,在Mercurial中合并更加优秀,但到目前为止,我未能找到有力的证据支持这一点。特别是,在HgInit.com上有一种说法,我无法验证:

例如,如果我稍微修改了一个函数,然后将其移动到其他地方,Subversion并不真正记住这些步骤,因此在合并时,它可能认为新的函数只是突然出现。而Mercurial将分别记住这些内容:函数已更改,函数已移动,这意味着如果您也稍微更改了那个函数,那么Mercurial成功合并我们的更改的可能性会更大。

这将是非常有吸引力的功能,但据我所知,这只是空话。我无法验证上述说法。

我创建了一个Mercurial仓库,做了一个Hello World,然后进行了克隆。在其中一个中,我修改了函数,提交了它,然后将其移动,并提交了它。在另一个中,我只是简单地向函数添加了另一个输出行,并进行了提交。

当我进行合并时,我得到的基本上是使用Subversion得到的相同合并冲突。

我知道Mercurial在跟踪文件重命名方面更加出色,还有其他DVCS合并之外的优点,但我对这个例子很感兴趣。Joel Spolsky在这里是错的吗?还是我漏掉了什么?

虽然我在这个领域经验不丰富,但由于Mercurial保留了更多信息,它可以理论上更好地执行合并(如果开发人员也进行频繁的提交)。例如,我认为Mercurial可以从比较多个更改中获取上下文变化,例如,我修改一个函数,提交,移动该函数,提交,然后Mercurial关联这两个部分。

但是,Mercurial的合并似乎没有真正利用这些额外的信息,并且看起来与Subversion操作方式相同。这是正确的吗?


1
+1 有趣 - 我们正在将多GB的svn代码库迁移到mercurial,其中一个重要的卖点是更容易合并。 - Seth
可能是重复的[Mercurial合并功能-我错过了什么?](https://dev59.com/PFHTa4cB1Zd3GeqPV_Tm) - Wim Coenen
2
我认为这个特定引用中至关重要的一点是“更有可能”。如果你观看Linus Torvalds展示Git像是切成薄片面包之后的下一个最好的东西的视频,他也声称Git可以跟踪移动到代码库中的函数。我猜大部分都是推销员的夸大其辞,因为我已经验证了Mercurial和Git都不能实际跟踪从一个文件移动到另一个文件的代码。代码只被跟踪到“在这里删除了某些内容”和“在那里添加了其他内容”的程度。 - Lasse V. Karlsen
话虽如此,我现在使用Mercurial后就不会再使用Subversion了。它在合并方面更加优秀,尽管在这里描述的特定场景上有所不足。所以,如果你能按照Mercurial的方式做事情,那么Mercurial比Subversion更好地进行合并。但是,如果你不这样做(但在Subversion中也可以这样做),你可能会把事情搞得一团糟。 - Lasse V. Karlsen
是的,那正是我遇到困难的确切短语。在像自动合并这样没有概率方法的情况下,它如何“更有可能”成功呢?最终,在Mercurial和SVN中合并变更集和合并最终结果似乎是相同的。 - JWman
3个回答

6
据我所知,任何声称Mercurial在小于文件大小的代码块中跟踪移动代码的人都是错误的。它可以独立地跟踪文件重命名和代码更改,因此,如果Dave重命名了一个文件并且Helen更改了该文件中的内容,它可以自动合并,但据我所知,Subversion也可以做到这一点!(CVS不能。)
但是,Mercurial的合并逻辑比Subversion的要好得多:它记住了冲突解决方案。请考虑以下历史图表:
base ---> HELEN1 ---> merge1 --> HELEN2 -> merge2
     \--> DAVE1 ---/                    /
                   \--> DAVE2 ---------/

海伦和戴夫独立进行更改。 海伦拉取了戴夫的树并合并,然后在此基础上又做了一些更改。 与此同时,戴夫继续编码而不打扰海伦拉取。 然后,海伦再次拉取了戴夫的树。 (也许戴夫正在主开发分支上工作,而海伦则正在功能分支上工作,但她想定期与主干同步更改。)在构建“merge2”时,Mercurial 将记住“merge1”中完成的所有冲突解决,并仅向海伦显示新的冲突,但Subversion会要求海伦从头开始重新执行合并。(有一些方法可以避免使用Subversion时必须这样做,但它们都涉及额外的手动步骤。Mercurial会为您处理这个问题。)
要了解更多信息,请阅读有关Monotone开发的标记合并算法,该算法现在被Mercurial和Git使用。

3
我不认为这是正确的,至少对于 SVN 的 v1.5 版本来说是如此。根据 http://svnbook.red-bean.com/en/1.5/svn-book.html#svn.branchmerge.cherrypicking 的说明,SVN 确实会记住先前的合并记录,甚至可以让你单独地合并特定版本的更改(即挑选)并在稍后“完成”合并而无需重新执行那些工作。 - JWman
此外,这是一个有趣的链接,介绍了mark-merge算法。我还没有完全阅读它,但我会的。然而,进一步挖掘后发现,根据这篇文章,Mercurial在自身上并不执行_任何_合并操作。 - JWman
我承认自从1.1版本以来就没有使用过SVN,但我仍经常看到人们抱怨它的问题,如果它能记住之前的合并,他们就不会有这些问题。也许他们仍在使用1.1版本。;-) 至于Mercurial和您发布的链接,他们没有很好地解释“解决冲突”和“决定是否存在需要解决的冲突”的区别,前者普遍需要外部工具,而后者是mark-merge的工作。 - zwol
啊,那确实有道理。虽然我已经使用SVN多年,但我很惭愧地承认我对合并的工作原理还不够了解。但似乎版本控制系统只是在找到需要合并的位置,然后使用外部工具来完成工作方面存在差异。我一直以为它们只是用外部工具来解决冲突(例如-KDiff)。 - JWman

1
据我所知,SVN在内部完成所有合并操作 - 合并工具仅用于存在冲突的情况下(显然),它需要告诉您并让您修复它。
非冲突情况基于应用补丁 - 即,svn将采取您在修订中进行的更改,并将其应用于目标。 SVN的最新版本(自1.5以来)记住了您之前执行的合并操作,并将此信息存储在与目录相关联的属性中。 与1.5相比,1.6在处理这些属性方面做得更好。

SVN不是通过比较两个树并对它们进行差异合并来进行合并的 - 请参阅该书 - 除非要合并的树没有关联,那么它将执行一种差异类型的合并操作(或者您指定了--ignore-ancestry选项)。这里有一个简要描述发生了什么。 当您合并过去的冲突时,您可以看到这一点 - 一旦您解决了棘手的修订合并,svn会记住哪些修订已经合并,并再次应用这些更改。您可以通过尝试它来证明这一点 - 分支,编辑2个文件,合并以获得冲突。仅在相同行上编辑分支文件,然后合并 - 即使目标文件没有更改,它也会弹出一个冲突,但因为合并正在将更改应用于与分支文件期望的不同的行(就像补丁一样,它显示它认为将要更改的行应该是什么)。实际上,您不会看到这一点,因为您不会反复拒绝您的合并更改。

然而,SVN在跟踪重命名文件方面表现不佳,因为它将其作为删除+添加操作进行跟踪。其他SCM做得更好 - 但即使是它们也无法真正确定文件是否被重命名,或者是否被删除并添加,特别是当该文件也被修改时。Git使用一些启发式方法来尝试确定这一点,但我看不到它能保证成功。除非我们有一个钩入文件系统的SCM,否则我认为这种情况将继续存在。


1

两件事:

  1. Mercurial确实拥有内部代码执行合并操作,只有在内部"预合并"失败后才会调用外部合并工具。

    您链接到 HG Book,其中提到没有内置工具来处理冲突(与没有内置合并不同),以及 Mercurial Wiki,其中说明Mercurial将在调用外部程序之前尝试在内部进行合并。

  2. 您链接到的问题中,我的答案 给出了Mercurial成功而Subversion在合并时失败的明确情况。这是使用Mercurial和Subversion中的内部合并代码进行的开箱即用比较。


谢谢澄清。是的,我在阅读时有点犯了错误。但就第二点而言,我明白了,Mercurial 处理重命名更好。但我的理解是,在内容方面合并两个文件在 SVN 和 Hg 中基本上是相同的。问题是由 Joel 暗示 Mercurial 从保留整个 changeset 的过程中获取更多信息。在我看来,这唤起了增量合并的想法,其中两个 changset 有很多增量变化,为 Mercurial 提供足够的上下文,使其在合并方面更加智能。但显然不是这样。 - JWman
不,Mercurial的合并机制中没有任何魔法涉及 :-) - Martin Geisler

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接