1. 程式人生 > >一個成功的 Git 分支模型

一個成功的 Git 分支模型

規則 人的 做的 現在 ges 每一個 管理工具 nta nac

在這篇文章中介紹的開發模型在大約一年前已經在我的私有項目和工作引入的,而且已經被證明是非常成功的。我想寫一些關於這個模型的東西已經好一段時間了,但是一直苦於沒有時間,不過現在可以了。我不想探討任何項目細節,只討論分支策略和發布管理。

技術分享

這篇文章圍繞著Git做為我們所有的源代碼版本控制工具而展開的。

為什麽是Git

為了深入探討git和集中式源碼版本控制系統的利弊,參見這些文章。這方面有太多的激烈爭論。作為一個開發者,相比其他工具,當前我更喜歡Git。Git的確改變了開發者關於合並與分支的思考方式。在那些經典的CVS/Subversion管理工具的世界中,合並/分支被認為是有些嚇人的(“當心合並沖突,它們咬你!”),而且偶爾你得做些操作解決一些問題。

但是使用Git,這些操作都變得極度簡單,這些操作被認為是你日常工作流程核心部分之一。例如,在CVS/Subversion 這本書中,分支與合並在很後的章節中才被第一次討論(針對高級用戶)。但是在每一本Git書籍中,在第三章就講到了(基礎部分)。

由於它的簡單性和操作命令的重復性,分支與合並操作變得不再可怕。版本控制工具被認為在分支/合並方面提供操作便利性比什麽都重要

關於工具本身,已經討論的足夠多了,下面針對開發模型進行展開。我將要介紹的這個模型不會比任何一套流程內容多,每個團隊成員都必須遵守,這樣便於管理軟件開發過程。

既分散又集中

我們使用的,且與這個分支模型配合的非常好的庫,他有一個“真正”的中央倉庫。註意,這個庫只是被認為是中央倉庫(因為Git是一個分布式的版本控制工具,在技術層面沒有所謂的中央倉庫)。我們將會為這個倉庫起名為origin

,因為所有的Git用戶對這個名字都比較熟悉。

技術分享

每個開發者從origin拉取和推送代碼。除了集中式的推送拉取關系,每個開發者也有可能從別的開發者處拉取代碼,形成自己的團隊。例如當與兩個或者更多的人開發一個大的特性時,或者在將代碼推送到origin之前,這種代碼管理模式可能有用。在上圖中,存在Alice和Bob,Alice和David,Clair 和David三個子團隊

技術上而言,這只不過意味著Alice定義了一個遠程Git倉庫,起名為bob,實際上指向Bob的版本庫,反之亦然(Bob定義了一個遠程Git倉庫,起名為alice,實際上指向Alice的版本庫)。

主分支

技術分享

老實說,我們討論的開發模型受到了當前已存在模型的很大啟發。集中式的版本庫有兩個永久存在的主分支:

  • master分支
  • develop分支

origin的master分支每個Git用戶都很熟悉。平行的另外一個分支叫做develop分支。

我們認為origin/master這個分支上HEAD引用所指向的代碼都是可發布的。

我們認為origin/develop這個分支上HEAD引用所指向的代碼總是反應了下一個版本所要交付特性的最新的代碼變更。一些人管它叫“整合分支”。它也是自動構建系統執行構建命令的分支。

develop分支上的代碼達到了一個穩定狀態,並且準備發布時,所有的代碼變更都應該合並到master分支,然後打上發布版本號的tag。具體如何進行這些操作,我們將會討論

因此,每次代碼合並到master分支時,它就是一個人為定義的新的發布產品。理論上而言,在這我們應該非常嚴格,當master分支有新的提交時,我們應該使用Git的鉤子腳本執行自動構建命令,然後將軟件推送到生產環境的服務器中進行發布。

輔助性分支

緊鄰masterdevelop分支,我們的開發模型采用了另外一種輔助性的分支,以幫助團隊成員間的並行開發,特性的簡單跟蹤,產品的發布準備事宜,以及快速的解決線上問題。不同於主分支,這些輔助性分支往往只要有限的生命周期,因為他們最終會被刪除。

我們使用的不同類型分支包括:

  • 特性分支
  • Release分支
  • Hotfix 分支

上述的每一個分支都有其特殊目的,也綁定了嚴格的規則:哪些分支是自己的拉取分支,哪些分支是自己的目標合並分支。

從技術角度看,這些分支的特殊性沒有更多的含義。只是按照我們的使用方式對這些分支進行了歸類。他們依舊是原Git分支的樣子。

特性分支

技術分享

特性分支可以從develop分支拉取建立,最終必須合並會develop分支。特性分支的命名,除了 masterdeveloprelease-*,或hotfix-*以外,可以隨便起名。

特性分支(有時候也成主題分支)用於開發未來某個版本新的特性。當開始一個新特性的開發時,這個特性未來將發布於哪個目標版本,此刻我們是不得而知的。特性分支的本質特征就是只要特性還在開發,他就應該存在,但最終這些特性分支會被合並到develop分支(目的是在新版本中添加新的功能)或者被丟棄(它只是一個令人失望的試驗)

特性分支只存在開發者本地版本庫,不在遠程版本庫。

創建特性分支

當開始開發一個新特性時,從develop分支中創建特性分支

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

在develop分支整合已經開發完成的特性

開發完成的特性必須合並到develop分支,即添加到即將發布的版本中。

Shell
1 2 3 4 5 6 7 8 $ 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歷史記錄中看到一個已經實現了的特性的所有提交對象-除非你去查看所有的日誌信息。要想獲取整個特性分支信息,在右面的例子中的確是一個頭疼的問題,但是如果使用--no-ff參數就沒有這個問題。

