关于hg命令选项

如果你是在windows系统下,使用的是图像界面,你很可能不常用它。但是一旦你了解这些命令之后,会觉得很方便。hg有很多命令,这些命令都有一定的选项,在开始的时候,只知道用它,有时候命令选项以”–”开头,有时又以”-”开头。翻阅资料,发现对于命令选项,hg有以下的约定。

  1. 每一个命令选项都有一个长的名称,如hg log 命令会使用 –rev选项;
  2. 大多数选项拥有一个短的名称,如—rev有一个-r的短名;
  3. 长名称以两条横线(–)作为起始,短名起始是一条横线(-)。如—rev、-r;
  4. 无论是长名还是短名,其用途是一致的。如-r和—rev 其后都跟一个变更集合的ID或版本号;
  5. 如果使用的是短名,多个名称可以连在一起运行。如hg log –v –r –p 2可以写作hg log –vpr 2。

关于hg log命令

使用该命令可以了解代码仓库变更的历史,其输出的结果中包含以下几个字段:

  1. changeset 该字段被一个分号(:)分成两部分,分号的前面是一个数字,其后是一个十六进制的字符串。在查看版本历史的时候,常常会用到前面得数字。这里需要注意的是,该数字所代表的变更集合不是唯一的(比如a,b,c在一个代码仓库中的顺序是0,1,2,在另外一个代码仓库中可能是0,2,1)。真正指向唯一的变更集合的是后面的十六进制的字符串。
  2. user 代表集合的创建者;
  3. date 代表集合创建的时间;
  4. summary 创建者提供的关于该集合的描述文本;
  5. tag 有一些集合会包含一个tag字段,这个特别的字段总是指向代码仓库中最新的版本。

明白以上字段的含义,对于你查看版本历史和提交代码时书写描述性文本很有意义。在后面的章节中,甚至可以根据这些字段改变其输出的格式。

要查看特定的版本,在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会尝试去找到一个有效的用户名。它会按下面的顺序去逐个尝试寻找有效的用户名:

  1. 如果在使用hg conmit提交代码时,使用-u命令选项,并在其后跟上一个用户名(如:dexibe@gmail.com)。此时,该命令将具有最高的优先权;
  2. 如果没有第一步的命令选项,它会查看你是否设置了HGUSER环境变量;
  3. 如果在你的home目录下,你创建了.hgrc文件,并且该文件设置了username,它会启用该用户名;
  4. 到这一步,它会查看你的email环境变量;
  5. 如果以上都没有找到合适的用户信息,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)。此时,窗口会被分成三部分:

  1. 左边是文件的的基础版本;
  2. 中间是你自己的版本,文件包括被修改的内容;
  3. 右侧是你将要与之合并的版本。

你可以在三个窗口之间来回切换,修改文件的冲突部分,将其更改为我们需要的部分。一旦完成文件合并,一定要记住提交合并之后的代码。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)之前对该文件“拍一张快照”。

在将文件添加到代码仓库中,需注意一下几点:

  1. 如果使用hg add后跟的是一个目录,该命令会将该目录下的所有文件添加到仓库之中,包括子目录中的文件。假如有一个目录b,其下有一个文件some.txt和d目录,d目录之下有一个文件c.txt。运行hg add b,你会看到:

  2. hg并不追踪文件夹信息,而是追踪文件的路径。在创建一个文件之前,首先创建文件所在的路径。删除一个文件后,它会删除文件所在路径的所有空的文件夹。一般情况下,很少用到空文件夹。如果在你的代码仓库中需要空文件夹,可以通过hg add来添加一个隐藏的文件。在linux系统中,任何以“.”开头的文件都被视作隐藏文件。除此之外,你可以在需要它之前通过自动化执行脚本方式创建空文件夹。

二、如何停止文件跟踪

如果你决定不让代码仓库跟踪你的文件,使用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有一些快捷方式可以一次完成文件的添加和删除文件操作。

  1. 使用hg addremove命令;
  2. 在提交代码时使用-A参数,即hg commint -A -m “提交代码”。

三、复制文件

hg提供了hg copy命令来复制一个文件。用该命令来复制文件,hg会记住新的的文件是源文件的副本。在你合并代码的时候,它会对其作特殊的处理。下图是使用hg copy复制文件后的截图:

从图中可以看到,hg status -C 可以看到myfile-copy.txt是从myfile.txt复制而来。复制的文件在代码合并的时候会发生什么呢?以下是在在两个工作目录中复制文件,并在一个仓库中修改文件时的截图。

