合并:汞/ Git与SVN

我经常读汞(和Git和…)比SVN合并更好,但我从来没有见过Hg / Git可以合并SVN失败(或SVN需要手动干预的地方)的实例。 你可以发布分支/修改/提交/ …操作的几个分步列表,显示当Hg / Git愉快地移动时,SVN会失败吗? 实用,不是非常例外的情况下,请…

一些背景:我们有几十个开发人员使用SVN来处理项目,每个项目(或一组类似的项目)都在它自己的仓库中。 我们知道如何应用发布和function分支,所以我们不会经常遇到问题(即,我们已经到了那里,但是我们已经学会了克服Joel的 “一个程序员对整个团队造成创伤”的问题或“需要六个开发人员两周才能重新整合一个分支”)。 我们有非常稳定的版本分支,只用于应用错误修正。 我们有足够稳定的中继线,能够在一周内创build发行版。 我们拥有单个开发人员或开发人员组可以使用的function分支。 是的,他们在重新整合之后被删除,所以他们不会混淆版本库。 ;)

所以我仍然在试图找出Hg / Git优于SVN的优势。 我很想获得一些实践经验,但是我们还没有任何更大的项目可以转移到Hg / Git,所以我一直坚持使用只包含less量文件的小型人工项目。 而且我正在寻找一些可以感受到Hg / Git令人印象深刻的能力的例子,因为到目前为止我经常阅读关于它们的内容,但是却没有亲自find它们。

我自己并没有使用Subversion,但是从Subversion 1.5的发行说明:合并跟踪(基础)看起来像合并跟踪如何在全DAG版本控制系统(如Git或Mercurial)中工作有以下差异。

  • 合并中继到分支不同于合并分支到中继:由于某种原因合并中继到分支需要 – --reintegrate svn merge选项到svn merge

    在像Git或Mercurial这样的分布式版本控制系统中,主干和分支之间没有技术上的区别:所有分支都是平等的(尽pipe可能有社会差异)。 在任一方向合并都是以同样的方式完成的。

  • 你需要提供新的-g (– --use-merge-history )选项来svn log svn blamesvn blame合并跟踪。

    在Git和Mercurial中,当显示历史logging(日志)和责备时,会自动考虑合并跟踪。 在Git中,你可以请求只跟随第一个父--first-parent第一父母(我猜Mercurial也有类似的选项)在git log “放弃”合并跟踪信息。

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

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

     * --- * --- x --- * --- y --- * --- * --- * --- M2 < -  A
              \ / /
               -  * ---- M1 --- * --- * --- / < -  B
    

    在上述ASCII-art被破坏的情况下:分支“B”从修订版本“x”处的分支“A”被创build(分叉),然后分支“A”在修订“y”被合并到分支“B”合并“M1”,最后将分支“B”合并为分支“A”,合并为“M2”。

     * --- * --- x --- * ----- M1  -  * --- * --- M2 < -  A
              \ / / 
               \  -  * --- y --- * --- * --- / < -  B
    

    在上述ASCII艺术被破坏的情况下:分支“B”从修订版“x”处的分支“A”被创build(分叉),在“y”被合并到分支“A”中作为“M1”再次合并为“M2”分支“A”。

  • Subversion可能不支持纵横交错的高级案例。

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

    Git在实践中使用“recursion”合并策略来处理这种情况。 我不确定Mercurial。

  • “已知问题”中 ,警告合并跟踪在文件重命名时不起作用,例如,当一侧重命名文件(也许修改它),而另一侧修改文件而不重命名(在旧名称下)。

    Git和Mercurial都可以在实践中处理这种情况:使用重命名检测的 Git,使用重命名跟踪的 Mercurial。

HTH

我也一直在寻找一个例子,说Subversion没有合并一个分支,Mercurial(和Git,Bazaar,…)做正确的事情。

SVN书籍描述了重命名的文件如何被不正确的合并 。 这适用于Subversion 1.5,1.6,1.7和1.8 ! 我试图重新创build下面的情况:

 cd / tmp
 rm -rf svn-repo svn-checkout
 svnadmin创buildsvn-repo
 svn checkout file:/// tmp / svn-repo svn-checkout
 cd svn-checkout
 MKDIR干线分支
回声“再见,世界!”  > trunk / hello.txt
 svn添加主干分支
 svn commit -m'初始导入'。
 svn copy'^ / trunk''^ / branches / rename'-m'创build分支。'
 svn switch'^ / trunk'。
回声'你好,世界!'  > hello.txt
 svn commit -m'在主干上更新'。
 svn切换“^ / branches / rename”。
 svn重命名为hello.txt hello.en.txt
 svn commit -m'在分支上重命名'。
 svn switch'^ / trunk'。
 svn merge --reintegrate'^ / branches / rename'

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

 ---将存储库URL之间的差异合并到“。”中:
 hello.en.txt
    C hello.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 ):

 @ changeset:2:6502899164cc
 | 标签:小费
 | 父母:0:d08bcebadd9e
 | 用户:Martin Geisler 
 | date:四月01日星期四12:29:19 2010 +0200
 | 总结:重命名。
 |
 | 变化集:1:9d06fa155634
 | /用户:Martin Geisler 
 | date:四月01日星期四12:29:18 2010 +0200
 | 总结:更新。
 |
变更集:0:d08bcebadd9e
   用户:Martin Geisler 
   date:四月01日星期四12:29:18 2010 +0200
   总结:初次import。

合并的输出是:

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

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

没有谈到通常的优点(离线提交, 发布过程 …),这里是我喜欢的“合并”示例:

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

如何合并主分支上的两个特征之一?
或者你如何在自己的分支中分离出这两个特征?

