1. 程式人生 > >一個diaodiao的Git分支模型(譯)

一個diaodiao的Git分支模型(譯)

原文出自:http://nvie.com/posts/a-successful-git-branching-model/

以下是翻譯內容:

在這篇文章裡,將展示我在大約一年前為我的一些專案(包括工作上的和私人的)所提出的開發模型,它最終也被證明是非常成功的。我早就打算寫這樣一篇文章,但直到現在才覺得是一個將其徹底完成的好時機。我不會討論這些專案任何的細節,而僅僅是關於分支策略和釋出管理的東西。


文章將圍繞Git作為管理我們所有原始碼版本的工具。(然後作者強行植入了一個廣告,略:)

為什麼用git?

網上有關於Git相比於集中式原始碼控制系統的優缺點的詳細討論,這自然免不了許多爭吵。作為開發者,現在我是喜歡Git甚於其它一切工具。Git真心改變了開發者對於合併與分支的看法。我來自經典的CVS/Subversion世界,在那裡,合併/分支一直被人們覺得有點可怕(“小心合併時產生的衝突,它們會咬你哦!”),它們卻是你經常要做的事情。

但是有了Git,這些行為就變得十分容易,代價也更低,而且它們都被當成你日常工作流程的核心部分之一了,真的。舉個栗子,在CVS/Subversion書籍中,分支與合併在很靠後的章節中才會被討論(面向高階使用者的),而在每一本Git書中,它們的內容在第3章就已經被覆蓋了(屬於基礎的)。

由於Git帶來的簡單性和重複性,分支與合併不再是讓人懼怕的東西。版本控制工具對分支/合併的幫助能力也可以超過其它一切事物。

關於工具說得已經夠多了,接著就來講本文的開發模型吧。我將要在此展示的模型,本質上不過是一系列步驟,每個團隊成員都應該遵循這些步驟,這有利於實現對軟體開發過程更好的管理。

分散的,又是集中的

我們所使用的倉庫配置應用了這一分支模型,運作良好,它有一箇中心的“真實”倉庫。請注意,這個倉庫只是被“當作”是中心倉庫(因為Git是一種DVCS,在技術層面上來說,並不存在所謂的中心倉庫)。我們將用origin來代指這一倉庫,這個名字對廣大的Git使用者來說一定很熟悉。


每個開發者都會對origin做很多pull和push操作。但除了上述集中的push-pull關係,每個開發者也會從其它人那裡拉取更新,這就形成了子團隊。比如說,當你和其它兩個或更多的開發者一起開發一個大的功能時,為了避免過早地將當前進度push到origin,開發者之間的pull就會很有必要。上述圖片中,存在Alice和Bob、Alice和David、Clair和David的工作小組。

從技術上來說,這也就是Alice定義了一個Git遠端倉庫,叫做bob,它指向Bob的倉庫,反之亦然。

主要分支

實質上,本文的開發模型很大程度上是受到了下邊這個已有的模型的啟發。中心倉庫持有兩個主要分支,它們有著無窮的生命週期:

  • master
  • develop


origin的master分支對每個Git使用者來說都應該很熟悉。與master分支相併列,存在另一個叫做develop的分支。

我們將origin/master視為主要分支,該分支上HEAD的原始碼總是反映了一個可以交付的狀態。

我們將origin/develop也當作主要分支,該分支上HEAD的原始碼總是反映了最近提交的、為下一個釋出版本而做的開發更新。有人稱之為“整合分支”。這也是所有夜間自動構造的來源之處。

當develop分支上的原始碼趨於穩定,已經可以釋出時,所有的程式碼更新都應該以某種方式合併回master,並被標記一個釋出號碼。上述行為具體是如何做到的,後面會進一步討論。

因此,每一次將更新合併到master時,從定義上來說,一個新的生產釋出版本誕生了。我們一般對此十分嚴格,於是從理論上講,我們可以用一個Git的hook指令碼,每次master上有提交,指令碼會自動構建軟體並推出到生產伺服器上。

輔助分支

緊接著主要分支master和develop,我們的開發模型使用了各種各樣的輔助分支,從而可以給團隊成員之間的並行開發提供支援,輕鬆地跟蹤功能,為生產釋出做準備,以及快速修復現場的生產問題。與主要分支不同的是,這些分支都只有有限的生命週期,因為它們最終都會被清除。

我們使用到的這些輔助分支有下述幾種:

  • 功能分支
  • 釋出分支
  • 熱補丁分支

