使用 Mercurial 的一个好处是团队能够进行协作开发. Mercurial 让你能够独立工作, 又能随时合并变更.
团队协作
用 Mercurial 进行协作开发最常用的方法是建立一个中央库, 同时我们各自的 PC 上也都有一份私有版本库. 我们可以把中央库当作交换中心, 相当于我们聚在一起交换我们所做变更的地方.

hg serve
Note
hg serve
启动一个 WEB 服务器以便当前版本库能通过 Internet 访问
快速建立中央库最简陋的方法是使用 Mercurial 内置的 WEB 服务器 – 你所作的仅仅是用 hg init 创建一个版本库, 然后用 hg serve 启动 WEB 服务. 默认情况下, 它会占用 8000 端口.
C:\> mkdir CentralRepo
C:\> cd CentralRepo
C:\CentralRepo> hg init
C:\CentralRepo> hg serve
这台 PC 的主机名是 joel.example.com, 我只要用浏览器登录 http://joel.example.com:8000/ 便可看到服务器已经启动并正在运行, 虽然版本库还是空白一片.

hg clone
Note
hg clone
获取版本库的完整副本
一旦中央 WEB 服务器开始服务, 我可以从服务器 克隆 (clone) 版本库到我的个人 PC 自主使用. 这个版本库现在还是空的, 所以我克隆后得到的也是空白库.
C:\Users\joel> hg clone http://joel.example.com:8000/ recipes
no changes found
updating to branch default
0 files updated, 0 files merged, 0 files removed, 0 files unresolved
C:\Users\joel> cd recipes
C:\Users\joel\recipes> dir
Volume in drive C has no label.
Volume Serial Number is 84BD-9C2C
Directory of C:\Users\joel\recipes
02/08/2010 02:46 PM <DIR> .
02/08/2010 02:46 PM <DIR> ..
02/08/2010 02:46 PM <DIR> .hg
0 File(s) 0 bytes
3 Dir(s) 41,852,125,184 bytes free
现在, 我们新建一个名为 guac 的文件, 记录本人有名的鳄梨酱配方.
guac:
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 serrano chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Crunch all ingredients together.
Serve with tortilla chips.
我将添加这个文件, 并作为我的第一个官方版本提交:
C:\Users\joel\recipes> hg add
adding guac
C:\Users\joel\recipes> hg commit
以及提交摘要:

现在, 我要编辑这个文件, 做些小改动, 这样版本库里就会留下些历史记录了.

接着提交这次变更:
C:\Users\joel\recipes> hg status
M guac
C:\Users\joel\recipes> hg diff guac
diff -r c1fb7e7fbe50 guac
--- a/guac Mon Feb 08 14:50:08 2010 -0500
+++ b/guac Mon Feb 08 14:51:08 2010 -0500
@@ -7,5 +7,5 @@
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
-Crunch all ingredients together.
+Smoosh all ingredients together.
Serve with tortilla chips.
C:\Users\joel\recipes> hg com -m "Change crunch to smoosh"
C:\Users\joel\recipes> hg log
changeset: 1:a52881ed530d
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
注意我这次提交的时候, 头一次使用了 -m 参数. 这只是在命令行下, 不通过编辑器输入提交摘要的一个方法.
OK, 我们在哪儿? 到目前为止, 我能访问中央库, 还克隆了一份本地库. 我已经做了两次变更并提交到本地库, 但是这些变更只存在于我的本地库 – 它们还没有进入中央库. 所以现在的状况是:

hg push
Note
hg push
把一个版本库的新增变更推送到另一个版本库
现在我将使用 hg push 命令, 把我的变更从我的本地库 推入 到中央库:
C:\Users\joel\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
ssl required
我靠, 居然报错. 我暂时不考虑随便运行一个 WEB 服务器, 还允许任何人把他们那愚蠢的变更推到中央库所带来的安全问题. 忍耐一小会; 我要去配置一下服务器, 让任何人都可以为所欲为. 这需要编辑一下 .hg\hgrc
文件.
.hg\hgrc
:
[web]
push_ssl=False
allow_push=*
显然, 这是很危险的, 但是如果你处在一个安全的局域网环境下工作, 有一个优秀的防火墙, 而且你信任局域网中的每个人, 这样的话是安全的. 否则, 你可能要去看看手册中关于安全的进阶章节.
好, 我们重新启动服务器:
C:\CentralRepo> hg serve
现在我应该可以 push 变更集了:
C:\Users\joel\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
Yay! 现在的状况变成这样了:

我知道你在想什么. 你在想, “喔呦, Joel, 太奇怪了. 为什么这些版本库里包含的是 变更 而不是 文件 呢? guac 文件在哪儿?”
是的, 很诡异. 但这就是分布式版本控制的工作方式. 版本库就是一个包含大量变更的堆栈. 把变更想象成一块干净的透明毯子. 现在你有一捆这样的透明毯子, 你把它们按顺序堆叠起来, 最新变更放在最上面, 然后从上往下俯视, 注意! – 你看到的便是当前最新版本的文件. 随着你从栈顶慢慢掀走透明毯, 你会看到越来越老的版本.
现在, 我们可以通过浏览器感受一下中央版本库:

正如你所预料的那样.
现在, 我想让 Rose 帮我一起写配方. ... (译注: 有意略掉了一段, 不影响教程内容) ...
C:\Users\rose> hg clone http://joel.example.com:8000/ recipes
requesting all changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
Rose 使用 hg clone 命令获取了一份完整的版本库副本. hg clone 接受两个参数: 版本库的 URL 和本地副本的目录. Rose clone
到她本地的 recipes 目录.
C:\Users\rose> cd recipes
C:\Users\rose\recipes> dir
Volume in drive C has no label.
Volume Serial Number is 84BD-9C2C
Directory of C:\Users\rose\recipes
02/08/2010 03:23 PM <DIR> .
02/08/2010 03:23 PM <DIR> ..
02/08/2010 03:23 PM <DIR> .hg
02/08/2010 03:23 PM 394 guac
1 File(s) 394 bytes
3 Dir(s) 41,871,872,000 bytes free
C:\Users\rose\recipes> hg log
changeset: 1:a52881ed530d
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
注意键入 hg log 后她看到的是整个历史记录. 实际上她下载了整个版本库, 包括所有完整的历史记录.
Rose 打算做些改动, 然后提交至版本库:

注意, 即使服务器没在运行, 她仍然可以做提交操作: 提交动作完全发生在本机上.
C:\Users\rose\recipes> hg diff
diff -r a52881ed530d guac
--- a/guac Mon Feb 08 14:51:18 2010 -0500
+++ b/guac Mon Feb 08 15:28:57 2010 -0500
@@ -1,6 +1,6 @@
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
-* 1-2 serrano chiles, stems and seeds removed, minced
+* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
C:\Users\rose\recipes> hg com -m "spicier kind of chile"
C:\Users\rose\recipes> hg log
changeset: 2:689026657682
tag: tip
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
changeset: 1:a52881ed530d
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
当 Rose 在做修改的时候, 与此同时, 我也可以做些改动.

你会看到我提交之后, 日志显示 #2 变更集和 Rose 的不太一样.
C:\Users\joel\recipes> hg com -m "potato chips. No one can eat just one."
C:\Users\joel\recipes> hg log
changeset: 2:4ecdb2401ab4
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 15:32:01 2010 -0500
summary: potato chips. No one can eat just one.
changeset: 1:a52881ed530d
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
我们的版本历史开始分道扬镳.

别担心... 很快我们就会看到如何把这些各自奔天涯的变更重新聚集在一起.
hg outgoing
Note
hg outgoing
列出当前版本库等待推送的变更列表
Rose 可以继续离线工作, 只要她愿意, 可以在她的本地版本库中做任何修改, 可以 commit
, 也可以 revert
. 到了一定阶段, 她想到要把她所做的变更和其他人分享. 她可以键入 hg outgoing, 然后得到等待发送到中央库的变更列表. 这个列表就是如果她执行 hg push 将会被送出的那些变更.
C:\Users\rose\recipes> hg outgoing
comparing with http://joel.example.com:8000/
searching for changes
changeset: 2:689026657682
tag: tip
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
你可以把 hg outgoing 想象成: 它只是简单的列出本地库中 中央库 没有的那些变更.
好的, Rose 决定推送她的变更.
C:\Users\rose\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
现在的状况是这样子的:

喝完今天第四杯拿铁咖啡, 我也准备推送我的变更了.
C:\Users\joel\recipes> hg outgoing
comparing with http://joel.example.com:8000/
searching for changes
changeset: 2:4ecdb2401ab4
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 15:32:01 2010 -0500
summary: potato chips. No one can eat just one.
C:\Users\joel\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
abort: push creates new remote heads!
(did you forget to merge? use push -f to force)
啊哈! 失败鸟! 顺便提一下... 你看到那条消息了吗? 就是提示 use push -f to force? 的那条. 那是个极其糟糕的建议. 千万不要使用 push -f 来强制推送. 相信我, 你会为此后悔的.
推送失败是因为我们同时做了修改, 所以它们需要做合并操作, 而 Mercurial 很清楚这一点.
我首先需要做的是, 获取中央库中我所没有的所有变更, 以便我进行合并操作.
C:\Users\joel\recipes> hg incoming
comparing with http://joel.example.com:8000/
searching for changes
changeset: 3:689026657682
tag: tip
parent: 1:a52881ed530d
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
C:\Users\joel\recipes> hg pull
pulling from http://joel.example.com:8000/
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)
(+1 heads)
的提示有些莫名其妙. 这是因为我的本地库里, 原本只有 3 个变更整齐的堆叠着, 现在却成了个双头怪, 两个不同的变更并行堆叠在栈顶, 看上去很不牢靠:

现在在我的本地库有两个版本... 我的那份:
C:\Users\joel\recipes> type guac
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 serrano chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
Serve with potato chips.
以及 Rose 的那份:
C:\Users\joel\recipes> hg cat -r 3 guac
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
Serve with tortilla chips.
hg merge
Note
hg merge
合并两个版本头
现在我要决定是否合并. 幸运的是, 合并过程很简单.
C:\Users\joel\recipes> hg merge
merging guac
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
C:\Users\joel\recipes> type guac
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
Serve with potato chips.
瞧! hg merge 命令把两个版本头合二为一了. 在本例中, 因为我们没有编辑文件的同一行, 所以完全没有冲突, 合并因此也就没有任何障碍.
我依然需要 commit
. 这很重要. 如果合并失败了, 我随时可以 revert
然后重试. 因为我们成功合并, 所以我决定把我的变更提交到中央库.
C:\Users\joel\recipes> hg commit -m "merge"
C:\Users\joel\recipes> hg log
changeset: 4:0849ca96c304
tag: tip
parent: 2:4ecdb2401ab4
parent: 3:689026657682
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 16:07:23 2010 -0500
summary: merge
changeset: 3:689026657682
parent: 1:a52881ed530d
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
changeset: 2:4ecdb2401ab4
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 15:32:01 2010 -0500
summary: potato chips. No one can eat just one.
changeset: 1:a52881ed530d
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
C:\Users\joel\recipes> hg out
comparing with http://joel.example.com:8000/
searching for changes
changeset: 2:4ecdb2401ab4
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 15:32:01 2010 -0500
summary: potato chips. No one can eat just one.
changeset: 4:0849ca96c304
tag: tip
parent: 2:4ecdb2401ab4
parent: 3:689026657682
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 16:07:23 2010 -0500
summary: merge
C:\Users\joel\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
现在中央库和我的本地库是一模一样的:

OK, 现在我的本地库包含 Rose 和我的变更, 但是 Rose 却还没有拿到我的变更.
... (译注: 再次有意忽略一段) ...
为此, Rose 需要从中央库取出新增的变更.
C:\Users\rose\recipes> hg pull
pulling from http://joel.example.com:8000/
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 1 files
(run 'hg update' to get a working copy)
搞定. 现在你可能留意到某些异样, 即使 Rose 已经把新的变更取至她的本地库, 这些变更仍然没有在她的工作目录生效.
C:\Users\rose\recipes> type guac
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
Serve with tortilla chips.
看到没? 文件没有发生任何变化!
但是她的本地库中某个地方 的确 有我的变更...
C:\Users\rose\recipes> hg log
changeset: 4:0849ca96c304
tag: tip
parent: 3:4ecdb2401ab4
parent: 2:689026657682
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 16:07:23 2010 -0500
summary: merge
changeset: 3:4ecdb2401ab4
parent: 1:a52881ed530d
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 15:32:01 2010 -0500
summary: potato chips. No one can eat just one.
changeset: 2:689026657682
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
changeset: 1:a52881ed530d
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:51:18 2010 -0500
summary: Change crunch to smoosh
changeset: 0:c1fb7e7fbe50
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 14:50:08 2010 -0500
summary: Initial version of guacamole recipe
hg parent
Note
hg parent
显示工作目录当前的变更集
变更只不过没有在工作目录下生效. 这是因为她还是在基于 changeset #2
工作. 你可以使用 “parent” 命令确认:
C:\Users\rose\recipes> hg parent
changeset: 2:689026657682
user: Rose Hillman <rose@example.com>
date: Mon Feb 08 15:29:09 2010 -0500
summary: spicier kind of chile
Mercurial 太友好了. 每次 pull
都是安全的; 它所做的只是让我们得到其他人的变更. 我们可以随时切换到新的变更下工作.
记住, 不带任何参数的 hg up 命令会把工作目录更新到 tip (始终为最新的变更集), 本例中, tip 是 4:
C:\Users\rose\recipes> hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
C:\Users\rose\recipes> type guac
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
Serve with potato chips.
现在, Rose 正在看着合并了所有人改动后的最新版本呢.
如果你作为团队协作的一员, 你的工作流大概会是这个样子:
- 如果你有一段时间没有更新代码, 你需要获取其他人已经完成的代码:
hg pull
hg up
- 修改代码
- 提交代码 (本地提交)
- 重复步骤 2~3 直到你的代码完成度还不错, 你决定让其他人都来 “享受” 你的成果
- 一旦你准备分享你的代码:
- 用
hg pull
获得其他所有人的变更 (如果有的话)- 用
hg merge
将这些变更合并到你的代码中- 测试! 以确保合并操作没有出乱子
hg commit
(合并结果)hg push
小测验
下面的一些操作是学完本章教程后, 你应该要学会的:
- 建立一个中央版本库, 让团队成员从中央库
clone
- 把变更推送 (push) 到中央库
- 从中央库取出 (pull) 变更
- 合并不同代码提交人的变更