您可以尝试生成某种types的修补程序,但问题在于您不能确定可能存在的function依赖关系:

  • 补丁中使用的提交(或SVN修订版)
  • 另一个提交不是补丁的一部分

Git(也是我想的Mercurial)提出的rebase – 选项来重定义(重置分支的根)部分的一个分支:

从Jefromi的职位

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

你可以解决这种情况,你有v2的补丁以及一个新的wssfunction:

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

,允许您:

  • 对每个分支进行单独testing,检查是否所有内容都按照预期进行了编译/
  • 只合并你想要的东西。

我喜欢的其他function(影响合并)是压缩提交 (在尚未推送到另一个回购的分支)的能力,以呈现:

  • 更清洁的历史
  • 提交更一致(而不是commit1为function1,commit2为function2,commit3再次为function1 …)

这确保合并,这是很容易,冲突较less。

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

我目前正在为一家在“function分支”开发模式中使用SVN的公司工作。 那是:

  • 没有工作可以在主干上完成
  • 每个开发者都可以创build自己的分支
  • 分支机构应该在所执行的任务期间持续下去
  • 每个任务应该有它自己的分支
  • 合并回trunk需要被授权(通常通过bugzilla)
  • 当需要高水平的控制时,合并可以由看门人完成

一般来说,它的工作。 SVN可以用于这样的stream程,但并不完美。 SVN的一些方面妨碍了人类的行为。 这给了它一些负面的方面。

  • 从低于^/trunk点开始,我们遇到了很多问题。 这使得合并整个树中的信息logging,并最终打破合并跟踪。 虚假的冲突开始出现,混乱统治着。
  • 从树干变成树枝是相对直截了当的。 svn merge做你想要的。 合并您的更改需要(我们被告知) – --reintegrate合并命令。 我从来没有真正明白这个开关,但这意味着分支不能再被合并到主干中。 这意味着它是一个死的分支,你必须创build一个新的继续工作。 (看注释)
  • 在创build和删除分支时,通过URL进行服务器操作的整个过程确实使人们感到困惑和恐惧。 所以他们避免它。
  • 在分支之间切换很容易出错,留下一部分树看着分支A,而另一部分看着分支B,所以人们更喜欢在一个分支上做所有的工作。

最可能发生的是一名工程师在第一天创build了一个分支。他开始工作,忘记了这一点。 过了一段时间,一位老板来了,问他是否可以把他的工作放到行李箱里。 工程师一直在害怕这一天,因为重新融合意味着:

  • 将他长期生活的分支合并到主干中,解决所有的冲突,并释放应该在单独的分支中的不相关的代码,但是不是。
  • 删除他的分支
  • 创build一个新的分支
  • 切换他的工作副本到新的分支

…因为工程师尽可能less地做这件事,所以他们不记得每一步的“魔法咒语”。 错误的开关和URL发生,突然间他们陷入了混乱,他们去“专家”。

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

  • 没有工作在干线上完成
  • 每个开发者都有一个主要分支
  • 分支持续到工作需要被释放
  • 收费错误修复往往会得到他们自己的分支
  • 合并回干线是在授权时完成的

…但…

  • 有时候,工作会让它变成干的,因为它和别的东西在同一个分支上。
  • 人们避免所有的合并(即使是简单的东西),所以人们经常在自己的小泡泡里工作
  • 大合并往往会发生,并导致有限的混乱。

值得庆幸的是,这个团队足够小,可以应付,但不会扩展。 事情是,这不是一个CVCS的问题,但更多的是因为合并不如DVCS那么重要,他们不是光滑的。 这种“合并摩擦”会导致行为,这意味着“特色分支”模式开始崩溃。 好的合并需要成为所有VCS的一个特征,而不仅仅是DVCS。


据此,现在有一个可以用来解决“ --reintegrate问题--record-only开关, 显然 v1.8select何时自动重新--reintegrate ,并且不会导致分支死亡

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

我可以告诉你, GIT比SVN更合适。 这显然是轶事,但有一个表。

以下是我们发现的一些事情:

  • SVN过去在似乎不应该的情况下会引发很多树冲突。 我们从来没有深究过,但在GIT中并没有发生。
  • 更好的是,GIT要复杂得多。 花点时间训练。
  • 我们习惯了我们喜欢的乌龟SVN。 乌龟GIT不太好,这可能会让你失望。 不过,我现在使用的GIT命令行,我更喜欢乌龟SVN或任何GIT GUI。

当我们评估GIT时,我们进行了以下testing。 这表明GIT在合并时成为赢家,但不是那么多。 实际上,差异要大得多,但我想我们还没有设法复制SVN严重处理的情况。

GIT vs SVN合并评估

在颠覆1.5之前(如果我没有弄错的话),颠覆有一个很大的缺点,那就是它不记得合并历史。

让我们来看一下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的合并需要几个小时的时间:这将是一个真正的痛苦再次通过,这在颠覆1.5之前就是这种情况。

另外, Hginit的合并行为可能更为相关:Subversion Re-education :

想象一下,你和我正在研究一些代码,我们分解了这些代码,然后我们分别进入了单独的工作空间,并分别对这些代码进行了大量的修改,所以他们有很大的分歧。

当我们需要合并的时候,Subversion会试着去查看这两个版本,我的修改过的代码和你修改过的代码,然后试图猜测如何把它们合并成一个大的邪恶的混乱。 它通常会失败,产生页面和页面的“合并冲突”,这并不是真正的冲突,只是Subversion未能弄清楚我们做了什么的地方。

相比之下,当我们在Mercurial单独工作时,Mercurial正在忙于保持一系列的变化。 所以,当我们想要将我们的代码合并在一起时,Mercurial实际上拥有更多的信息:它知道我们每个人都改变了什么,并且可以重新应用这些改变,而不仅仅是看最后的产品,并试图猜测如何把它一起。

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