其中每條分支都有一個特定的目的,並被嚴格的規則所限定,如它們的起源分支可以是哪條,它們要合併進的目標分支又必須是哪條等。我們一會兒就詳細討論他們。

從技術角度看,這些分支一點也不特殊。上述的分支型別只是根據我們如何使用它們來分類的。它們當然就是普普通通的、老掉牙的Git分支啦。

功能分支

可以來源於:develop。

必須合併到:develop。

分支命名慣例:隨意,除了叫mater、develop、release-*,或者hotfix-*(它們都名花有主啦)。


功能分支(有時也叫主題分支)是用來為即將到來、或是在遙遠未來的釋出而開發新功能的。當開始一個功能的開發,該功能將會整合到哪個釋出版本里面去,這個時候可能還不得而知。功能分支的本質是,只要這個功能還在開發,該分支就會一直存在,但最終都會合並回develop(確定要將該新功能新增到即將到來的釋出裡)或是被丟棄(萬一實驗結果讓人失望呢)。

功能分支通常只存在於開發者的倉庫,而不是在origin裡。

建立功能分支

當開始開發一個新功能,從develop分支上建立分支。

$ git checkout -b myfeature develop
Switched to a new branch "myfeature"

將完成的功能整合到develop

可以將完成的功能合併到develop分支上,從而把它們新增到即將釋出的版本中:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff myfeature
Updating ea1b82a..05e9557
(Summary of changes)
$ git branch -d myfeature
Deleted branch myfeature (was 05e9557).
$ git push origin develop
其中 --no-ff 標誌使得合併時總是會建立一個新的提交物件,即使可以用快進(fast-forward)來執行合併。這就避免了丟失功能分支的歷史資訊,也將所有形成該功能的提交組合到了一起。比較下面兩者:

在後面一種情況中,想要從Git歷史中看出到底是哪些提交物件加在一起實現了某個功能,這是不可能的——你將不得不去人工閱讀所有的日誌資訊。該情況下想要revert掉整個功能(即一組提交)也會很頭痛,不過要是用了 --no-ff 標誌就會簡單多了。

是的,它會建立一些額外的(空的)提交物件,但是得到的好處遠比付出的成本要多。

釋出分支

可以分支於:develop

必須合併到:develop和master

分支命名慣例:release-*

釋出分支為一次新的生產釋出的準備工作提供支援,它可以讓你在最後一分鐘仍然一絲不苟、精益求精。而且,它允許小的bug修復以及為釋出準備元資料(版本號、構建日期等)。通過在釋出分支上完成上述所有的工作,develop分支就可以繼續接受為下次的大發布更新的功能了。

從develop上分出一個新分支的重要時機,就是當develop(基本上)已經反映了新發布的預想狀態時。此時,至少所有針對此次釋出的功能必須及時合併到develop上。而面向將來發布版本的功能就不需要合併了——它們必須等到目標釋出分支被建立。

也就是在釋出分支被建立的時候,即將釋出的版本才得到一個版本號——而不是更早。在那之前,develop分支雖然反映了下次釋出版本的更新,但是下一個釋出版本會是0.3還是1.0,這要到釋出分支建立的時候才能確定。這個問題在相應的釋出分支開始時,由專案的版本號選取規則所決定。

建立釋出分支

釋出分支從develop分支建立。舉例來說,假設當前生產釋出版本是1.1.5,我們將要釋出一個大的版本。develop已經準備好了下次釋出,並且我們該釋出將會是版本1.2(而不是1.1.6或2.0)。於是我們分出釋出分支,給它一個發映新版本號的名字:

$ git checkout -b release-1.2 develop
Switched to a new branch "release-1.2"
$ ./bump-version.sh 1.2
Files modified successfully, version bumped to 1.2.
$ git commit -a -m "Bumped version number to 1.2"
[release-1.2 74d9424] Bumped version number to 1.2
1 files changed, 1 insertions(+), 1 deletions(-)

建立了新分支並切換到它以後,我們更新(bump)版本號。這裡,bump-version.sh是一個假想的指令碼,它更新工作拷貝目錄裡的一些檔案來反映新版本(這當然可以手工完成——重點在於“一些”檔案的更新)。然後,提交更新的版本號。

