【问题标题】:What makes merging in DVCS easy?是什么让 DVCS 中的合并变得容易?
【发布时间】:2011-02-06 11:56:25
【问题描述】:

我在Joel on Software阅读:

使用分布式版本控制, 分布式部分实际上不是 最有趣的部分。

有趣的是,这些 系统根据变化来思考,而不是 就版本而言。

HgInit:

当我们必须合并时,Subversion 试图查看两个修订版——我的 修改过的代码,和你修改过的 代码——它试图猜测如何 将它们粉碎成一个大邪恶 混乱。它通常会失败,产生 “合并冲突”的页面和页面 这不是真正的冲突,只是 Subversion 失败的地方 弄清楚我们做了什么。

相比之下,在我们工作的时候 分别在 Mercurial 中,Mercurial 是 忙着维护一系列变更集。 所以,当我们想要合并我们的代码时 在一起,Mercurial 实际上有一个 更多信息:它知道 我们每个人都改变了什么并且可以 重新应用这些更改,而不是 只看最终产品 试图猜测如何表达 在一起。

通过查看 SVN 的存储库文件夹,我的印象是 Subversion 将每个修订都维护为 changeset。据我所知,Hg 同时使用 changesetsnapshot 而 Git 纯粹使用 snapshot 来存储数据。

如果我的假设是正确的,那么一定有其他方法可以使 DVCS 中的合并变得容易。那些是什么?

* 更新:

  • 我对技术角度更感兴趣,但非技术角度的答案是可以接受的
  • 更正:
    1. Git 的概念 模型完全基于快照。快照可以存储为其他快照的差异,只是差异纯粹是为了存储优化。 – Rafał Dowgirdcomment
  • 从非技术角度:
    1. 这只是文化问题:如果合并很困难,DVCS 将根本无法工作,因此 DVCS 开发人员投入了大量时间和精力来简化合并。 CVCS 用户 OTOH 习惯于糟糕的合并,因此开发人员没有动力让它工作。 (当您的用户为一些垃圾支付给您同样高的费用时,为什么要制作一些好的东西?)
      ...
      回顾一下:DVCS 的全部意义在于拥有许多分散的存储库并不断地来回合并更改。如果没有良好的合并,DVCS 根本就没用。然而,CVCS 仍然可以在糟糕的合并中存活下来,特别是如果供应商可以调整其用户以避免分支。 – Jörg W Mittaganswer
  • 从技术角度:
    1. 记录历史的真实 DAG 确实有帮助!我认为主要区别在于 CVCS 并不总是将合并记录为具有多个父级的变更集,从而丢失了一些信息。 – tonfacomment
    2. 因为合并跟踪,以及每个修订版本都知道其父版本这一更基本的事实。 ...当每个修订版(每个提交),包括合并提交,都知道它的父级(对于合并提交,这意味着拥有/记住多个父级,即合并跟踪),您可以重建修订图(DAG = Direct Acyclic Graph)历史。如果您知道修订图,则可以找到要合并的提交的共同祖先。而且,当您的 DVCS 知道自己如何找到共同祖先时,您不需要将其作为参数提供,例如在 CVS 中。
      .
      请注意,两个(或多个)提交的共同祖先可能不止一个。 Git 使用所谓的“递归”合并策略,它合并合并基础(共同祖先),直到您留下一个虚拟/有效的共同祖先(在某些简化中),并且可以进行简单的 3 路合并。 – Jakub Narębski's answer

也检查How and/or why is merging in Git better than in SVN?

【问题讨论】:

  • “Git 纯粹使用快照来存储数据”——这只是部分正确。 Git 的conceptual 模型完全基于快照。快照可以存储为其他快照的差异,只是差异纯粹是为了存储优化。
  • 请注意,HgInit 是错误的,或者至少在该页面的某个方面具有误导性:它声称如果您移动一个函数并同时更改它,DVCSes 将能够合并它。实际上,这种移动/复制跟踪只发生在整个文件级别。
  • @LaurensHolst:我的理解是 Git 确实会跟踪这种变化(尽管我还没有运行测试来确认)。我不知道 Mercurial 有没有。
  • 有人愿意将它与stackoverflow.com/q/2475831/157957 和/或stackoverflow.com/q/43995/157957 合并吗?我认为所有这些都有有趣的答案(以及一些相当过时的答案),但它们涵盖的领域完全相同。

