合并:Hg/Git 与 SVN

Posted

技术标签:

【中文标题】合并:Hg/Git 与 SVN【英文标题】:Merging: Hg/Git vs. SVN 【发布时间】:2011-01-29 08:52:26 【问题描述】:

我经常读到 Hg(以及 Git 和...)在合并方面比 SVN 更好,但我从未见过 Hg/Git 可以在 SVN 失败(或 SVN 需要手动干预的地方)合并某些东西的实际示例。您能否发布一些分支/修改/提交/...操作的分步列表,以显示当 Hg/Git 愉快地继续前进时 SVN 会失败的地方?实用的,不是非常特殊的情况请...

一些背景知识:我们有几十名开发人员使用 SVN 开发项目,每个项目(或一组类似项目)都在自己的存储库中。我们知道如何应用发布分支和功能分支,因此我们不会经常遇到问题(即,我们一直在那里,但我们已经学会克服 Joel's problems 的“一个程序员对整个团队造成创伤” ”或“需要六个开发人员两周时间来重新集成一个分支”)。我们有非常稳定的发布分支,仅用于应用错误修复。我们的主干应该足够稳定,可以在一周内创建一个版本。而且我们有单个开发人员或开发人员组可以处理的功能分支。是的,它们在重新集成后被删除,因此它们不会弄乱存储库。 ;)

所以我仍在努力寻找 Hg/Git 相对于 SVN 的优势。我很想获得一些实践经验,但是我们还没有任何更大的项目可以迁移到 Hg/Git,所以我一直在玩只包含几个组成文件的小型人工项目。而且我正在寻找一些你可以感受到 Hg/Git 令人印象深刻的力量的案例,因为到目前为止我经常阅读它们,但我自己却没有找到它们。

【问题讨论】:

我认为你应该注意精确重复:***.com/questions/43995/…***.com/questions/459891/… 第一本我已经看过了,另一本是新的。但是它们已经 1-2 岁了,而且似乎主要是关于 svn-1.5 之前的问题(svn 还没有合并跟踪)。 只是一个评论,您也可以将 Bazaar 与 git/hg 混为一谈,作为另一个可以正确处理以下问题的 DVCS。既然你提到试图找到优势:git/hg/bzr 的一个简单的后勤优势是分支不像 svn 那样是全局的。当只有一对适用于您时,您不必看到 67 个分支机构。每个人都在“私有”分支中工作,然后使用出色的合并功能重新合并,而无需担心合并是否在 99% 的情况下有效。 @wade:您认为“私人”分支机构在企业环境中是一种优势吗?我担心备份。我经常有功能分支在重新整合之前可以存活 1-2 个月。 @stmax:一个有效的问题。但是,您在许多具有颠覆性的公司环境中发现的是,人们推迟签入直到他们的代码完美无缺,而您在那里也有同样的风险。 【参考方案1】:

我自己不使用 Subversion,但从 release notes for Subversion 1.5: Merge tracking (foundational) 看来,合并跟踪在 Git 或 Mercurial 等完整的DAG 版本控制系统中的工作方式存在以下差异。

将主干合并到分支不同于将分支合并到主干:由于某种原因,将主干合并到分支需要 --reintegrate 选项到 svn merge

在 Git 或 Mercurial 等分布式版本控制系统中,主干和分支之间没有技术差异:所有分支都是平等创建的(但可能存在社会差异) .任一方向的合并都以相同的方式完成。

您需要为svn logsvn blame 提供新的-g (--use-merge-history) 选项以考虑合并跟踪。

在 Git 和 Mercurial 中,显示历史记录(日志)和责备时会自动考虑合并跟踪。在 Git 中,您可以请求仅使用 --first-parent 关注第一个父母(我猜 Mercurial 也存在类似的选项)以“丢弃”git log 中的合并跟踪信息。

据我了解,svn:mergeinfo 属性存储有关冲突的每个路径信息(Subversion 是基于变更集的),而在 Git 和 Mercurial 中,它只是提交对象,可以有多个父对象。

Subversion 中合并跟踪的“已知问题” 小节表明重复/循环/反射合并可能无法正常工作。这意味着对于以下历史,第二次合并可能不会做正确的事情(“A”可以是主干或分支,“B”可以分别是分支或主干):

*---*---x---*---y---*---*---*---M2

在上述 ASCII-art 被破坏的情况下:分支 'B' 是从分支 'A' 在修订版 'x' 创建(分叉),然后分支 'A' 在修订版 'y' 合并到分支' B'作为合并'M1',最后分支'B'合并到分支'A'作为合并'M2'。

*---*---x---*-----M1--*---*---M2

在上述 ASCII-art 被破坏的情况下:分支 'B' 是从分支 'A' 在修订版 'x' 中创建(分叉)的,它在 'y' 处合并到分支 'A' 中作为 'M1' ,然后再次合并到分支“A”中作为“M2”。

Subversion 可能不支持 criss-cross merge 的高级情况。

*----b-----B1--M1--*---M3 \ \ / / \ X / \ / \ / \--B2--M2--*

Git 在实践中使用“递归”合并策略很好地处理了这种情况。我不确定 Mercurial。

“已知问题”中,警告说合并跟踪可能不适用于文件重命名,例如当一侧重命名文件(并且可能修改它),而另一侧修改文件而不重命名(在旧名称下)时。

Git 和 Mercurial 在实践中都能很好地处理这种情况:Git 使用 rename detection,Mercurial 使用 rename tracking

HTH

【讨论】:

不知何故(Markdown 解析器出错?)<pre>...</pre> 块之后的部分没有缩进,因为它应该是...... +1 获取许多详细示例。我还不明白为什么第一个 ascii-art 中的示例可能会导致问题。它看起来像是处理特征分支的标准方法:假设 A 是主干,B 是特征分支。您每周从 A 合并到 B,当您完成该功能后,您将所有内容从 B 合并到 A,然后删除 B。这对我一直有效。我误解了图表吗? 请注意,我不知道(我没有检查过)上面给出的示例在 Subversion 中确实存在问题。我认为重命名和交叉合并是 SVN 中的真正问题。 reintegrate 合并是一个特殊选项,可以在最常见的合并情况下帮助您解决问题 - svn 中的分支和主干之间也没有技术差异。我倾向于从不使用它,并坚持使用标准合并选项。尽管如此,svn merge 的唯一问题是它将移动/重命名视为删除+添加。 --reintegrate 已弃用。【参考方案2】:

我们最近从 SVN 迁移到 GIT,并面临同样的不确定性。有很多轶事证据表明 GIT 更好,但很难找到任何例子。

我可以告诉你,GIT 在合并方面比 SVN 好很多。这显然是轶事,但有一个表格可以遵循。

以下是我们发现的一些内容:

SVN 过去常常在看起来不应该的情况下引发大量树冲突。我们从未深入了解这一点,但它不会发生在 GIT 中。 虽然更好,但 GIT 明显更复杂。花一些时间进行培训。 我们习惯了我们喜欢的 Tortoise SVN。 Tortoise GIT 不太好,这可能会让你望而却步。但是我现在使用 GIT 命令行,我更喜欢 Tortoise SVN 或任何 GIT GUI。

在评估 GIT 时,我们进行了以下测试。这些表明 GIT 在合并方面是赢家,但幅度不大。实际上,差异要大得多,但我想我们还没有设法复制 SVN 处理不好的情况。

【讨论】:

【参考方案3】:

我也一直在寻找一种情况,例如,Subversion 无法合并分支,而 Mercurial(以及 Git、Bazaar 等)做了正确的事情。

SVN 书籍describes how renamed files are merged incorrectly。这适用于 Subversion 1.5、1.6、1.7 和 1.8!我试图重现以下情况:

光盘 /tmp
rm -rf svn-repo svn-checkout
svnadmin 创建 svn-repo
svn checkout file:///tmp/svn-repo svn-checkout
cd svn 结帐
mkdir 主干分支
回声“再见,世界!” > 树干/hello.txt
svn 添加主干分支
svn commit -m '初始导入。'
svn copy '^/trunk' '^/branches/rename' -m '创建分支。'
svn 开关 '^/trunk' 。
回声“你好,世界!” > 你好.txt
svn commit -m '更新主干。'
svn switch '^/branches/rename' 。
svn 重命名 hello.txt hello.en.txt
svn commit -m '在分支上重命名。'
svn 开关 '^/trunk' 。
svn merge --reintegrate '^/branches/rename'

根据这本书,合并应该干净地完成,但重命名文件中的数据错误,因为trunk 上的更新被遗忘了。相反,我得到了一个树冲突(这是与 Subversion 1.6.17,在撰写本文时 Debian 中的最新版本):

--- 将存储库 URL 之间的差异合并到 '.' 中: 一个你好.en.txt C你好.txt 冲突总结: 树冲突:1

根本不应该有任何冲突——更新应该合并到文件的新名称中。当 Subversion 失败时,Mercurial 会正确处理:

rm -rf /tmp/hg-repo
hg init /tmp/hg-repo
cd /tmp/hg-repo
echo 'Goodbye, World!' > hello.txt
hg add hello.txt
hg commit -m 'Initial import.'
echo 'Hello, World!' > hello.txt
hg commit -m 'Update.'
hg update 0
hg rename hello.txt hello.en.txt
hg commit -m 'Rename.'
hg merge

在合并之前,存储库看起来像这样(来自hg glog):

@变更集:2:6502899164cc |标签: 小费 |父:0:d08bcebadd9e |用户:马丁·盖斯勒 |日期:2010 年 4 月 1 日星期四 12:29:19 +0200 |摘要:重命名。 | | o 变更集:1:9d06fa155634 |/ 用户:马丁·盖斯勒 |日期:2010 年 4 月 1 日星期四 12:29:18 +0200 |摘要:更新。 | o 变更集:0:d08bcebadd9e 用户:马丁·盖斯勒 日期:2010 年 4 月 1 日星期四 12:29:18 +0200 摘要:初始导入。

合并的输出是:

将 hello.en.txt 和 hello.txt 合并到 hello.en.txt 0 个文件已更新,1 个文件已合并,0 个文件已删除,0 个文件未解决 (分支合并,别忘了提交)

换句话说:Mercurial 将修订版 1 的更改合并到修订版 2 (hello.en.txt) 的新文件名中。为了支持重构,处理这种情况当然是必不可少的,而重构正是你想要在分支上做的事情。

【讨论】:

+1 查看详细示例,您可以点击键盘并亲自查看会发生什么。作为一个 Mercurial 菜鸟,我想知道这个示例的 hg 版本是否以明显的方式逐行遵循? @DarenW:我已经添加了相应的Mercurial命令,希望能让事情更清楚!【参考方案4】:

其他人已经涵盖了更多的理论方面。也许我可以提供一个更实际的观点。

我目前在一家公司工作,该公司在“功能分支”开发模型中使用 SVN。那就是:

不能在主干上做任何工作 每个开发人员都可以创建自己的分支 分支应在所承担的任务期间持续存在 每个任务都应该有自己的分支 合并回主干需要授权(通常通过 bugzilla) 有时需要高级别的控制,合并可能由看门人完成

一般来说,它有效。 SVN 可以用于这样的流程,但并不完美。 SVN 的某些方面阻碍并塑造了人类行为。这给了它一些负面的方面。

在人们从低于^/trunk 的点进行分支时,我们遇到了很多问题。这会在整个树中丢弃合并信息记录,并最终破坏合并跟踪。虚假冲突开始出现,混乱盛行。 将更改从主干提取到分支相对简单。 svn merge 做你想做的事。合并您的更改需要(我们被告知)合并命令上的--reintegrate。我从来没有真正理解过这个开关,但这意味着分支不能再次合并到主干中。这意味着它是一个死分支,您必须创建一个新分支才能继续工作。 (见注) 在创建和删除分支时通过 URL 在服务器上进行操作的整个业务确实令人困惑和害怕。所以他们避免了。 在分支之间切换很容易出错,让一棵树的一部分看着分支 A,而另一部分看着分支 B。所以人们更喜欢在一个分支中完成所有工作。

往往会发生这样的情况:工程师在第一天创建了一个分支。他开始工作而忘记了它。过了一段时间,一个老板过来问他是否可以把他的工作放到主干上。工程师一直很害怕这一天,因为重新整合意味着:

将他长期存在的分支合并回主干并解决所有冲突,并释放本应位于单独分支中但没有的无关代码。 删除他的分支 创建新分支 将他的工作副本切换到新分支

...由于工程师尽可能少地执行此操作,因此他们无法记住执行每一步的“魔法咒语”。错误的开关和 URL 发生了,突然他们一团糟,他们去找“专家”。

最终一切都会平息下来,人们学会了如何处理缺点,但每个新手都会遇到同样的问题。最终的现实(与我在他开始时设定的相反)是:

主干上没有做任何工作 每个开发人员都有一个主要分支 分支持续到需要发布的工作 出票的错误修复往往有自己的分支 授权后合并回主干

...但是...

有时工作使它不应该成为主干,因为它与其他东西在同一个分支中。 人们避免所有合并(即使是简单的事情),因此人们经常在自己的小气泡中工作 大合并往往会发生,并导致有限的混乱。

谢天谢地,团队足够小,可以应付,但无法扩展。问题是,这些都不是 CVCS 的问题,但更重要的是,因为合并不如 DVCS 重要,所以它们没有那么流畅。这种“合并摩擦”会导致行为,这意味着“特征分支”模型开始崩溃。良好的合并需要成为所有 VCS 的功能,而不仅仅是 DVCS。


根据this,现在有一个--record-only 开关可以用来解决--reintegrate 问题,apparently v1.8 选择何时自动重新集成,它不会导致分支之后就死了

【讨论】:

据我了解, --reintegrate 选项告诉 svn 您在合并到功能分支时已经解决了冲突更改。实际上,它不是将其视为补丁,而是使用分支版本覆盖整个文件,已经检查了所有主干修订已合并到分支的合并历史记录。 @IMSoP:可能,这是有道理的。它并没有向我解释为什么它是必要的,或者为什么它不可能从那个分支进一步合并。该选项在很大程度上也没有记录在案,这也无济于事。 我只通过 TortoiseSVN 使用过它,它总是在合并 UI 中突出显示。我相信 SVN 1.8 会自动选择正确的策略,并且不需要单独的选项,但我不知道他们是否修复了正常的合并算法以正确处理以这种方式重置的分支。跨度> 【参考方案5】:

在 subversion 1.5 之前(如果我没记错的话),subversion 有一个明显的缺点,就是它不会记住合并历史。

让我们看一下VonC概述的案例:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - A - x (v2-only)
           \
             x - B - x (wss)

注意修订 A 和 B。假设您将“wss”分支上的修订 A 的更改合并到修订 B 的“v2-only”分支(无论出于何种原因),但继续使用这两个分支。如果您尝试使用 mercurial 再次合并两个分支,它只会合并修订 A 和 B 之后的更改。使用 subversion,您必须合并所有内容,就好像您之前没有进行合并一样。

这是我自己的经验中的一个示例,由于代码量大,从 B 合并到 A 需要几个小时:如果要再次执行 会很痛苦,如果subversion pre-1.5 就是这种情况。

与Hginit: Subversion Re-education 的合并行为的另一个可能更相关的区别:

想象一下你和我正在努力 一些代码,我们分支该代码, 我们每个人都进入我们各自的 工作区,做很多很多 分别更改该代码,所以 他们分歧很大。

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

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

简而言之,Mercurial 分析差异的方式(曾经?)优于颠覆的。

【讨论】:

我已经阅读了 hginit。太糟糕了,它没有显示更多关于 hg 比 svn 做得更好的实际例子。基本上它告诉你“相信 joel”,hg 更好。他展示的简单示例可能也可以使用 svn 完成。实际上这就是我打开这个问题的原因。 根据这个说法,我想到了一个天真的问题:如果将 Mercurial 的合并算法放入 Subversion 会怎样?那么svn会和hg一样好吗?不,因为 hg 的优势在于更高级别的组织,而不是从文件中合并行的低级文本数学。这是我们 svn 用户需要了解的新颖想法。 @stmax:我明白你的意思。然而,Joel 或其他任何人的意见并不重要:一种技术要么比另一种更好(对于一组用例),要么不。 @DarenW 和 @stmax:根据我自己的个人经验,Hg 之所以胜出,是因为它的分布式操作(我不是一直连接)、性能(大量本地操作)、由卓越的合并算法提供支持的极其直观的分支, hg 回滚、模板化日志输出、hg glog、单个 .hg 文件夹……我可以继续,继续……除了 git 和 bazaar 之外的任何东西都感觉像是一件紧身衣。 引用的关于“变​​更集”的 hg 评论对我来说似乎相当不准确。 SVN 非常清楚它正在合并哪些变更(变更集基本上是两个快照之间的差异,反之亦然,对吗?),并且可以根据需要依次应用它们中的每一个;当然不必“猜测”任何事情。如果它制造了“一个大混乱”,那么这是一个实现问题,而不是设计的任何基础。在当前架构设计之上难以解决的主要问题是文件移动/复制/重命名。【参考方案6】:

不谈通常的优势(离线提交,publication process,...),这是一个我喜欢的“合并”示例:

我一直看到的主要场景是一个分支...... 两个实际上开发了不相关的任务 (它从一个功能开始,但它导致了另一个功能的发展。 或者它从一个补丁开始,但它导致了另一个功能的开发)。

如何只合并主分支上的两个功能之一? 或者如何将这两个特征隔离在各自的分支中?

您可以尝试生成某种补丁,问题是您不再确定可能存在于以下之间的 functional dependencies

补丁中使用的提交(或 SVN 的修订) 其他不属于补丁的提交

Git(我想也是 Mercurial)建议使用 rebase --onto 选项来对分支的一部分进行 rebase(重置分支的根):

来自Jefromi's post

- x - x - x (v2) - x - x - x (v2.1)
           \
            x - x - x (v2-only) - x - x - x (wss)

您可以解决这种情况,即您有 v2 的补丁以及新的 wss 功能:

- x - x - x (v2) - x - x - x (v2.1)
          |\
          |  x - x - x (v2-only)
           \
             x - x - x (wss)

,允许您:

单独测试每个分支以检查所有内容是否按预期编译/工作 只合并您想要主的内容。

我喜欢的另一个功能(影响合并)是能够squash commits(在尚未推送到另一个仓库的分支中)以呈现:

更清晰的历史 更连贯的提交(而不是函数 1 的提交 1,函数 2 的提交 2,函数 1 的再次提交 3...)

确保合并更容易,冲突更少。

【讨论】:

svn 没有离线提交?罗弗?如果是这样,怎么会有人远程考虑使用它? @Lohoris SVN 出现时,还没有广泛使用的开源 DVCS;在这一点上,我认为人们仍在使用它主要是惯性。 @MaxNanasy 是一种非常糟糕的惯性……不过,现在选择它简直是愚蠢的。 @Lohoris Online(更准确地说,集中式)提交对于一个存储库可以简单地位于共享本地服务器上的小团队来说并不是什么大问题。 DVCS 主要是为地理上分布的大型团队(git 和 mercurial 都旨在管理 Linux 内核代码)和开源项目(因此 GitHub 的流行)而发明的。惯性也可以被视为对更改团队工作流程核心工具的风险与收益的评估。 @Lohoris 我认为你误解了我关于数据库、防火墙等的观点:如果我不能真正运行,我几乎无法在我的家用机器上提交先看那个代码。我可以盲目地工作,但我不能在某处做事这一事实并不是让我失望的主要原因。

以上是关于合并:Hg/Git 与 SVN的主要内容,如果未能解决你的问题,请参考以下文章

SVN与TortoiseSVN实战:TortoiseSVN新建及合并分支

SVN分支创建与合并

SVN 分支及合并的介绍和实践---命令行

SVN分支与合并

详细说明svn分支与合并---命令行

Tortoise SVN 合并两个分支