這個新分支可能會存在一段時間,直到釋出版本被真正推出。在存在期間,bug的修復可能會應用到這一分支上(而不是到develop上)。在這裡增加大型的新功能是被嚴格阻止的,它們應該被合併到develop裡面去,並且等待下一個大的釋出。

結束髮布分支

當釋出分支準備好被真正釋出時,需要進行一些措施。首先,將釋出分支合併到master上(因為從定義上講,master上面的每次提交都是一個新的釋出版本,還記得嗎)。然後,master上的這次提交必須被加上標籤,以便於將來能方便地引用這一歷史版本。最終,釋出分支上的更新需要被合併回develop,這樣以後的釋出版本也可以包含這些bug的修復。

Git上首先要做的兩步是:

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2

釋出現在完成了,而且也為未來的引用打好了標籤。

(編者按:你可能也會想使用 -s 或 -u <key> 標誌來加密地標記。)

為了記錄釋出分支裡的更新,我們也需要把它們合併回develop。在Git裡:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff release-1.2
Merge made by recursive.
(Summary of changes)

這一步可能也會導致合併衝突(可能,因為我們更改了版本號)。如果真的有,就解決它再提交。

至此我們確實搞定了,釋出分支可以被移除了,因為我們不再需要它了:

$ git branch -d release-1.2
Deleted branch release-1.2 (was ff452fe).

熱補丁分支

可以分支於:master

必須合併回:develop和master

分支命名慣例:hotfix-*


熱補丁分支在某種程度上和釋出分支很像,因為它也是用來為一次新的生產釋出做準備的,儘管它是計劃之外的。當一個現場的生產版本發生了意外且有必要立即對其採取行動時,熱補丁分支就出現了。當生產版本中的一個致命bug必須被立即解決,可以從master分支上找到相應的標記了該生產版本的tag,分出一個熱補丁分支。

這樣做的本質是,團隊成員的工作(在develop分支上的)可以繼續進行,同時另外的人在準備快速地修復生產版本。

建立熱補丁分支

熱補丁分支建立自master分支。比如,假設1.2版本是當前實際執行的生產釋出版本,由於一個嚴重的bug造成了問題,而develop上的更新尚未穩定。於是我們可以分出一條熱補丁分支,開始修復問題:

$ git checkout -b hotfix-1.2.1 master
Switched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1
Files modified successfully, version bumped to 1.2.1.
$ git commit -a -m "Bumped version number to 1.2.1"
[hotfix-1.2.1 41e61bb] Bumped version number to 1.2.1
1 files changed, 1 insertions(+), 1 deletions(-)

建立分支後,不要忘記更新版本號哦!

然後,修復bug並將更改提交,這可能會需要一次或多次的提交。

$ git commit -m "Fixed severe production problem"
[hotfix-1.2.1 abbe5d6] Fixed severe production problem
5 files changed, 32 insertions(+), 17 deletions(-)

結束熱補丁分支

當修復完成,修復更新需要合併回master,也需要合併回develop,從而保證在之後的釋出版本中bug也得到了修復。這與釋出分支的結束方式完全類似。

首先,更新master併為釋出作標記。

$ git checkout master
Switched to branch 'master'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)
$ git tag -a 1.2.1

(編者按:你可能也會想用 -s 或 -u <key> 標誌來加密地標記。)

接下來,將熱補丁也包括進develop中:

$ git checkout develop
Switched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1
Merge made by recursive.
(Summary of changes)

這裡的規則有一個例外,如果目前存在釋出分支,那麼熱補丁分支的更新需要被合併到該釋出分支裡去,而不是develop。將熱補丁合併回釋出分支,最終也會被合併回develop,只要釋出分支的週期結束。(如果develop上的工作即刻需要這一熱補丁,且不能等到釋出分支的結束,你可以將熱補丁安全地合併回develop裡。

最後,移除這個臨時的分支:

$ git branch -d hotfix-1.2.1
Deleted branch hotfix-1.2.1 (was abbe5d6).

總結

雖然這一分支模型沒有什麼真正讓人震驚的新東西,那張本文開始處的“大圖”已經在我們的專案中被證實其卓越的價值所在。它構造了一個優雅的心理模型,該模型很容易理解,也使得團隊成員們對分支和釋出過程有了共識。這裡也提供該圖片的高質量PDF版本。去吧騷年,把它掛在牆上,以後隨時可以迅速地參考它。

Git-branching-model.pdf

如果想聯絡我(原文作者啦,本少你當然直接在部落格就可以聯絡到),我是twitter上的@nvie