2011年1月28日星期五

git vs hg

看到一篇文章,重新又勾起了我对版本控制工具的回忆和兴趣,拿出最好用的两种来比较比较吧:git和hg 。

1.对象模型(Object Model)

hg是采用增量式存储的版本控制系统,它保存相邻版本间的差异,通过在基础版本之上叠加差别的方式记录版本的更新,其组织方式自然采用链表。这点和subversion一致,数据库版本控制工具dbdeploy也采用相同原理。

hg中的基本对象有三种:file、manifest 和changeset

* changeset可以理解为版本的代名词了,它记录了该版本的信息,changeset间的采用链式索引,由一个根开始;
* 每一个changeset持有对应版本的manifest,这是一个记录repo内当前工程版本下的各文件版本信息的列表;
* 每个file对应的Revlog 文件才是真正存储各版本以及其数据节点的列表。

git并非采用 增量式存储,而是为每个版本创建一个快照,最重要的组织结果是树。

基本对象有三种,自底向上为:blob、tree和commit(还有个tag, 和hg的bookmark类似,略了 )

* blob存储的是repo中某文件的内容,单纯的二进制方式,不存储包括文件名在内的其他东西。它是脱离于版本之外的存储结构,通过对文件数据的hash值比较可快速区分两个blob,不同版本、或不同目录下、或不同名称但内容相同的文件共有相同的blob对象;
* tree存储了所有directory和file的组织结构,对directory存为子树,对file保存其对应blob对象的hash值;
* commit也是版本的标志,它持有tree的root节点地址,相邻commit之间也是链接在一起的,以方面找到上一个版本。

2. 工作目录与专用目录(working dir and Git/Hg dir)

两种版本控制工具都将代码checkout到工作目录下,工作目录中保存的是项目当前branch的当前版本;同时在本地repo的根目录(也是工作目录)下为各自系统建立一个专用的目录(.hg , .git), 存储版本历史、对象与索引和配置文件等。

不同之处在于建立codebase时,这里首先要提到bare repositories的概念。

# Why bare?

一个bare repo与普通repo的区别是没有项目文件的working copy,即repo根目录下只有专用目录,而没有任何其他代码文件和文件夹;这是为了响应作为codebase应当遵循的“Only store, never update from revisions(只存储版本,不更新到实际代码文件)”原则。

hg管理的repo天生就能做codebase使用,无论是否是bare的,这点是由其分布式版本控制系统的本质决定的,它可以随时把当前的repo通过自带的http server发布代码,特别适合分布式开源项目的代码分享。

git也是分布式代码版本管理工具,不过它对作为codebase的repo做了严格的bare要求。可以看到的是许多人在初学git时不了解这一点,抱怨自己做spike时不知如何提交代码到在本机上的codebase。这里顺手写下两个tips:

* 初始建立一个bare repo

$ git init --bare

* 如果已有一个repo了,使用下面的方法将其转化为bare的

$ git config --bool core.bare true , 之后可删除除了repo根目录.git文件夹之外的所有文件,即只保留专用目录

3. 分支与合并(branch and merge)

git中是有同一个repo上不同branch的概念,而hg没有。就是说,git在切换到别的branch上时,仍然是在原来的repo中,工作目录下的代码也会相应地切换到某个版本上;而hg上新建branch 则意味着有了另一个repo,不能算做轻量级的分支(lightweight branch)。

hg中是以heads的概念来维护不同开发者checkout的代码的,它不像SVN这样的集中式版本控制,不需要回归到某一根主线上来(当然常常我们需要这样用,方便持续集成),所以向某个codebase提交时需要先与该branch上的head来merge,结果合并为一个新的共有的 head(这显然需要一次提交),此时两条branch交汇到一处。

git中可以在同一repo下,实现在指定branch上独立提交和在不同branch间merge。提交和更新是灵活和易控制的。

另外,hg中鼓励以changeset为单位进行merge后提交,即先将本地修改做本地提交,之后采用mercurial queue或fetch的方式合并后提交到codebase。其原理是head之间的合并。

git中鼓励在未形成本地commit时更新代码并做merge,之后将本地提交和向codebase提交连起来做。因为本地commit之后做更新和合并,并不是git对象模型所擅长的,被迫merge时容易将被引入“no branch”的临时branch状态。这种状态上也是可以进行开发的,但风险是没有正规的branch来记录版本。这里又有一个tip。

# 'no branch' problem

* 从“no branch”状态转回正常branch上, 比如master

1)在“no branch”状态上完成与代码库的同步,即保证没有重要的本地修改和本地提交残留;

2)$ git checkout master ,直接转到master上,这时对no branch的索引就丢失了,该状态不复存在;

3)$ git log ,检查master branch上的log,找到是否有本地修改/提交残留(通常是有的,因为是在做merge时出的问题);

4)$ git reset --hard HEAD~3, 放弃已有的本地提交(假设是3个,如果是两个可用HEADE^^),--hard是删除指针及内容;

5)在没有本地修改和提交的基础上,就可放心地同步到master上最新的版本了,你知道怎么做这步。

其他

诸如git的unstaged area,mercurial queue以及两个工具的应用模式,都不在此介绍了。

参考

http://mercurial.selenic.com/wiki/GitConcepts

《Git Community Book》

没有评论:

发表评论