HgInit (中文版)
翻译: | |
---|---|
项目主页: |
译者前言
Mercurial 一款非常优秀的 DVCS, 文档也日趋齐全. 但实际上要掌握 Hg, 手头上有两本书就足够了. 一本就是你正在阅读的 Hg Init: a Mercurial tutorial, 另一本则是官方推荐的大部头 Mercurial : The Definitive Guide.
Joel Spolsky 在技术写作和表达方面可谓是非常优秀. HgInit 不仅仅是扔给我们几条命令, 它深入浅出, 层层推进, 不断的融入和帮助我们理解 Hg 和 DVCS 的理念. 阅读 HgInit 会有一种同作者一起探索软件开发的复杂性及其应对策略的感觉.
相比其它教程, HgInit 是 Hg 初学者的最佳入门指导. 之后手边再放一本 “Mercurial : The Definitive Guide” 供随时查阅, 便可独步水星, 行走江湖了.
术语翻译对照表
英文术语 | 中文翻译 | 解释 |
---|---|---|
push |
推送 | 对应于 hg push 动作,
用于将变更集同步至 中央 (或其它)
版本库 |
pull |
取出/获取 | 对应于 hg pull 动作,
用于将变更集从 中央 (或其它) 库
同步至本地库 |
changeset |
变更集 | 指对版本库一系列的修改, 包括 添加/删除/修改文件, 以及 修改版本库属性, 配置等操作 |
head |
版本头 | 处于变更堆栈栈顶的变更集 |
tip |
顶端变更 | tip 可以理解为 head 的代
号 (标签), 始终指向变更堆栈栈顶 |
目录索引
如果您以前用过 Subversion, Mercurial 会让你感觉很困惑. 这篇教程将介绍 Mercurial 工作方式最大的区别. 如果你从没接触过 Subversion, 可以直接跳过本章.
重新审视 Subversion
我们公司的程序员们决定用 Mercurial 替换掉 Subversion, 老兄, 你把我搞糊涂了.
最初, 我提出了各种愚蠢的观点来反对迁移. “我们应当把版本库放在一个中央服务器上, 这样会更安全些,”. 你知道吗, 我的观点是错误的. 对于 Mercurial, 每个开发人员的硬盘都会有一份完整的版本库副本. 实际上 Mercurial 更安全. 而且几乎每一个 Mercurial 项目组也都会有一个中央版本库. 你可能会情不自禁的对中央库进行备份, 或者任何其它应 IT 部门要求的操作.
“分布式版本控制的问题是太容易创建分支了, 而分支是最容易出问题的”, 这个观点同样是错误的. Subversion 的分支容易出问题, 主要是因为 Subversion 没有提供足够多的信息来保证分支合并的正常工作. 对于 Mercurial, 分支合并是一件很轻松惬意的事, 所以创建分支是很稀松平常的, 而且无毒副作用.
最后我说 “好吧, 我会看看, 但别指望我能搞定.” 我叫 Jacob 做了张小抄, 列出了常用的 Subversion 操作, 以及在 Mercurial 中的相对应操作.
但我不会给你看这个列表, 这张列表让我迷糊了好几个月.
这件事说明, 如果你以前用过 Subversion, 那你的脑袋会有些转不过弯. 你需要一些反思来更新自己的观念. 在长达六个月的时间内, 我一直认为 Mercurial 要比 Subversion 复杂得多. 但事实上是我没有真正搞明白 Mercurial 到底是如何工作的, 最终我弄清楚了, 发现原来一切是如此的简单!
在这份教程里我将尽量不用 Subversion 来做参照, 因为这除了使你觉得头痛外没有任何意义. 这个世界上让人头痛的事已经够多了. 相反, 对于 Subversion 的用户, 我会在本章节让他们尽量忘记 Subversion, 把大脑清空以便重头学习 Mercurial.
如果你从来没有接触过 Subversion, 你可以直接跳到下一章节, 你不会遗漏任何内容的.
准备好了没? 让我们来个简单的问答吧.
问题 1. 你可以在第一遍的时候就写出完美的代码?
如果你的回答是 “Yes”, 那你绝对是个骗子.
新写好的代码总是充满了 bug. 你需要花些时间调试后才能保证它正常工作. 在此期间, 它可能会影响团队内其他开发人员的工作热情.
这是 Subversion 的传统做法:
- 在你 checkin 新的代码, 每个开发人员都获取这份新代码.
由于你新写的代码中存在 bug, 你会有如下选择.
- 你可以 checkin 有 bug 的代码, 然后把所有人搞疯, 或者
- 在做好充分调试前不要进行 checkin 操作
Subversion 总是给你出这样可恶的难题. 要么库里的代码满是 bug, 要么新写的代码都放在本地.
作为 Subversion 的用户, 我们早已对此习以为常, 以至于很难想象另外一番景象.
使用 Subversion 时, 组员们经常数天甚至数星期都不 checkin 任何东西. 新手们总会担心在代码 checkin 后会导致程序无法编译, 或者让 Mike (高级工程师) 窝火. Mike 有一次因为无法编译而出离愤怒, 他冲进实习生的小隔间, 把掀翻他办公桌上的所有东东, 然后吼道 “这是你最后一天!” (虽然没被炒, 但可怜的实习生差点没湿了裤子.)
因为害怕数周都不 checkin 代码, 使版本控制无法发挥其应有的作用. 最终他们还是需要有经验的开发人员来协助完成代码的 checkin. 如果你根本就不去用, 那版本控制还有什么意义?
下面的图展示了 Subversion 是如何被使用的:
对于 Mercurial, 每个开发人员在自己 PC 上有自己的版本库:
只要你愿意, 你可以随时提交提交代码到你的私人版本库, 从版本控制中获益. 每当你的代码到达一定的逻辑点, 你都可以将代码提交.
等到代码比较稳定后, 你想让其他人使用你的新代码, 你可以将你所做的变更 push 到中央版本库. 其他人从中央版本库 pull 代码后, 就可以看到你的代码了.
提交新代码 和 影响他人工作的操作, Mercurial 将这两者分割成两个步骤.
这意味着你提交后 (hg com) 不会影响到其他人. 等到你完成一系列修改, 代码相对稳定后你可以将变更 push (hg push) 到中央版本控制库.
另一个理念上的重大分歧
你确定每条街道都有名字?
好吧, 其实在日本, 没几条有名字的街. 他们通常只给街道两边的 街区 进行编号, 只有非常非常重要的街道才会有自己的名字.
这就如 Subversion 和 Mercurial 之间的区别.
Subversion 采用 revisions (修订号) 的方式. 修订号看起来就像是某个时间点整个文件系统的一个快照.
Mercurial 则采用 changesets (变更集) 的方式. 变更集是版本与版本之间变更情况的简明列表.
这不是半斤和八两吗, 有啥区别?
区别在这里. 试想一下, 我和你在修改同一份代码, 我们分别建立的不同的分支并独立工作. 期间我们都分别各自做了大量的改动, 两份代码基本上分道扬镳了.
等到我们需要合并的时候, Subversion 尝试分别查看我们俩的修订版本, 然后尝试将两个版本合并到一起. 文件需要如何合并, 多半是靠猜. 所以失败也是很正常的. Subversion 会给出大量的合并冲突 (merge conflicts), 而这些冲突并不是真正存在的, 真正的问题是 Subversion 并没有搞清楚我们具体做了什么操作.
与之相对应, 我们在各自使用 Mercurial 时, Mercurial 频繁的 维护一系列变更集. 等到我们需要合并代码的时候 Mercurial 有足够多的信息用来合并: 它知道 我们分别做了哪些修改, 而且可以 重新应用这些变更, 而不是仅仅看看最终版本, 然后瞎蒙如何把它们撮合起来.
举个例子吧, 如果我小小修改了一个函数, 然后将这个函数挪了个位置. Subversion 不会记录你的操作步骤, 所以等到合并的时候, 它会认为这是一个新的函数. Mercurial 则会分别记录函数修改和函数移动的操作, 这意味着. 如果你也同时对该函数做了修改, Mercurial 更有可能成功的将我们俩的改动合并起来.
由于 Mercurial 把所有事情都当作变更集 (changesets) 处理, 你可以借此来做一些有趣的事情. 你可以将它们 push 给你团队中的朋友试用, 而不是 push 到中央库摧残所有组员.
或许你现在会感觉有些迷惑, 不过不要担心 – 等你看完本教程后自然会有清楚的认识. 至于现在, 你只要清楚 Mercurial 是以 “变更集” (changesets) 而不是 “修订号” (revisions) 的方式处理, 这使得代码合并方面比 Subversion 更要更加容易.
这意味着你可以随心所欲的拉出新分支, 因为合并将不再是噩梦一场.
想知道一些更有趣的事情? 几乎每个我接触使用 Subversion 的项目组都同我谈起过类似的问题. 这个问题实在是太普遍了, 我把她命名为 “Subversion 案例 #1”. 案例如下: 在某个阶段, 他们需要为客户创建一个独立于主干的开发分支. 这通常是一个很自然而然的做法, 但等到需要进行分支合并的时候, 这一切演变成了一场噩梦. 本是应是几分钟内完成的事情, 最终让六个程序员花了两周来修复稳定分支合并到开发分支所产生的 bug.
几乎每个 Subversion 团队都告诉我, 他们再也不想经历一次类似的事情了, 同时他们发誓将远离分支. 现在的他们这么干: 所有的新需求都写在一个巨大的 #ifdef
条件句块中. 因此他们可以只用一个 主干``trunk`` 就搞定. 在完成调试前, 用户看不到任何新代码引入的功能. 坦率的说, 这实在是一个非常可笑的做法.
将开发版本和稳定版本隔离开, 正是源码版本控制的建议做法.
当切换到 Mercurial, 你可能没有意识到, 分支已经变得可行, 你不必再为使用分支而感到恐惧.
这意味着你可以针对新需求组建一个小开发团队协作开发, 等到他们完成后, 再将这个开发库合并到中央开发库, 这是完全可行的!
这意味着你可以创一个 QA 库. 让 QA 团队试用里面的代码. 如果能正常工作, QA 则将代码 push 到中央库, 这样保证了中央库里总是稳定的, 经过测试的代码. 而且这是完全可行的!
这意味着你可以在各个相互独立的库中进行调试, 如果代码正常工作, 你可以将他们合并到主库, 如果出现问题, 你可以直接丢弃它们, 这也是完全可行的!
最后一个理念上的重大分歧
Subversion 与 Mercurial 之间的这个分歧或许并不是什么大不了的事. 但如果你不了解它的话, 很可能会因此栽跟头, 问题是这样的:
Subversion 的版本控制是基于 文件 的. 但是 Mercurial 的版本控制是作用于整个目录 – 包括所有的子目录.
在 Subversion 中你可以通过以下方法注意到这一点. 你在子文件夹进行 commit 操作的时候, 只会对当前目录和当前目录的所有子目录生效, 这样很可能导致你忘记提交其它目录的变更. 相反, Mercurial 的所有命令都是针对整个库的. 如果你的代码放在 c:/code, 不管你是在 c:/code 或是任何 c:/code 的子目录下执行 hg commit 命令, 效果都是一样的.
这个问题没什么大不了的, 但是如果你习惯让整个公司共用一个巨无霸的版本控制库, 然后让每个人在各自的目录下工作, 这对 Mercurial 并不是一个合理的使用方式 – 你最好让每个项目都有自己独立的库.
最后...
你可以相信我下面所说的每一句话.
Mercurial 比 Subversion 更好.
对个人和团队而言, 这都是一个更好的代码管理方式.
Mercurial 确实 更好.
记住我的话, 去理解 Mercurial 的工作方式, 以 Mercurial 的方式去使用 Mercurial, 不要试图去抗拒, 不要试图以 Subversion 的老方法去使用 Mercurial. 总之, 适应 Mercurial 的工作方式, 将会使你的工作变得愉快, 有成就感, 而且舒适.
在初期, 你可能会感受到诱惑 (我知道你会的), 然后非常想抛弃 Mercurial, 重返 Subversion 的温暖怀抱, 它是如此陌生, 就像生活在陌生的国度, 接着你就犯了思乡病, 然后你会找出各种各样的理由支撑这个观点, 比如, Mercurial 工作目录太费硬盘空间, 事实上, 这是瞎扯, 实际上 Mercurial 比 Subversion 还更省空间. (这是真的!)
然后你会继续走到 Subversion 的老路上去, 因为你试图使用 Subversion 的方式去创建分支, 但使你困惑的是, 这似乎不太好用, 因为你真正需要的是 Mercurial 的分支管理方式, 通过 clone 一个新的库, 不要尝试将 Subversion 里的经验套用到 Mercurial, 去学习 Mercurial 的工作方式, 然后一切会变得 如此简便, 相信我.
接着你会找人给做张 “Subversion 和 Mercurial 操作对照表”, 你会花三个月去想 hg fetch 和 svn update 其实没啥区别, 却从来没有想过 hg fetch 具体是做什么的, 如果有一天出问题了, 你会责怪 Mercurial, 而真正应当责怪的是你自己并没有理解 Mercurial 的工作原理.
我想你会这么干, 因为我当初就这么干来着.
不要再犯相同的错误了. 学习 Mercurial, 信任 Mercurial, 找到适合 Mercurial 的工作方式, 你将走在源码控制整个时代的前列. 当你的竞争对手们为了解决分支合并所带来的冲突忙得焦头烂额的时候, 你输入 hg merge 然后得意的说: “太棒了, 顺利搞定.” 然后 Mike 冷静下来, 和实习生出去抽一根 (译注: 原文是美俚 “大麻烟”), 一切其乐融融, 生活多美好.
Next
即便你是一个人在战斗, 你也应该使用 Mercurial 并从版本控制中获益. 下面的指南会告诉你把一个目录提交到 Mercurial 并追根溯源, 是一件多么简单的事情.
Mercurial 从零开始
Mercurial 是一个 版本控制系统. 开发者可以用它来管理源代码. 它的主要有两大功能:
- 它保留每个文件的所有变更/编辑历史
- 它能够合并源码的改动, 这样你的队友可以各自编码, 然后合并他们的变更
如果不用 Mercurial, 你就得很土的去保存 N 多份代码拷贝:
这么做太土了, 占用了大量的硬盘空间, 而且混乱不堪. 使用版本控制会是更好的方法.
大部分人在命令行下使用 Mercurial, 命令行在 Windows, Unix, 和 Mac 下都可以很好的工作. 执行 Mercurial 的命令是 hg:
c:\hginit> hg
Mercurial Distributed SCM
basic commands:
add add the specified files on the next commit
annotate show changeset information by line for each file
clone make a copy of an existing repository
commit commit the specified files or all outstanding changes
diff diff repository (or selected files)
export dump the header and diffs for one or more changesets
forget forget the specified files on the next commit
init create a new repository in the given directory
log show revision history of entire repository or files
merge merge working directory with another revision
pull pull changes from the specified source
push push changes to the specified destination
remove remove the specified files on the next commit
serve export the repository via HTTP
status show changed files in the working directory
summary summarize working directory state
update update working directory
use "hg help" for the full list of commands or "hg -v" for details
单单输入 hg 不加任何选项, 会列出最常用的命令. 你也可以试试 hg help 来得到完整的命令列表.
要利用版本控制的优势, 你需要一个 版本库. 版本库保存了每个文件的所有历史版本. 为了节约硬盘空间, 它实际上不会保存所有的历史版本 – 它只是保存了一份紧凑的变更列表.
在以往的岁月, 要创建一个版本库可是件大事. 你需要一个中央服务器, 还要在它上面装上相应的软件. Mercurial 是 分布式 的, 所以你不用买昂贵的中央服务器就可以使用它. 你能在自己的 PC 上使用它的全部功能. 而且创建一个版本库也超级简单: 只需要切换到源码所在的根目录...:
c:\hginit> cd CountDown
c:\hginit\CountDown> dir /w
Volume in drive C has no label.
Volume Serial Number is 9862-36C5
Directory of c:\hginit\CountDown
[.] [..] a.txt
AlternateMessages.xml App.config App.xaml
App.xaml.cs CountDown.xaml CountDown.xaml.cs
DevDaysCountDown.csproj favicon.ico [Images]
[Properties] [TweetSharp]
9 File(s) 155,932 bytes
5 Dir(s) 76,083,609,600 bytes free
hg init
Note
hg init
创建一个版本库
... 这就是我的代码, 然后键入 hg init:
c:\hginit\CountDown> hg init
c:\hginit\CountDown>
等一下, 你做了什么? 看上去啥事都没干啊? 如果你有仔细观察, 你会发现已经创建了一个新的隐藏目录 – .hg:
c:\hginit\CountDown> dir /w
Volume in drive C has no label.
Volume Serial Number is 9862-36C5
Directory of c:\hginit\CountDown
[.] [..] [.hg]
a.txt AlternateMessages.xml App.config
App.xaml App.xaml.cs CountDown.xaml
CountDown.xaml.cs DevDaysCountDown.csproj favicon.ico
[Images] [Properties] [TweetSharp]
9 File(s) 155,932 bytes
6 Dir(s) 76,083,650,560 bytes free
那个目录就是版本库! 这个目录包含了 Mercurial 所需要的一切. 配置, 文件的历史版本, 标签, 雨天的备用袜之类的. 别到那个目录搞破坏. 你可能永远都不会想到去乱操作那个目录.
hg add
Note
hg add
将文件放到入库等候队列中. 它们在你执行 commit
操作之前实际上是不会入版本库的
好, 现在我们有一个全新的版本库, 我们准备把所有源文件添加进去. 这很简单: 只需要键入 hg add.
c:\hginit\CountDown> hg add
adding AlternateMessages.xml
adding App.config
adding App.xaml
adding App.xaml.cs
adding CountDown.xaml
adding CountDown.xaml.cs
adding DevDaysCountDown.csproj
adding Images\background_city.jpg
adding Images\carsonified_presents.png
adding Images\darkpanel.png
adding Images\devdays.png
adding Images\failwhale.png
adding Images\holding_image.jpg
adding Images\jeff_atwood.jpg
adding Images\joel_spolsky.jpg
adding Images\logo_stackoverflow.png
adding Images\matt_lacey.jpg
adding Images\sideDarkpanel.png
adding Images\vertical_lines2.png
adding Properties\AssemblyInfo.cs
adding Properties\Resources.Designer.cs
adding Properties\Resources.resx
adding Properties\Settings.Designer.cs
adding Properties\Settings.settings
adding TweetSharp\Dimebrain.TweetSharp.dll
adding TweetSharp\Dimebrain.TweetSharp.xml
adding TweetSharp\Newtonsoft.Json.dll
adding a.txt
adding favicon.ico
还有一个额外的步骤... 你需要 提交 你的变更. 什么变更? 添加所有这些文件即为一次变更.
hg commit
Note
hg commit
把当前所有文件的状态保存到版本库
为什么你必须提交? 对于 Mercurial, 提交意味着 “嘿, 这些文件现在是这个状态 – 请记牢咯.” 这就像是备份了整个目录... 每次你做了类似的变更, 都需要提交.
c:\hginit\CountDown> hg commit
Mercurial 会弹出一个编辑器让你输入提交摘要. 提交摘要只是方便日后提示你本次提交做了哪些改动.
当你保存并退出后, 你所有的源文件都将被提交.
hg log
Note
hg log
显示已经提交到版本库的变更历史
你可以键入 hg log 查看提交历史. 你可以把它想象成版本库的 Blog:
c:\hginit\CountDown> hg log
changeset: 0:da5f372c3901
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:04:30 2010 -0500
summary: Initial version of the CountDown code
我们编辑一个文件, 然后看看会发生什么.
现在, 我们修改了文件 a.txt
, 可以用 hg commit 进行提交:
c:\hginit\CountDown> hg commit
注意 Mercurial 检查到只有一个文件 a.txt
被修改了:
让我们看看提交后, 日志的显示情况:
c:\hginit\CountDown> hg log
changeset: 1:a9497f468dc3
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:26:13 2010 -0500
summary: Capitalized "Scott Adams"
changeset: 0:da5f372c3901
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:04:30 2010 -0500
summary: Initial version of the CountDown code
和现在流行的 Blog 系统一样, Mercurial 把最新的内容放在最上面.
接下来我还要做一个变更, 就当是自娱自乐吧.
提交:
c:\hginit\CountDown> hg commit
我的提交摘要:
现在日志显示成啥样?
c:\hginit\CountDown> hg log
changeset: 2:55490459b740
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:47:43 2010 -0500
summary: Fixed some grammar
changeset: 1:a9497f468dc3
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:26:13 2010 -0500
summary: Capitalized "Scott Adams"
changeset: 0:da5f372c3901
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:04:30 2010 -0500
summary: Initial version of the CountDown code
OK, 这很有趣. 我做了一些变更, 并且每次我做了一个有意义的变更, 我便提交到版本库.
我知道你在想什么. 你在想, “JOEL, 这整一个就是浪费时间.” 为什么提交的时候尽是些罗里罗嗦的废话?
耐心, 年轻人. 你马上就会学到如何从中获益.
NO1. 我们假设你编辑的时候犯了一个重大失误.
接着, 我嘞个去, 你又删掉了几个非常重要的文件.
c:\hginit\CountDown> del favicon.ico
c:\hginit\CountDown> del App.xaml
在没有 Mercurial 的那些岁月, 这是一个跑去向系统管理哭诉大好机会, 你还可以凄惨的质问 为什么 备份系统 “临时” 罢工了, 而且已经八个月了.
系统管理员 (大家都叫他 Taco), 很害羞一小伙, 从不和团队的其他成员共进午餐. 偶尔他离开他那心爱的办公转椅, 你会看到座位上有个三角形状的沙司色的污渍, 那是他经常吃墨西哥午餐, 掉在他两腿间油滴... 肯定没人会拖走他的椅子, 即使它是公司创始人给自己买的 Herman Miller 公司豪配系列, 而不是把其他人弄得背痛的 Staples $99 标配特价版.
反正, 就是没有备份.
hg revert
Note
hg revert
将变更的文件恢复到最近一次提交后的状态
感谢 Mercurial, 当你想反悔时, 你可以使用快捷的 hg revert 命令, 立刻将你的目录恢复到最后一次提交之后的状态.
c:\hginit\CountDown> hg revert --all
reverting App.xaml
reverting a.txt
reverting favicon.ico
c:\hginit\CountDown> type a.txt
SCOTT ADAMS: Normal people believe that if it isn't
broken, don't fix it. Engineers believe that if it
isn't broken, it doesn't have enough features yet.
我使用了命令行参数 –all, 这是因为我希望把 所有 文件都恢复到原来的状态.
所以, 用 Mercurial 管理代码时:
- 做些代码修改
- 看是否能正常运行
- 可以, 则 commit 变更
- 不行, 则 revert 它们
- GOTO 1
(我明白. 还停留在 Windows 命令提示符和 GOTO 语句, 我是有史以来最矬的程序员)
hg status
Note
hg status
显示改动过的文件列表
随着时间推移, 你可能会困惑现在身处何处, 还有自从最后一次提交, 你做了哪些变更. Mercurial 帮你追踪所有操作. 你所需要做的只是键入 hg status, Mercurial 便会为你提供一份变更的文件列表.
假如我创建了一个文件, 编辑了一个文件, 同时删除了一个文件.
c:\hginit\CountDown> copy a.txt b.txt
1 file(s) copied.
c:\hginit\CountDown> notepad2 a.txt
c:\hginit\CountDown> del favicon.ico
c:\hginit\CountDown> hg status
M a.txt
! favicon.ico
? b.txt
hg status 会列出所有改动过的文件, 并在每行用一个字符指代变更类型. “M” 表示 “Modified” – 文件已经被修改. ”!” 表示丢失 – 文件原本应该在那儿, 但是不见了. ”?” 表示未知 – 该文件尚未被 Mercurial 管理, 版本库里没有任何信息.
让我们逐个处理这些变更. 那个被修改的文件, a.txt. 做了哪些改动? 你可能已经忘记的一干二净了! 嘿, 我有几天连我早餐吃了些啥都不记得. 更令我焦虑的实际上是每天都是麦片. 反正, a.txt 就是被修改了. 那到底改了些啥?
hg diff
Note
hg diff
显示一个文件的改动详情
有条命令可以为你提供答案: hg diff 会告诉你文件自最后一次提交后具体做了哪些改动.
c:\hginit\CountDown> hg diff a.txt
diff -r 55490459b740 a.txt
--- a/a.txt Fri Feb 05 13:47:43 2010 -0500
+++ b/a.txt Fri Feb 05 14:31:18 2010 -0500
@@ -1,3 +1,3 @@
-SCOTT ADAMS: Normal people believe that if it isn't
+SCOTT ADAMS: Civilians believe that if it isn't
broken, don't fix it. Engineers believe that if it
isn't broken, it doesn't have enough features yet.
格式看上去挺神秘的, 但更有意思的是: 以减号开头的是那些被删除的行, 以加号开头的则是增加的行, 所以上面的信息表明 “Normal people” 被修改成 “Civilians”.
hg remove
Note
hg remove
将文件放到出库等候队列中. 它们在你执行 commit
操作之前实际上是不会从版本库移除的
我们再来看看那个丢失的文件, favicon.ico
. 如之前所说, 如果你没打算删除它, 你可以用 hg revert 命令. 我们假设你就是想移除它. 每当移除 (或添加) 一个文件, 你都需要通知到 Mercurial:
c:\hginit\CountDown> hg remove favicon.ico
c:\hginit\CountDown> hg status
M a.txt
R favicon.ico
? b.txt
“R” 表示 “Removed”, 这样下次我们执行提交操作, favicon.ico
便会从 Mercurial 版本库中删除. (文件的 历史数据 仍然保存在版本库中, 自然我们可以随时把它恢复回来).
最后, 我们再来处理那个新增的文件 b.txt:
c:\hginit\CountDown> hg add
adding b.txt
c:\hginit\CountDown> hg st
M a.txt
A b.txt
R favicon.ico
“A” 表示 “Added”. 另外你注意到了吗? 我已经懒得每次都完整的键入 hg status 了. 只要不产生冲突, Mercurial 不需要键入完整的命令 – 除了 status 没有其它命令是以 st 开头的.
在解决完所有的 ?
和 !
后, 我们可以继续提交变更了:
c:\hginit\CountDown> hg commit
c:\hginit\CountDown> hg log
changeset: 3:2f4718ee168e
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 14:54:45 2010 -0500
summary: A few highly meaningful changes. No favicon.ico no more.
changeset: 2:55490459b740
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:47:43 2010 -0500
summary: Fixed some grammar
changeset: 1:a9497f468dc3
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:26:13 2010 -0500
summary: Capitalized "Scott Adams"
changeset: 0:da5f372c3901
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 05 13:04:30 2010 -0500
summary: Initial version of the CountDown code
hg log 的输出结果带来另外一个问题: 每次提交 changeset 一行都会显示一个编号... 事实上是两个: 一个比较直观简洁, 比如初始版本编号 “0”, 但另外一个是冗长, 诡异的十六进制编码, 不过你可以暂时无视它.
谨记 Mercurial 在版本库中存储了足够的信息来重建任何文件的历史版本.
hg cat
Note
hg cat
显示任何文件的任何版本
首先, 一条简单的 hg cat 命令可以用来显示一个文件的任何历史版本. 比如, 想知道 a.txt
有啥内容:
c:\hginit\CountDown> hg cat a.txt
SCOTT ADAMS: Civilians believe that if it isn't
broken, don't fix it. Engineers believe that if it
isn't broken, it doesn't have enough features yet.
要想知道它以往的内容, 我们可以从日志里挑一个变更集编号. 然后用 -r (“revision”) 参数配合 cat 命令:
c:\hginit\CountDown> hg cat -r 0 a.txt
Scott Adams: Normal people believe that if it ain't
broke, don't fix it. Engineers believe that if it
ain't broke, it doesn't have enough features yet.
如果文件冗长复杂, 而且只做了非常小的改动, 我们可以用 -r 参数配合 hg diff 命令, 输出任意两个版本之间的区别. 比如, 让我们看看版本 0 和 1 之间做了哪些修改:
c:\hginit\CountDown> hg diff -r 0:1 a.txt
diff -r da5f372c3901 -r a9497f468dc3 a.txt
--- a/a.txt Fri Feb 05 13:04:30 2010 -0500
+++ b/a.txt Fri Feb 05 13:26:13 2010 -0500
@@ -1,3 +1,3 @@
-Scott Adams: Normal people believe that if it ain't
+SCOTT ADAMS: Normal people believe that if it ain't
broke, don't fix it. Engineers believe that if it
ain't broke, it doesn't have enough features yet.
hg update
Note
hg update
将工作目录更新到指定版本
最后, 如果你还没有精疲力尽到昏厥, 我在本章结束前还想讲讲 另外一个小功能: 你可以用 hg update 命令在任何历史版本间来回时空穿梭. 当然, 本质上 你是不能飞跃到未来的, 虽然我们都知道这肯定是一件非常酷的事儿. 如果你只有四个版本, 在敲入 hg update -r 103994 后立马得到一些很酷的, 反万有引力, 科幻式的未来版本源码. 别做梦了, 这是不可能的.
但追溯任何版本都是可能的. 比如:
c:\hginit\CountDown> hg update -r 0
2 files updated, 0 files merged, 1 files removed, 0 files unresolved
c:\hginit\CountDown> type a.txt
Scott Adams: Normal people believe that if it ain't
broke, don't fix it. Engineers believe that if it
ain't broke, it doesn't have enough features yet.
c:\hginit\CountDown> hg up -r 1
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
c:\hginit\CountDown> type a.txt
SCOTT ADAMS: Normal people believe that if it ain't
broke, don't fix it. Engineers believe that if it
ain't broke, it doesn't have enough features yet.
c:\hginit\CountDown> hg up
2 files updated, 0 files merged, 1 files removed, 0 files unresolved
c:\hginit\CountDown> type a.txt
SCOTT ADAMS: Civilians believe that if it isn't
broken, don't fix it. Engineers believe that if it
isn't broken, it doesn't have enough features yet.
hg update 命令实际上是修改目录下的所有文件内容达到按时间来回穿梭的效果. 如果需要添加或移除一个文件, hg update 命令也会照做. hg update 不带任何参数的话, 即默认为最新版本.
小测验
OK! 以上就是本章的教程. 接下来的几个知识点你现在应该都知道怎么操作了:
- 创建一个版本库
- 从版本库中添加和移除文件
- 做些修改, 然后看看你做了哪些尚未提交的改动, 然后
- ... 如果你愿意, 就提交这些变更.
- ... 或者你也可以不提交, 恢复 (revert) 这些变更
- 查看一下这些文件历史版本, 甚至让你的目录来个时空穿梭
使用 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) 变更
- 合并不同代码提交人的变更
使用 Mercurial 的一个最大好处是, 你可以使用私有本地库来尝试和开发新特性... 如果新特性不管用, 你能在短时间内还原.
失误补救
Mercurial 让你能够尽情尝试. 假设在日常编辑过程中, 你的编辑器发生了异常, 结果你的代码悲剧了:
hg revert
Note
hg revert
将修改的文件恢复到最近一次提交后的状态
非得爱上 emacs 才行吗 (译注: emacs 是 Linux/Unix 下一款功能强大但入门困难的编辑器). 不管怎样, 啥也没弄丢. 从这类情况中恢复回来最常用的操作是使用 hg revert:
C:\Users\joel\recipes> hg revert guac
之后文件便恢复到它们最近一次提交完成时的状态. Mercurial 不会乱删任何东西, 所以它没有删掉 Pig Latin (译注: 故意颠倒英语字母顺序拼凑而成的行话) 式的配方, 而是采用重命名的方式:
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/11/2010 11:16 AM <DIR> .
02/11/2010 11:16 AM <DIR> ..
02/11/2010 11:16 AM <DIR> .hg
02/11/2010 11:16 AM 393 guac
02/11/2010 11:15 AM 510 guac.orig
2 File(s) 903 bytes
3 Dir(s) 40,958,005,248 bytes free
C:\Users\joel\recipes> del guac
C:\Users\joel\recipes> rename guac.orig guac
如果你多走了一步, 已经提交了该怎么办?
C:\Users\joel\recipes> hg com -m "Pig Latin ftw"
C:\Users\joel\recipes> hg log -l 3
changeset: 5:c7af1973de6d
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 11:32:27 2010 -0500
summary: Pig Latin ftw
changeset: 4:0849ca96c304
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
hg rollback
Note
hg rollback
撤销最后一次提交, 前提是你还没有把它推送 (push) 给其他人
hg rollback 可以挽救你, 但当且仅当你还没有把变更推送 (push) 给其他人. 它只能撤销 一次 提交.
C:\Users\joel\recipes> hg rollback
rolling back last transaction
C:\Users\joel\recipes> hg log -l 3
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.
C:\Users\joel\recipes> hg stat
M guac
C:\Users\joel\recipes> hg revert guac
假设你私底下在做一项重要尝试. 你的老板雇了一个新设计师, Jim, 但最近你从他那拿到的规格说明非常愚蠢. 正文采用绿色荧光, 而且参差不齐 (说是出于 “艺术效果” 的考虑), 可用性非常糟糕. 你甚至都想用一个周末的时间重新处理一下, 但是你为提交的事犯愁, 因为你没法 100% 确保你的想法比那个疯子图形设计师会更好. Jim 从他起床的那一刻到睡觉前基本上都在抽大麻. 你不想用这件事来攻击他, 而且所有人都认为只要他的设计不错, 这不关任何人的事, 但说实话, 这总得有个底限. 对吧? 而且他的设计也说不上好. 加上他还有些让人不爽.
使用 Mercurial, 你可以 clone
出一份完整的试验库:
C:\Users\joel\recipes> cd ..
C:\Users\joel> hg clone recipes recipes-experiment
updating to branch default
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
效率并不像看上去的那么低. 由于 recipes 和 recipes-experiment 共享了 (目前为止) 所有的历史, Mercurial 会使用文件系统的一个特性 - “硬链接 (hard links)” 使复制操作异常迅速, 而且占用更少的磁盘空间.
现在, 我们来对试验分支做些变更:
C:\Users\joel> cd recipes-experiment
以下就是我那顶级鳄梨酱试验配方:
在试验库里, 我们可以自主决定 commit 与否.
C:\Users\joel\recipes-experiment> hg com -m "Queso = Cheese!"
你可以尽情修改文件, 在任何时候提交变更. 它给予你源代码控制的所有权力, 甚至支持你做一些疯狂的试验, 而不会给其他人制造麻烦.
如果你最终发现试验失败, 你只须删除整个试验目录, 问题就解决了, 一去不复返.
但如果试验成功, 你只要 push 新的变更即可:
C:\Users\joel\recipes-experiment> hg push
pushing to c:\Users\joel\recipes
searching for changes
adding changesets
adding manifests
adding file changes
added 1 changesets with 1 changes to 1 files
hg paths
Note
hg paths
显示远程版本库列表
提交的变更上哪儿去了?
C:\Users\joel\recipes-experiment> hg paths
default = c:\Users\joel\recipes
“default” 的键值为 hg push 缺省 (即命令行参数中未指定其它版本库) 推送变更的目标库路径. 一般而言, “default” 的键值指向你 clone
时的目标库. 在本例情况中, 它的值为本地目录, 当然这个值也可以是一个 URL.
C:\Users\joel\recipes-experiment> cd ..\recipes
注意别忘了, 变更正是推送到这个 版本库 ...
C:\Users\joel\recipes> hg log -l 3
changeset: 5:9545248f3fc9
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 12:59:11 2010 -0500
summary: Queso = Cheese!
changeset: 4:0849ca96c304
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
hg parent
Note
hg parent
显示当前正基于哪 (几) 个变更集进行开发工作
... 但这并不表示我们当前正工作在推送后的版本.
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.
C:\Users\joel\recipes> hg parent
changeset: 4:0849ca96c304
parent: 2:4ecdb2401ab4
parent: 3:689026657682
user: Joel Spolsky <joel@joelonsoftware.com>
date: Mon Feb 08 16:07:23 2010 -0500
summary: merge
看到了没? “Queso” 相关的修改在 5 号变更集. 但是我的个人主库当前工作在 4 号变更集, 这是因为某人向 版本库 推送新的变更并不意味着这些变更就会在我的工作目录立即生效, 所以我当前所有工作仍然是基于 4 号变更集.
如果我想看看 5 号变更集里面到底有些啥, 我可以使用 hg update 命令:
C:\Users\joel\recipes> hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
C:\Users\joel\recipes> hg parent
changeset: 5:9545248f3fc9
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 12:59:11 2010 -0500
summary: Queso = Cheese!
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.
This recipe is really good served with QUESO.
QUESO is Spanish for "cheese," but in Texas,
it's just Kraft Slices melted in the microwave
with some salsa from a jar. MMM!
瞧瞧发生了什么? 变更生效了, 但是是基于我之前工作的版本之上. push 和 pull 仅仅是把变更从一个版本库发送到另一个 – 不影响当前工作的文件.
这便是版本库当前的状态:
Mercurial 在版本库之间传递变更的方式上非常灵活. 你可以把变更直接从试验库推送到中央库:
C:\Users\joel\recipes> cd ..\recipes-experiment
C:\Users\joel\recipes-experiment> hg outgoing http://joel.example.com:8000/
comparing with http://joel.example.com:8000/
searching for changes
changeset: 5:9545248f3fc9
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 12:59:11 2010 -0500
summary: Queso = Cheese!
C:\Users\joel\recipes-experiment> hg push http://joel.example.com:8000/
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
以上操作把 5 号变更从试验库直接推送到了中央库. 现在, 如果我返回到我的个人主库, 会发现没有可推送的变更!
C:\Users\joel\recipes-experiment> cd ..\recipes
C:\Users\joel\recipes> hg out
comparing with http://joel.example.com:8000/
searching for changes
no changes found
这是因为 Mercurial 分析出中央库已经从其它地方得到了指定的变更集. 这个功能非常有用, 否则会发生重复推送, 把版本库搞得杂乱而冗余.
... (译注: 有意忽略一段, 不影响阅读) ...
hg backout
有时你可能会发现, 早在几个月前, 你犯了个错误:
C:\Users\joel\recipes> hg diff -r 1:2 guac
diff -r a52881ed530d -r 4ecdb2401ab4 guac
--- a/guac Mon Feb 08 14:51:18 2010 -0500
+++ b/guac Mon Feb 08 15:32:01 2010 -0500
@@ -8,4 +8,4 @@
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
-Serve with tortilla chips.
+Serve with potato chips.
Potato chips? WTF?! (译注: What the Fuck 缩写)
Mercurial 能够帮助你拆除 (backout) 早些时候的变更集. 它会分析变更集, 得出 反向变更集, 并应用于你当前的工作目录. 让我们试着拆除 2 号历史版本.
C:\Users\joel\recipes> hg backout -r 2 --merge
reverting guac
created new head
changeset 6:d828920f7f85 backs out changeset 2:4ecdb2401ab4
merging with changeset 6:d828920f7f85
merging guac
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
Oh shit, 什么状况?
C:\Users\joel\recipes> hg diff
diff -r 9545248f3fc9 guac
--- a/guac Thu Feb 11 12:59:11 2010 -0500
+++ b/guac Thu Feb 11 14:19:34 2010 -0500
@@ -8,7 +8,7 @@
* 1/2 ripe tomato, seeds and pulp removed, chopped
Smoosh all ingredients together.
-Serve with potato chips.
+Serve with tortilla chips.
This recipe is really good served with QUESO.
C:\Users\joel\recipes> hg com -m "undo thing from the past"
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
时隔久远以后, “chips” 这个单词可能都从配方中消失了. 这类问题很可能会导致本次变更无法合并. 这种情况下, 你得手工解决合并冲突. 我们将在下一章节探讨这个问题.
小测验
下面的一些操作是学完本章教程后, 你应该要学会的:
- 还原意外的破坏 (在提交之前或之后)
- 从本地版本库
clone
一份试验库 - 在版本库之间推送 (push)
- 恢复年代久远的错误变更
合并有时会产生冲突. 通常这些冲突很容易解决, 你也必须得去解决, 不然你的版本库就会变成多头怪 (多个最新版本). 有谁希望自己肩膀上扛着好几个脑袋?
合并
版本管理的一个重要功能是帮助多人团队进行协同代码开发.
假设 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)
C:\Users\rose\recipes> hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
然后编辑:
(译注: Hass avocados - 哈斯鳄梨)
接着, 她提交改动并把变更推送到中央版本库:
C:\Users\rose\recipes> hg diff
diff -r 549d45f24c37 guac
--- a/guac Thu Feb 11 17:07:41 2010 -0500
+++ b/guac Thu Feb 11 17:10:40 2010 -0500
@@ -1,4 +1,4 @@
-* 2 ripe avocados
+* 2 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 habanero chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
C:\Users\rose\recipes> hg com -m "better avocados"
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 diff
diff -r 549d45f24c37 guac
--- a/guac Thu Feb 11 17:07:41 2010 -0500
+++ b/guac Thu Feb 11 17:12:09 2010 -0500
@@ -1,6 +1,6 @@
* 2 ripe avocados
* 1/2 red onion, minced (about 1/2 cup)
-* 1-2 habanero chiles, stems and seeds removed, minced
+* 1-2 jalapeno 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\joel\recipes> hg com -m "better chile"
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)
这可以说是 Mercurial 里面最没有意义的错误提示. 它应该这么说:
C:\Users\joel\recipes> hg push
pushing to http://joel.example.com:8000/
searching for changes
ZOMG!!! There are changes in that repo that you don't have yet.
Don't push now. Pull the latest changes and merge them first.
而且我的确打算这么做:
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)
想知道获取了哪些东西? hg log -P . 命令可以方便的让你知道.
C:\Users\joel\recipes> hg log -P .
changeset: 9:44aefdeef9e0
tag: tip
parent: 7:549d45f24c37
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 17:10:48 2010 -0500
summary: better avocados
实际上获取下来的是 Rose 之前所作的变更. 我的本地库现在是什么状态?
C:\Users\joel\recipes> hg heads
changeset: 9:44aefdeef9e0
tag: tip
parent: 7:549d45f24c37
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 17:10:48 2010 -0500
summary: better avocados
changeset: 8:bf5854ca20f7
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 17:12:23 2010 -0500
summary: better chile
C:\Users\joel\recipes> hg parent
changeset: 8:bf5854ca20f7
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 17:12:23 2010 -0500
summary: better chile
我有 “多个版本头 (multiple heads)”. 我的本地库现在应该是这个样子地:
.. image:: _images/04-repo.png
看到 “双头” 了吗? 这是因为我和 Rose 都是在工作目录下的 7 号变更集的基础上进行修改. 所以现在必须要合并.
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> hg log -l 4
changeset: 10:8646f8cd7154
tag: tip
parent: 8:bf5854ca20f7
parent: 9:44aefdeef9e0
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 21:51:26 2010 -0500
summary: merge
changeset: 9:44aefdeef9e0
parent: 7:549d45f24c37
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 17:10:48 2010 -0500
summary: better avocados
changeset: 8:bf5854ca20f7
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 17:12:23 2010 -0500
summary: better chile
changeset: 7:549d45f24c37
parent: 5:d8b5146ab630
parent: 6:470aea67ee96
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 17:07:41 2010 -0500
summary: undo thing from the past
合并命令 - hg merge 把两个栈顶变更集进行合并. 然后它会把合并结果保存到工作目录. 它不会尝试提交, 留给我一个检查合并是否正确的机会:
C:\Users\joel\recipes> type guac
* 2 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 jalapeno 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.
This recipe is really good served with QUESO.
QUESO is Spanish for "cheese," but in Texas,
it's just Kraft Slices melted in the microwave
with some salsa from a jar. MMM!
嗯, 没错; 鳄梨是哈斯的 (Hass avocados), 红番椒是墨西哥的 (Jalapeno chiles). 我决定提交合并, 并推送到中央库服务器.
C:\Users\joel\recipes> hg com -m "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
我推送了两个变更集: 我原来的 “墨西哥红番椒” (Jalapeno chiles) 改动, 以及后来的合并结果 (也被当作独立的变更集).
注意我们两次变更并没有冲突, 因为 Rose 和我修改的不是同一个地方. 所以合并过程超级简单. 这是最常见的情况, 大部分团队, 每个开发人员一般都会分配到不同的模块代码.
如果你不幸处在一个奇怪的团队, 没人愿意承担明确的开发职责. 这会导致开发人员之间常常出现突发性的悲观情绪. 这种情绪很难被察觉. 临床症状包括: 开发人员把自个儿锁在浴室; 或者锁在机房; 人员流失严重; 在小隔间里默默啜泣; 以及长时间处于军用来复枪枪击声场下引起突发性的耳膜损伤.
但即便你身处最伟大, 最健全的组织, 合并冲突有时还是会发生, Mercurial 会要求你解决冲突. 让我们看看应该怎么做.
首先... 我让 Rose 继续改进我的 “墨西哥红番椒” (Jalapeno chiles) 变更:
C:\Users\rose\recipes> hg in
comparing with http://joel.example.com:8000/
searching for changes
changeset: 9:bf5854ca20f7
parent: 7:549d45f24c37
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 17:12:23 2010 -0500
summary: better chile
changeset: 10:8646f8cd7154
tag: tip
parent: 9:bf5854ca20f7
parent: 8:44aefdeef9e0
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 21:51:26 2010 -0500
summary: merge
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)
C:\Users\rose\recipes> hg up
1 files updated, 0 files merged, 0 files removed, 0 files unresolved
现在我们看看当碰到该死的冲突时会发生些什么: 我们将在配料上动点手脚.
我先往配方中添加一个香蕉:
紧接着提交本次变更:
C:\Users\joel\recipes> hg diff
diff -r 8646f8cd7154 guac
--- a/guac Thu Feb 11 21:51:26 2010 -0500
+++ b/guac Thu Feb 11 22:46:27 2010 -0500
@@ -6,6 +6,7 @@
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
+* 1 delicious, yellow BANANA.
Smoosh all ingredients together.
Serve with tortilla chips.
C:\Users\joel\recipes> hg com -m "bananas YUM"
C:\Users\joel\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
然后 Rose, OMG, 她在 同一行 添加了 芒果.
确切的说, 是 “成熟新鲜的 (ripe young)” 芒果.
C:\Users\rose\recipes> hg diff
diff -r 8646f8cd7154 guac
--- a/guac Thu Feb 11 21:51:26 2010 -0500
+++ b/guac Thu Feb 11 22:49:26 2010 -0500
@@ -6,6 +6,7 @@
* 1/2 teaspoon coarse salt
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
+* 1 ripe young Mango, in season.
Smoosh all ingredients together.
Serve with tortilla chips.
C:\Users\rose\recipes> hg com -m "mmmmango"
这次是我先提交的变更, 所以轮到 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 1 changesets with 1 changes to 1 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
C:\Users\rose\recipes> hg merge
Mercurial 立马检测到了冲突, 并弹出一个 GUI 界面的冲突解决工具, 它的用户界面估计没人会喜欢, 但是一旦你搞清楚了, 它们是很能胜任这项任务的. 我们常用的一个合并冲突解决工具是 KDiff3, 下图就是 Rose 看到的用户界面:
KDiff3 界面中包含了 4 个面板. 左上角是原始文件. 上方居中的是 Rose 自己修改后提交的版本. 右上方是我同步后的版本. 下方面板则是一个编辑器, Rose 将用它来解决冲突并手工合并文件.
修正冲突是件相对简单的事情 - 逐个检查冲突, 然后决定如何解决它. Rose 太疯狂了, 居然认为 香蕉芒果鳄梨酱 也行得通:
对了, 我还没告诉你 Rose 好像在约会吧? 前些天有内部八卦说她下班后和一个长得像 Dennis Franz 的家伙在一起. 总之, 她最近的情绪之佳前所未有.
Rose 保存了所作的改动, 并退出 KDiff3.
merging guac
0 files updated, 1 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
C:\Users\rose\recipes> hg diff
diff -r f923c9049234 guac
--- a/guac Thu Feb 11 22:49:31 2010 -0500
+++ b/guac Thu Feb 11 23:01:45 2010 -0500
@@ -7,6 +7,7 @@
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
* 1 ripe young Mango, in season.
+* 1 delicious, yellow BANANA.
Smoosh all ingredients together.
Serve with tortilla chips.
C:\Users\rose\recipes> hg com -m "merge"
C:\Users\rose\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
至此冲突已经被解决了.
还有一件事你应该谨记: 你无须依照其他人的 push 进度安排做合并. 你可以在任何时候选择执行 hg pull, 如果你暂时不想合并冲突, 你可以继续开发, 提交, 并沉浸于其中, 直到你有时间思考合并的事情.
小测验
下面的一些操作是学完本章教程后, 你应该要学会的:
- 和其他人编写同一份代码
- 获得他们的变更
- 推送你所作的变更
- 解决时不时冒出来的合并冲突
- 诊断某些类型的开发人员职业抑郁症
Next
Mercurial 带给你极具灵活性的版本库组织方式. 由于合并操作如此和谐, 你完全可以信赖合并, 这意味着你可以建立一些特殊用途的版本库, 以匹配你的开发流程.
版本库组织方式
我们的配方已经非常完美了:
C:\Users\joel\recipes> hg log -l 3
changeset: 13:1b03ab783b17
tag: tip
parent: 12:f923c9049234
parent: 11:0bd396c9b89b
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 23:01:55 2010 -0500
summary: merge
changeset: 12:f923c9049234
parent: 10:8646f8cd7154
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 22:49:31 2010 -0500
summary: mmmmango
changeset: 11:0bd396c9b89b
user: Joel Spolsky <joel@joelonsoftware.com>
date: Thu Feb 11 22:46:47 2010 -0500
summary: bananas YUM
让我们再来仔细看看变更集编号:
changeset: 13:1b03ab783b17
编号的第一部分, 13, 简短方便. 但有一个问题... 它很不靠谱!
当团队的每个成员各自开发, 然后合并代码, 这些短编码并未被同步:
所以, 实际上我根本没法跟其他人说 “好, 让我们发布第 13 号变更集的版本吧”, 因为他们可能对 13 这个数字有不同的理解. 这就是为什么还有一串古怪的 16 进制编码.
changeset: 13:1b03ab783b17
16 进制编码在所有版本库 均 保持一致, 而且永远不会改变.
OK, 我现在可以告诉其他人, “嘿, 我们今天发布! 变更集编号是 1b03ab783b17!” 如果我能给这个变更集取个 名字 不是更好?
嗯, 你的确可以. 我们称之为 标签.
C:\Users\joel\recipes> hg tag Version-1.0
我们回过来看看日志:
C:\Users\joel\recipes> hg log -l 2
changeset: 14:1adc88356f40
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 09:38:06 2010 -0500
summary: Added tag Version-1.0 for changeset 1b03ab783b17
changeset: 13:1b03ab783b17
tag: Version-1.0
parent: 12:f923c9049234
parent: 11:0bd396c9b89b
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 23:01:55 2010 -0500
summary: merge
注意, 之前添加标签的动作是一个标准的变更集, 它还帮我自动提交了本次变更. 从现在开始, 每次我要提及发布代码的版本号, 我都可以用 Version-1.0 代替 1b03ab783b17.
CEO 从 31 楼下来参加官方发布聚会, 还顺带了一整箱看上去相当昂贵的香槟. Stan 有点醉了. 嗯, 醉得不止一点儿. 没人看过他这个样子. 他脱掉了衬衫, 显示他尚未松弛的肌肉, 试图吸引市场部女同事的目光. “我能拉住那些吊灯,” 他瞎吹道 (我们有几盏长型荧光吊灯). 接着他一跃而起, 抓住灯座, 然后可想而知了, 整个灯架立马被他拽了下来, 因为那台十磅重的灯座只是用几根细细的钢琴弦吊着, 但这家伙的体重在 290~300 磅之间. 他把整个灯具和天花板部分吊顶给拽了下来, 玻璃和吸声瓦材料破碎一地, 他自己也重重的摔在地板上, 凄惨的叫道他打算起诉公司没有创造一个安全的工作环境.
其他人都回到了各自的办公隔间, 继续开发 鳄梨酱 2.0
提交:
C:\Users\joel\recipes> hg com -m "more avocado flavor"
显而易见, 配方现在处于非稳定状态. 它没有经过测试或者其它验证. 接着, 某客户一个电话过来.
“这太咸了!” 他悲嗥道. 而且, 他不想等到 2.0 版本才修正这个问题.
幸运的是, 我们之前打了标签. 我可以使用 hg up 切换到版本库中的任何版本.
C:\Users\joel\recipes> hg up -r Version-1.0
1 files updated, 0 files merged, 1 files removed, 0 files unresolved
C:\Users\joel\recipes> type guac
* 2 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 jalapeno chiles, stems and seeds removed, minced
...
现在我可以开始修正他这个愚蠢的用盐问题了:
接着:
C:\Users\joel\recipes> hg diff
diff -r 1b03ab783b17 guac
--- a/guac Thu Feb 11 23:01:55 2010 -0500
+++ b/guac Fri Feb 12 10:44:19 2010 -0500
@@ -3,7 +3,7 @@
* 1-2 jalapeno 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
+* 1 grain table salt, split in half
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
* 1 ripe young Mango, in season.
C:\Users\joel\recipes> hg com -m "less salt"
created new head
Mercurial 提醒我创建了一个新的版本头 (head). 现在我们有两个版本头, 2.0 版本头是我不久前创建的, 1.1 版本头是我刚提交的.
现在我可以把它发布给客户, 打上 1.1 标签, 然后返回继续 2.0 的开发.
C:\Users\joel\recipes> hg tag -r . Version-1.1
C:\Users\joel\recipes> hg log -l 3
changeset: 17:f4220e321145
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 11:17:02 2010 -0500
summary: Added tag Version-1.1 for changeset 60ddc0122eb4
changeset: 16:60ddc0122eb4
tag: Version-1.1
parent: 13:1b03ab783b17
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 10:44:32 2010 -0500
summary: less salt
changeset: 15:90c349eca2e8
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 10:31:24 2010 -0500
summary: more avocado flavor
C:\Users\joel\recipes> hg up -r 15
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
C:\Users\joel\recipes> type guac
GUACAMOLE 2.0 THIS IS GOING TO BE AWESOME
* 200 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 jalapeno 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
* 1 ripe young Mango, in season.
* 1 delicious, yellow BANANA.
...
但还有一个问题... 用盐过量的问题在 2.0 中并未得到修正. 我们该怎么解决呢?
C:\Users\joel\recipes> hg merge
merging .hgtags
啊哦. 我需要合并标签. 这是 Mercurial 中一个很恶心的 Bug. 主因是 Mercurial 中的标签只是一个文件 - .hgtags
, 也被置入版本库进行版本管理, 所以有时你得手工合并不同版本的 .hgtags
文件. 不论在什么情况下, 你只须做非常简单的处理... 始终保留标签文件 所有 版本的每一行.
merging guac
0 files updated, 2 files merged, 0 files removed, 0 files unresolved
(branch merge, don't forget to commit)
C:\Users\joel\recipes> hg diff guac
diff -r 90c349eca2e8 guac
--- a/guac Fri Feb 12 10:31:24 2010 -0500
+++ b/guac Fri Feb 12 11:32:43 2010 -0500
@@ -5,7 +5,7 @@
* 1-2 jalapeno 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
+* 1 grain table salt, split in half
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
* 1 ripe young Mango, in season.
C:\Users\joel\recipes> hg com -m "bringing in salt fix from 1.1"
当你只是想对已发布的代码小做计划之外的改动时, 这个简单的方法能让你快速回退到打过标签的版本. 但现实情况是, 对于大部分软件项目, 这类事情会不停的出现, Mercurial 有更稳健的处理方式应对这种情况.
现在我要将刚刚做过的改动全部推倒重来, 让版本库恢复到我刚发布 1.0 版本时的状态, 然后我再向你演示如何非常优雅的修正客户报告的 Bug, 同时不影响未来版本新特性的开发.
C:\Users\joel\recipes> cd ..
C:\Users\joel> hg clone -r 14 recipes recipes-stable
requesting all changes
adding changesets
adding manifests
adding file changes
added 15 changesets with 15 changes to 2 files
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
C:\Users\joel> cd recipes-stable
C:\Users\joel\recipes-stable> hg log -l 3
changeset: 14:1adc88356f40
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 09:38:06 2010 -0500
summary: Added tag Version-1.0 for changeset 1b03ab783b17
changeset: 13:1b03ab783b17
tag: Version-1.0
parent: 12:f923c9049234
parent: 11:0bd396c9b89b
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 23:01:55 2010 -0500
summary: merge
changeset: 12:f923c9049234
parent: 10:8646f8cd7154
user: Rose Hillman <rose@example.com>
date: Thu Feb 11 22:49:31 2010 -0500
summary: mmmmango
主要思路是, 我们这次不再基于一个版本库执行所有操作, 而是使用两个版本库, 一个叫 stable, 另一个称之为 dev.
stable 版本库托管我们发布给客户的最新重要版本. 每当一个紧急的 Bug 被报告, 我们在 stable 中修正它. 本例中, 对应的是 1.0 的相关补丁.
dev 版本库则是驶向下一个里程碑的开发活动, 通往 2.0 版本.
一旦 1.0 发布, 我便从 stable clone
一份至 dev:
C:\Users\joel\recipes-stable> cd ..
C:\Users\joel> hg clone recipes-stable recipes-dev
updating to branch default
2 files updated, 0 files merged, 0 files removed, 0 files unresolved
现在我有两个完全一致的版本库:
由于这些版本库的历史截止到 14 号变更集为止都是相同的, Mercurial 实际上会使用文件系统的一个内部功能 (硬链接) 避免做实际的复制操作. 这使得 hg clone 操作快速而廉价, 所以你不用在一次次的 clone
操作时纠结了.
现在我们基于 dev 版本库继续编辑 guac
:
C:\Users\joel> cd recipes-dev
C:\Users\joel\recipes-dev> edit guac
C:\Users\joel\recipes-dev> hg diff
diff -r 1adc88356f40 guac
--- a/guac Fri Feb 12 09:38:06 2010 -0500
+++ b/guac Fri Feb 12 15:15:01 2010 -0500
@@ -1,4 +1,6 @@
-* 2 ripe Hass avocados (not Haas)
+GUACAMOLE 2.0 THIS IS GOING TO BE AWESOME
+
+* 200 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 jalapeno chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
C:\Users\joel\recipes-dev> hg commit -m "more avocado flavor"
接着在 stable 版本库中修正用盐过量的问题:
C:\Users\joel\recipes-dev> cd ..\recipes-stable
C:\Users\joel\recipes-stable> edit guac
C:\Users\joel\recipes-stable> hg diff
diff -r 1adc88356f40 guac
--- a/guac Fri Feb 12 09:38:06 2010 -0500
+++ b/guac Fri Feb 12 15:18:31 2010 -0500
@@ -3,7 +3,7 @@
* 1-2 jalapeno 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
+* 1 grain table salt, split in half
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
* 1 ripe young Mango, in season.
C:\Users\joel\recipes-stable> hg com -m "less salt"
现在, 我打上标签然后当作 1.1 版本发布:
C:\Users\joel\recipes-stable> hg tag Version-1.1
此后, 每隔一段时间, 我们定期把 bugfixes 从 stable 取出到 dev:
C:\Users\joel\recipes-stable> cd ..\recipes-dev
C:\Users\joel\recipes-dev> hg in
comparing with c:\Users\joel\recipes-stable
searching for changes
changeset: 15:e05c954f961f
tag: Version-1.1
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 15:28:27 2010 -0500
summary: less salt
changeset: 16:f0e8768829ed
tag: tip
user: Joel Spolsky <joel@joelonsoftware.com>
date: Fri Feb 12 15:28:40 2010 -0500
summary: Added tag Version-1.1 for changeset e05c954f961f
C:\Users\joel\recipes-dev> hg pull
pulling from c:\Users\joel\recipes-stable
searching for changes
adding changesets
adding manifests
adding file changes
added 2 changesets with 2 changes to 2 files (+1 heads)
(run 'hg heads' to see heads, 'hg merge' to merge)
C:\Users\joel\recipes-dev> 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-dev> hg com -m "merge"
C:\Users\joel\recipes-dev> type guac
GUACAMOLE 2.0 THIS IS GOING TO BE AWESOME
* 200 ripe Hass avocados (not Haas)
* 1/2 red onion, minced (about 1/2 cup)
* 1-2 jalapeno chiles, stems and seeds removed, minced
* 2 tablespoons cilantro leaves, finely chopped
* 1 tablespoon of fresh lime or lemon juice
* 1 grain table salt, split in half
* A dash of freshly grated black pepper
* 1/2 ripe tomato, seeds and pulp removed, chopped
* 1 ripe young Mango, in season.
* 1 delicious, yellow BANANA.
Smoosh all ingredients together.
Serve with tortilla chips.
这就是我们的成果:
嗯, 如果你看懂了这张 令人抓狂 的图, 你离真正理解 Mercurial 为时不远了. 本例的要旨是, stable 版本库最终只包含 bugfixes, 而 dev 版本库包含了新的代码和合并入的 bugfixes.
多版本库的使用方式还有很多.
- 你可以建立一个团队版本库, 用于部分成员协作开发一个新特性. 当他们完成开发而且可以跑起来了, 你再把变更从团队库推送至中央开发库, 让其他成员都能得到该功能.
- 你可以为测试人员建立一个 QA 版本库. 开发人员把测试过的代码推送到 QA 版本库, 而不是中央库. 一旦测试人员核准, 便可从 QA 版本库推送至中央开发库. 这样中央库的代码都将是已测试过的代码.
- 由于每个开发人员有他们自己的本地库, 你可以从同事那儿直接获取试验性的变更集进行联调, 避免变更牵连团队的其他成员.
在大型, 复杂的组织内, 你可以组合以上技巧, 建立一堆的版本库, 然后相互 pull
. 随着每个特性逐级进行测试和集成, 新特性沿着下面的树型结构中被越送越高, 直到新的代码抵达中央发布库, 可以交付给客户:
小测验
下面的一些操作是学完本章教程后, 你应该要学会的:
- 给历史版本打上标签, 然后退回到此版本
- 用 “stable” 和 “dev” 版本库组织你的团队
嗯, 不知不觉我们的教程接近尾声了. 我尚未能 涉及 到 Mercurial 的方方面面, 但是有大量资料会帮助你更深入的去挖掘. 有 一本书 涵盖了 Mercurial 的所有功能和完整细节. 而且如果你有任何疑问, 我都毫不犹豫的邀请你访问 Kiln Knowledge Exchange (它和 StackOverflow 很像, 但只有 Kiln 和 Mercurial 相关问题才会受到关注和欢迎.)
(译注: Kiln 和 StackOverflow 都是 Joel Spolsky 所在公司的产品, Joel 趁势打了个广告 :) )