标签: svn git version-control mercurial dvcs


【解决方案1】:

在 DVCS 中没有什么特别的东西可以让合并变得更容易。这只是文化问题:如果合并很困难,DVCS 根本行不通,因此 DVCS 开发人员投入大量时间和精力来简化合并。 CVCS 用户 OTOH 习惯于糟糕的合并,因此开发人员没有动力让它工作。 (当你的用户为一些废话付给你同样高的报酬时,为什么要做出好的东西呢?)

Linus Torvalds 在他的一次 Git 演讲中说,当他在 Transmeta 使用 CVS 时,他们在开发周期中留出了 整整一周 用于合并。每个人都只是接受这是正常的事态。如今,在合并窗口期间,Linus 在短短几个小时内完成了数百次合并。

如果 CVCS 用户只是去找他们的供应商并说这种废话是不可接受的,CVCS 可能具有与 DVCS 一样好的合并功能。但他们陷入了 Blub 悖论:他们只是不知道这是不可接受的,因为他们从未见过一个有效的合并系统。他们不知道那里有更好的东西。

当他们尝试 DVCS 时,他们神奇地将所有优点归功于“D”部分。

理论上,由于集中的性质,CVCS 应该具有更好的合并功能,因为它们具有整个历史的全局视图,不像 DVCS 只是每个存储库有一个小碎片。

回顾一下:DVCS 的重点是拥有许多分散的存储库并不断地来回合并更改。如果没有良好的合并,DVCS 根本就没用。然而,CVCS 仍然可以在糟糕的合并中存活下来,特别是如果供应商可以调整其用户以避免分支。

因此,就像软件工程中的其他所有事情一样,这是一个努力的问题。

