合并:汞/ 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 blame
和svn 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严重处理的情况。
在颠覆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分析差异的方式(颠覆)优于颠覆。