使用這個參數後,的確創建了一些新的提交對象(那怕是空提交對象),但是很值得。

不幸的是,我還沒有找到一種方法使Git默認的merge操作帶著--no-ff參數,但的確應該這樣。

發布分支

develop分支去建立Release分支,Release分支必須合並到develop分支和master分支,Release分支名可以這樣起名:release-*。

Release分支用於支持一個新版本的發布。他們允許在最後時刻進行一些小修小改。甚至允許進行一些小bug的修改,為新版本的發布準要一些元數據(版本號,構建時間等)。通過在release分支完成這些工作,develop分支將會合並這些特性以備下一個大版本的發布。

develop分支拉取新的release分支的時間點是當開發工作已經達到了新版本的期望值。至少在這個時間點,下一版本準備發布的所有目標特性必須已經合並到了develop分支。更遠版本的目標特性不必合並會develop分支。這些特性必須等到個性分支創建後,才能合並回develop分支

在release分支創建好後,就會獲取到一個分配好即將發布的版本號,不能更早,就在這個時間點。在此之前,develop分支代碼反應出了下一版本的代碼變更,但是到底下一版本是 0.3 還是 1.0,不是很明確,直到release分支被建立後一切都確定了。這些決定在release分支開始建立,項目版本號等項目規則出來後就會做出。

創建release分支

develop分支創建release分支。例如1.1.5版本是當前產品的發布版本,我們即將發布一個更大的版本。develop分支此時已經為下一版本準備好了,我們決定下一版的版本號是1.2(1.1.6或者2.0也可以)。所以我們創建release分支,並給分支賦予新的版本號:

Shell
1 2 3 4 5 6 7 $ 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-version.sh是一個虛構的shell腳本,它更改了工作空間的某些文件來反映新版本特征。(當然也可以手動改變這些文件),然後版本就被提交了。

新的分支會存在一段時間,直到新版本最終發布。在這段時間裏,bug的解決可以在這個分支進行(不要在develop分支進行)。此時是嚴禁添加新的大特性。這些修改必須合並回develop分支,之後就等待新版本的發布。

結束一個release分支

當release分支的準備成為一個真正的發布版本時,一些操作必須需要執行。首先,將release分支合並回master分支(因為master分支的每一次提交都是預先定義好的一個新版本,謹記)。然後為這次提交打tag,為將來去查看歷史版本。最後在release分支做的更改也合並到develop分支,這樣的話,將來的其他版本也會包含這些已經解決了的bug。

在Git中需要兩步完成:

Shell
1 2 3 4 5 6 $ 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

這樣release分支已經完成工作,tag也已經打了。

備註:你可以使用-s or -u <key>參數為你的tag設置標簽簽名。

為了保存這些在release分支所做的變更,我們需要將這些變更合並回develop分支。執行如下Git命令:

Shell
1 2 3 4 5 $ git checkout develop Switched to branch ‘develop‘ $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes)

這步有可能會有合並沖突(極有可能,因為我們已經改變了版本號)。如果有沖突,解決掉他,然後提交。
現在我們已經完成了工作,release分支可以刪除了,因為我們不在需要他:

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

Hotfix分支

技術分享

Hotfix分支從master分支建立,必須合並回develop分支和master分支,為Hotfix分支可以這樣起名:hotfix-*

Hotfix分支在某種程度上非常像release分支,他們都意味著為某個新版本發布做準備,並且都是預先不可知的。Hotfix分支是基於當前生產環境的產品的一個bug急需解決而必須創建的。當某個版本的產品有一個嚴重bug需要立即解決,Hotfix分支需要從master分支上該版本對應的tag上進行建立,因為這個tag標記了產品版本

創建hotfix分支

Hotfix分支從master分支進行創建。例如當前線上1.2版本產品因為server端的一個Bug導致系統有問題。但是在develop分支進行更改是不靠譜的,所以我們需要建立hotfix分支,然後開始解決問題:

Shell
1 2 3 4 5 6 7 $ 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,提交一次或多次。

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

結束hotfix 分支

完成工作後,解決掉的bug代碼需要合並回master分支,但同時也需要合並到develop分支,目的是保證在下一版中該bug已經被解決。這多麽像release分支啊。

首先,對master分支進行合並更新,然後打tag

Shell
1 2 3 4 5 6 $ 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 or -u <key>參數為你的tag設置標簽簽名。

緊接著,在develop分支合並bugfix代碼

Shell
1 2 3 4 5 $ git checkout develop Switched to branch ‘develop‘ $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes)

這裏可能會有一些異常情況,當一個release分支存在時,hotfix 分支需要合並到release 分支,而不是develop分支。當release分支的使命完成後,合並回release分支的bugfix代碼最終也會被合並到develop分支。(當develop分支急需解決這些bug,而等不到release分支的結束,你可以安全的將這些bugfix代碼合並到develop分支,這樣做也是可以的)。

最後刪除這些臨時分支

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

總結

這個分支模型其實沒有什麽震撼人心的新東西,這篇文章開始的那個“最大圖片”已經證明了他在我們工程項目中的巨大作用。它會形成一種優雅的理想模型,而且很容易理解,該模型也允許團隊成員形成一個關於分支和版本發布過程的相同理念。

這裏有提供一個高質量的分支模型圖的PDF版本。去吧,把它掛在墻上隨時快速參考。

技術分享
Git-branching-model.pdf

更新:任何需要他的人,這裏有一個主圖的gitflow-model.src.key文件

如果想和我取得聯系,在推特上@nvie

一個成功的 Git 分支模型