【讨论】:

  • 记录历史的真实 DAG 确实有帮助!我认为主要区别在于 CVCS 并不总是将合并记录为具有多个父级的变更集,从而丢失了一些信息。
  • @tonfa:你是对的,当然。但同样,这并不是 CVCS 的真正限制,只是部分开发人员的懒惰。没有理由 CVCS 不能记录完整的 DAG,包括合并。 Subversion 花了 10 年时间来记录合并,尤其是因为第三方工具已经存在至少 5 年,这一事实足以说明问题。我的意思是,他们没有对数据格式进行任何更改就做到了!换句话说:他们需要的一切在十年前就已经存在了。
  • 我认为到服务器的往返行程也有贡献。由于 Hg 有完整的本地历史记录,因此信息就在那里,而不是 Subversion。
  • @msemack:您可以通过在 SVN 网络协议中添加一个新命令来在服务器上执行合并:“merge A into B”。或者,您可以在客户端缓存整个历史记录。 Subversion已经缓存一个版本,它没有理由不能缓存所有版本。 (虽然他们这样做:清理缓存,因为目前 Subversion 需要更多的磁盘空间缓存 one 修订版本,而不是 Git 和 Mercurial 需要缓存 1000。Subversion 的 Mercurial 签出(~20000 revs)是仅比 Subversion (1 rev) 的 Subversion 检出略大。
【解决方案2】:

在 Git 和其他 DVCS 中合并很容易,这不是因为某些神秘的 一系列变更集 视图(除非您使用 Darcs 及其补丁理论,或者一些受 Darcs 启发的 DVCS;它们是少数,不过)Joel 漫不经心,但由于 合并跟踪,以及 每个修订版本都知道其父版本这一更基本的事实。为此,您需要(我认为)整个树/完整存储库提交......不幸的是,这限制了进行部分签出的能力,并且只提交文件的子集。

当每个修订版(每个提交),包括合并提交,都知道它的父级(对于合并提交,这意味着拥有/记住多个父级,即 合并跟踪),您可以重建图表(DAG = 直接无环图)的修订历史。如果您知道修订图,则可以找到要合并的提交的共同祖先。而且当您的 DVCS 知道自己如何找到共同祖先时,您不需要将其作为参数提供,例如在 CVS 中。

请注意,两个(或多个)提交的共同祖先可能不止一个。 Git 使用所谓的“递归”合并策略,它合并合并基础(共同祖先),直到你只剩下一个虚拟/有效的共同祖先(在某种简化中),并且可以进行简单的 3 路合并。

重命名检测 的 Git 使用被创建为能够处理涉及文件重命名的合并。 (这支持Jörg W Mittag 论证 DVCS 具有更好的合并支持,因为他们必须拥有它,因为合并比 CVCS 更常见,其合并隐藏在“更新”命令中,在 update-then-commit 工作流程中,参见 @987654322 @(WIP)埃里克·S·雷蒙德)。

【讨论】:

  • 那么,实际上,很酷的“合并很容易” DCVS 的唯一区别是让您在合并时省略祖先的功能?在您看来,祭祖活动真的很痛苦而且很耗时吗?
  • @systempuntoout:DVCS,如 Git 和 Mercurial 存储查找共同祖先所需的信息; Subversion 没有(即使 Subversion 1.6 的 merged-in infosvn:mergeinfo 属性的形式也不容易:恕我直言 svn:mergeinfo 设计错误并解决了错误的问题,但它是“分支”的颠覆概念的结果)。
  • 我一直在努力解决的问题的绝佳答案。谢谢。
  • 我认为找到共同祖先的能力是我看到的第一个功能,它如何 DVCS 的架构使得复杂的合并从根本上更容易计算(例如,几个多个方向的分支),而不是以巧妙的方式(例如更好地处理重命名)使用相同的信息(每个分支上记录的一系列更改)。
  • @JakubNarębski 我现在很好奇,如果 Git/Hg 和 SVN 之间仍然存在这些差异,我们已经过去了 5 年?
【解决方案3】:

我认为,正如其他人所提到的,变更集的 DAG 有很大的不同。 DVCS:es 需要在基本层面上拆分历史记录(和合并),而我认为 CVCS:es(较旧)从第一天开始构建以首先跟踪修订和文件,并在事后添加合并支持。

所以:

  • 当标签/分支与源目录树分开跟踪时,合并很容易进行和跟踪,因此可以一次性合并整个 repo。
  • 由于 DVCS:es 有本地存储库,这些很容易创建,因此很容易将不同的模块保存在不同的存储库中,而不是在一个大存储库中跟踪它们。 (因此,repo 范围的合并不会像在 svn/cvs 中那样造成相同的中断,其中一个 repo 通常包含许多不相关的模块,这些模块需要具有单独的合并历史记录。)
  • CVS/SVN 允许工作目录中的不同文件来自不同的修订版,而 DVCS:es 通常对整个 WC 有一个修订版,始终(即即使文件恢复到早期版本,它也会显示在状态中修改,因为它与签出版本中的文件不同。SVN/CVS 并不总是显示这一点。)

我相信混合这些概念(就像 Subversion 所做的那样)是一个大错误。例如,在源代码树中有分支/标签,因此您必须跟踪文件的哪些修订已合并到其他文件。这显然比仅跟踪已合并的修订更复杂。

所以,总结一下:

  • DVCS:es 需要简单的合并,有基于此的功能集。设计决策使这些合并易于执行和跟踪(通过 DAG),并实施其他功能(分支/标签/子模块)以适应这种情况,而不是相反。
  • CVCS:es 从一开始就具有一些功能(例如模块),这些功能使一些事情变得简单,但使得 repo 范围的合并非常难以实现。

至少这是我在使用 cvs、svn、git 和 hg 的经验中的感受。 (可能还有其他 CVCS:es 也做对了这件事。)

【讨论】:

  • 嗯.. Subversion 将目录作为版本历史中的第一类对象进行跟踪。当时是一个伟大的决定(因为它可以轻松地跟踪副本和删除;这在以前非常困难),但不是一个让合并和重命名处理更容易的决定。混合修订工作副本在基于 pre-svn 文件的版本控制世界中很常见,但这是另一件使合并更加困难的事情。新的 DVCS 做出了其他选择,吸取了过去的教训;这解决了一些场景,但同时引入了其他问题。 (但这些都不是 DVCS 或 CVCS 特定的;只是实现)
【解决方案4】:

部分原因当然是技术论点,即 DVCS 比 SVN 存储更多信息(DAG、副本),并且还具有更简单的内部模型,这就是为什么它能够执行更准确的合并,如在其他回复。

然而,一个更重要的区别可能是,因为您有一个本地存储库,所以您可以进行频繁的小提交,也可以频繁地提取和合并传入的更改。这更多是由“人为因素”造成的,即人类使用集中式 VCS 与 DVCS 的工作方式不同。

使用 SVN,如果您更新并且存在冲突,SVN 将合并它可以合并的内容,并在您的代码中插入不能执行的标记。这样做的大问题是,在您解决所有冲突之前,您的代码现在将不再处于可工作状态。

这会分散您对尝试完成的工作的注意力,因此通常 SVN 用户在执行任务时不会合并。结合这一点,SVN 用户也倾向于让更改累积在一个大型提交中,以免破坏其他人的工作副本,并且在分支和合并之间会有很长一段时间。

使用 Mercurial,您可以在较小的增量提交之间更频繁地合并传入的更改。根据定义,这将减少合并冲突,因为您将使用更新的代码库。

并且如果结果证明存在冲突,您可以决定推迟合并并在自己的空闲时间进行。这尤其使合并变得不那么烦人了。

【讨论】:

  • 请注意,上面我主要讨论的是匿名分支(SVN 工作副本,因此由svn update 执行的合并),但这也适用于命名分支(SVN 分支)。
【解决方案5】:

哇,五段散文的攻击!

简而言之,没有什么能让它变得容易。这很难,而且我的经验表明确实会发生错误。但是:

  • DVCS 强制您处理合并,这意味着您需要花几分钟时间熟悉现有的可帮助您的工具。仅此一项就有帮助。

  • DVCS 鼓励您经常合并,这也很有帮助。

您引用的 hginit 的 sn-p 声称 Subversion 无法进行三向合并,而 Mercurial 通过查看两个分支中的所有变更集进行合并,在这两个方面都是错误的。

【讨论】:

  • 简而言之,我认为既有技术组件,更好的合并算法,也有工作流组件,DVCSes更好地支持频繁合并。
  • DVCSes 也有一个更简单的工作副本视图。在 Subversion 中,您可以拥有混合的修订工作副本和部分工作副本,这当然不是使合并更容易的组件。
  • @Laurens 你的意思是什么更好的合并算法?除非我大错特错,Mercurial 没有做任何比svn up 更出色的事情,就合并所有可能的更改而言。而且我认为这两个程序都通过将问题委托给某些外部合并程序来处理冲突(或者,如果失败,则在文件中放置冲突标记)。
  • 也许我弄错了,但即使 SVN 跟踪副本,我也从未真正看到过正确的副本合并。
【解决方案6】:

我发现使用 DVCS 更容易的一件事是,每个开发人员都可以将他们自己的更改合并到他们想要的任何存储库中。当您合并自己的代码时,处理合并冲突要容易得多。我曾在一些可怜的灵魂通过找到涉及的每个开发人员来解决合并冲突的地方工作过。

您还可以使用 DVCS 执行以下操作,例如克隆存储库、将两个开发人员的工作合并到克隆中、测试更改,然后从克隆合并回主存储库。

很酷的东西。

【讨论】:

    【解决方案7】:

    可能是 DVCS 用户从不做使合并变得困难的事情,例如更改和重命名/复制项目中大多数文件的重构,或从数百个文件中使用的分层 API 重新设计。

    【讨论】:

      【解决方案8】:

      一点是svn合并被巧妙地破坏了;请参阅http://blogs.open.collab.net/svn/2008/07/subversion-merg.html 我怀疑这与 svn 记录 mergeinfo 即使在樱桃采摘合并中也是如此。在处理边界情况时添加一些简单的错误,svn 作为 CVCS 的当前典型子项使它们看起来很糟糕,而不是所有刚刚做对的 DVCS。

      【讨论】:

      【解决方案9】:

      作为一个历史记录,现在过时的PRCS 系统也知道共同的祖先并且可以有效地合并,尽管它不是分布式的(它是建立在 RCS 文件之上的!)。这意味着它可以在保留历史记录的同时有效地迁移到 git。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2021-08-12
        • 2015-06-06
        • 2011-04-22
        • 2019-01-31
        • 1970-01-01
        • 2023-04-08
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多