第 5 章 Mercurial 的日常使用

目录

5.1. 告诉 Mercurial 要跟踪哪些文件
5.1.1. 明确与隐含文件命名
5.1.2. Mercurial 只跟踪文件,不跟踪目录
5.2. 如何停止跟踪文件
5.2.1. 删除文件不影响历史
5.2.2. 丢失的文件
5.2.3. 旁白: 为什么要明确告诉 Mercurial 删除文件?
5.2.4. 有用的技巧—一个步骤添加和删除文件
5.3. 拷贝文件
5.3.1. 合并后拷贝文件的内容
5.3.2. 为什么要传递变更?
5.3.3. 如何禁止变更传递?
5.3.4. 命令hg copy的行为
5.4. 重命名文件
5.4.1. 重命名文件与合并变更
5.4.2. 分歧的更名与合并
5.4.3. 收敛重命名与合并
5.4.4. 其它名称相关的信息
5.5. 从错误恢复
5.6. 合并的技巧
5.6.1. 文件的解决状态
5.6.2. 解决文件合并
5.7. 差异的更多技巧
5.8. 哪些文件需要管理,那些不需要
5.9. 备份与镜像

5.1. 告诉 Mercurial 要跟踪哪些文件

Mercurial does not work with files in your repository unless you tell it to manage them. The hg status command will tell you which files Mercurial doesn't know about; it uses a ? to display such files.

使用hg add命令来让Mercurial跟踪一个文件。 一旦添加了一个文件,该文件的hg status的输出就从?变成了A.

$ hg init add-example
$ cd add-example
$ echo a > myfile.txt
$ hg status
? myfile.txt
$ hg add myfile.txt
$ hg status
A myfile.txt
$ hg commit -m 'Added one file'
$ hg status

在运行hg commit之后,你在提交之前添加的文件将不会再出现在hg status命令的输出中。原因在于,在缺省情况下,hg status仅仅告诉你那些你可能感兴趣的文件—那些你已经(比如)修改,删除,改名的文件。如果你的版本库中包含几千个文件,你基本上不会想知道Mercurial跟踪了哪些文件,如果这些文件并没有被更改的话。(你还是可以得到这一信息的,以后我们还会讨论它。)

添加一个文件之后,Mercurial并不会马上对它做任何操作。相反,在下次你提交的时候 ,它会给这个文件作一个快照。并且在你以后每次提交的时候继续跟踪这个文件,直到你删除它。

5.1.1. 明确与隐含文件命名

Mercurial一个有用的特征是如果你将一个目录名传递给一个命令,任何一个Mercurial命令都会作如下处理我要在这个目录和它的子目录中的所有文件上执行操作

$ mkdir b
$ echo b > b/somefile.txt
$ echo c > b/source.cpp
$ mkdir b/d
$ echo d > b/d/test.h
$ hg add b
adding b/d/test.h
adding b/somefile.txt
adding b/source.cpp
$ hg commit -m 'Added all files in subdirectory'

注意在这个上面这个例子中,Mercurial输出了它添加的文件的名字,然而在前面的例子中当我们添加文件myfile.txt的时候,它没有输出任何东西。

在先前的那个例子中,我们在命令中明确的给出了要添加的文件。在这种情况下,Mercurial假设我们知道我们在做什么,所以它不输出任何东西。

然而,当我们通过目录隐含地指定文件的时候,Mercurial会将其操作的每个文件的文件名都输出。这样会更清晰,同时减少可能意外情况。大多数Mercurial命令都有这样的行为。

5.1.2. Mercurial 只跟踪文件,不跟踪目录

Mercurial并不跟踪目录信息。相反,它会跟踪文件的路径。在创建一个文件之前,它首先会创建该文件路径中缺少的目录。在删除文件之后,它会删除在被删除文件路径上的任何空目录。这看起来区别不大,但是却导致这样的结果:Mercurial不可能管理一个完全为空的目录。

空目录基本上没有什么用,而且你可以用其他的方法达到相同的效果。因此Mercurial的开发者认为管理空目录带来的复杂性超过它带来的好处。

如果你真的希望在版本中包含空目录,有几种方法。其一是创建一个目录,然后用hg add命令在目录中添加一个隐藏文件。比如在UNIX类似系统上,大多数命令和GUI工具都认为文件名以点(.) 开头的文件是隐藏文件。这种方法如下。

