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