介紹一個成功的 Git 分支模型(master - hotfix - develop - feature - release)


英文原文:A successful Git branching model
        在這篇文章中,我提出一個開發模型。我已經將這個開發模型引入到我所有的項目里(無論在工作還是私人)已經一年有余,並且它被證明是非常成功的。我打算寫這些已經很久了,但我一直找不到時間來做,現在終於有時間了。我不會講任何項目的具體細節,僅是關於分支策略和釋放管理相關內容。


它主要體現了Git對我們源代碼版本的管理。

*一般情況:

● master和develop並行。
● master上始終是最穩定的代碼,develop是正在開發的代碼。
● feature則是某個開發為了自己的功能拉的分支。
不一般情況:
● develop正在開發,如果你上線突然被拒絕了,這時候就要從master上開一個熱分支,或者release分支也行,改好之后在分別合並到其他分支。但本人感覺release通常意味着終止。別在從release上拉分支了。

為何是Git?

        對於Git與其他集中式代碼管理工具相比的優缺點的全面討論,請參見這里。這樣的爭論總是喋喋不休。作為一個開發者,與現今的其他開發工具相比較,我更喜歡Git。Git真得改變了開發者對於合並和分支的思考。我曾經使用經典的CVS/Subversion,然而每次的合並/分支和其他行為總讓人擔驚受怕(“小心合並里的沖突,簡直要命!”)。
但是對於Git來說,這些行為非常簡單和搞笑,它們被認為是日常工作中的核心部分。例如,在很多CVS/Subversion書里,分支與合並總是在后面的章節中被討論(對於高級用戶使用),然而在每個Git書中,在第3章就已經完全涵蓋了(作為基礎)。
簡單和重復的特性帶來的結果是:分支與合並不再是什么可以害怕的東西。分支/合並被認為對於版本管理工具比其他功能更重要。
關於工具,不再多說,讓我們直接看開發模型吧。這個模型並不是如下模型:在管理軟件開發進度方面,面對每個開發過程,每個隊員必須按一定次序開發。

分布式而非集中式

        對於這種分支模型,我們設置了一個版本庫,它運轉良好,這是一個"事實上" 版本庫。不過請注意,這個版本庫只是被認為是中心版本庫(因為Git是一個分布式版本管理系統,從技術上來講,並沒有一個中心版本庫)。我們將把這個版本庫稱為原始庫,這個名字對所有的Git用戶來說都很容易理解。

        每個開發者都對origin庫拉代碼和提交代碼。但是除了集中式的存取代碼關系,每個開發者也可以從子團隊的其他隊友那里獲得代碼版本變更。例如,對於2個或多個開發者一起完成的大版本變更,為了防止過早地向origin庫提交工作內容,這種機制就變得非常有用。在上述途中,有如下子團隊:Alice和Bob,Alice和David,Clair和David。
從技術上將,這意味着,Alice創建了一個Git的遠程節點,而對於Bob,該節點指向了Bob的版本庫,反之亦然。

主分支


在核心部分,研發模型很大程度上靠其他現有模型支撐的。中心庫有2個可一直延續的分支:
● master分支
● develop分支
每個Git用戶都要熟悉原始的master分支。與master分支並行的另一個分支,我們稱之為develop分支。
我們把原始庫/master庫認作為主分支,HEAD的源代碼存在於此版本中,並且隨時都是一個預備生產狀態。
        我們把origin/develop庫認為是主分支,該分支HEAD源碼始終體現下個發布版的最新軟件變更。有人稱這個為“集成分支”,而這是每晚自動構建得來的。
當develop分支的源碼到達了一個穩定狀態待發布,所有的代碼變更需要以某種方式合並到master分支,然后標記一個版本號。如何操作將在稍后詳細介紹。
所以,每次變更都合並到了master,這就是新產品的定義。在這一點,我們傾向於嚴格執行這一點,從而,理論上,每當對master有一個提交操作,我們就可以使用Git鈎子腳本來自動構建並且發布軟件到生產服務器。

輔助性分支

我們的開發模型使用了各種輔助性分支,這些分支與關鍵分支(master和develop)一起,用來支持團隊成員們並行開發,使得易於追蹤功能,協助生產發布環境准備,以及快速修復實時在線問題。與關鍵分支不同,這些分支總是有一個有限的生命期,因為他們最終會被移除。
我們用到的分支類型包括:
● 功能分支
● 發布分支
● 熱修復分支
每一種分支有一個特定目的,並且受限於嚴格到規則,比如:可以用哪些分支作為源分支,哪些分支能作為合並目標。我們馬上將進行演練。
從技術角度來看,這些分支絕不是特殊分支。分支的類型基於我們使用的方法來進行分類。它們理所當然是普通的Git分支。

功能分支


