刚读一篇关于Joel博客的文章(遗憾的是他的最后一篇)。这个是关于Mercurial的,但它实际上讨论了像Git这样的分布式VC系统的优点。
使用分布式版本控制 分布式部分实际上不是 最有趣的部分。有趣的是,这些系统考虑的是变化,而不是版本。
阅读文章 这里 。
为什么合并在DVCS中比在Subversion中更好的主张很大程度上取决于前一段时间Subversion中分支和合并的工作方式。 Subversion之前 1.5.0 没有存储有关何时合并分支的任何信息,因此当您想要合并时,您必须指定必须合并的修订范围。
思考这个例子:
1 2 4 6 8 trunk o-->o-->o---->o---->o \ \ 3 5 7 b1 +->o---->o---->o
当我们想要的时候 合并 b1进入主干后我们会发出以下命令,同时站在已检出主干的文件夹上:
svn merge -r 2:7 {link to branch b1}
将试图合并来自的变化 b1 进入你的本地工作目录。然后在解决任何冲突并测试结果后提交更改。提交修订树时,如下所示:
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
然而,当版本树增长时,这种指定修订范围的方式很快就会失控,因为subversion没有关于何时和哪些修订被合并在一起的任何元数据。关于以后发生的事情的思考:
12 14 trunk 鈥�-->o-------->o "Okay, so when did we merge last time?" 13 15 b1 鈥�----->o-------->o
这主要是Subversion拥有的存储库设计的一个问题,为了创建一个你需要创建一个新的分支 虚拟目录 在存储库中将容纳一个主干的副本,但它不存储任何关于何时和什么东西被合并回来的信息。这将导致有时令人讨厌的合并冲突。更糟糕的是,Subversion默认使用双向合并,当两个分支头与其共同祖先不进行比较时,自动合并存在一些严重的限制。
为了缓解这种颠覆,现在存储分支和合并的元数据。这会解决所有问题吗?
在集中式系统上,如颠覆, 虚拟目录 吸。为什么?因为每个人都可以查看它们 - 甚至是垃圾实验的。如果你想进行实验,分支是好的 的 但你不想看到每个人和他们的阿姨实验 强> 。这是严重的认知噪音。你添加的分支越多,你就会看到越多的垃圾。
您在存储库中拥有的公共分支越多,跟踪所有不同分支的难度就越大。因此,您将遇到的问题是,如果分支仍在开发中,或者它是否真的死了,这在任何集中式版本控制系统中都很难说清楚。
大多数时候,从我看到的情况来看,组织无论如何都会默认使用一个大分支。这是一种耻辱,因为反过来很难跟踪测试和发布版本,以及其他任何好处来自分支。
有一个非常简单的原因: 的 分支是一流的概念 强> 。有 没有虚拟目录 通过设计和分支是DVCS中的硬对象,它需要这样才能简单地同步存储库(即 推 和 拉 )。
使用DVCS时,您要做的第一件事就是克隆存储库(git的 clone ,hg's clone 和bzr的 branch )。克隆在概念上与在版本控制中创建分支相同。有人称之为 分叉 要么 分枝 (虽然后者通常也用于指代共处的分支),但它也是一样的。每个用户都运行自己的存储库,这意味着你有一个 每用户分支 继续
clone
branch
版本结构是 的 不是树 强> ,而是一个的 图形 强> 代替。更具体地说 有向无环图 (DAG,表示没有任何循环的图表)。除了每个提交都有一个或多个父引用(提交所基于的内容)之外,您实际上不需要详述DAG的细节。因此,下面的图表将显示反向修订之间的箭头。
合并的一个非常简单的例子是这样的;想象一个名为的中央仓库 origin 并且用户Alice将存储库克隆到她的机器上。
origin
a鈥� b鈥� c鈥norigin o<---o<---o ^master | | clone v a鈥� b鈥� c鈥nalice o<---o<---o ^master ^origin/master
在克隆期间发生的事情是每个修订都完全按原样复制到Alice(由唯一可识别的哈希id验证),并标记原点的分支所在的位置。
爱丽丝然后处理她的回购,在她自己的存储库中提交并决定推送她的更改:
a鈥� b鈥� c鈥norigin o<---o<---o ^ master "what'll happen after a push?" a鈥� b鈥� c鈥� d鈥� e鈥nalice o<---o<---o<---o<---o ^master ^origin/master
解决方案相当简单,唯一的事情就是 origin 存储库需要做的是接受所有新版本并将其分支移动到最新版本(git称之为“快进”):
a鈥� b鈥� c鈥� d鈥� e鈥norigin o<---o<---o<---o<---o ^ master a鈥� b鈥� c鈥� d鈥� e鈥nalice o<---o<---o<---o<---o ^master ^origin/master
用例,我在上面说明, 的 甚至不需要合并任何东西 强> 。所以问题实际上不是合并算法,因为三向合并算法在所有版本控制系统之间几乎相同。 的 问题更多的是结构而不是任何东西 强> 。
不可否认,上面的例子是一个非常简单的用例,所以让我们做一个更加扭曲的例子,尽管是一个更常见的例子。记住这一点 origin 开始时有三个版本?好吧,那个做过他们的人,可以打电话给他 短发 ,一直在自己工作,并在自己的存储库上提交:
a鈥� b鈥� c鈥� f鈥nbob o<---o<---o<---o ^ master ^ origin/master "can Bob push his changes?" a鈥� b鈥� c鈥� d鈥� e鈥norigin o<---o<---o<---o<---o ^ master
现在鲍勃无法将他的改变直接推到了 origin 库。系统如何检测到这一点是通过检查Bob的修订是否直接来自 origin 是的,在这种情况下没有。任何推动的尝试都会导致系统说出类似于“ 呃......恐怕不能让你做那个鲍勃 “。
所以鲍勃必须拉入然后合并更改(使用git的 pull ;或者是hg pull 和 merge ;或者bzr's merge )。这是一个两步过程。首先,Bob必须获取新的修订版本,这些修订版本将按原样复制它们 origin 库。我们现在可以看到图表有所不同:
pull
merge
v master a鈥� b鈥� c鈥� f鈥nbob o<---o<---o<---o ^ | d鈥� e鈥n +----o<---o ^ origin/master a鈥� b鈥� c鈥� d鈥� e鈥norigin o<---o<---o<---o<---o ^ master
拉取过程的第二步是合并分歧的提示并提交结果:
v master a鈥� b鈥� c鈥� f鈥� 1鈥nbob o<---o<---o<---o<-------o ^ | | d鈥� e鈥� | +----o<---o<--+ ^ origin/master
希望合并不会遇到冲突(如果您预计它们可以在git中手动执行这两个步骤 fetch 和 merge )。以后需要做的是再次推进这些变化 origin ,这将导致快进合并,因为合并提交是最新的直接后代 origin 库:
fetch
v origin/master v master a鈥� b鈥� c鈥� f鈥� 1鈥nbob o<---o<---o<---o<-------o ^ | | d鈥� e鈥� | +----o<---o<--+ v master a鈥� b鈥� c鈥� f鈥� 1鈥norigin o<---o<---o<---o<-------o ^ | | d鈥� e鈥� | +----o<---o<--+
还有另一个选项可以在git和hg中合并,称为 变基 ,这将在最新的变化之后将Bob的变化移动。既然我不希望这个答案更加冗长,我会让你读一读 混帐 , 善变 要么 市场 关于这个的文档。
作为读者的练习,试着弄清楚它将如何与其他用户合作。它与Bob的上述示例类似。存储库之间的合并比您想象的更容易,因为所有修订/提交都是唯一可识别的。
还有在每个开发人员之间发送补丁的问题,这是Subversion中的一个巨大问题,它通过唯一可识别的修订在git,hg和bzr中得到缓解。一旦有人合并了他的更改(即进行合并提交)并通过推送到中央存储库或发送补丁将其发送给团队中的其他人消费,那么他们就不必担心合并,因为它已经发生了。 Martin Fowler称这种方式起作用 混杂的整合 。
因为该结构与Subversion不同,所以通过使用DAG,它使得分支和合并能够以更容易的方式完成,不仅对于系统而且对于用户也是如此。
SVN在Git跟踪时跟踪文件 <击> 内容 击> 变化。它足够聪明地跟踪从一个类/文件重构到另一个类/文件的代码块。他们使用两种完全不同的方法来跟踪您的来源。
我仍然大量使用SVN,但我很高兴我几次使用Git。
如果你有时间,这是一个很好的阅读: 为什么我选择Git
其他答案中没有提到的一件事,那就是DVCS的一大优势,就是你可以在推送更改之前在本地提交。在SVN中,当我进行一些更改时,我想要检查,并且有人在此期间已经在同一分支上完成了提交,这意味着我必须做一个 svn update 在我承诺之前。这意味着我的更改以及来自其他人的更改现在混合在一起,并且无法中止合并(例如 git reset 要么 hg update -C ),因为没有承诺要回去。如果合并非常重要,则意味着在清理合并结果之前无法继续处理功能。
svn update
git reset
hg update -C
但是,对于那些过于愚蠢而无法使用单独分支的人来说,这可能只是一个优势(如果我没记错的话,我们在使用SVN的公司中只有一个用于开发的分支)。
从历史上看,Subversion只能执行直接的双向合并,因为它没有存储任何合并信息。这涉及进行一系列更改并将其应用于树。即使使用合并信息,这仍然是最常用的合并策略。
默认情况下,Git使用3向合并算法,其中包括找到要合并的头部的共同祖先,并利用合并两侧存在的知识。这使Git能够更加智能地避免冲突。
Git还有一些复杂的重命名查找代码,这也有帮助。它 不 存储变更集或存储任何跟踪信息 - 它只存储每次提交时文件的状态,并使用启发式方法根据需要定位重命名和代码移动(磁盘存储比这更复杂,但它提供给的界面逻辑层暴露无跟踪)。