| 級(jí)別: 初級(jí) 運(yùn) 國蓮 (yunguol@cn.ibm.com), 軟件工程師, IBM李 振剛 (lizg@cn.ibm.com), 軟件工程師, IBM
 
 2008 年 9 月 04 日 是一個(gè)輕量級(jí)的分布式版本控制系統(tǒng),它以方便的控制、極強(qiáng)的擴(kuò)展性贏得了眾多開放源代碼項(xiàng)目的青睞。本文從版本控制系統(tǒng)中的基本概念、操作和擴(kuò)展性等方面,有側(cè)重的介紹了 Mercurial。此外本文列出了幾個(gè)其他常用的分布式版本控制系統(tǒng),并和 Mercurial 作了簡(jiǎn)單的比較。通過閱讀本文,讀者可以了解基礎(chǔ)的Mercurial操作,進(jìn)而熟悉這個(gè)備受青睞的工具。 Mercurial 簡(jiǎn)介 Mercurial 是一種輕量級(jí)分布式版本控制系統(tǒng),采用 Python 語言實(shí)現(xiàn),易于學(xué)習(xí)和使用,擴(kuò)展性強(qiáng)。其是基于 GNU General Public License (GPL) 授權(quán)的開源項(xiàng)目。相對(duì)于傳統(tǒng)的版本控制,具有如下優(yōu)點(diǎn): 
                            更輕松的管理。傳統(tǒng)的版本控制系統(tǒng)使用集中式的 repository,一些和 repository相關(guān)的管理就只能由管理員一個(gè)人進(jìn)行。由于采用了分布式的模型,Mercurial 中就沒有這樣的困擾,每個(gè)用戶管理自己的 repository,管理員只需協(xié)調(diào)同步這些repository。
                            更健壯的系統(tǒng)。分布式系統(tǒng)比集中式的單服務(wù)器系統(tǒng)更健壯,單服務(wù)器系統(tǒng)一旦服務(wù)器出現(xiàn)問題整個(gè)系統(tǒng)就不能運(yùn)行了,分布式系統(tǒng)通常不會(huì)因?yàn)橐粌蓚€(gè)節(jié)點(diǎn)而受到影響。
                            對(duì)網(wǎng)絡(luò)的依賴性更低。由于同步可以放在任意時(shí)刻進(jìn)行,Mercurial 甚至可以離線進(jìn)行管理,只需在有網(wǎng)絡(luò)連接時(shí)同步。  
 
 
 從 repository 開始 版本控制系統(tǒng)中的 repository 就像一個(gè)倉庫一樣,用來存儲(chǔ)被管理的數(shù)據(jù)文件,包含數(shù)據(jù)文件的不同版本。傳統(tǒng)的版本控制系統(tǒng)中,這樣的repository 是集中式的。除了這樣一個(gè)集中式的 repository 之外,每個(gè)用戶會(huì)有一份自己的工作版本拷貝。用戶通過命令同步自己的拷貝和集中式的repository。 分布式版本控制系統(tǒng)中的 repository 則采用的是對(duì)等網(wǎng)絡(luò)式的方式。傳統(tǒng)集中式的管理中,只有一份 repository,其他的只是工作拷貝,不包含額外的版本。分布式的管理當(dāng)中,每個(gè)用戶所持有的都是一個(gè)真實(shí)的 repository,當(dāng)中存儲(chǔ)有不同的版本信息和維護(hù)一個(gè) repository 的必要的輔助元數(shù)據(jù)。這樣一個(gè)對(duì)等工作模式當(dāng)中,用戶通過交換下文即將提到的 changeset 來完成同步。 這樣做的一些優(yōu)點(diǎn)在于,工作的并行度將大大的提高。每個(gè)用戶都可以帶著這樣的repository,從這里他可以把當(dāng)前的工作拷貝切換到 repository 里面存儲(chǔ)的任何一個(gè)版本。這個(gè)版本可以是之前正在工作的版本,現(xiàn)在需要合并進(jìn)一些別人的意見,也可以是用戶私有的一個(gè)版本,當(dāng)前正在做很多前瞻性的工作,還沒有能同步給其他用戶使用。也同樣是因?yàn)檫@樣的模式,每個(gè)用戶可以任意把自己的 repository 當(dāng)中的一個(gè)版本交換給其他用戶,而不需要對(duì)自己手頭正在工作的版本進(jìn)行回退。下圖是這樣一個(gè)靈活的工作模式的演示。 圖 1. 工作模式的演示
 
   
 
 
 
 Mercurial 里的元素 Revision 在使用 Mercurial 的系統(tǒng)中每個(gè)改動(dòng)隔離在各自的 repository 里,既避免把不相關(guān)的代碼混雜起來, 又便于一個(gè)接一個(gè)的測(cè)試每一部分工作,用戶做的每個(gè)改動(dòng)稱為一個(gè) revision。一般會(huì)有一個(gè)所有用戶都可以訪問得到的 repository 保存了項(xiàng)目的“主要”版本,工作repository 是用戶自己做事情的地方,實(shí)現(xiàn)新的特性,修改漏洞,重構(gòu),實(shí)驗(yàn)等,當(dāng)完成改變后,你可以 push 到共用的 repositor y中,即完成了一個(gè) revision。 Changeset 一個(gè)或多個(gè)文件的改變集合在一起形成一個(gè)邏輯單元,稱為 changeset。每一個(gè) changeset由兩部分內(nèi)容描述,版本號(hào)和 changeset 標(biāo)識(shí),例如: 
                            
                                
                                    | 	changeset:   207:58e4906e69e3
                                     |  
 冒號(hào)前面的數(shù)字代表版本號(hào),它用來標(biāo)識(shí)本地 changeset。這個(gè)版本號(hào)只有在用戶的本地repository 中才有意義。冒號(hào)后面的那個(gè)很長(zhǎng)的十六進(jìn)制串是 changeset標(biāo)識(shí), 它是確定changeset的全局唯一標(biāo)識(shí)符, 在所有包含這個(gè) changese 的 repository 中都相同。多個(gè)用戶之間討論changeset,一般使用這個(gè) changeset 標(biāo)識(shí),而不是上面說的版本號(hào),因?yàn)橥耆锌赡苊總€(gè)用戶的 repository 中同樣的 changeset 版本號(hào)不同。 Head Head 表示 repository 中每個(gè)分支最新的 revision,通常在合并幾個(gè)分支時(shí)會(huì)用到這個(gè)概念。 Tip Tip 是最新的一個(gè) changeset 的版本號(hào)的一個(gè)別名。在命令中任何使用版本號(hào)的地方都可以使用 tip 來代替最新的 changeset的版本號(hào)。Tip在各個(gè)repository中是不同的,同時(shí)一個(gè)repository 中只有一個(gè) tip。 Log Log 命令按時(shí)間順序從近到遠(yuǎn)的記錄著在 repository 中發(fā)生的每一次事件。可以通過指定-v診斷輸出選項(xiàng)來獲得更多更詳細(xì)的歷史信息,或者指定—debug選項(xiàng)來獲得歷史信息中的一切細(xì)節(jié)。 
 
 
 動(dòng)手操作起來 以下是一些實(shí)際使用 Mercurial 中常用的例子。Mercurial 的原意是元素周期表當(dāng)中的汞元素,但是 Mercurial 這樣的單詞顯然不太適合日常使用。事實(shí)上 Mercurial 的命令取了元素周期表當(dāng)中汞元素的化學(xué)符號(hào):hg,所有的 Mercurial 命令都以 hg 開始。 
 其中 command 是 Mercurial 的命令。每個(gè)命令的具體的命令行選項(xiàng)可以使用: 
 來獲得。 克隆一個(gè) repository 我們使用 clone 命令克隆一個(gè) repository,它生成一個(gè)完整的 repository 復(fù)本,這樣我們就有一個(gè)本地私有的 repository 來工作。 例如: 
                            
                                
                                    |      $ hg clone  http:///hg/project1
                                     |  
 如果所有都沒問題,clone 命令輸出: 
                            
                                
                                    |     destination directory: project1
                                    requesting all changes
                                    adding changesets
                                    adding manifests
                                    adding file changes
                                    added 127 changesets with 448 changes to 143 files
                                    139 files updated, 0 files merged, 0 files removed, 0 files 	unresolved
                                     |  
 此時(shí),我們應(yīng)該在當(dāng)前目錄下發(fā)現(xiàn)一個(gè)目錄叫 project1,目錄下的文件是我們剛克隆的 repository 的精確復(fù)本。在 Mercurial 中,每一個(gè) repository 是自包含的。當(dāng)你克隆一個(gè)repository 后,新 repository 變成克隆時(shí)它的精確復(fù)本,但是后續(xù)的兩個(gè) repository 當(dāng)中任一方改變都不會(huì)在對(duì)方顯示,除非用戶使用 pull 或 push 命令明確地傳遞改變,這個(gè)將在后面介紹。 另外,每個(gè)用戶可以使用 init 命令將本地的一個(gè)目錄初始化為一個(gè) Mercurial 的 repository,只需要在那個(gè)目錄下運(yùn)行: 
 如果設(shè)置好了同步共享的發(fā)布方式,其他用戶就可以來克隆該用戶的 repository 了。 發(fā)布你的改動(dòng) 進(jìn)入工作目錄,使用我們喜歡的編輯軟件修改,例如我們要修改 main.py 讓它增加打印一行輸出: 
                            
                                
                                    | def main():
                                    print "I'm in the a function."
                                    print "Great joy of using Mercurial!"  #新加的一行
                                    if __name__ == "__main__":
                                    sys.exit(main())
                                     |  
 完成之后退出編輯器,任務(wù)完成。有了剛才的修改我們就可以創(chuàng)建一個(gè)changeset。 在創(chuàng)建 changeset 之前如果想確認(rèn)一下哪些文件被改動(dòng)了,可以使用 status 命令。 
 使用 diff 命令可以檢查實(shí)際文件內(nèi)容的改變: 
                            
                                
                                    | diff -r a58db6f0e482 main.py
                                    --- a/main.py   Thu Nov 29 13:38:38 2007 +0800
                                    +++ b/main.py   Thu Nov 29 13:46:10 2007 +0800
                                    @@ -1,5 +1,6 @@ def main():
                                    def main():
                                    print "I'm in the a function."
                                    +    print "Great joy of using Mercurial!" #新加的一行
                                    if __name__ == "__main__":
                                    sys.exit(main())
                                     |  
 diff 命令的默認(rèn)輸出是通用的補(bǔ)丁格式,易于在各種系統(tǒng)之間交換和討論。 創(chuàng)建一個(gè) changeset 后我們就可以用 commit 命令提交了。 
 這個(gè)命令把我們帶到一個(gè)編輯器內(nèi),缺省的編輯器是 vi,同時(shí)給我們展示了幾行如下的文字: 
                            
                                
                                    | HG: user: Guolian Yun <yunguol@cn.ibm.com>
                                    HG: changed main.py
                                     |  
 第一行是空的,接下來的幾行表明哪些文件將進(jìn)入本 changeset。為了改變 changeset,我們必須描述它的原因,這通常稱為 changeset注釋。讓我們輸入一些: 
                            
                                
                                    | I’m using Mercurial!
                                    HG: user: Guolian Yun <yunguol@cn.ibm.com>
                                    HG: changed main.py
                                     |  
 接著,保存并退出編輯器,如果一切正常,commit 命令將沒有任何提示地輸出。 讓我們看看status命令現(xiàn)在告訴我們什么?  
 什么也沒有!我們的改動(dòng)已經(jīng)提交到changeset里了,那里沒有修改的文件需要提交的。Repository 中內(nèi)容和當(dāng)前工作目錄的內(nèi)容一致了。 現(xiàn)在可以檢查以下最新的改動(dòng)是不是包含剛才所添加的 changeset 注釋,使用 tip 命令就可以顯示了: 
                            
                                
                                    | $ hg tip:
                                    changeset:   2:2874393e3d9c
                                    tag:         tip
                                    user:        Guolian Yun <yunguol@cn.ibm.com>
                                    date:        Thu Nov 29 10:10:39 2007 +0800
                                    summary:     I'm using Mercurial!
                                     |  
 目前新的 changeset 只存在本地 repository 中,如果想和其它 repository 分享改動(dòng),我們需要使用 push。 
 project2 為你想要 push 的目標(biāo) repository 的名字。 引入他人的改動(dòng) 想要得到所有在別的 repository中而在本地repository中沒有的改動(dòng),可以采用 pull命令。 
 project3為我們想要得到更新的目標(biāo)repository的名字。 在 Pull 后,缺省情況下Mercurial不更新工作目錄。這意味著雖然repository現(xiàn)在有changeset,但在工作目錄中的 main.py 文件仍然是 pull 之前老的內(nèi)容。 如果只想從hg clone的 repository 中更新當(dāng)前 revision 到最新版,可以直接采用: 
 Repository 之間的同步 上文當(dāng)中的 push 和 pull 的操作,是處于不同位置的 repository 之間的同步。之前給出的兩個(gè)例子是本地目錄 repository 之間的同步。Mercurial 還支持以下形式的 repository 之間的同步: 
                            
                                
                                    | file://local/filesystem/path
                                    http://[user@]host[:port]/[path]
                                    https://[user@]host[:port]/[path]
                                    ssh://[user@]host[:port]/[path]
                                     |  
 其中 file 協(xié)議和本地目錄相同。在 http 和 https 協(xié)議上使用push命令,需要在遠(yuǎn)端的服務(wù)器上啟用相關(guān)的屬性。ssh協(xié)議是眾多系統(tǒng)中支持的shell。 標(biāo)準(zhǔn)的 Mercurial 發(fā)行包中還附帶一個(gè) Python CGI 腳本 hgweb.cgi, 可以用來參考搭建一個(gè)多用戶可以集中式的同步改動(dòng)的界面,如下圖所示: 圖 2. 界面
 
   
 
 
 
 擴(kuò)展 Mercurial Mercurial 系統(tǒng)中提供一種擴(kuò)展機(jī)制來添加新的命令。通過擴(kuò)展添加的命令可以在現(xiàn)有的Mercurial 系統(tǒng)的基礎(chǔ)上添加新的功能,這些命令跟隨 hg 后被調(diào)用時(shí)就像原生的命令一樣。本文介紹兩個(gè)常用的Mercurial擴(kuò)展:Patchbomb和Mq。 Patchbomb Patchbomb是一個(gè)在Mercurial系統(tǒng)中利用發(fā)送郵件的方式來交換changeset的擴(kuò)展。Patchbomb添加了一個(gè)新的email命令。通過調(diào)用hg email 命令,changeset 提交時(shí)的信息的第一行將作為郵件的主題,信件的正文包括完整的 changese t提交信息,以 patch 的形式發(fā)布出來的 changeset 完整補(bǔ)丁。如果一次發(fā)送的是多個(gè)changeset,那么Patchbomb會(huì)提示輸入本次 changeset 集的總提示信息,這部分信息將作為第一封信,信件主題以[PATCH 0 of N]開頭,changeset 則會(huì)以[PATCH i of N]將的主題開頭發(fā)出,其中i是 changeset在本地 repository 當(dāng)中的順序。多changeset 系列郵件中,每封信會(huì)在郵件頭中包含合適的回復(fù)信息,這樣在郵件客戶端可以清晰的顯示出系列 changeset 之間的層次關(guān)系。 圖 3. 層次關(guān)系
 
   
 Patchbomb 支持使用本地系統(tǒng)中的 sendmail 程序來發(fā)送郵件,同時(shí)支持使用 SMTP 郵件服務(wù)器。用戶如果長(zhǎng)期固定為某個(gè)項(xiàng)目工作,還可以將郵件的收件地址和發(fā)信地址提供給Patchbomb,免去每次手動(dòng)輸入的麻煩。這一切都可以在 Mercurial 統(tǒng)一的 .hgrc 當(dāng)中設(shè)置,以下是一個(gè)完整的例子。 
                            
                                
                                    | [extensions]
                                    hgext.patchbomb =
                                    [email]
                                    method = smtp # 還可以在這里指定/usr/sbin/sendmail
                                    from = Zhengang Li <lizg@cn.ibm.com>
                                    to = groupmail@
                                    [smtp]
                                    host = smtp.
                                     |  
 Patchbomb 作者為Bryan O’Sullivan,該擴(kuò)展現(xiàn)在隨同 Mercurial 系統(tǒng)一起發(fā)布,用戶不需額外下載安裝,只需如上例中一樣啟用即可。 Mq Mq 擴(kuò)展的全名是Mercurial Queues,顧名思義該擴(kuò)展將用戶本地的多個(gè) changeset 排列到隊(duì)列中。原先分布式版本控制系統(tǒng)中,changeset 一旦提交并不能修改。有了 Mq 擴(kuò)展之后,用戶可以將本地的任意數(shù)量的 changeset 存放至一個(gè)本地的隊(duì)列當(dāng)中,對(duì)這些 changeset 用戶除了可以使用傳統(tǒng)的 changeset 上的任何命令之外,還可以修改changeset,包括提交信息和版本補(bǔ)丁的改動(dòng)。 啟用 Mq 擴(kuò)展的辦法同其他擴(kuò)展一樣,在 .hgrc 當(dāng)中添加如下信息: 
 Mq的命令是一系列以字母q打頭的命令,qinit,qnew,qrefresh,qdiff,qpop和qpush等。Qinit 用來初始化用來存放補(bǔ)丁隊(duì)列的目錄,qnew創(chuàng)建一個(gè)新的補(bǔ)丁changeset,qrefresh 將改動(dòng)刷新到當(dāng)前的補(bǔ)丁當(dāng)中去,qdiff 將當(dāng)前的補(bǔ)丁打印到屏幕,qpop 和qpush 用來移動(dòng)當(dāng)前存放在隊(duì)列頂部的補(bǔ)丁。完整的 q 系列命令可以從hg help給出的列表中獲得。 Mq中所有的 changeset 補(bǔ)丁存放在項(xiàng)目頂層目錄的.hg/patches下面,用戶可以手動(dòng)修改這些補(bǔ)丁當(dāng)中的提交信息。Changeset 補(bǔ)丁的順序存放在.hg/patches/series文件當(dāng)中,同樣的,用戶可以修改這些補(bǔ)丁的順序。 Mq的作者是Chris Mason,該擴(kuò)展現(xiàn)在隨同 Mercurial 系統(tǒng)一起發(fā)布,用戶不需額外下載安裝。 如果現(xiàn)有的擴(kuò)展不能滿足用戶的要求,編寫自己的擴(kuò)展也不困難。Mercurial 使用 Python編寫,編寫一個(gè)新的擴(kuò)展相當(dāng)于在 Mercurial 系統(tǒng)的 hgext 包當(dāng)中編寫一個(gè)新的模塊。具體的擴(kuò)展實(shí)現(xiàn)還有些約定的規(guī)則,用戶可以參考 Mercurial 所提供的文檔。 
 
 
 其他分布式版本控制系統(tǒng) 分布式版本控制系統(tǒng)領(lǐng)域還有一些其他的系統(tǒng),如GNU arch,monotone,Bazaar,git,darcs。 各類系統(tǒng)在各在一定的領(lǐng)域內(nèi)長(zhǎng)處,如GNU arch在GNU Savannah主機(jī)上應(yīng)用,Bazaar 主要用于 Ubuntu Linux 系統(tǒng)的開發(fā)當(dāng)中,git 源于Linux kernel 的開發(fā),現(xiàn)在在多處和內(nèi)核相關(guān)的項(xiàng)目中使用。他們大多數(shù)提供友好的Web界面和多種版本同步協(xié)議的支持。Git 和Gnu arch 由 C 和 shell 腳本語言編寫實(shí)現(xiàn),monotone 由C++語言實(shí)現(xiàn),darcs 由 Haskell語言實(shí)現(xiàn),Bazaar 和本文介紹的 Mercurial 由 Python 語言實(shí)現(xiàn)。從開放和擴(kuò)展性方面來說,類似 Python 這樣的腳本語言的更易于用戶編寫自己的擴(kuò)展。 在眾多的分布式版本控制系統(tǒng)中,Mercurial 是最年輕的,它的第一個(gè)版本發(fā)布于2005年4月。Mercurial 吸收了眾多前輩的特性,被眾多的項(xiàng)目采用。 
 |