$ hg init hidden-example
$ cd hidden-example
$ mkdir empty
$ touch empty/.hidden
$ hg add empty/.hidden
$ hg commit -m 'Manage an empty-looking directory'
$ ls empty
$ cd ..
$ hg clone hidden-example tmp
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ ls tmp
empty
$ ls tmp/empty

另外一种方法是在使用自动化创建脚本,在需要空目录的时候创建。

5.2. 如何停止跟踪文件

一旦你决定要将一个文件从版本库中删除,使用 hg remove命令。它会删除该文件,同时通知Mercurial停止跟踪它(下次提交的时候生效)。已被删除的文件在hg status的输出中以R标识。

$ hg init remove-example
$ cd remove-example
$ echo a > a
$ mkdir b
$ echo b > b/b
$ hg add a b
adding b/b
$ hg commit -m 'Small example for file removal'
$ hg remove a
$ hg status
R a
$ hg remove b
removing b/b

在你使用hg remove删除一个文件之后,Mercurial不再跟踪这个文件的变化,即使你在工作目录以同样的名字重新创建了一个文件。如果你以相同的名字重新创建了一个文件,并且希望Mercurial跟踪新的文件,只要用hg add添加它就可以了。Mercurial会知道这个新加的文件虽然和以前的文件名字相同,但是毫无关系。

5.2.1. 删除文件不影响历史

要理解删除一个文件仅仅两个方面的影响,这非常重要。

  • 文件的当前版本从工作目录中删除。

  • 从下次提交开始,Mercurial将不会在跟踪这个文件的变化。

删除一个文件不会以任何方式修改这个文件的历史

如果你在某一版本中将一个文件删除,而后将工作目录更新到以前的某个版本,那时这个文件还没有被删除,那么这个文件会重新在工作目录中出现,其内容和你提交那个变更集的内容相同。然后如果你更新到比较新的版本,这时这个文件已经被删除了,那么Mercurial会再次将这个文件从工作目录中删除。

5.2.2. 丢失的文件

如果一个文件被删除了,但是不是用hg remove命令删除的,Mercurial认为它丢失了。 丢失的文件在hg status的输出中以!标识。一般情况下Mercurial命令不会对丢失的文件作任何处理。

$ hg init missing-example
$ cd missing-example
$ echo a > a
$ hg add a
$ hg commit -m 'File about to be missing'
$ rm a
$ hg status
! a

如果hg status报告版本库中的一个文件丢失了,而且你确实不需要这个文件了,你可以在以后的任何时间运行hg remove --after命令,告诉Mercurial你希望删除这个文件。

$ hg remove --after a
$ hg status
R a

另一方面,如果你是不小心把那个文件删掉的,可以用hg revert命令加上要恢复的文件名。它会将文件恢复到未修改前的状态。

$ hg revert a
$ cat a
a
$ hg status

5.2.3. 旁白: 为什么要明确告诉 Mercurial 删除文件?

你可能奇怪为什么Mercurial要求你明确的告诉它要删除一个文件。在Mercurial开发的早期,只要你喜欢就可以删掉文件;Mercurial会自动的注意到文件不在了,当你下一次运行hg commit的时候,它就会停止跟踪这个文件。实际上,这很导致没有注意到的误删。

5.2.4. 有用的技巧—一个步骤添加和删除文件

Mercurial提供一个组合命令, hg addremove,它会添加未跟踪的文件,同时将丢失的文件标志为删除。

$ hg init addremove-example
$ cd addremove-example
$ echo a > a
$ echo b > b
$ hg addremove
adding a
adding b

hg commit命令同样也提供一个-A选项,在提交之后进行相同的添加删除。

$ echo c > c
$ hg commit -A -m 'Commit with addremove'
adding c

5.3. 拷贝文件

Mercurial提供了一个hg copy命令,可以用来拷贝文件。当你用这个命令拷贝文件时,Mercurial会记录下这个文件是由原来的文件拷贝而来的。以后在你把工作和其他人的工作合并的时候,它会对这些拷贝的文件进行特殊处理。

5.3.1. 合并后拷贝文件的内容

在合并过程中,变更会传递给拷贝。为了更好的解释它,我们创建一个例子。我们从一个仅仅包含一个文件的小版本库开始。

$ hg init my-copy
$ cd my-copy
$ echo line > file
$ hg add file
$ hg commit -m 'Added a file'

我们需要并行的工作,所以我们要进行合并。所以我们将版本库克隆。

