Mr.*_*Boy 398 svn git version-control merge mercurial
我在一些地方听说分布式版本控制系统闪耀的主要原因之一是比SVN这样的传统工具更好地融合.这实际上是由于两个系统如何工作的固有差异,或者像Git/Mercurial 这样的特定 DVCS实现是否只有比SVN更聪明的合并算法?
Spo*_*ike 553
为什么合并在DVCS中比在Subversion中更好的主张很大程度上取决于前一段时间Subversion中分支和合并的工作方式.1.5.0之前的Subversion 没有存储有关何时合并分支的任何信息,因此当您想要合并时,您必须指定必须合并的修订范围.
思考这个例子:
1 2 4 6 8
trunk o-->o-->o---->o---->o
\
\ 3 5 7
b1 +->o---->o---->o
Run Code Online (Sandbox Code Playgroud)
当我们想将 b1的更改合并到主干时,我们会发出以下命令,同时站在已检出主干的文件夹上:
svn merge -r 2:7 {link to branch b1}
Run Code Online (Sandbox Code Playgroud)
...将尝试将更改合并b1
到您的本地工作目录中.然后在解决任何冲突并测试结果后提交更改.提交修订树时,如下所示:
1 2 4 6 8 9
trunk o-->o-->o---->o---->o-->o "the merge commit is at r9"
\
\ 3 5 7
b1 +->o---->o---->o
Run Code Online (Sandbox Code Playgroud)
然而,当版本树增长时,这种指定修订范围的方式很快就会失控,因为subversion没有关于何时和哪些修订合并在一起的任何元数据.关于以后发生的事情的思考:
12 14
trunk …-->o-------->o
"Okay, so when did we merge last time?"
13 15
b1 …----->o-------->o
Run Code Online (Sandbox Code Playgroud)
这主要是Subversion拥有的存储库设计的一个问题,为了创建一个分支,你需要在存储库中创建一个新的虚拟目录,它将容纳一个主干的副本,但它不存储任何关于何时和什么的信息.事情已经合并回来.这有时会导致令人讨厌的合并冲突.更糟糕的是,Subversion默认使用双向合并,当两个分支头与其共同祖先不进行比较时,自动合并存在一些严重的限制.
为了缓解这种颠覆,现在存储分支和合并的元数据.这会解决所有问题吗?
在集中式系统上,如颠覆,虚拟目录很糟糕.为什么?因为每个人都可以查看它们......甚至是垃圾实验的.如果你想进行实验,分枝是好的,但你不想看到每个人和他们的阿姨实验.这是严重的认知噪音.你添加的分支越多,你就会看到越多的垃圾.
您在存储库中拥有的公共分支越多,跟踪所有不同分支的难度就越大.因此,您将遇到的问题是,如果分支仍在开发中,或者它是否真的死了,这在任何集中式版本控制系统中都很难说清楚.
大多数时候,从我看到的情况来看,组织无论如何都会默认使用一个大分支.这是一种耻辱,因为反过来很难跟踪测试和发布版本,以及其他任何好处来自分支.
有一个非常简单的原因:分支是一流的概念.设计中没有虚拟目录,分支是DVCS中的硬对象,它需要这样才能简单地同步存储库(即推送和拉取).
使用DVCS时,您要做的第一件事就是克隆存储库(git clone
,hg clone
和bzr branch
).克隆在概念上与在版本控制中创建分支相同.有些人称之为分叉或分支(虽然后者通常也用于指代同位分支),但它也是一样的.每个用户都运行自己的存储库,这意味着您可以进行每用户分支.
版本结构不是树,而是图形.更具体地,有向无环图(DAG,意味着没有任何循环的图).除了每个提交都有一个或多个父引用(提交所基于的内容)之外,您实际上不需要详述DAG的细节.因此,下面的图表将显示反向修订之间的箭头.
合并的一个非常简单的例子是这样的; 想象一个叫做中央存储库origin
的用户Alice,将存储库克隆到她的机器上.
a… b… c…
origin o<---o<---o
^master
|
| clone
v
a… b… c…
alice o<---o<---o
^master
^origin/master
Run Code Online (Sandbox Code Playgroud)
在克隆期间发生的事情是每个修订都完全按原样复制到Alice(由唯一可识别的哈希id验证),并标记原点的分支所在的位置.
爱丽丝然后处理她的回购,在她自己的存储库中提交并决定推送她的更改:
a… b… c…
origin o<---o<---o
^ master
"what'll happen after a push?"
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Run Code Online (Sandbox Code Playgroud)
解决方案相当简单,origin
存储库唯一需要做的就是接受所有新版本并将其分支移动到最新版本(git称之为"快进"):
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
a… b… c… d… e…
alice o<---o<---o<---o<---o
^master
^origin/master
Run Code Online (Sandbox Code Playgroud)
我在上面说明的用例甚至不需要合并任何东西.所以问题实际上不是合并算法,因为三向合并算法在所有版本控制系统之间几乎相同.问题更多的是结构而不是任何东西.
不可否认,上面的例子是一个非常简单的用例,所以让我们做一个更加扭曲的例子,尽管是一个更常见的例子.还记得origin
开始时有三个修订版吗?好吧,那个做过他们的人,让我叫他Bob,一直在自己工作并在他自己的存储库上做了一个提交:
a… b… c… f…
bob o<---o<---o<---o
^ master
^ origin/master
"can Bob push his changes?"
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Run Code Online (Sandbox Code Playgroud)
现在Bob无法将他的更改直接推送到origin
存储库.系统如何检测到这一点是通过检查Bob的修订是否直接来自于origin
s,在这种情况下不是.任何推动的尝试都会导致系统说出类似于" 呃......恐怕不能让你做那个鲍勃."
所以鲍勃必须拉入然后合并更改(使用git pull
;或者hg pull
和merge
;或者bzr merge
).这是一个两步过程.首先,Bob必须获取新的修订版本,这些修订版本将从origin
存储库中复制它们.我们现在可以看到图表有所不同:
v master
a… b… c… f…
bob o<---o<---o<---o
^
| d… e…
+----o<---o
^ origin/master
a… b… c… d… e…
origin o<---o<---o<---o<---o
^ master
Run Code Online (Sandbox Code Playgroud)
拉取过程的第二步是合并分歧的提示并提交结果:
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
^ origin/master
Run Code Online (Sandbox Code Playgroud)
希望合并不会碰到冲突(如果你预计他们,你可以手动做git的两个步骤,fetch
和merge
).后来需要做的是再次推送这些更改origin
,这将导致快进合并,因为合并提交是origin
存储库中最新的直接后代:
v origin/master
v master
a… b… c… f… 1…
bob o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
v master
a… b… c… f… 1…
origin o<---o<---o<---o<-------o
^ |
| d… e… |
+----o<---o<--+
Run Code Online (Sandbox Code Playgroud)
还有另一个选项可以在git和hg中合并,称为rebase,它会在最新的更改后将Bob的更改移动到其中.因为我不希望这个答案更加冗长,所以我会让你阅读git,mercurial或bazaar docs.
作为读者的练习,试着弄清楚它将如何与其他用户合作.它与Bob的上述示例类似.存储库之间的合并比您想象的更容易,因为所有修订/提交都是唯一可识别的.
还有在每个开发人员之间发送补丁的问题,这是Subversion中的一个巨大问题,它通过唯一可识别的修订在git,hg和bzr中得到缓解.一旦有人合并了他的更改(即进行合并提交)并通过推送到中央存储库或发送补丁将其发送给团队中的其他人消费,那么他们就不必担心合并,因为它已经发生了.Martin Fowler称这种混杂的方式是混杂的.
因为该结构与Subversion不同,所以通过使用DAG,它使得分支和合并能够以更容易的方式完成,不仅对于系统而且对于用户也是如此.
And*_*ett 29
从历史上看,Subversion只能执行直接的双向合并,因为它没有存储任何合并信息.这涉及进行一系列更改并将其应用于树.即使使用合并信息,这仍然是最常用的合并策略.
Git默认使用3向合并算法,包括找到合并头部的共同祖先,并利用合并两侧存在的知识.这使Git能够更加智能地避免冲突.
Git还有一些复杂的重命名查找代码,这也有帮助.它不存储更改集或存储任何跟踪信息 - 它只是存储每次提交时文件的状态,并使用启发式方法根据需要定位重命名和代码移动(磁盘存储比这更复杂,但接口它呈现给逻辑层暴露没有跟踪).
And*_*rey 17
简而言之,合并实现在Git中比在SVN中更好.在1.5 SVN没有记录合并动作之前,没有用户需要提供SVN没有记录的信息的帮助就无法进行未来的合并.随着1.5它变得更好,实际上SVN存储模型比Git的DAG稍微强大.但是SVN以一种相当复杂的形式存储了合并信息,这使得合并比Git花费更多的时间 - 我在执行时间内观察到了300个因子.
此外,SVN声称跟踪重命名以帮助合并已移动的文件.但实际上它仍然将它们存储为副本和单独的删除操作,并且合并算法仍然在修改/重命名情况下偶然发现它们,即,在一个分支上修改文件并在另一个分支上重命名,并且这些分支是合并.这种情况仍然会产生虚假的合并冲突,并且在目录重命名的情况下,它甚至会导致无声的修改丢失.(然后SVN人员倾向于指出修改仍然在历史中,但是当它们不在合并结果中时它们应该出现时没有多大帮助.
另一方面,Git甚至不跟踪重命名,而是在事后(合并时)将它们计算出来,并且非常神奇.
SVN合并表示也存在问题; 在1.5/1.6中你可以自动地从主干到分支合并,但是需要公布另一个方向的合并(--reintegrate
),并使分支处于不可用状态.很久以后他们发现事实并非如此,并且a)--reintegrate
可以自动计算出来,并且b)可以在两个方向上重复合并.
但毕竟这个(IMHO表示对他们正在做的事情缺乏了解),我会(好吧,我)非常谨慎地在任何非平凡的分支场景中使用SVN,理想情况下会尝试看看Git的想法合并结果.
答案中的其他要点,因为SVN中分支的强制全局可见性与合并功能无关(但是对于可用性).此外,'Git存储变化,而SVN存储(不同的东西)'大多不合时宜.Git在概念上将每个提交存储为一个单独的树(如tar文件),然后使用相当多的启发式来有效地存储它.计算两次提交之间的更改与存储实现是分开的.真实的是,Git以更直接的形式存储历史DAG,SVN执行其mergeinfo.任何试图理解后者的人都知道我的意思.
简而言之:Git使用更简单的数据模型来存储修订而不是SVN,因此它可以将大量精力投入到实际的合并算法中,而不是试图应对表示=>实际上更好的合并.
dan*_*ann 11
其他答案中没有提到的一件事,那就是DVCS的一大优势,就是你可以在推送更改之前在本地提交.在SVN中,当我进行一些更改时,我想要检查,并且有人在此期间已经在同一分支上完成了提交,这意味着我必须先做一次svn update
才能提交.这意味着我的更改以及来自其他人的更改现在混合在一起,并且无法中止合并(例如with git reset
或hg update -C
),因为没有提交可以返回.如果合并非常重要,则意味着在清理合并结果之前无法继续处理功能.
但是,对于那些过于愚蠢而无法使用单独分支的人来说,这可能只是一个优势(如果我没记错的话,我们在使用SVN的公司中只有一个用于开发的分支).
Pet*_*ter 10
我读了接受的答案.这是完全错的.
SVN合并可能是一种痛苦,也可能很麻烦.但是,忽略它实际上如何运作一分钟.没有Git保留或可以推导出SVN不会保留或可以派生的信息.更重要的是,没有理由保留版本控制系统的单独(有时是部分)副本将为您提供更多实际信息.这两种结构完全相同.
假设你想做"一些聪明的事情"Git"更好".而你的东西被检入SVN.
将您的SVN转换为等效的Git表单,在Git中执行,然后检查结果,可能使用多个提交,一些额外的分支.如果你能想象一种将SVN问题转化为Git问题的自动方式,那么Git没有任何根本优势.
在一天结束时,任何版本控制系统都会让我
1. Generate a set of objects at a given branch/revision.
2. Provide the difference between a parent child branch/revisions.
Run Code Online (Sandbox Code Playgroud)
另外,对于合并它也是有用的(或关键的)知道
3. The set of changes have been merged into a given branch/revision.
Run Code Online (Sandbox Code Playgroud)
Mercurial,Git和Subversion(现在原来使用svnmerge.py)都可以提供所有三条信息.为了通过DVC更好地展示一些东西,请指出Git/Mercurial/DVC中提供的第四条信息,这些信息在SVN /集中式VC中不可用.
这并不是说他们不是更好的工具!