大约是在去年的这个时候,开始使用 hg作为代码的管理工具。由于之前一直使用svn,对这个新鲜的东西有些不适应。不过在使用了一段之间之后,渐渐发现自己越来越喜欢这个工具了—-几个简 单的命令就能提交代码,很容易查询版本的历史,很方便的进行代码合并。近来查看资料,对hg有了较为深入的认识,接下来的几篇文章将记录我学习hg的一些 笔记,以备将来之用。
关于hg命令选项
如果你是在windows系统下, 使用的是图像界面,你很可能不常用它。但是一旦你了解这些命令之后,会觉得很方便。hg有很多命令,这些命令都有一定的选项,在开始的时候,只知道用它, 有时候命令选项以”–”开头,有时又以”-”开头。翻阅资料,发现对于命令选项,hg有以下的约定。
关于hg log命令
使用该命令可以了解代码仓库变更的历史,其输出的结果中包含以下几个字段:
明白以上字段的含义,对于你查看版本历史和提交代码时书写描述性文本很有意义。在后面的章节中,甚至可以根据这些字段改变其输出的格式。
要查看特定的版本,在hg log 后跟上版本号即可,如:
1.
//查看特定的版本;
2.
hg log -r 2
同时查看几个版本,可以使用如下的命令:
1.
//查看版本2,3,4
2.
Hg log -r 2:4
如果要查看版本的详细内容,包括文件发生改变的内容,可以添加-p(–patch)命令选项。
1.
//查看版本的详细内容
2.
hg -log -vpr 1
下图是使用该命令后的输出结果:
使用hg commit 提交代码的需要注意的问题
第一次使用hg commit命令提交代码时,很可能会出现代码无法提交的问题。这是因为在代码提交时hg会尝试去找到一个有效的用户名。它会按下面的顺序去逐个尝试寻找有效的用户名:
下图是没有设置用户名提交代码时的输出结果:
拉取和推送代码
拉取代码,使用hg pull即可,有一个hg incoming命令我很少用到,仔细看了看,这个命令还是很管用。hg incoming命令不会真正的拉取代码到本地仓库,它只是告诉你使用hg pull会将哪些变更集合拉至本地仓库。这对于你拉取特定的版本集合很管用。
推送代码,使用hg push即可。在真正推送之前,可以使用hg outgoing查看有哪些改变将会被推送至代码仓库。
在项目推进的过程中,经常会出现两 个人共同修改一个文件的情况。这个时候,我们就要进行代码合并。使用hg合并代码非常简单。Hg记录每一次变更集合的父级,如果变更集合一个父级,一个 head就是一个没有子级的集合。当多人修改同一个文件时,就会出现不止一个head的情况,此时需要进行代码合并。
如果一个文件同时被几个人修改,hg pull命令会告知你”heads”的情况。此时如果使用hg update命令,会出现以下的提示语:abort: crosses branches (use ‘hg merge’ or ‘hg update -C’)
如果要强制更新,可以使用hg update –C。要查看heads可以使用hg heads命令,该命令会告知你head的情况,使用hg merge就会完成两个head之间的代码合并。此时使用hg parents命令会看到父级的情况。在完成代码合并之后,一定不要忘记使用hg commit命令提交更改的代码。
大多数情况下,代码合并都很简单。 但是,如果多个人修改文件的同一个地方,情况就有所不同了。除非修改的内容一样,否则会导致冲突发生。Hg不能很好的处理这些冲突,但是它能运行外部程序 来处理这些冲突,如kdiff3。这种合并就是所谓的“三路合并”(three-way merge)。此时,窗口会被分成三部分:
你可以在三个窗口之间来回切换,修改文件的冲突部分,将其更改为我们需要的部分。一旦完成文件合并,一定要记住提交合并之后的代码。Hg本身不能处理有冲突的变更集合,它会在存在冲突文件上留下一个标记,告知用户该文件存在冲突。
仔细回忆一下,从代码仓库中拉取代 码(hg pull)后,更新(hg update)本地仓库,进行代码合并(hg merge),提交(hg commit)合并的代码。在hg中有一个命令可以完成前三步操作,它就是fetch。要启用它,只需在.hgrc中的[extensions] 设置fetch = 即可,这是个标准的扩展,hg知道在何处去查找这个扩展,使用hg help extensions命令,你可以看到关于fetch的描述: pull ,update and merge one command。启用成功之后,使用hg fetch试试。
前面的两节简要地记录了使用hg命令的一些注意事项,并提到了文件merge的一些知识,这一节将回顾使用hg来添加、删除、复制文件的一些操作。
一、如何让HG跟踪你的文件
hg不会自动的管理你仓库中文件,hg status命令会告诉你那些它不知道的文件,它会在这些文件的前面打上一个“?”号。假如在你的仓库中有一个myfile.txt的文件,执行hg status你会看到:
在myfile.txt前面,我们会看到一个“?”号。如要让hg管理该文件,执行如下命令:
1.
hg add myfile.txt
此时运行hg st,你会看到:
文件myfile.txt前面的“?”号变成了“A”。 执行hg commit命令之后,myfile.txt就不会再出现在hg status的输出结果之中。hg status命令的输出结果中仅包含删除的(removed)、被修改的(modified)、重命名的(renamed)文件。添加一个文件,hg什么 也不会做,它只是在执行下一次提交(hg commit)之前对该文件“拍一张快照”。
在将文件添加到代码仓库中,需注意一下几点:
二、如何停止文件跟踪
如果你决定不让代码仓库跟踪你的文件,使用hg remove命令即可。文件删除之后,运行hg status后的输出结果中,被删除的文件之前会打上“R”标识符。假如我移除我仓库中的myfile.txt文件,你会看到:
使用hg remove删除一个文件后,hg将停止追踪该文件的任何改变。即使你重新创建一个与之同名的文件, hg也会无动于衷。假设你重新创建一个与之同名的文件,运行hg add命令让hg跟踪它。hg会认为新添加的文件与先前的同名文件毫无关联。
删除一个文件并不会对该文件的历史带来任何影响。它仅仅从当前工作目录中移除文件的当前版本,并在下一次代码提交的时候让hg停止对该文件的追踪。
如果你不是使用hg remove来删除一个文件,hg会认为文件丢失。它会在丢失的文件之前打上“!”标识符。假设我手动删除工作目录下的myfile.txt,然后运行hg status命令,你会看到如下结果:
假设你的本意是将该文件删除,你可以在任何时候运行hg remove –after命令来真正的删除该文件。紧跟上面的例子,执行该命令会看到如下结果:
可以看到,myfile.txt前面打上了“R”标识符。
如果意外的删除了丢失的文件,可以使用hg revert命令将其恢复。还是紧跟上面的例子,运行该命令,你会看到:
你可能认为文件添加和删除操作相当繁琐。令人兴奋的是,hg有一些快捷方式可以一次完成文件的添加和删除文件操作。
三、复制文件
hg提供了hg copy命令来复制一个文件。用该命令来复制文件,hg会记住新的的文件是源文件的副本。在你合并代码的时候,它会对其作特殊的处理。下图是使用hg copy复制文件后的截图:
从图中可以看到,hg status -C 可以看到myfile-copy.txt是从myfile.txt复制而来。复制的文件在代码合并的时候会发生什么呢?以下是在在两个工作目录中复制文件,并在一个仓库中修改文件时的截图。
查看myfile- copy.txt,你会看到,对myfile.txt所作的修改同步到了myfile-copy.txt。这看上去有些怪异,但是其结果却相当尽如人意。 可以想见,如果hg不将其同步到文件副本,那么该副本就会保持原来的版本不变。当然,你也可以手动去修改副本文件。这里需要注意的是,这种信息直播仅发生 在以下几种情况:
如果说这种文件复制所导致的信息直播并不是你所需要的结果,你可以使用系统正常的文件复制操作。比如使用unix中的cp命令,然后用hg add命令将其添加至代码仓库。
如果你修改一个文件,但是在没有提交代码的情况下复制该文件,那么在该副本文件中仍将包含文件被修改的部分。
hg copy命令与unix系统中的cp命令极其相似,只需要提供两个或三个以上的参数即可,最后的一个参数被视作目的地,其它都被视作复制源。这种操作包括以下几种情况:
1.
hg copy a k
//目录k不存在,重建k目录,并将a复制到k文件夹之下
1.
hg copy a b d
// 将a 和b 复制到d目录之下
1.
hg copy z d
//假设z的目录树结构为z/a/c,复制到目录d后,d的目录树结构为d/z/a/c
1.
hg copy z e
//假设z的目录结构为z/a/c,复制完成后e的目录结构为e/a/c
与hg remove命令一样,如果你手动复制一个文件,并让hg能跟踪此文件,可以使用hg copy –after [源文件] [目标文件],如:
1.
cp a b
2.
hg copy --after a b
四、文件重命名
与文件复制操作相比,文件重命名操作更为常见。在hg代码管理中,文件重命名操作在本质上等同于文件复制操作。因此,理解了文件的复制操作,文件重命名操作就不是太难。
使用hg rename进行重命名操作时,hg先对文件进行复制操作,然后删除该文件,并标注该文件已经删除。如:
从图中可以看到,重命名的newfile.txt前面打上了“A”标识符,源文件myfile.txt前面打上了“R”标识符。运行hg status -C你会看到如下的结果:
同样,如果你手动重命名一个文件,你也可以使用–after选项来让hg追踪你重命名的文件。在 unix系统中,可以使用hg mv命令来重命名一个文件。以下是在windows系统中操作的截图:
既然重命名操作与复制操作相似,那么在代码合并时对被重命名文件所作的操作将会同步至被重命名后的文件。比如,当你对一个文件进行修改,而你的同伴对该文件进行了重命名操作。在代码合并的时候,你所做的修改将被同步到被重命名的文件之中。
对文件进行重命名操作时,会遇到以下两种情况:
五、如何处理较为复杂的代码合并
一般来说,合并代码是一件很简单的 事情。不过有些时候代码合并也不是那么顺利。假如一个项目中有一个较大的文件同时被几个人编辑,冲突就很容易出现,运行hg heads命令就会看到两个或多个head的情况,此时就需要分别与每一个head进行合并,并提交代码,直至看到一个head为止。如果代码合并没有完 成,hg将会阻止代码提交。
如果合并代码后提交代码失败,可以 使用hg resolve命令。进行代码合并后,如果 hg发现文件为unresolved 状态,则认为代码合并失败。hg resolve –list或hg resolve -l会输出每一个合并文件的状态,如果文件前面有一个标识符“U”,则认为合并不成功,提交代码就会失败。对于hg resolve,有以下几个常见的选项:
六、关于hg diff
hg diff有一个选项–git或者-g,该选项我们很少用到。假如你修改了一个文件的权限,运行hg status会看到文件被修改,而运行hg diff将看不到任何输出结果。如果在hg diff后跟上-g选项,你将会看到文件的实际状态。在unix系统下运行以下代码,看看输出结果。
1.
chmod +x a
2.
hg diff -g
七、后话
版本控制系统通常用来管理文本文件,一些集中式版本控制系统也能处理二进制文件。集中式版本控制系统提供一种锁定机制来编辑文件,这会很好的处理文件冲突的发生。假如一个项目中有多个人频繁的修改一些二进制文件,那么hg或其它的分布式管理系统也许就不是最好的工具。
在修改文件时,hg仅仅存贮文件当前版本与上一个版本间的差异。如果对文件做一次小小的改动导致文件的逻辑发生了巨大的改变,hg就不能很有效的存储这些差异,并直接影响到存储的空间和clone代码所花费的时间。因此,对于以下的两种情形应慎之又慎:
hg维护着克隆的所有历史,任何人 的代码仓库都可以作为备份源。当然,可以在远程服务器上构建一个小小的脚本来定时备份代码仓库。如果只是作传统的备份,运行hg clone -U myrepo myrepo.bak即可,这里的选项-U会在clone完成后不对工作目录进行更新操作,详情可以参看hg help clone。
loading