可能是develop分支的分支版本,最終必須合並到develop分支中。
分支命名規則:除了master、develop、release-*、orhotfix-*之外,其他命名均可。
功能分支(有時被稱為topic分支)通常為即將發布或者未來發布版開發新的功能。當新功能開始研發,包含該功能的發布版本在這個還是無法確定發布時間的。功能版本的實質是只要這個功能處於開發狀態它就會存在,但是最終會或合並到develop分支(確定將新功能添加到不久的發布版中)或取消(譬如一次令人失望的測試)。
功能分支通常存在於開發者的軟件庫,而不是在源代碼庫中。
創建一個功能分支
開始一項功能的開發工作時,基於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 標志導致合並操作創建一個新commit對象,即使該合並操作可以no-fast-forward。這避免了丟失這個功能分支存在的歷史信息,將該功能的所有提交組合在一起。 比較:

后一種情況,不可能從Git歷史中看到哪些提交一起實現了一個功能——你必須手工閱讀全部的日志信息。如果對整個功能進行回退 (比如一組提交),后一種方式會是一種真正頭痛的問題,而使用--no-ffflag的情況則很容易.
是的,它會創建一個新的(空)提交對象,但是收益遠大於開銷。
不幸的是,我還沒找到一種方法,讓--no-ff時作為合並操作的默認選項,但它應該是可行的。

Release 分支

Release分支可能從develop分支分離而來,但是一定要合並到develop和master分支上,它的習慣命名方式為:release-*。
Release分支是為新產品的發布做准備的。它允許我們在最后時刻做一些細小的修改。他們允許小bugs的修改和准備發布元數據(版本號,開發時間等等)。當在Release分支完成這些所有工作以后,對於下一次打的發布,develop分支接收features會更加明確。
從develop分支創建新的Release分支的關鍵時刻是develop分支達到了發布的理想狀態。至少所有這次要發布的features必須在這個點及時合並到develop分支。對於所有未來准備發布的features必須等到Release分支創建以后再合並。
在Release分支創建的時候要為即將發行版本分配一個版本號,一點都不早。直到那時,develop分支反映的變化都是為了下一個發行版,但是在Release分支創建之前,下一個發行版到底叫0.3還是1.0是不明確的。這個決定是在Release分支創建時根據項目在版本號上的規則制定的。


創建一個release分支
Release分支是從develop分支創建的。例如,當前產品的發行版本號為1.1.5,同事我們有一個大的版本即將發行。develop 分支已經為下次發行做好了准備,我們得決定下一個版本是1.2(而不是1.1.6或者2.0)。所以我們將Release分支分離出來,給一個能夠反映新版本號的分支名。

$ 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分支上)。在這里嚴格禁止增加大的新features。他們必須合並到develop分支上,然后等待下一次大的發行版。

完成一個release分支
當一個release分支准備好成為一個真正的發行版的時候,有一些工作必須完成。首先,release分支要合並到master上(因為每一次提交到master上的都是一個新定義的發行版,記住)。然后,提交到master上必須打一個標簽,以便以后更加方便的引用這個歷史版本。最后,在release分支上的修改必須合並到develop分支上,以便未來發行版也包含這些bugs的修復。
在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

 

發行版現在已經完成,為以后引用打上標簽。
編輯:你可能也想使用the-sor-u <key>flags來標記你的標簽。
為了是修改保持在release分支上,我們需要合並這些到develop分支上去,在Git上:

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

 

這個步驟可能會導致合並沖突(可能由於改變版本號更是如此)。如果是這樣,修復它然后提交。
現在我們真正的完成了,這個release分支將被刪除,因為我們不再需要它了。

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

 

熱修復分支


可以基於master分支,必須合並回develop和master分支。
分支名約定:hotfix-*
熱修復分支與發布分支很相似,他們都為新的生成環境發布做准備,盡管這是未經計划的。他們來自生產環境的處於異常狀態壓力。當生成環境驗證缺陷必須馬上修復是,熱修復分支可以基於master分支上對應與線上版本的tag創建。
其本質是團隊成員(在develop分支上)的工作可以繼續,而另一個人准備生產環境的快速修復。
創建修補bug分支
hotfix branch(修補bug分支)是從Master分支上面分出來的。例如,1.2版本是當前生產環境的版本並且有bug。但是開發分支(develop)變化還不穩定。我們需要分出來一個修補bug分支(hotfix branch)來解決這種情況。

$ 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(-)

 

分支關閉的時侯不要忘了更新版本號(bump the version)
然后,修復bug,一次提交或者多次分開提交。

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

完成一個hotfix分支

完成一個bugfix之后,需要把butfix合並到master和develop分支去,這樣就可以保證修復的這個bug也包含到下一個發行版中。這一點和完成release分支很相似。
首先,更新master並對release打上tag:

$ 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

編輯:你可能也會想使用 -sor-u <key>參數來對你的tag進行加密

下一步,把bugfix添加到develop分支中:

$ 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分支完成后, 將bugfix分支合並回release分支也會使得bugfix被合並到develop分支。(如果在develop分支的工作急需這個bugfix,等不到release分支的完成,那你也可以把bugfix合並到develop分支)

最后,刪除臨時分支:

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

 

摘要

盡管這個分支模型沒有任何震撼的新東西, 文章開頭的圖表在我們的項目中表現出驚人的實用性。它形成了一個優雅的思維模型,易於領悟並使團隊成員發展出對分支和發布過程的共同理解。

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM