Git 分支策略

2022-03-23 16:04 更新

本文將展示我一年前在自己的項目中成功運用的開發(fā)模型。我一直打算把這些東西寫出來,但總是沒有抽出時間,現(xiàn)在終于寫好了。這里介紹的不是任何項目的細節(jié),而是有關分支的策略以及對發(fā)布的管理。


在我的演示中,所有的操作都是通過 git 完成的。

為什么選擇 git ?

為了了斷 git 和中心源代碼控制系統(tǒng)的比較和爭論,請移步這里看看 鏈接1 鏈接2。作為一個開發(fā)者,我喜歡 git 超過其它任何現(xiàn)有的工具。Git 真正改變了開發(fā)者對于合并和分支的認識。在傳統(tǒng)的 CVS/SVN 里,合并/分支總是有點令人害怕的(“注意合并沖突,它們會搞死你的”)。

但是 git 中的這些操作是如此的簡單有效,它們真正作為你每天工作流程的一部分。比如,在 CVS/SVN 的書籍里,分支和合并總是最后一個章節(jié)的討論重點(對于高級用戶),而在每一本 git 的書里 鏈接1 鏈接2  鏈接3,這些內容已經(jīng)被包含在第三章(基礎)里了。

因為它的簡單直接和重復性,分支和合并不再令人害怕。版本控制工具比其它任何東西都支持分支/合并。

有關工具就介紹到這里,我們現(xiàn)在進入開發(fā)模型這個正題。我要展現(xiàn)的模型本質上無外乎是一個流程的集合,每個團隊成員都有必要遵守這些流程,來達到管理軟件開發(fā)流程的目的。

分散但也集中

我們的分支模型中使用良好的代碼庫的設置方式,是圍繞一個真實的中心代碼庫的。注意,這里的代碼庫僅僅被看做是一個中心代碼庫(因為 git 是 DVCS,即分散版本控制系統(tǒng),從技術層面看,是沒有所謂的中心代碼庫的)。我們習慣于把這個中心代碼庫命名為 origin,這同時也是所有 git 用戶的習慣。


每一位開發(fā)者都向 origin 這個中心結點 pull 和 push。但是除此之外,每一位開發(fā)者也可以向其它結點 pull 改變形成子團隊。比如,對于兩個以上開發(fā)者同時開發(fā)一項大的新特性來說,為了不必過早向 origin 推送開發(fā)進度,這就非常有用。在上面的這個例子中,Alice 和 Bob、Alice 和 David、Clair 和 David 都是這樣的子團隊。

從技術角度,這無非意味著 Alice 定義一個名為 Bob 的 git remote,指向 Bob 的代碼庫,反之亦然。

主分支


該開發(fā)模型的核心基本和現(xiàn)有的模型是一樣的。中心代碼庫永遠維持著兩個主要的分支:

  • master
  • develop

在 origin 上的 master 分支和每個 git 用戶的保持一致。而和 master 分支并行的另一個分支叫做 develop。

我們認為 origin/master 是其 HEAD 源代碼總是代表了生產(chǎn)環(huán)境準備就緒的狀態(tài)的主分支。

我們認為 origin/develop 是其 HEAD 源代碼總是代表了最后一次交付的可以趕上下一次發(fā)布的狀態(tài)的主分支。有人也把它叫做“集成分支”。該源代碼還被作為了 nightly build 自動化任務的來源。

每當 develop 分支到達一個穩(wěn)定的階段,可以對外發(fā)布時,所有的改變都會被合并到master 分支,并打一個發(fā)布版本的 tag。具體操作方法我們稍后討論。

因此,每次改動被合并到 master 的時候,這就是一個真正的新的發(fā)布產(chǎn)品。我們建議對此進行嚴格的控制,因此理論上我們可以為每次 master 分支的提交都掛一個鉤子腳本,向生產(chǎn)環(huán)境自動化構建并發(fā)布我們的軟件。

支持型分支

我們的開發(fā)模型里,緊接著 master 和 develop 主分支的,是多種多樣的支持型分支。它們的目的是幫助團隊成員并行處理每次追蹤特性、準備發(fā)布、快速修復線上問題等開發(fā)任務。和之前的主分支不同,這些分支的生命周期都是有限的,它們最終都會被刪除掉。

我們可能會用到的不同類型的分支有:

  • feature 分支
  • release 分支
  • hotfix 分支

每一種分支都有一個特別的目的,并且有嚴格的規(guī)則,諸如哪些分支是它們的起始分支、哪些分支必須是它們合并的目標等。我們快速把它們過一遍。

這些“特殊”的分支在技術上是沒有任何特殊的。分支的類型取決于我們如何運用它們。它們完完全全都是普通而又平凡的 git 分支。

feature 分支


  • 可能派發(fā)自:develop
  • 必須合并回:develop
  • 分支命名規(guī)范:除了 master、develop、release-* 或 hotfix-* 的任何名字

Feature 分支(有時也被稱作 topic 分支)用來開發(fā)包括即將發(fā)布或遠期發(fā)布的新的特性。當我們開始開發(fā)一個特性的時候,發(fā)布合并的目標可能還不太確定。Feature 分支的生命周期會和新特性的開發(fā)周期保持同步,但是最終會合并回 develop (恩,下次發(fā)布的時候把這個新特性帶上)或被拋棄(真是一次杯具的嘗試啊)。

Feature 分支通常僅存在于開發(fā)者的代碼庫中,并不出現(xiàn)在 origin 里。

創(chuàng)建一個 feature 分支

當開始一個新特性的時候,從 develop 分支派發(fā)出一個分支

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

把完成的特性合并回 develop

完成的特性可以合并回 develop 分支并趕上下一次發(fā)布:

$ git checkout develop
Switched to a new 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 標記使得合并操作總是產(chǎn)生一次新的提交,哪怕合并操作可以快速完成。這個標記避免將 feature 分支和團隊協(xié)作的所有提交的歷史信息混在主分支的其它提交之后。比較一下:


在右邊的例子里,我們不可能從 git 的歷史記錄中看出來哪些提交實現(xiàn)了這一特性——你可能不得不查看每一筆提交日志?;謴鸵粋€完整的特性(比如通過一組提交)在右邊變成了一個頭疼事情,而如果使用了 --no-ff 之后,就變得簡單了。

是的,這會創(chuàng)造一些沒有必要的(空的)提交記錄,但是得到的是大量的好處。

不幸的是,我還沒有找到一個在 git merge 時默認就把 --no-ff 標記打上的辦法,但這很重要。

release 分支

  • 可能派發(fā)自:develop
  • 必須合并回:develop 和 master
  • 分支命名規(guī)范:release-*

Release 分支用來支持新的生產(chǎn)環(huán)境發(fā)布的準備工作。允許在最后階段產(chǎn)生提交點(dotting i's)和交匯點(crossing t's)。而且允許小幅度的問題修復以及準備發(fā)布時的meta數(shù)據(jù)(比如版本號、發(fā)布日期等)。在 release 分支做了上述這些工作之后,develop 分支會被“翻篇兒”,開始接收下一次發(fā)布的新特性。

我們選擇(幾近)完成所有預期的開發(fā)的時候,作為從 develop 派發(fā)出 release 分支的時機。最起碼所有準備構建發(fā)布的功能都已經(jīng)及時合并到了 develop 分支。而往后才會發(fā)布的功能則不應該合并到 develop 分支——他們必須等到 release 分支派發(fā)出去之后再做合并。

在一個 release 分支的開始,我們就賦予其一個明確的版本號。直到該分支創(chuàng)建之前,develop 分支上的描述都是“下一次”release 的改動,但這個“下一次”release 其實也沒說清楚是 0.3 release 還是 1.0 release。而在一個 release 分支的開始時這一點就會確定。這將成為有關項目版本號晉升的一個守則。

創(chuàng)建一個 release 分支

Release 分支派發(fā)自 develop 分支。比如,我們當前的生產(chǎn)環(huán)境發(fā)布的版本是 1.1.5,馬上有一個 release 要發(fā)布了。develop 分支已經(jīng)為“下一次”release 做好了準備,并且我們已經(jīng)決定把新的版本號定為 1.2 (而不是 1.1.6 或 2.0)。所以我們派發(fā)一個 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(-)

創(chuàng)建好并切換到新的分支之后,我們完成對版本號的晉升。這里的 bump-version.sh是一個虛構的用來改變代碼庫中某些文件以反映新版本的 shell 腳本。(當然你也可以手動完成這些改變——重點是有些文件發(fā)生了改變)然后,晉升了的版本號會被提交。

這個新的分支會存在一段時間,直到它確實發(fā)布出去了為止。期間可能會有 bug 修復(這比在 develop 做更合理)。但我們嚴格禁止在此開發(fā)龐大的新特性,它們應該合并到 develop 分支,并放入下次發(fā)布。

完成一個 release 分支

當 release 分支真正發(fā)布成功之后,還有些事情需要收尾。首先,release 分支會被合并到 master (別忘了,master 上的每一次提交都代表一個真正的新的發(fā)布);然后,為master 上的這次提交打一個 tag,以便作為版本歷史的重要參考;最后,還要把 release 分支產(chǎn)生的改動合并回 develop,以便后續(xù)的發(fā)布同樣包含對這些 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

現(xiàn)在發(fā)布工作已經(jīng)完成了,同時 tag 也打好了,用在未來做參考。

補充:你也可以通過 -s 或 -u <key> 標記打 tag。

為了保留 release 分支里的改動記錄,我們需要把這些改動合并回 develop。git 操作如下:

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

這一步有可能導致沖突的發(fā)生(只是有理論上的可能性,因為我們已經(jīng)改變了版本號),一旦發(fā)現(xiàn),解決沖突然后提交就好了。

現(xiàn)在我們真正完成了一個 release 分支,該把它刪掉了,因為它的使命已經(jīng)完成了:

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

hotfix 分支



  • 可能派發(fā)自:master
  • 必須合并回:develop 和 master
  • 分支命名規(guī)范:hotfix-*

Hotfix 分支和 release 分支非常類似,因為他們都意味著會產(chǎn)生一個新的生產(chǎn)環(huán)境的發(fā)布,盡管 hotfix 分支不是先前就計劃好的。他們在實時的生產(chǎn)環(huán)境版本出現(xiàn)意外需要快速響應時,從 master 分支相應的 tag 被派發(fā)。

我們這樣做的根本原因,是為了讓團隊其中一個人來快速修復生產(chǎn)環(huán)境的問題,其他成員可以按工作計劃繼續(xù)工作下去而不受太大影響。

創(chuàng)建一個 hotfix 分支

Hotfix 分支創(chuàng)建自 master 分支。例如,假設 1.2 版本是目前的生產(chǎn)環(huán)境且出現(xiàn)了一個嚴重的 bug,但是目前的 develop 并不足夠穩(wěn)定。那么我們可以派發(fā)出一個 hotfix 分支來開始我們的修復工作:

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

別忘了在派發(fā)出分支之后晉升版本號!

然后,修復 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 分支

當我們完成之后,對 bug 的修復需要合并回 master,同時也需要合并回 develop,以保證接下來的發(fā)布也都已經(jīng)解決了這個 bug。這和 release 分支的完成方式是完全一樣的。

首先,更新 master 并為本次發(fā)布打一個 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> 標記打 tag。

然后,把已修復的 bug 合并到 develop:

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

這個規(guī)矩的一個額外之處是:如果此時已經(jīng)存在了一個 release 分支,那么 hotfix 的改變需要合并到這個 release 分支,而不是 develop 分支。因為把對 bug 的修復合并回 release 分支之后,release 分支最終還是會合并回 develop 分支的。(如果在 develop 分支中立刻需要對這個 bug 的修復,且等不及 release 分支合并回來,則你還是可以直接合并回 develop 分支的,這是絕對沒問題的)

最后,刪掉這個臨時的分支:

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

摘要

其實這個分支模型里沒有什么新奇的東西。文章開頭的那張大圖對我們的項目來說非常有用。它非常易于團隊成員理解這個優(yōu)雅有效的模型,并在團隊內部達成共識。

這里還有一份那張大圖的 高清PDF版本,你可以把它當做手冊放在手邊快速瀏覽。

補充:還有,如果你們需要的話,這里還有一份 Keynote 版本


本文譯自:A successful Git branching model ? nvie.com


以上內容是否對您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號
微信公眾號

編程獅公眾號