合并:Hg / Git与SVN

时间:2010-03-19 08:18:58

标签: git svn mercurial merge dvcs

我经常读到Hg(和Git和......)在合并方面比SVN更好但我从未见过Hg / Git可以合并SVN失败的地方(或者SVN需要人工干预的地方)的实际例子。您可以发布一些分支/修改/提交/ ....-操作的逐步列表,显示SVN在Hg / Git愉快地移动时会失败的位置吗?实际的,不是非常特殊的情况请...

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

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

6 个答案:

答案 0 :(得分:119)

我也一直在寻找一个案例,比如Subversion无法合并分支,Mercurial(和Git,Bazaar,......)做对了。

SVN Book describes how renamed files are merged incorrectly。这适用于Subversion 1.51.61.71.8!我试图重新创建以下情况:

cd /tmp
rm -rf svn-repo svn-checkout
svnadmin create svn-repo
svn checkout file:///tmp/svn-repo svn-checkout
cd svn-checkout
mkdir trunk branches
echo 'Goodbye, World!' > trunk/hello.txt
svn add trunk branches
svn commit -m 'Initial import.'
svn copy '^/trunk' '^/branches/rename' -m 'Create branch.'
svn switch '^/trunk' .
echo 'Hello, World!' > hello.txt
svn commit -m 'Update on trunk.'
svn switch '^/branches/rename' .
svn rename hello.txt hello.en.txt
svn commit -m 'Rename on branch.'
svn switch '^/trunk' .
svn merge --reintegrate '^/branches/rename'

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

--- Merging differences between repository URLs into '.':
A    hello.en.txt
   C hello.txt
Summary of conflicts:
  Tree conflicts: 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
|  tag:         tip
|  parent:      0:d08bcebadd9e
|  user:        Martin Geisler 
|  date:        Thu Apr 01 12:29:19 2010 +0200
|  summary:     Rename.
|
| o  changeset:   1:9d06fa155634
|/   user:        Martin Geisler 
|    date:        Thu Apr 01 12:29:18 2010 +0200
|    summary:     Update.
|
o  changeset:   0:d08bcebadd9e
   user:        Martin Geisler 
   date:        Thu Apr 01 12:29:18 2010 +0200
   summary:     Initial import.

合并的输出是:

merging hello.en.txt and hello.txt to hello.en.txt
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)

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

答案 1 :(得分:90)

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

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

    在像Git或Mercurial这样的分布式版本控制系统中,主干和分支之间没有技术区别:所有分支都是相同的(尽管可能存在社交差异) 。任何方向的合并都是以同样的方式进行的。

  • 您需要向-g--use-merge-history提供新的svn log svn blame)选项,以便考虑合并跟踪。

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

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

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

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

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

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

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

  • Subversion可能不支持criss-cross merge的高级案例。

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

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

  • “已知问题”中,警告合并跟踪与文件重命名无关,例如当一方重命名文件(并可能修改它)时,第二方修改文件而不重命名(旧名称下)。

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

HTH

答案 2 :(得分:17)

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

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

如何只合并主分支上的两个功能中的一个?
或者,您如何在自己的分支中隔离这两个功能?

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

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

Git(以及我认为的Mercurial)建议使用rebase --onto选项来重组(重置分支的根)部分分支:

来自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(在尚未推送到另一个回购的分支中)才能呈现:

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

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

答案 3 :(得分:6)

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

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

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

  • SVN过去常常在不应该出现的情况下引发很多树冲突。我们从未深究这一点,但它并没有在GIT中发生。
  • 虽然更好,但GIT要复杂得多。花一些时间进行培训。
  • 我们习惯了Tortoise SVN,我们很喜欢。 Tortoise GIT不是那么好,这可能会让你失望。但是我现在使用GIT命令行,我更喜欢Tortoise SVN或任何GIT GUI。

当我们评估GIT时,我们进行了以下测试。这些显示GIT在合并方面是赢家,但不是那么多。在实践中,差异要大得多,但我想我们还没有设法复制SVN处理不当的情况。

GIT vs SVN Merging Evaluation

答案 4 :(得分:5)

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

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

  • 无法在主干上完成工作
  • 每个开发人员都可以创建自己的分支
  • 分支应在任务期间持续
  • 每个任务都应该拥有自己的分支
  • 合并回主干需要获得授权(通常通过bugzilla)
  • 在需要高级别控制的情况下,合并可以由看门人完成

一般情况下,它有效。 SVN可以用于这样的流程,但它并不完美。 SVN的某些方面妨碍了人类的行为。这给了它一些消极方面。

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

当然,工程师会在第1天创建一个分支机构。他开始工作并忘掉它。一段时间后,一位老板走了进来,询问他是否可以将他的工作释放到行李箱。这一天工程师一直在担心,因为重新融合意味着:

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

......因为工程师尽可能少地做到这一点,他们不记得每一步都做了“神奇的咒语”。错误的交换机和URL发生了,突然间它们变得一团糟,它们就变成了“专家”。

最终这一切都得到了解决,人们学会了如何应对这些缺点,但每个新的首发都会遇到同样的问题。最终的现实(与我在他开始时提出的相反)是:

  • 在主干上没有工作
  • 每位开发人员都有一个主要分支
  • 分支持续到工作需要发布
  • 购票的错误修复往往会得到自己的分支
  • 授权后合并回主干

...但是...

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

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


根据this,现在有一个--record-only开关可用于解决--reintegrate问题,而apparently v1.8选择何时自动重新整合,并且它不会导致分支在之后死亡

答案 5 :(得分:3)

在颠覆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”分支(无论出于何种原因),但继续使用两个分支。如果您尝试使用mercurial再次合并两个分支,它只会在修订A和B之后合并更改。使用subversion时,您必须合并所有内容,就像之前没有进行合并一样。

根据我自己的经验,这是一个例子,由于代码量的原因,从B到A的合并需要几个小时:再次通过 将会非常痛苦颠覆前的情况就是如此。

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

  

想象一下你和我正在努力   一些代码,我们分支代码,   我们每个人都分开了   工作区和制作很多很多   分别更改该代码,所以   他们分歧了很多。

     

当我们必须合并时,Subversion   试图看看这两个修订 - 我的   修改过的代码,并修改了   代码 - 它试图猜测如何   在一个大的邪恶中将他们粉碎在一起   一塌糊涂。它通常会失败,产生   “合并冲突”的页面和页面   简单来说,这不是真正的冲突   Subversion失败的地方   弄清楚我们做了什么。

     

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

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