Mercurial的简要概念及使用

来源:互联网 发布:程序编辑软件 编辑:程序博客网 时间:2024/05/15 07:44

使用Mercurial已经有一年多的时间了,个人认为除了安全性以外相比SVN这样的集中式版本管理有很多的优势,尤其最近TortoiseHG 1.0.1版本发布了,使用相比以前也更加简捷和方便了。

作为分布式的版本管理软件我也无法将它与其它DVCS进行比较,因为是我用过的第一个的DVCS。从简要的概念上来讲,我就将它与SVN进行一翻比较,不过SVN太长时间没用,有些东西可能记不太清甚至是错误的。

 

为了简化概念,我把mercurial分隔成两个部分:本地版本和外地版本。

 

首先说下本地版本

在SVN中,主要会用到的几条指令是add/commit/update/branch,当然这些指令多数都会修改SVN所在服务器的版本库内容:commit,提交一个新的版本;add,添加文件/目录/链接文件等;branch,建立一个新的分支,操作来讲始终觉得有点复杂。

这些指令mercurial自然是一个不少。commit,提交新版本;add,添加文件;branch,耶,似乎没有这条或类似的指令,但其实更加强大。

 

这些指令当中,似乎看来差别不大,确实,但他们有一个重要的差别:那就是他们操作的目标。众所周知,svn会修改一个svn地址(或是其他什么如http等共享方式)上服务器的内容,然后其它用户就可以看到这个修改并获取。

Mercurial则不然,这一类的指令都只会修改“本地的库”,也就是说其他人目前看来是无法获得你这个版本修改的相关信息的(至于如何获得,稍后会提到)。

正如svn的目录下会有个隐藏的.svn目录,mercurial有一个类似的隐藏的.hg(汞)目录,但svn在这个目录下面保存的是一些诸如什么文件被修改的信息(是吗?记不清了),而mercurial的这个目录保存的却是完整的版本数据,对的,包含一切东西,changelog、branch、谁修改的、那个版本修改了什么文件什么内容,一个都不少。它就是本地库所存在的地方 .hg 目录。在mercurial中,commit、版本历史浏览、update都是修改/读取的.hg这个目录下的信息。即使你把该除了.hg目录的其他所有文件都删掉,你依然可以通过update指令把工作文件恢复到你所指定的某个版本,而这一切不需要任何网络。

 

.svn与.hg目录还有一个很大的差别,.svn目录遍布整个工作目录下所有的子文件夹中,.hg则只在工作目录的根文件夹存在。比如SVN可能是这样的目录结构:

| code

|---- .svn

|---- include

|-------- .svn

|-------- *.h

|---- src

|-------- .svn

|-------- *.cpp

|---- some file

|---- some folder

|-------- .svn

|-------- subfile and subfolder

而mercurial则会是这样:

| code

|---- .hg

|---- include

|-------- *.h

|---- src

|-------- *.cpp

|---- some file

|---- some folder

|-------- subfile and subfolder

 

关于mercurial的branch相当灵活,分支的创建与合并(当然,冲突解决是无法避免的)也很简单,或者说分支随时与你同在,一旦你commit之后,那么就可能创建了一个分支。假设我们现在有2个版本号,2号版本基于1号版本进行了一些修改。如果我继续修改然后提交,那么3号版本没有创建了来的新的分支。分支的图形可能就是这样

●    3

|

●    2

|

●    1

但如果我是先把工作目录update到版本1,然后进行了一些修改再提交,那么此时版本3就建立了一个新的branch,图表可能是这样

●       3

|

|  ●    2

| /

●       1

这表示版本2和3是2个不同的分支,这种情况很可能发生在2个程序员目前都在版本1上进行了修改,然后各自进行了提交,当然他们都只是提交到本地的.hg目录这个库里,暂时他们只会看到1和2这两个版本,但是他们的2这个版本修改的内容却完全不同。mercurial中的数字版本号只针对本地库才有效,他的全局唯一版本号是一串十六进制字串。我们这里先假设这些操作都是一个人在本地做的(其实这也是完全可能的,我曾经就经常这样干)。

既然建立了2个分支且有不同的修改,当我们想把2个修改合并的时候就会用到merge指令,merge影响到的只是工作拷贝,完成之后我们需要commit(到本地库),图表就会是这样了:

●       4

| /

●  |    3

|  |

|  ●    2

| /

●       1

象这样的创建分支、合并,创建分支、合并的情况在mercurial中相当普遍,尤其是多人协作时。实际应用中,这个图表看上去更加复杂。

 

到目前为止,请记住最重要的一点,所有这些操作都是在本地版本库(.hg目录)和工作拷贝中进行的,不涉及到任何第三者(老婆太漂亮了)

 

 

 

 

 

 

是时候说下外地版本了,什么是外地版本?不是你这个目录下的.hg目录和工作拷贝都称做外地版本,那怕是同样的目录内容,只是路径名不一样,他们对彼此来说都是外地版本。比如E:/foobar和E:/foobar_clone,2个目录下的文件及子文件夹.hg的目录内容都一致(比如复制e:/foobar后粘贴到e:/复件 foobar),那么他们彼此之间都称对方为外地版本。前面说到多人合作,如何合作呢?这时就需要用到四条指令,它们分别是:incoming,pull一对,outgoing,push一对。

incoming是比较你所指定的某个目录或地址下的.hg目录下的内容与你所在的.hg目录下内容两者之间有那些他有,而你没有的版本号,如何辨认不同的版本,就是前面提到的一个16进制字串,也就是ID。

