(本文寫作於 2014 年 7 月,所有相關論述均以此時間點為準)
Mercurial 是一款分散式的版本管理軟體。
所謂版本管理軟體,是一種可在程式開發過程中,有規律地保留程式碼的歷史訊息、讓人能放心地做各種開發實驗,並在開發不幸走進死胡同時,將程式碼回復到舊有版本的系統……
細節很複雜,一言以蔽之,就是一種程式碼管理器。詳細說明網路上可以找到很多,我就不在此囉唆贅述了。
Mercurial 經常被拿來和另一款同類軟體 Git 比較,然而不知是故意貶低或缺乏了解,大部份能在網路上讀到的中文文章,都傾向於認為 Mercurial 比 Git 弱小、彈性差、功能低落,甚至只是個「教學用軟體」。但隨著我同時跨足使用這兩套系統後,我發現實況卻非如此--甚至大部份時候都是反過來的。
Mercurial 沒能得到它該有的評價,這十分可惜。是故我決定撰文將輿論平衡一下,以餉讀者。
原則上,凡本文提到的特性,無論是 Git 或 Mercurial ,咱都有親自實驗過,各位大致上能夠安心閱讀。
一如題目所言,這篇文章是兩者的比較文,本文將以版本管理使用者的角度來說明,為什麼 Mercurial 比 Git 更好?
做人要實在,優點等會兒再說,先從 Mercurial 的弱點開始說起好了。
Mercurial 現在主要的弱項在於跨平台時,處理非英文檔名可能會有編碼問題。
稍微強調一下:
總地來說,即使您在源碼庫中用了中文檔名,只要您的開發平台都是 utf8 的,就不需要擔心這件事。
相關說明請見:http://mercurial.selenic.com/wiki/EncodingStrategy#Overview
如今用 Git 的人比較多(特別是在中文圈中),對於開發者來說,和他人合作時碰到 Git 的機會比較大。
不過話說回來,Mercurial 的社群也絕不算小。您可以在網路上找到大批用 Mercurial 維護的專案,從 OpenJDK、Python、Go、Nginx、Vim 等多不勝數。其中 Nginx 是全球市佔率超過 20% 的網站後端,平均您每看五個網站就有一個是用 Nginx 跑起來的,而如果只算前 10000 個受歡迎的網站,這個比例甚至可以上升到 40 %;而 Vim 就更不用提了,他甚至永久影響了全世界程式設計師的文化體系。
在 http://stackoverflow.com 中,我們能查到幾乎所有能想像得到的,關於 Mercurial 的討論與情報。總之社群支持度無需擔心。
受限於 Mercurial 早期的資料結構限制,如果您將檔案重命名,或僅僅只是變更檔案放在原碼庫中的路徑,則原碼庫中會產生一份額外的副本,消耗兩倍的硬碟空間。
這蠢死了。聽說 Git 就沒有這種問題(儘管我沒試)。
這個缺陷讓人心情不爽,不過話又說回來,如果您沒在倉庫中堆滿一堆巨大的二進位檔案,又三天兩頭搬動它,也不會造成什麼很嚴重的問題。
儘管有以上這些瑕疵,Mercurial 仍然是一款棒呆了的版本管理系統,因為 Mercurial 有著足以抵消上述一切缺陷的眾多性感特徵。我馬上就會和各位說明。
本文主要的比較對象是 Git,這篇文章才剛開始呢。
1.1. 分支分離
請看以下分支圖:
用戶下命令「請分別列出 Branch 1 與 Branch 2 中含有的提交」時……
Mercurial 的回答是……
Git 的回答是……
毫無疑問 Mercurial 的答案才是您想要的。
(請想像一下 branch 1 是穩定分支,而 Branch 2 是某功能分支的狀況)
這不是 Git 用戶下錯指令,事實上,Git 從資料結構層面就沒辦法紀錄 Mercurial 在上述問題中所能提供的訊息。GIT 就是做不到。(註一)
1.2. 保留但隱藏分支
Mercurial 可以「關閉分支」,但 Git 則否。
在 Mercurial 中,被關閉的分支預設不會出現在分支列表裡,因此 Mercurial 可以在不砍任何分支的前提下,確保分支列表清爽好管理。此外,因為分支資料依然存在,所有分支都能在必要時刻被完整召回。
反之,Git 不能將分支封存起來。要嘛砍掉,要嘛任由分支曝露在外。想讓兩者並行,它就是做不到。
換個方式問,如果不去刪分支呢--用戶能處理一個擁有一千個分支項目的列表嗎?嗯,馮紐曼大概可以吧,反正我不行就是了。當 Git 用戶得意地說他們的分支很廉價可以隨創隨刪時,他們同時也是在說,Git 絕大部分的分支無法被保存下去。
不管用哪種分支模型進行開發,我們總有些時候想保留不再使用的分支--有時甚至想保留住大部分的分支資訊,然而 Git 並沒有給我們這種選擇權。為了管理,我們非砍它不可。(註二)
1.3. 毫不遜於 Git 的輕量性分支
有些用戶誤以為 Mercurial 的分支比較笨重遲鈍,而 Git 的分支比較輕,但事實上絕非如此。
Mercurial 的分支 (branch),與 Git 中的 branch 不同,不是將標籤紀錄在頭部,而是在「每個提交」中紀錄一個「這個提交屬於哪個 branch」的訊息標籤,每次提交時因此額外消耗的容量充其量就幾十 bytes 而已,這顯然不會對速度與硬碟容量造成任何負面影響。
反過來說,這種作法帶來的直接好處,您可以在前文「分支分離」段落中看到。
此外,Mercurial 也支援 Git 式的分支模型--也就是將標籤記在單一變更集上,並隨提交而自動往前推進的方式。這種類似 Git 運作方式的分支,在 Mercurial 中被稱為 bookmark。
如果用戶希望,Mercurial 也可以以 Git 用戶慣用的方式,單獨運用 bookmark 分支,甚至混用 branch 與 bookmark 兩種分支方式--比方說,branch 產生的分支可用在大家共享的大工程上,而 bookmark 類型的分支標記則留在本機供自己使用(bookmark 同樣可以推送給其他人,但預設不推送,這和 Git 一樣)。同時使用這兩者不會產生絲毫衝突,而不甩其中一方,單用其中一種分支方法也毫無問題,這僅僅只是不同專案的分支習慣不同而已。
反過來看, Git 則無法以 Mercurial 的方式進行分支,它從根本上就沒辦法。
1.4. Mercurial 的分支不能刪
有人宣稱 Mercurial 的分支 (branch) 不能刪除,這是它用起來得小心翼翼,感覺很「重」的原因。
嗯,這點倒是沒錯,Mercurial 的 branch 確實是不能刪的。但這有什麼不好嗎?對於 Mercurial 而言,分支 (branch) 就和變更集本身一樣,是版本庫中不可分割的重要訊息。Mercurial 的分支訊息是版本庫的一部份,而不是像 Git 那樣僅僅把它當成某種「外加」的東西--Git 的分支訊息與歷史無關,隨時想插就插想拔就拔,刪掉找不回來也無所謂。
認真對待 branch 歷史是 Mercurial 的核心設計邏輯,這和 Git 完全不同。
有時候,這種設計邏輯無法滿足用戶對臨時分支的要求,但別擔心,Mercurial 也允許例外。
如果您想在 Mercurial 中隨手建立一些幾天後就不再需要的暫時性分支,或您就是不想永久錄下分支資訊,您應該要用的是 Bookmark,若用分支 (branch) 就是用錯了工具。因為很重要所以再重複一次:如果您覺得某些分支訊息不重要(或僅僅在某些時候覺得不重要),沒那個意思將分支訊息永久保存下來的話,您應該要用 bookmark,而不是 branch。
只是,我建議您再多考慮一下用 branch 永久保有分支資訊的好處。雖然您不能刪它,但就算您搞出了 10000 個分支那又怎樣?它又不會咬你。
1.5. 匿名的超輕量分支
受益於強大的變更集定位能力(見後),Mercurial 中還有比 bookmark 分支更輕量的分支方式存在--也就是「匿名分支」。
▲當 A, B, C, D 提交路線已經完成後,退回 B,再進行一次提交,就會自動產生一個 E 分支。
如果用 Mercurial 進行以上操作,技術上來說 ABCDE 都會處於同一個 branch(指 Mercurial 中的 branch,見前文說明)中,換句話說一個 branch 中可能有兩個以上的「頭部 (此例中為 D 與 E)」。其中變更集 E 是最後加入倉庫的,所以切換到這個 Mercurial branch 時,預設會切到 E 版上。這時另一邊的 D 就被視為一個沒有標記的「匿名分支」。
您可以把 E 重新合併回 D,或是乾脆就這樣把 CD 變更集放棄繞過去,改為沿著 E 路徑繼續開發。這不需要額外處理,只要修改想修改的地方後直接提交就行,非常輕鬆。
與 Git 相比,Git 也能如上這樣僅靠提交產生新的頭部,但用戶幾乎無法追蹤它(除非記住 hash 值),一旦沒取名又跳離了它,很難再把它找回來,不但沒有實用性反而更像是一種操作錯誤的結果,如果將 Git 切到這種沒取名的變更集上工作還會給您跳警告。但在 Mercurial 中,因為 revset(見後)的強大定位能力,不取名也無妨,使用起來非常容易。儘管因為匿名分支沒有語義標記而不適合推到遠端與他人共享,但在本地臨時修些小 bug 時依然相當好用。(註三)
註:有些 Git 用戶認為 Mercurial 的匿名分支是個危險的蠢主意,因為在 Git 用戶的觀念中這種無名分支難以追蹤的緣故。不過對 Mercurial 來說,追蹤匿名分支易如反掌,完全不是問題。
2.1. 定位變更集
Mercurial 中有一系列定位變更集的作法。
2.1.1. 數字序列
Mercurial 可以用序列數字,如 1、3、15 來指定某個特定的提交,而不用非打 hash code 不可。
舉例來說……
Mercurial:
hg update 133
Git:
git checkout 4a9cb402d55357b534ef28f74b85ca7bb16c87ed
前者遠比後者簡單,也容易識別得多,至少用戶光看數字就能認知到不同變更集之間的先後關係,以及兩個變更集的距離大致有多遠,這在實際操作時直覺又實用。
另一方面,因為在實際使用中 hash code 實在很難處理,這也意味了 Git 用戶通常是以分支為粒度進行各種操作,而 Mercurial 用戶則可以以每次提交為粒度進行操作--就和喝水吃飯一樣容易。
2.1.2. Revset
Mercurial 可以用一種稱為 revset 的語句,來幫助使用者定位「一個或一系列」的變更集。這種語句可以清晰地對大量變更集進行操作。
以下是些官網提供的例子……
hg log -r "branch(default)"
列出 default 分支中的所有變更集。
hg log -r "branch(default) and 1.5:: and not merge()"
列出 default 分支中,繼承 1.5 版變更集的後續所有變更集,但 merge 除外。
hg log -r "head() and not closed()"
列出所有未被關閉的分支末端。
hg log -r "1.3::1.5 and keyword(bug) and file('hgext/*')"
列出 1.3 到 1.5 兩個變更集之間,(在註解或用戶名等處)含有關鍵字 “bug”,且有更動過 “hgext” 資料夾下內容的變更集。
hg log -r "sort(date('May 2008'), user)"
列出 2008 年 5 月提交的所有變更集。列出時,以提交者排序。
hg log -r "(keyword(bug) or keyword(issue)) and not ancestors(tag())"
列出涉及關鍵字 “bug” 或 “issue” 的提交,且這些版本必須尚未被包含在任一個被 tag 標記過的版本中。
感到振奮嗎?這是當然的!從這些例子中您可看出它的無窮威力。這是 Git 難以望其項背的,Git 根本沒法做到這些,更別說如此清晰且語義化地去達成它。正因為缺乏這種優雅的版本定位能力,Git 雖然能將資料存在版本倉庫中,但卻無法輕易拿出使用。這是 Mercurial 的巨大優勢。
順便一提,revset 還可以設定 alias,您可以將複雜的長串運算整理成一個簡單的--甚至可以加入變數的函式版本,然後隨意運用之。
revset 詳細語法說明請見:
2.2. 清晰的指令介面
Mercurial 的指令介面遠比 Git 更容易使用。
2.2.1. 功能切割
Mercurial 與 Git 的指令設計邏輯不同。
Mercurial 每個指令專注於較少的事情,並提供更多指令供我們使用,而 Git 則更傾向於用一個大指令,將一堆種類完全不同的操作框在一起,然後透過一長串參數來操控它。
網路上有人這樣形容:「Git 提供一柄瑞士刀,而 Mercurial 提供一間裝備齊全的廚房。」
可以想見,Git 的指令設計理念,使得它每個指令文件都非常地難讀難查找,更別說因為功能混在一起,很多時候就連要查哪個指令文件都不知道。初學者哪能知道 git checkout 既可以切換分支,又能創建分支,還能復原工作目錄中檔案的舊版本……許多全然不同的操作全混在一起了。
請務必觀察看看 git help checkout (接近 400 行) 與 hg help update + hg help revert + hg help branch (合計 110 行) 的壓倒性差異。缺乏容易理解的介面與文件,讓用戶在使用 Git 時很容易被咬--甚至也更容易因此讓資料在操作錯誤下意外損失掉!
2.2.2. 預設提供簡打
Mercurial 所有指令都有預設提供簡打,如:
Git 也能由用戶自行設定簡打。但它就是沒有內建。換個角度看,Mercurial 也能由用戶自行設定自己專屬的簡打,簡打擴展性方面並不輸給 Git。
雖然簡打稱不上多重要。但每天都要與之打交道的工具,一點方便就會讓人舒服許多。Mercurial 毫不吝嗇地提供了這種方便--而且沒有因此降低任何指令的可識別性。更別說您甚至可以在別人的機器上使用這些簡打指令,用簡打和他人溝通也沒問題。
2.3. Mercurial 有防呆
Mercurial 預設不允許使用者變更歷史紀錄。這意味著 Mercurial 的初學者,使用 Mercurial 時幾乎不可能失手摧毀自己或他人的倉庫。
(但如果您是進階使用者,想要取得更多的控制能力,只需要打開某些擴充套件就能這麼作。見後。)
初學者不用擔心摧毀版本庫,使本來就很好上手的 Mercurial 變得更友善。一旦我們能放膽去玩去嘗試它,自然也能更快熟悉這整套系統--就算某個指令不懂,直接試用也不怕。反過來看,Git 某個指令不懂,直接試用看看通常表示妳要惹上大麻煩了。使用 Git 時,因為 reset 或 rebase 把東西玩壞的經驗,差不多每個人都會遇上個那麼幾次。
2.4. Mercurial 便於共享
與 Git 相比,Mercurial 的設計哲學使它更適合與他人協同工作,分享工作成果。
比方說 push 的預設處理範圍就不同……
這意味著即使是 Mercurial 初學者,也能自然而然地使本地端與遠端資料正確同步,而不會因為分支未同步或沒有正確設定關聯,而搞出現些莫名其妙的蠢問題。
本地端的所有分支資料會很自然地備份到遠端,這讓使用者不容易因本地問題而遺失資料之餘,也方便和其他開發者共享開發狀況。
此外,Mercurial 分支避免引入命名空間的概念,故從根本上就減少了誤解;也沒有像 Git 那樣引入「遠端分支名稱可能與本地分支名稱不同」等不知何時才能派上用場的無謂複雜度,無需擔心推拉時打錯字等問題,這都是 Mercurial 比 Git 更便於共享的優點。(註四)
注意,push 的處理範圍只是預設。無論 Mercurial 與 Git,進階用戶都可以變更這些預設行為。
另外,雖然 Mercurial 預設讓用戶共享所有變更集,但如果用戶有進行某些本地實驗的需要,Mercurial 也可以簡單將某變更集(以及由他長出的後續變更集)設為不會被推送出去的「秘密開發路徑」,這些秘密的開發路徑不但不會被推送,也不會被其他人拉取或 clone 走,私下實驗時相當方便。這方面請參閱 Mercurial 的 Phases 概念。
以下是一些常常能在國內外討論中看到的,關於 Mercurial 的誤解。我將其整理了一下。
3.1. Mercurial 並未缺少功能
有人認為 Mercurial 相比 Git 的功能較少,對版本庫的控制能力較低,這是誤解。Mercurial 只是沒有將所有功能都整合進程式核心而已。
Mercurial 的設計邏輯,在於提供一個簡單強固的核心,與優秀的擴充介面。在 Mercurial 中,更多更強更危險的功能,以及某些正在開發的實驗性功能,都是由擴充套件來提供的。
請將官方發佈的擴充套件視為 Mercurial 的一部份,您就不會覺得 Mercurial 功能不足了--不管是 rebase、strip、stage、合併多個變更集等等--沒啥是 Git 能做而 Mercurial 做不到的。
比方說 Git 經常提及的 Stage 概念,在 Mercurial 中就是由 mq 擴充套件提供的。在 mq 的支持下,您不只像 Git 那樣可以暫存一個未完成的變更集,只要您想,您甚至可以暫存整串變更序列;更進一步地說,您不只可以 stage 一整串變更序列,您還可以分頭建立「好幾串」不同的序列,並行開發。再更進一步,如果您想,您甚至可以替您的暫存區做版本管理,甚至將暫存區版本庫推送到遠端……反過來說,如果您不需要 stage 的概念(我相信大部份的人都不需要),您也不會像 Git 那樣在提交時,三天兩頭因為忘記 stage (-a) 而得到「您啥也沒提交」的惱人訊息。
啟用擴充套件也非常輕鬆,畢竟重要的擴充套件都是由官方製作並直接合併在 Release 中發佈的。雖說名字叫作「擴充套件」,卻無需大費周章抓取安裝,取而代之,只要在設定檔中多加一行就能立刻啟用。
與 Mercurial 核心一併發佈的擴充套件,其品質都有著徹底的保證,其功能變更甚至會一併寫入 Mercurial 的 Release Changelog 中,強固性、相容性與被官方重視的程度,都是絲毫不遜於核心功能的。
3.2. Mercurial 一點也不慢
許多人認為 Mercurial 相比 Git 的問題在於它速度很慢,這是誤解。
雖然 Git 是用 C 寫的,Mercurial 是用 Python 寫的,看似會有速度差異,但事實上版本管理系統的速度瓶頸在於網路與 I/O,而 CPU 運算速度則影響不大。實際運用時我也從沒感覺 Mercurial 速度慢過 Git,更別說許多世界上最大型的專案都用他。 Firefox 與 Facebook 這些專案都是用 Mercurial 在管理他們的程式碼,我們的程式會比他們更龐大複雜嗎?
至少絕大多數狀況下,速度實在不是值得擔心的重點來著。
註:本節假設 Git 確實比 Mercurial 快,但事實上在 Facebook 近期的幫助下,如今的 Mercurial 在某些條件下甚至有 Git 的數倍快,雙方的速度差距正在縮小甚至逆轉。
另外還有一部份操作,原本就是 Mercurial 壓倒性的快。比方說在整個版本庫中搜尋特定字串時,hg grep --all “TEXT” 遠比 git grep “TEXT” $(git rev-list --all) 快,有時速度可差到 100 倍以上。(在一個約 1600 次提交,大小 300 M 的倉庫中,以上指令 Mercurial 需時 40 秒,Git 需時 45 分鐘)
3.3. 進行危險操作時的安全性
有人提到,Git 在進行如 rebase 這種破壞性操作時,其實是在版本庫中新增一個新的節點,並保留未來可用來回退這次破壞性操作的訊息;這些訊息會成為版本庫的一部份,因此用戶可以美妙地回退危險行為。
而另一方面, Mercurial 在處理破壞性操作時,只會自動匯出一個單獨的 bundle 檔案作為備份,而不是加在版本庫中,這使得回退很難被管理。
技術上來說,他們說得沒錯:Git 確實是把破壞性操作的復原訊息保留在版本庫的 reflog 中,而 Mercurial 則是將被刪除的資料匯出到版本庫之外。不過事實上,對那些東西進行操作的難度,卻完全顛倒了過來。
就從 Mercurial 開始吧。在 Mercurial 中,您要恢復一些被破壞性操作(如 rebase)毀滅的紀錄,只需要下這樣的指令:
hg strip rebase_result_changeset # 先刪掉現有的 rebase 成果
hg unbundle bundle.hg # 將原本的分支取回來
就行了。
在實際取回分支前,您甚至可以先看看您究竟會匯入些什麼:
hg incoming --graph bunble.hg # 加上 --graph 後可用線圖顯示
另一方面,Git 要怎麼做?這邊有一篇教學可以參考……總而言之您需要施展一點 shell 魔法。
雖然原作者堅持這很簡單,事實上也確實不算太難(好吧,我是說對一個有能力編寫 shell 腳本的程式設計師而言,另外這個程式設計師還得對 Git 檔案系統與維護方式有著一定程度的理解,再者他還要有寫 unittest)……但是,和 Mercurial 的版本相比那又如何?
另外,基於容量與效率考量,Git 的復原紀錄會被 Git 不定時刪除(!)。至於 Mercurial 的復原記錄則只是一個普通的檔案,用戶想留就留,想扔就扔,想改名存到別的地方那就儘管去做,一切都看自己,不會有意外狀況發生。
Git 不會比 Mercurial 更安全。
Mercurial 的開發十分穩定有節奏。
它每月一號都會推出一個 bugfix 版本,每隔三個月會推出一個包含新特徵的版本,除了 hotfix 以外,兩年下來累計的誤差連十天都沒有。
除了穩定地修 bug、將操作介面改得更好用以外,它也持續在發展各種各樣新特徵。
【1.9 版】fileset:讓 Mercurial 用戶能輕鬆地指定檔案。
例→
hg locate "set: binary() and modified() and not **.png"
列出所有 png 以外,於目前工作目錄中被變更過的二進位檔案
【2.1 版】phase:使 rebase 這種重寫歷史紀錄的行為能夠被管理。
……當然還有更多!
我有提到 Mercurial 最近正在搞 Bid Merge 嗎?這將讓 Mercurial 能自動處理極為複雜的合併問題。(已經實驗性引入 3.0 版中)
此外還有將「更動歷史」這件事也視為歷史之一部份--讓 rebase 與 strip 事件能正確被 push pull 的 Evolve 組件。本組件目前仍以擴充套件型式單獨存在,但幾乎所有 Mercurial 核心指令與外掛都在提供它所需要的資料了。這個專案持續發展了超過兩年,在本文撰寫的這個時間點,版本號已到達了 4.0.1 版,且依然在高速前進中。
Mercurial 的開發團隊也持續從 Git 最棒的特徵中汲取養份來改善自己。Bookmark 是如此, 用 XXX~1 這種優美語法來定位變更集相對位置也是。Mercurial 甚至還引入了 Git 的 diff 格式,只因為它真的更優秀。
總而言之,Mercurial 的進步鮮明,態度積極,其未來的發展與維護,是可靠而值得我們期待的。
Mercurial 倉庫不能 Hosting 在 GitHub 上?沒有關係!我們有 BitBucket!
BitBucket 可以幫用戶代管/發佈任意數量的 Mercurial 專案到網路上,無論是私密或開源專案都沒有問題,其網站介面與功能都有持續維護翻新,穩定度、流量、運行速度均有第一線水平。截至本文撰寫為止,最近一次大的介面改進發生在 2014 年 5 月底,在本來就很優秀的架構上,它正不停地變得更先進。Mercurial 用戶不好好利用實在是太可惜了。
對了,BitBucket 也能代管 Git 倉庫。就算您不喜歡 Mercurial 也大可試試。
Mercurial 內建零配置的網路伺服器,用戶只要在倉庫根目錄處打上:
hg serve
就可以立刻把本地伺服器建立起來。
這個伺服器除了可供他人 push pull 以外,還能讓我們非常方便地透過瀏覽器查找各種資料。舉例如下:
▴ 某檔案的更動是由哪幾次提交造成的
▴ 提交產生的樹狀圖,版本庫結構一目了然。
▴ 顯示誰修改了哪個檔案中的哪一行,追打他人時輕而易舉。
▴ 水平比較檔案的兩個版本,比傳統的 diff 更容易讀。
▴ branch 列表,灰掉的是被關閉的分支。
如此方便的工具不好好利用實在太可惜了。Mercurial 的本地伺服器還支援模版,內建甚至有和 git 網頁介面完全一樣的模版,如果您比較喜歡 Git 的網頁介面也可以選用。
Mercurial 具有圖形化且完全開源的工具 TortoiseHG,在網路上一直有著不錯的評價。Mercurial 官網甚至將自身的更新日期與 TortoiseHG 的更新日期並列在一起,可見其地位。
不過我個人倒是沒有使用 TortoiseHG 的日常習慣,這方面就不便多做說明,就請有興趣的朋友自行試玩囉。
▲TortoiseHG 的截圖,左側與上方標籤頁可同時追蹤好幾個倉庫,至於右側有著完善的線圖與提交資料。程式是用 PyQt4 寫的,相當具有現代感,而且全中文翻譯也需要讚一下。就算不用來提交更動,只用來檢視版本庫結構也不錯。
前面的話題中有部份瑣碎的細節不好講,故補充在此。各位可斟酌跳過。
如果您願意加上一堆但書,Git 也不是完全做不到,前題是您得紮實地用 --no-ff 與 rebase 小心整理線圖、保留 Git 分支不把它刪除、並想辦法計算 merge 與 branch 的分岔點合併點……又或是固定在提交紀錄中寫入當時的分支名稱,而且透過人工保證這名稱不會和其他用過的名稱重複。而且--這是最難的--您還得確保您的所有協作伙伴都有確實這麼做。
但無論如何,這是人的苦勞,而不是工具的功勞。要說 Git 能辦到這個實在相當勉強。
另外補充一下,git 的 reflog 也可以在某種程度上保留分支資訊,不過有以下問題:
總之,Git 就是沒有一個簡單的方法讓人找回「約 250 次提交前,為了開發 X 功能而在某分支中進行的 N 個提交」。Git 用戶只能靠自己的腦子與嚴謹的紀律--而非工具--來處理這些問題。
然而上述兩種方法大量依賴人工,光看就讓人想哭了,實際上難以正式投入使用。
最值得推荐的作法或許是利用指令……
git branch --no-merged
……藉此來列出所有「未合併進當前分支的分支」,換句話說,已被合併到工作目錄的分支就不會顯示出來。
不過這個意義與「封存特定分支」其實有差,只能在特定的開發方式或較小的協作規模中才能被有效使用。實際上依然無法達成封存分支的效果。
「Branch 中能有多個 Head」這是 Mercurial 中一項相當有彈性的特徵,然而隨便將它推送到遠端反而可能搞混您的開發伙伴。Mercurial 用戶通常會用以下方法控制 heads:
不推送匿名 head 到遠端
Mercurial 允許同一個 branch 中有很多 Head,但他不希望您將一堆匿名 head 推送給他人共享(本地使用則無所謂),因為這會讓您的合作伙伴在 pull 後感到困惑:
「奇怪,原本的 branch 怎麼突然多出了 N 個頭(而且都沒特殊標記),我現在要用哪個頭進行開發?」
--像是如此這般。
對此 Mercurial 的建議是:如果您在嘗試 push 時得到了「這麼做 head 會增加」的警告,先不要勉強下 --force 硬推,取而代之,在絕大多數情況下,您應該先將遠端現有的變更拉下來看看多了什麼,然後在本地和您現有的變更合併(或 rebase),完成後再 push 到中央倉庫去,這可確保頭部數目不會增加。
當然,有時您推送的目的就是想要增加一個額外的頭部,那麼您可以在這些頭部加上 bookmark 後,再用 --force 進行推送,以利用戶識別。
察看當前分支中的 head 們
使用如下指令:
hg heads .
這可以讓您列出當前的 branch 中的所有頭部(預設不含已經被關閉的頭部),然後您就能一目了然地,任意在他們之間切來換去了。
關閉無用的頭部
您可以用 hg commit --close-branch 來關閉那些無用的頭部,而不需要將其合併到任何地方。這可以用來處理一些您不想繼續使用的變更路徑。
被關閉的頭部預設不會被列入 hg update BRANCH_NAME 的切換候選之中,所以除非刻意指定那個變更集,否則 update 時不會切到它頭上去。
Mercurial 的 Branch 沒有命名空間的概念,所有 Branch 名稱都共用同一個命名空間--這點常常被 Git 用戶指出,並視為缺點。
不過真相是:Mercurial branch 與 Git branch 的實現邏輯並不相同--Mercurial Branch 是標記一群變更集,而 Git Branch 則是標記頭部--故 Mercurial 的 branch 不會因為名稱相同而無法工作,也因此不需要有命名空間的概念。
Mercurial 的 branch 名稱總是唯一,且相同名字的 branch 總是會指向「同一群」提交--這不允許混淆也不會混淆。(但在 branch 擁有多個頭部的情況下,如果用 branch 來指定頭部,不見得會指向同一個頭部,請參考註三)
另一方面,與 Git branch 運作邏輯相似的 Mercurial bookmark,則與 Git 同樣有著「遠端與本地 bookmark 同名,但其實是指不同變更集」的問題存在。
然而,與其像 Git 那樣引入命名空間的概念,Mercurial Bookmark 寧願選擇「儘可能讓遠端與本地的 bookmark 在推拉時自然同步」這種策略--Mercurial 會自動讓同名 bookmark 在推拉時依照開發樹的方向自然同步推進。唯有因為路徑分岔而無法自然同步時,才會臨時產生一個命名空間,如「bookmark@default」這樣以資區別。
這同樣也是利於共享的設計。
這篇文章,到此也算告一段落了。在本文中,我描述了大量我注意到的,關於 Mercurial 與 Git 相比較時的優點。
然而,我也完全明白,您可能根本不在乎我所說的那些優點。
比方說,您可能根本不在乎能否保留分支、對輕鬆檢索變更集的能力嗤之以鼻、覺得簡單易學的命令行操作算不上什麼好處。您可能不認為自己需要一個零設定就能運行的網頁伺服器、覺得嚴肅對待版本與分支歷史的設計理念沒啥用處、不在乎版本管理器需不需要一個易寫易用的插件系統、對更多可能的分支模型聳聳肩、覺得用 shell script 來復原 rebase 只是小事一樁……
沒問題的,您當然可以有您自己的選擇。就像我一開始所說的那樣,我只是出來辟謠的。