查看myfile-copy.txt,你会看到,对myfile.txt所作的修改同步到了myfile-copy.txt。这看上去有些怪异,但是其结果却相当尽如人意。可以想见,如果hg不将其同步到文件副本,那么该副本就会保持原来的版本不变。当然,你也可以手动去修改副本文件。这里需要注意的是,这种信息直播仅发生在以下几种情况:

  1. 代码合并的时;
  2. 你的代码仓库中还没有该文件的副本时。

如果说这种文件复制所导致的信息直播并不是你所需要的结果,你可以使用系统正常的文件复制操作。比如使用unix中的cp命令,然后用hg add命令将其添加至代码仓库。

如果你修改一个文件,但是在没有提交代码的情况下复制该文件,那么在该副本文件中仍将包含文件被修改的部分。

hg copy命令与unix系统中的cp命令极其相似,只需要提供两个或三个以上的参数即可,最后的一个参数被视作目的地,其它都被视作复制源。这种操作包括以下几种情况:

  1. 目的地不存在,源文件为单个文件,将创建同名的新文件;如:
    1.hg copy a k//目录k不存在,重建k目录,并将a复制到k文件夹之下
  2. 目的地是一个目录,将源文件复制到该目录之下;如:
    1.hg copy a b d// 将a 和b 复制到d目录之下
  3. 复制源跟目的地都是目录,则将复制源复制到目标文件夹之下,并在目标文件夹之下根据复制源目录的名称重建目录,且保持复制源的目录树结构;如:
    1.hg copy z d //假设z的目录树结构为z/a/c,复制到目录d后,d的目录树结构为d/z/a/c
  4. 复制源是一个目录,目的地不存在,则创建新的目录,并将复制源下的目录树复制到新的目录之下;如:
    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系统中操作的截图:

既然重命名操作与复制操作相似,那么在代码合并时对被重命名文件所作的操作将会同步至被重命名后的文件。比如,当你对一个文件进行修改,而你的同伴对该文件进行了重命名操作。在代码合并的时候,你所做的修改将被同步到被重命名的文件之中。

对文件进行重命名操作时,会遇到以下两种情况:

  1. 同一个文件被重命名为不同的名称。在这种情况下,进行代码合并的时候,hg会发出警告,提示用户该文件存在不同的文件名,让用户去决定选择哪一个文件名;
  2. 不同的文件被命名为相同的文件名。在这种情况下,hg会按正常合并程序来合并代码,让用户去选择合适的方案
  3. 在你的代码仓库中的有一个目录文件,它的名称与另外一个代码仓库中的文件名相同。这种情况下进行代码合并时,合并将会终止,这是Mercurial 遗留下来的一个bug。详情可以查看http://mercurial.selenic.com/bts/issue29。

五、如何处理较为复杂的代码合并

一般来说,合并代码是一件很简单的事情。不过有些时候代码合并也不是那么顺利。假如一个项目中有一个较大的文件同时被几个人编辑,冲突就很容易出现,运行hg heads命令就会看到两个或多个head的情况,此时就需要分别与每一个head进行合并,并提交代码,直至看到一个head为止。如果代码合并没有完成,hg将会阻止代码提交。

如果合并代码后提交代码失败,可以使用hg resolve命令。进行代码合并后,如果 hg发现文件为unresolved 状态,则认为代码合并失败。hg resolve –list或hg resolve -l会输出每一个合并文件的状态,如果文件前面有一个标识符“U”,则认为合并不成功,提交代码就会失败。对于hg resolve,有以下几个常见的选项:

  1. –all或-a,尝试去合并所有unresolved 状态的文件;
  2. –mark或-m,直接将文件修改为resolved 状态;
  3. –unmask或-u,直接将文件修改为unresolved 状态。

六、关于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代码所花费的时间。因此,对于以下的两种情形应慎之又慎:

  1. 较大而且不能压缩的文件;
  2. 文件频繁编辑导致版本间产生巨大差异。

hg维护着克隆的所有历史,任何人的代码仓库都可以作为备份源。当然,可以在远程服务器上构建一个小小的脚本来定时备份代码仓库。如果只是作传统的备份,运行hg clone -U myrepo myrepo.bak即可,这里的选项-U会在clone完成后不对工作目录进行更新操作,详情可以参看hg help clone。