pull则是把他有的版本数据(版本号数字忽略,其它如修改的文件及内容、唯一ID号)复制(拉取)一份到本地并与本地的版本信息进行合并,为新复制的版本数据生成新的数字版本号。

 

outgoing和push则刚好相反,outgoing是看你有而他没有的,push则是把你有而他没有的版本“推”给他。这样他就有你了新加的版本数据了,如果他并没有在他的本地update到最新的数据,那么他的工作拷贝是不会改变。因为你只是改变了他的.hg目录数据。

 

这样一来,在2个人合作时这种情况就极有可能发生了

●       0xdddd

| /

●  |    0xcccc

|  |

|  ●    0xbbbb

| /

●       0xaaaa

 

1、你和同事目前本地的版本都在0xaaaa

2、你们开始分别基于0xaaaa版本进行了不同的修改

3、你和你的同事先后把修改的内容进行了提交(时间前后没有关系)

4、你通过incoming指令发现你的版本库中有一个0xbbbb的版本他没有(你刚才提交到你的本地库)

5、你再通过outgoing指令发现他的版本库中有一个0xcccc版本而你没有(他刚才提交到他的本地库)

6、这时你(或者同事)可能会做2件事情:第一是把你的0xbbbb“强行”推给他(为什么是强行,因为默认情况下mercurial不希望push给别人一个没有合并过的版本,非要这样做就必须加上"force"选项);第二件事是常做的,分成以下几步

7、使用pull指令把他的0xcccc版本“拉”到你的本地库中,图表如下

●       0xcccc

|

|  ●    0xbbbb

| /

●       0xaaaa

8、将0xbbbb与0xcccc进行合并,合并后的结果反映到工作拷贝,而不是本地库(.hg目录)

9、解决完冲突后把合并结果提交到你的本地库中,图表如下

●       0xdddd

| /

●  |    0xcccc

|  |

|  ●    0xbbbb

| /

●       0xaaaa

10、再次使用incoming指令,如果他在你合并期间没有新的提交则继续下一步,否则重复7至10步

11、使用outgoing指令,发现他的版本库中没有0xbbbb和0xdddd这2个版本

12、使用push指令把你的修改及你和他的修改合并后的版本推到他的本地库中

13、他现在使用log指令可以直接看到0xbbbb和0xdddd版本了(注意,他并没有使用incoming/pull/outgoing/push就有了你刚才做的所有东西,因为你push给他了),使用update指令就可以更新工作拷贝到0xdddd版本了。

 

那么DVCS这样的模式有什么好处呢?

1、工作中难免会遇到你正在做一处较大的代码调整,这通常意味着你需要花费很长的时间来进行这项工作,而在这期间如果使用svn,你可以去建立一个新的分支,然后switch过去。mercurial什么都不需要做,直接开始工作,只要你不共享你的.hg目录,你也可以随时提交,因为你只影响到你的本地。而另外一个好处是你可以每隔一定时间,比如一天、一个星期将其他人最新工作的成果pull下来和你的本地版本进行合并,不用等到整个修改完成之后再去解决可能存在的大量冲突,那时可能同事已经忘记了一个月前具体修改的内容了

2、如果你是需要和某位同事一起对代码做大的调整和修改,但完成之前并不希望其他同事被影响。OK,你仍然不需要做更多的事情,你们2人之间互相pull/merge/push,你们也随时可以将其他同事的最新工作成果立即进行合并到你们当前工作的内容来,完成之日,简单的push给他们就OK

3、我可以把代码push到u盘上,u盘带回家,在家里从u盘pull今天公司的工作内容,然后在家写一些东西,睡觉了,push到u盘,第二天带到公司,从u盘pull到在家改的东西。而这一切,对于其他人都是透明的,当然我最终把这些修改push给同事时,他们才会看到我的changelog可能有点多。嗯,如果公司对代码的管理上很严格,那么你可能是被禁止将代码带出公司的。

4、工作中我有遇到这样的情形,有一个功能有2个版本,其中一个是临时性的,它修改了一些代码,但明确的知道日后会被移除,但却会在最近多次的发布中以2种不同的版本发布给最终用户使用。而在这段时间内的多次发布中,所有的其他修改的功能都是一致的。这时mercurial让我大呼过瘾,我建立了一个分支,临时功能的相关代码做了修改,然后与正式功能的版本同时发布,在每次其他新功能发布时,我将正式版本的分支与临时版本的分支合并,立即可以编译程序生成临时版本的发布程序却不影响正式版本的代码。最后当临时版本不再需要时,我直接删掉这个分支,而其他同事自始至终都没有得到这个临时分支的任何修改。其实在后来我用了更方便的使用方法:

正式版的工作目录照常做日常修改,与其他同事的修改进行同步。然后另外创建一个临时的工作目录,在这个临时的版本库中做了需要的修改。每次新版本发布时,正式版获取其他同事的修改内容,编译、发布,然后进入临时工作目录,从正式版的工作目录pull到最新的修改,合并,提交,编译,发布,而临时的修改和多次的合并内容只存在于临时版本的目录中,最终不再需要时,直接删除掉该目录,正式版的代码不需要做任何调整。

 

 

在我日常的工作中,我在服务器上共享出两个目录Release和Development,我告诉大家,本地也分别建立这2个目录,Bug修改及即将发布的内容在Release中修改,并通过服务器Release目录进行pull和push操作,新功能开发则在Development目录中修改,并与服务器Development目录进行pull和push操作。于是我们项目组则是通过服务器上的这2个目录进行代码的同步,而Development和Release之间的同步则主要由我来完成,虽然其实任何人都可以做。大家也不用再去辨认自己应该update到那个development还是release分支来开展他们的工作,只是简单地进入相应的目录即可。