一個成功的 Git 分支模型(適用於商業應用開發)

在這篇文章中,我將推廣一下大約一年前我介紹過的一些項目(公私皆有)中使用的開發模型,它們的結果都很是成功。有段時間我很是想寫出來分享一下,可是我至今才抽出時間來。我不會言及任何項目細節,僅討論分支策略和發佈管理。laravel

file

爲什麼使用 git? 

關於 Git 和集中式源碼版本控制系統的優缺點對比討論,  此 web。這裏有不少精彩激烈的論戰。做爲一名開發者,如今我更偏好使用 Git 。Git 真的改變了開發者關於合併和分支的認知。我來自傳統的 CVS/Subversion 世界,合併/分支是件恐怖的事情 (「當心合併衝突,它們會反噬你!」),並且你們偶爾纔會作這件事。git

可是使用 Git,這些操做就顯得垂手可得,它們會成爲你 平常 工做流的核心部分。例如,在 CVS/Subversion 手冊中,分支和合並直到最後章節才首次出現(僅供高級用戶參考),可是在 每一個 Git 手冊中,第三章就覆蓋到了(基本上)。github

由於具備簡單和可重複的基因,分支和合並再也不是什麼值得擔憂的問題了。版本控制工具應該專一於分支/合併,而非其餘事情。web

關於工具的瞭解已經夠了,那麼咱們就開始進入開發模型了。這個我要介紹的開發模型,不過是每一個團隊成員進入軟件開發流程以前必須遵循的規範。安全

去中心化和中心化 

基於一個「真正」中心庫的這種分支模型可讓咱們很好的協同工做。注意這個庫僅僅是被認爲一箇中心庫(由於 GIT 在技術角度並無中心庫這一說法)。咱們將此倉庫命名爲 origin,由於這名字被 Git 用戶熟知。服務器

!併發

全部開發者都從 origin 庫拉取或向其推送代碼。在中心化的推送-拉取關係以外,開發者也能夠從其餘的開發者庫拉取代碼更改。例如,爲了開發一個大型功能,有多位開發者組成一個開發小組,在功能完成以前,開發過程沒必要推送至 origin 庫。在上面的圖中就有 Alice 和 Bob, Alice 和 David,還有 Clair 和 David 之間組成的小組。svn

技術層面,Alice 要在本地庫加一個遠程 Git 分支並命名爲 bob,指向 Bob 的倉庫。其餘人也同樣。工具

主分支 

file

git 的核心在開發模式上受到了現有模式的極大啓發,中心倉庫在整個生命週期保持了兩個主要的分支:ui

  • master
  • develop

每一個 Git 用戶都對在 origin 的  master 分支很熟悉。 跟 master 分支並行的是另外一個稱爲 develop 的分支。

 咱們稱 origin/master 爲主分支,這個分支源碼的 HEAD 一直指向 可用於生產環境 的狀態。

咱們稱 origin/develop 爲主分支,這個分支源碼的 HEAD 老是反映下一個版本的最新開發情況。有些人稱這個分支爲 "整合分支" 。全部的每日自動構建都是從這兒構建的。

當 develop 分支上的源代碼達到一個穩定點並準備發佈時, 全部的更改都應該以某種方式合併回 master 分支, 而後使用發行版本進行標註。 接下來將從細節上討論這是如何完成的。

所以, 每次將變動合併回 master分支時, 這是一個 根據定義 的新產品發佈。 咱們趨向於對此很是嚴格,所以理論上來說, 咱們能夠在每次提交到 master 分支時, 使用一個 Git 鉤子腳原本自動完成構建和發行咱們的軟件到生產服務器。

輔助分支 

在討論完 master 分支和 develop 分支後,將要討論的多樣化的輔助分支,支持成員間並行開發, 輕鬆跟蹤功能開發、生產版本發佈、還能快速修復生產環境中產生的 Bug 。和主分支不一樣的是,輔助分支只有有限的生命週期,一般在完成使命後會被刪除。

可能用到的輔助分支分類有:

  • 功能分支
  • 發佈分支
  • 修復 Bug 分支

每一個分支都有特殊的用途。這些分支的來源分支和他們要合併回的分支都有嚴格的定義。在後面咱們再具體討論。

技術上,輔助分支都沒有特殊含義。咱們就是根據具體使用功能對輔助分支進行分類。輔助分支和普通的 Git 分支沒有區別。

功能分支 

!

功能分支可能源自於:

develop 分支

功能分支必須合併回:

develop 分支

功能分支命名慣例:

任何名字均可以,但不能包含 master, develop, release-*, 或者 hotfix-*

功能分支(或稱爲特性分支)是被用來開發新功能的,這些新功能是要即將上線或更長時間後發佈的。功能分支建立後開始開發時,以後將要合併的時間點是不知道的。功能分支的精髓是伴隨開發過程一直存在,可是確定會被合併回 develop (在下一個預期的發佈版本中清晰的添加新功能 ) 或被丟棄 (萬一實驗不盡如人意)。

功能分支一般存在開發人員的倉庫中,不會出如今 origin 倉庫。

建立一個功能分支 

當咱們開始寫一個新的功能時,請從 develop 分支中切換出來

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

在開發中加入已經完成的功能 

完成的功能可能被合併進入 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-forwark方法進行提交。這就能夠避免丟失功能分支的歷史信息而且能夠把全部的功能在疊加在一塊兒提交上去,請看圖片對比:

!

在後一種狀況中,從 Git 歷史中是沒法查看到是哪幾個提交對象在一塊兒實現了一個功能 -——您必須手動讀取全部日誌消息。還原整個功能(即一組完整的提交)在後一種狀況下是真的讓人很頭疼的事情,可是若是使用使用 --no-ff 標誌,就能夠很容易完成這個任務的。

固然啦,它雖然創做更多的空的提交對象,可是增益卻遠遠大於成本。

發佈分支 

該分支可能源自於:

develop 分支

必須合併到:

develop 和 master 分支

分支命名習慣:

release-*

發佈分支(Release branches) 支持新產品發佈的準備。 它們容許在最後一刻追求細節。此外,它們容許小錯誤修復以及爲發佈準備元數據(版本號,構建日期等)。經過在發佈分支上作的這些工做, develop 分支被清除以接收下一個大版本的功能特性。

從 develop 分支檢出一個新發布分支的重要時刻就是當開發(基本上)反映了新版預期狀態的時候。 至少,在那時,全部以『即將構建的發佈版』( release-to-be-built )爲目標的功能特性必須合併回 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-version.sh 是一個虛構的腳本文件,用以改變工做副本中的一些文件來反映新版本。(固然也能夠手動啦,重點是 那些 改變的文件)而後,碰撞後的版本號就被提交了。

直到肯定會推出發佈版的這段時間裏,新分支都會一直存在。在此期間,bug 修復可能會應用到這個分支上(而不是 develop 分支)。 嚴禁在此分支添加大的新功能特性。 這些分支必須合併回 develop 分支,而後,等待下一個大版本的到來。

完成併發布你的分支 

當你真的準備好要發佈分支的時候,還須要執行一些別的操做。首先,發行版必須合併進 master 分支中(必定確保每次提交到 master 分支的都是最新的版本)。接下來,請必定標記對 master 分支的更新記錄,用於之後查看該版本時進行參考。最後, 發佈新分支所作的更改須要從新合併爲 develop 分支,以確保之後的版本也修復了這些錯誤。

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-*

熱修復(hotfix)分支和發佈(release)分支很像,由於它們都意味着即將有新的生產版本發佈,儘管不是意料之中的。它們產生的緣由是現有的生產版本出現了不受歡迎的狀況,從而必須當即起做用。當生產版本中的一個嚴重的 bug 必須立刻被修復,熱修復分支可能從 master 分支上用於標記生產版本的對應標籤上建立分支。

其本質是當有人準備對生產版本進行快速修復時,團隊成員(在 develop 分支)能夠繼續工做。

建立修復 bug 分支 

修復 bug 分支建立於 master 分支。 例如,1.2版本是當前生產環境的版本而且有 bug 。可是 develop 分支上的修改還不夠穩定。這時咱們能夠建立一個修復 bug 分支來解決這個問題:

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

完成一個修復 bug 分支

當完成一個修復 bug 分支以後,bug 分支須要合併到 masterdevelop 分支上,以保證在下一版本中也包含該 bug 修復。 這與完成發佈分支徹底類似。

首先,更新 master 並對此次發佈打上 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

注: 你可能還想要使用 -s 或者 -u <key> 來加密簽名。

而後,在 develop 分支裏包含 bug 修復分支的改動:

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

對於上面的規則,有一點是例外的: 當發佈分支已經存在時, bug 修復分支的改動應該合併到該發佈分支,而不是 develop 分支。當發佈分支完成的時候, 把 bug 修復分支反向合併到發佈分支中,最終也會致使 bug 修復被合併到 develop 分支中去。(若是 develop 分支中的工做立刻就要這個 bug 修復的改動而且不能等待發布分支完成,那麼如今你也能夠安全地將 bug 修復合併到 develop 分支中去。)
最後,移除臨時分支:

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

小結 

雖然這種分支模式目前看來已經不是什麼新鮮事了,但這篇文章開頭的 「大圖」已經證實,這種模式對咱們的項目實實在在的是很是有用。它造成了一個優雅而且更加容易理解的模型,而且能增強團隊中成員們對於分支和其釋放過程的理解。

這裏提供給你們上面大圖更加清晰的 PDF 版本,建議把它打印出來掛在牆上膜拜而且以便開發過程當中快速查看。

更新: 若是有人須要這張圖片: 這裏附上下載文件 gitflow-model.src.key 

Git 分支模型.pdf 下載

更多現代化 PHP 知識,請前往 Laravel / PHP 知識社區

相關文章
相關標籤/搜索