$ cd ..
$ hg clone my-copy your-copy
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

回到初始的版本库,我们用hg copy命令创建我们开始时建立的文件的拷贝。

$ cd my-copy
$ hg copy file new-file

然后如果我们看一下hg status的输出,发现拷贝的文件看起来像普通的新添加的文件。

$ hg status
A new-file

当时如果我们给hg status加上-C选项,它会输出另外一行:新添加的文件是从那个文件拷贝而来的。

$ hg status -C
A new-file
  file
$ hg commit -m 'Copied file'

现在,回到我们克隆的那个版本库,我们并行的作一点改动,给我们最开始创建的文件添加一行。

$ cd ../your-copy
$ echo 'new contents' >> file
$ hg commit -m 'Changed file'

现在版本库里面有了修改过的文件。我们从第一个版本库拖变更,并且合并两个顶版本,Mercurial会将变更从我们修改的文件file传递给它的拷贝,new-file

$ hg pull ../my-copy
pulling from ../my-copy
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge
merging file and new-file to new-file
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ cat new-file
line
new contents

5.3.2. 为什么要传递变更?

这一行为—文件的变更会传递到它的拷贝中— 可能看起来难以理解, 但在大多数情况下,它很受欢迎。

首先要记住这种传播仅仅发生在合并的时候。所以如果你用hg copy拷贝了一个文件,此后在工作过程这原文件进行了修改,这时什么也不会发生。

其次要了解的是,只有当你在合并的变更还没有看到这个拷贝的时候,修改才会通过拷贝传播。

Mercurial这样设计的原因如下。假设我们在源代码中修正了一个重要的bug,然后提交了变更。与此同时,你决定用hg copy命令在你的版本库中拷贝这个文件,但你并不知到这个bug也没有发现它已经被修复了,并且你已经开始在你的拷贝上进行修改了。

如果你把我的变更拖进来并且合并,但Mercurial不会将变更传递给拷贝,那么你的新代码文件将仍然包含那个bug,除非你知道并且手动修复那个它,这个bug会一直保留在你的拷贝里面。

Mercurial将修复了bug的变更从原始文件自动的传播到拷贝,从而避免了这样的问题。据我了解,Mercurial是唯一的像这样在拷贝间传播变更的版本控制系统。

一旦你的变更历史中有了拷贝和随后的合并记录,通常再次将变更从原始文件传递到拷贝文件,这就是Mercurial仅仅在第一次合并的时候在拷贝间传递变更的原因,而不是此后。

5.3.3. 如何禁止变更传递?

如果出于某种原因,你觉得这种自动在拷贝间传递变更的方式不适合你,那么你可以使用系统的拷贝命令(在类Unix系统上就是cp命令)来复制文件,然后使用hg add手动添加拷贝的文件。在你这么作之前,请重新阅读第 5.3.2 节 “为什么要传递变更?”,确认这一功能是不是真的不适合你的情况,然后作出正确的决定。

5.3.4. 命令hg copy的行为

在使用hg copy命令的时候,Mercurial会复制工作目录中当前的文件。也就是说,如果你对文件作了修改,并且没有提交,那么hg copy生成的新的文件将包含这些修改。(我觉得这样有点不合常理,所以在这里提一下。)

hg copy命令和Unix的cp命令功能类似(如果你喜欢,可以用hg cp作为它的别名)。我们必须提供两个或者两个以上参数, 其中最后一个为目标,其他的是

如果你传给hg copy一个文件作为源,而目标文件不存在,它创建该文件。

$ mkdir k
$ hg copy a k
$ ls k
a

如果目标是一个目录,Mercurial会将所有的源文件拷贝到目标目录。

$ mkdir d
$ hg copy a b d
$ ls d
a  b

目录拷贝是递归的,保留源目录的结构。

$ hg copy z e
copying z/a/c to e/a/c

如果源和目标都是目录,源目录的结构会在目标目录中重建。

$ hg copy z d
copying z/a/c to d/z/a/c

hg remove命令一样,如果你手动拷贝了一个文件并且希望Mercurial知道你拷贝了这个文件,可以给hg copy命令加上--after选项。

$ cp a n
$ hg copy --after a n

5.4. 重命名文件

与拷贝文件比起来,重命名文件使用频率更高。我在讨论重命名文件之前讨论hg copy命令是因为Mercurial对于拷贝和重命名的处理方式相同。因此知道了Mercurial怎么处理拷贝文件也就知道了Mercurial怎么处理重命名文件。

