首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 软件管理 > 软件开发 >

Git和Mercurial(Hg)的分析

2014-03-30 
Git和Mercurial(Hg)的分析

  注:这篇分析完成于2008年夏季,当时我们正第一次为Google Code支持DVCS而作的研究工作。

  简介

  这份文档简要的介绍了为Google Code增加DVCS支持而作的研究工作。基于流行因素,我们需要考虑Git和Hg这两个DVCS,这份文档描述了这两个DCVS的提醒,并且提供了将它们集成到Google Code中所需要的工作的概况。

  分布式版本控制

  传统的版本控制系统中有一个中心仓库,用于维护所有的版本历史。客户端必须跟这个中心仓库进行交互,才可以进行查看文件历史,查看其他分支或者是提交修改。通常来说,客户端只有一份当前工作分支的本地文件,没有之前版本或者其他分支的本地文件。

  分布式版本管理系统(DVCS)使用另一种不同的架构。在DVCS中,每个用户都有她们自己的本地仓库,其中完整包含了项目历史和所有分支等。切换分支,查看文件历史甚至是提交修改都是本地操作。每个仓库都能通过推送(push)和拉取(pull)操作跟其他仓库交换信息。推送操作能将本地信息推送到一个远程的仓库,而操作能将远程仓库的信息复制到本地仓库。请注意,这其中没有一个仓库是“权威”的仓库。两个仓库都有一些在另一个仓库中没有的本地历史。所有的DVCS都具有一个主要特性,就是仓库可以很方便地明确描述它们的版本历史(和它们所请求的版本历史)。Git和Hg都是使用SHA1哈希值来辨别数据(文件、修订树、修改集等等)。

  DVCS为开发者的工作流程提供很多灵活性。它们可以像传统的拥有一个中心“权威”的仓库并且所有开发者都与之同步的版本控制系统那么使用。对于大型项目,DCVS也可以提供一个由多个服务器仓库组成的有层级的结构,它能维护各个仓库之间来自开发者的修改并且转播这些信息。DVCS同样允许开发者之间直接分享它们的工作。例如,两个开发者能够在相同的分支上共同开发一个新特性而且不需要一个“权威”的服务器即可互相分享工作成果。一旦他们的开发的功能稳定运行,就能将这些修改推送到一个公共的仓库中发布给更多的关注者。

  由于没有中心仓库,也就没有必要使用客户端和服务器这样的术语。当我们在谈论两个仓库时,通常是以本地和远程来代替客户端和服务器来称呼他们。尽管如此,在Google Code中实现DVCS这样的情况下,托管在Google的仓库将被视为是服务器,而用户的仓库则是客户端。

  特性比较

  Git和Hg之间有很多的相似之处。与其提供一个很长的特性支持列表来进行比较,这部分尝试列出这两个系统中有象征意义的区别来进行来比较。

  Git的优势

  客户端存储管理。Hg和Git斗允许用户有选择性地从其他仓库中拉取他们所需的分支。这样的前置机制可以减少存储在本地的历史的体积。另外,Git允许丢弃先前拉取的分支。Git也允许将历史数据从本地仓库中删除(但在那些分支上依旧保留近期的数据)。反观Hg,如果一个分支存在本地仓库里,那么该分支的所有历史数据(能退到最初始提交的状态)必须存在,而且也不能删除分支,除非创建一个新的仓库然后有选择性地拉取所需分支到仓库里。已经有一些方法能解决Hg的这个问题,但是还没有官方的解决方法。

  多个分支合并。Git支持从不限数量的分支中进行合并操作。Hg只允许两个分支进行合并。在Hg中为了从多个分支进行合并,用户不得不进行n-1次的合并。尽管在大多数案例中,不论是不是DVCS,都偏向于进行多次的合并操作,然而Git用户如果想要实现一步到位的合并的话还是可以的。

  Rebasing(复位基础)。Git有一个rebase的命令能让你提取一个本地分支并且将该分支的改变应用到你当前的修订版本中。例如,一个开发者正在为某产品一个基于1.0版的本地分支上增加一个新特性。当新特性的开发工作还在进行中的时候,主产品线已经升级到1.1版,这时它将能拉取1.0版到1.1版的修改并将这些修改应用到当前的新特性的开发分支上,该分支可被视为是在1.1版本的基础上进行开发而不是1.0版。在其他的版本控制系统中,这个操作应该是合并1.1版的修改到当前的分支上。从SCM的观点来看,因为更关注与“再现过去的状态”,所以合并是正确的选择。尽管如此,当我们关注的是“编写一个干净的软件修订历史”的时候,rebase看起来是一个更高级的方法。git-rebase让你将之前的非线性历史改造成线性,让历史保持干净。公平的说,这只是提供一个简单地将1.0版到1.1版的每个提交都进行合并且将他们单独提交的增量操作。(To be fair, the same deltas are produced by simply merging every commit from the 1.0 version to the 1.1 version individually, and committing them individually.)rebase是能够安全地完成这项工作然后在不让修订树(tree)杂乱的情况下移除基于1.0的版本。

  注:在本文发表后,Hg已经加入了rebase命令的支持。

  Hg的优势

  学习曲线。由于一些因素,Git的学习曲线比Hg更加陡峭。Git有更多的命令和设置项,这就足够吓跑新用户。Hg的文档相比起来更完善,对新手来说也更容易阅读。Hg的术语和命令也更接近于Subversion和其他CVS,对于从其他系统迁移过来的人们显得更容易亲近。

  支持Windows。Git有很强的Linux遗传,并且官方提供的在windows下的使用方法是使用cygwin,这与Windows用户所期望背离的比较远。一个基于MinGW的Git接口区域流行。但是在Git的世界里,Windows依旧被认为是“二等公民”。基于有限的测试,MinGW的接口功能完善,但是速度有些迟滞。在Linux或者是Mac OS X下瞬间能完成的操作,在Windows下要花费10倍的时间。Hg是基于Python的,而且官方发布的版本也能够干净地在Windows下运行(和Linux、Mac OS X之类的一样)。

  维护。Git需要周期性地进行仓库维护(比如:git-gc),Hg不需要这样的定期维护。注意,尽管如此,Hg关于用户磁盘空间的管理还是有不少不成熟的地方(参照上面的“客户端存储管理”)。修订历史是神圣的。Git非常强大,而且几乎能做任何你想要做的事。不幸的是,这也意味着Git完全乐意丢失修订历史。例如,git-push --force将导致远程仓库中部分修订的丢失。Hg的仓库更倾向于构建一个不可变对象的日益增长的合集。尽管在一些情况下(比如rebase),重写修订历史是一个优势,但这也可能比较危险并且可能导致一些意想不到的结果。值得注意的是,尽管如此,但是一些定制的Git服务器能被重写为不允许丢失数据,所以这个优势是非常小的。

  其他差异

  重命名/复制的跟踪。Git没有明确地跟踪文件的重命名或者复制。然而,像git-log这样的命令是查找是否有一个完全相同的文件出现在仓库的修订历史中并且推断是重命名还是是复制。(Git does not explicitly track file renaming or copying. Instead, commands such as git-log look for cases where an identical file appears earlier in the repository history and infers the rename or copy.)Hg采用我们更熟悉的处理方法,假如明确重命名和复制的命令,并将这保存为这个文件的历史的一部分。每种处理方法都有各自的优势和劣势,而且无法清楚地界定某一种方法一定优于另一种方法。

  架构。Git是源于很多的shell脚本和用c语言实现的unix命令。久而久之,就有人开发了一个共享两种命令通用的库,并且很多的命令被内建到git的主执行命令中。Hg主要是通过Python语言(和一个C语言的子集)实现的,附带有一个供第三方通过自定义Python模块增强Hg的扩展API。

  私有修订历史。在Git中,默认的操作模式是开发者有他们自己本地(私有)的标签/分支/修订版本,然后通过运用多种控制方式让它们变成公开的。Hg针对的重点却是倒过来的。默认的推送/拉取动作降回分享所有的信息,并且需要通过额外的步骤来分享一个子集。这没有作为优势列在这两个系统中,因为两个系统分别适用于不同的人群。分支命名空间。在Git中,每个仓库都有自己的分支命名空间,用户能够分别为本地分支名和远程分支名之间设立一个映射。在Hg中,只有一个在所有仓库间共享的分支命名空间。

  实现

  数据存储

  Git和Hg以非常类似的内部结构运作:处于版本控制下的文件都有相应的一个元数据信息(父分支,作者等等)。它们都有代表整个项目的提交的对象,而且也都进行了版本控制。它们都有将提交与一个文件版本控制的合集联系起来的对象。在Git中,这是一个树对象(一个由对象的目录树和文件修订版本的引用的叶子组成的树形结构)。在Hg中,则是有一个清单(一个平面的保存着文件修订版本对象和路径名映射的列表)。抛开清单和树形结构的区别,Git和Hg关于搜索和遍历对象的方式都是非常相似的。

  Git将所有直接在文件系统中的存储的对象合并一起(通过SHA1哈希值进行索引),并将多个对象打包放到更大的压缩文件中。而Hg使用的是revlog(版本记录的缩写)的结构(简单来说是一个定期完整版本的快照和修订版本的差异组成的串联)。在这两种情况下,将不会使用自带的存储方式,取而代之的是将对象保存在Bigtable中。由于Git和Hg数据对象的基础十分相似,解决这样的问题所做的努力应该是不管使用的是哪一个DVCS。

  数据存储层唯一的一个大的区别是所用的实现语言。如果想重用Git/Hg代码的一个显著区别,那就是Git的实现将使用的是C,而Hg使用的将是Python(或许有可能是绑定了SWIG的C++)。

  Hg的集成

  Hg对于HTTP这样的无状态的对远程仓库的推送和拉取操作的支持非常好。已经做了很大的努力来减少客户端与服务器之间为决定需要被交换的数据而产生的数据交换次数并且一旦决定将把相关的信息捆绑成一个单独的大传输块进行传输。这与Google的基础设施非常匹配,而且客户端也不需要任何的修改。

  Git的集成

  Git包含支持HTTP方式的拉取(和WebDAV方式的推送),但这是基于假定服务器完全不了解Git的情况的实现。它被设计于就像你有一个将Gti仓库作为静态内容的方式提供的服务器一样。这种方式需要多次的往返的同步请求,这不适合Google Code。(注1)

  Git也有一个定制的有状态的支持更快的信息交换的协议,但是这和Google的基础建设不太匹配。尤其是,在已经存在支持稳定和高效的数据交换的基础设施的情况下,是多么渴望可以使用无状态的HTTP协议。

  注:在本文完成后,在Git社区里已经有些关于提升HTTP支持的讨论了。

  总结

  从实现的努力来说,Hg由于对HTTP传输协议的支持是一个明显的优势从特性来说,Git更强大,但是也因此使用起来更加复杂而往往会遭到抵触的情绪。

  -------------------------------------------------------------

  注1:Git和Hg的仓库使用接近1500个文件(约35M的数据)作为测试基准。服务器运行在芝加哥而客户端在山景城(ping延迟51ms),克隆远程仓库的操作(对于传统的版本控制系统来说相当于一次初始化检出)Hg平均花费8.1秒而Git花费178秒(慢了22倍)。仓库里的单个文件修改了50次,然后客户端拉取这些更新。在这个测试例子里,Hg花费了1.5秒而Git需要18秒(慢了12倍)。当Git使用HTTP协议时,Git的表现和Hg相当(克隆仓库花费8.7秒而拉取花费2.8秒)。


热点排行