在你运行hg rename命令的时候,Mercurial 首先对每个源文件都做一份拷贝,然后删除它,并将其标识为删除。

$ hg rename a b

hg status命令显示新拷贝的文件的状态是添加,拷贝的那个文件的状态是删除。

$ hg status
A b
R a

hg copy命令的结果一样,我们必须给hg status命令加上-C选项才能看到Mercurial是将新加的文件是作为原始文件的拷贝进行管理的,同时原始文件已经删除。

$ hg status -C
A b
  a
R a

hg removehg copy命令一样,你在事后可以使用--after选项告诉Mercurial改名。大多数情况下,hg renamehg copy的行为和接受的选项都是相似的。

如果你熟悉Unix命令行,那么告诉你一个好消息,可以用hg mv替代hg rename

5.4.1. 重命名文件与合并变更

因为Mercurial的重命名是以拷贝删除的方式实现的,如果拷贝了一个文件,然后又将其重命名,变更还是会从原来的文件传播到更名后的文件。

如果我修改了一个文件,同时你又将其更名,然后我们合并相关变更,那么我在原始文件名下作的修改将会传播到你的新文件中去。(这个功能你可能认为很简单,但是不是所有的版本控制系统都有这个功能。

对于变更会跟随拷贝这个功能,你可能不以为意地说 好吧,这个可能会有用 这里要说明让变更跟随重命名非常重要。 如果没有这个功能,那么在文件重命名之后,变更很容易被丢进黑洞。

5.4.2. 分歧的更名与合并

分歧更名是这样的情况,假设两个开发者的版本库中有一个文件—文件名为foo

$ hg clone orig anne
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone orig bob
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

Anne将文件改名为bar.

$ cd anne
$ hg rename foo bar
$ hg ci -m 'Rename foo to bar'

同时, Bob将它改名为quux。(记住hg mvhg rename的别名。)

$ cd ../bob
$ hg mv foo quux
$ hg ci -m 'Rename foo to quux'

我认为这是一个冲突,因为开发者对这个文件应该如何命名有了不同的意见。

你觉得他们合并的时候会发生什么呢?在合并包含分歧更名的变更集的时候,Mercurial实际上会将个文件都保留。

# See http://www.selenic.com/mercurial/bts/issue455
$ cd ../orig
$ hg pull -u ../anne
pulling from ../anne
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ hg pull ../bob
pulling from ../bob
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
$ hg merge
warning: detected divergent renames of foo to:
 bar
 quux
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
$ ls
bar  quux

注意虽然Mercurial会对分歧更名产生警告,但是由你来决定在合并后如何解决分歧。

5.4.3. 收敛重命名与合并

另一种更名冲突是两个人将不同的文件改名为相同的目标文件。这种情况下,Mercurial会执行正常的合并过程,然后让你帮助他找到合适的解决方案。

5.4.4. 其它名称相关的信息

Mercurial一直都有一个bug,如果它在进行合并的时候发现一边有一个文件,而另外一边有一个相同名称的目录,那么合并就会失败。这个问题记录在issue 29

$ hg init issue29
$ cd issue29
$ echo a > a
$ hg ci -Ama
adding a
$ echo b > b
$ hg ci -Amb
adding b
$ hg up 0
0 files updated, 0 files merged, 1 files removed, 0 files unresolved
$ mkdir b
$ echo b > b/b
$ hg ci -Amc
adding b/b
created new head
$ hg merge
abort: Is a directory: /tmp/issue29thzCc3/issue29/b

5.5. 从错误恢复

Mercurial提供了一些有用的命令,它们可以帮助你从一些常见的错误中恢复。

可以hg revert命令取消对工作目录做的变更。比如,你不小心用hg add命令添加了一个文件,只要运行hg revert加上你添加的文件的文件名就可以了,文件的内容不会有任何改变,仅仅是Mercurial不再跟踪它了。你也可以用hg revert消除对文件作出的错误的更改。

要记住hg revert命令仅仅适用于你的变更还没有提交的时候。一旦你提交了变更,然后发现这是个错误,你仍然有机会修正,虽然能做的很有限。

关于hg revert命令的更多信息,还有如何处理已经提交的变更,请参考第 9 章 查找和修改错误

5.6. 合并的技巧

在庞大而且复杂的项目中,两个变更集的合并常常让人头痛。假设有关大的源文件,合并的两边都作了很多修改:这不可避免的会导致很多冲突,有些要试几次才能解决。

我们用一个简单的例子看看如何处理这种情况。我们将包含一个文件的版本库克隆两次。

$ hg init conflict
$ cd conflict
$ echo first > myfile.txt
$ hg ci -A -m first
adding myfile.txt
$ cd ..
$ hg clone conflict left
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg clone conflict right
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved

在第一个克隆中,我们将文件修改成这样。

$ cd left
$ echo left >> myfile.txt
$ hg ci -m left

在另外一个克隆中,我们做完全不同的修改。

$ cd ../right
$ echo right >> myfile.txt
$ hg ci -m right

接下来,我们将两个变更集都推到原始的版本库。

$ cd ../conflict
$ hg pull -u ../left
pulling from ../left
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
$ hg pull -u ../right
pulling from ../right
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files (+1 heads)
not updating, since new heads added
(run 'hg heads' to see heads, 'hg merge' to merge)

我们希望版本库现在有两个头版本。

$ hg heads
changeset:   2:1887833b02ba
tag:         tip
parent:      0:30272197d5ab
user:        Bryan O'Sullivan <bos@serpentine.com>
date:        Thu Mar 17 02:59:50 2011 +0000
summary:     right

changeset:   1:49bc86613f81
user:        Bryan O'Sullivan <bos@serpentine.com>
date:        Thu Mar 17 02:59:50 2011 +0000
summary:     left

正常情况下,如果这时我们运行hg merge。它会运行一个GUI程序,让我们解决myfile.txt的冲突。但是,为了简化这里的演示,我们希望合并失败。我们可以按照下面的方法做。

$ export HGMERGE=false

我们告诉Mercurial的合并状态机,如果它检测到不能自己解决的冲突的话,就运行命令false(像我们希望的一样,立即失败返回)。

如果现在我们运行hg merge,他会停止运行同时报告一条错误。

$ hg merge
merging myfile.txt
merging myfile.txt failed!
0 files updated, 0 files merged, 0 files removed, 1 files unresolved
use 'hg resolve' to retry unresolved file merges or 'hg update -C' to abandon

即使我们没有注意到合并失败,Mercurial也会阻止我们意外地提交失败的合并结果。

$ hg commit -m 'Attempt to commit a failed merge'
abort: unresolved merge conflicts (see hg resolve)

这种情况下,hg commit失败的时候,它建议我们使用陌生的hg resolve命令。和以前一样, hg help resolve会输出帮助的摘要。

5.6.1. 文件的解决状态

合并发生时,大多数文件没有任何变化。Mercurial对于每个需要进行操作的文件,都会跟踪它的状态。

  • resolved表示文件已经成功合并,不管是Mercurial自动完成的还是手工修改完成的。

  • unresolved表示文件没有成功的合并,需要特别注意。

如果Mercurial在合并后发现任何文件处于未解决状态,它会认为这次合并失败。幸运的是,我们不需要再次从头开始进行合并。

hg resolve--list或者-l选项会打印出每个合并过的文件的状态。

$ hg resolve -l
U myfile.txt

hg resolve的输出中,已经解决的文件标识为R,而未解决文件标识为U。 如果有任何文件被标识为U,那么我们就不能提交合并的结果。

5.6.2. 解决文件合并

我们有几种方法将文件从未解决状态变成解决状态。至今为止最常用的命令是重新运行hg resolve。如果我们将文件名或者目录名传递给它,那么它会重新在给定为止合并任何未解决的文件。如果我们将--all或者-a选项传给它,那么它会重新合并所有的未解决文件。

Mercurial also lets us modify the resolution state of a file directly. We can manually mark a file as resolved using the --mark option, or as unresolved using the --unmark option. This allows us to clean up a particularly messy merge by hand, and to keep track of our progress with each file as we go.

5.7. 差异的更多技巧

缺省情况下,hg diff命令的输出与普通的diff命令兼容,但是这样有缺点。

设想我们使用 hg rename 命令来重命名文件。

$ hg rename a b
$ hg diff
diff -r 1c08e0ba14c4 a
--- a/a	Thu Mar 17 02:59:49 2011 +0000
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -1,1 +0,0 @@
-a
diff -r 1c08e0ba14c4 b
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/b	Thu Mar 17 02:59:49 2011 +0000
@@ -0,0 +1,1 @@
+a

我们给一个文件改名,而hg diff的输出却掩盖了事实。hg diff命令可以接受选项--git或者-g,使用新的差异格式以更加可读的方式显示这些信息。

$ hg diff -g
diff --git a/a b/b
rename from a
rename to b

这个选项在以下情况下十分有用,否则就很令人费解:一个文件如果用hg status查看,显示的状态是被修改了,但是如果用hg diff检查,却什么输出也没有。如果我们修改了文件的执行属性就会出现这种情况。

$ chmod +x a
$ hg st
M a
$ hg diff

The normal diff command pays no attention to file permissions, which is why hg diff prints nothing by default. If we supply it with the -g option, it tells us what really happened.

$ hg diff -g
diff --git a/a b/a
old mode 100644
new mode 100755

5.8. 哪些文件需要管理,那些不需要

版本控制系统最擅长管理人们书写的文本文件,例如源代码,不同的版本间文件的改动不会很大。集中式版本控制系统可以很好的出来二进制文件,例如位图文件。

例如,一个典型的游戏开发团队不仅要在版本控制系统中管理源代码,还要管理二进制财产(像地理数据,贴图,地图布局)。

因为一般不可能合并两个冲突的二进制文件,集中式系统通常提供文件锁机制来解决这个问题。它允许一个用户说 我是唯一能修改这个文件的人

和集中式系统相比,在分布式版本控制系统中,决定要管理那些文件和怎么管理的指导思路发生了变化。

例如,在本质上,一个分布式版本控制系统不能提供文件锁定机制。没有内建的机制可以防止两个人对一个二进制文件作相互冲突的修改。如果在你的团队中,有些人要频繁的编辑二进制文件,那么这时就不适合使用Mercurial—或者任何其他的分布式版本控制系统—来管理这些文件。

在存储文件的变更的时候,Mercurial通常只会保存当前版本和上一个版本之间的差异。对于大多数文本文件而言,这时非常高效的。但是有些文件(特别是二进制文件)而言,对于文件逻辑内容上的很小的改动,可能导致文件中很多或者绝大多数字节发生变化。例如,压缩文件就会这样。如果一个文件连续的版本之间的差异总是很大,Mercurial就不能有效的存储文件的版本历史。这会影响本地的存储需求和克隆版本库需要的时间。

要了解在这实际的使用中的影响,假设你准备用Mercurial管理一个OpenOffice文档。OpenOffice使用zip压缩的格式存储文件。即使你的文档在OpenOffice中仅仅修改了一个字符,在你存档的时候几乎文件中的每个字节都发生了变化。现在假设文件大小为2MB。因为每次你存档的时候文件中大部分都发生了变化,Mercurial不得不在你每次提交的时候都存储所有2MB的文件,即使是从你的角度来说,每次的变化只有几个单词。一个不符合Mercurial存储假设的频繁编辑的文件,将很快使版本库的膨胀。

更加糟糕的是,如果你和其他人都在编辑一个OpenOffice文档,没有什么办法合并你的工作。实际上,也没有办法告诉你不同变更之间的差异。

下面是应该小心处理的几种特殊类型文件的一些建议。

  • 非常大而且不能压缩的文件,如ISO CD-ROM映像,会使版本库大小从而剧增导致通过网络的克隆非常缓慢。

  • 不同版本间变化很多,如果频繁编辑会耗费很多空间,同时并行编辑导致的冲突很难解决的文件。

5.9. 备份与镜像

因为Mercurial在每个克隆中都含有完整的历史拷贝,所以在一个项目中,每个使用Mercurial进行协作的人都可以在灾难发生的时候成为备份。如果中央版本库发生故障,你可以从一个贡献者那里克隆版本库作为替代,然后可以拖出其他版本库没有的变更。

It is simple to use Mercurial to perform off-site backups and remote mirrors. Set up a periodic job (e.g. via the cron command) on a remote server to pull changes from your master repositories every hour. This will only be tricky in the unlikely case that the number of master repositories you maintain changes frequently, in which case you'll need to do a little scripting to refresh the list of repositories to back up.

If you perform traditional backups of your master repositories to tape or disk, and you want to back up a repository named myrepo, use hg clone -U myrepo myrepo.bak to create a clone of myrepo before you start your backups. The -U option doesn't check out a working directory after the clone completes, since that would be superfluous and make the backup take longer.

If you then back up myrepo.bak instead of myrepo, you will be guaranteed to have a consistent snapshot of your repository that won't be pushed to by an insomniac developer in mid-backup.