成功的GIT開發分支模型和策略

世有因果知因求果發表於2015-05-17

詳細圖文並茂以及git flow工具解釋參考: http://danielkummer.github.io/git-flow-cheatsheet/index.zh_CN.html

原文地址:http://nvie.com/posts/a-successful-git-branching-model/  

本文中我將介紹我在多年的專案開發中使用的開發模型,這個模型被實踐檢驗為正確有效的模式。本文中,我將不會涉及到專案的任何細節,只討論關於分支策略和release管理

為什麼要使用Git?

網上有很多關於Git和集中式程式碼管理控制系統的比較,作為一個開發人員,我更喜歡Git,Git實際上顛覆了開發人員關於merge/branch的想法。從經典的CVS/SVN世界過來,merging/branching總是被認為是一個痛苦和令人膽寒的事情。但是在Git中,這些行為是非常簡單和代價低廉的。比如在CVS/SVN的書籍中,branching和merging往往都在最後幾章中討論(專為高階使用者所寫),而在每一本Git的書籍中,這些merge/branch總在前三章中介紹(因為它們很基礎)。

正是源於這些操作在Git中簡單,重複的特性,branching/merging不再是一個令人生畏的事情。

Decenteralized but cdentralized:

庫作為中央“truth”repo.在Git中,雖然這個repo可能被認為是中央的repo,但是你要明白GIT是一個分散式的版本控制系統,也就是說從技術角度來說沒有這個central repo!我們更確切地將這個central repo稱為origin

 

每個開發人員pull/push to origin.但是除了中央式的pull/push關係,每一個開發人員之間也可能從他們的子team之間pull changes。例如,對於兩個或更多的開發人員同時工作在一個大的feature上,這種模型就更加有用了(特別是正在工作中的feature程式碼永久性push到origin前)。上面的圖中,在alice和bob,alice和david,clair和david之間形成了subteam。

技術上說,實際上就是Alice定義了一個Git remote, 命名為bob,指向bob的repo,同樣反之亦然。

The main branches

核心上說,開發模型被已有的模型所啟發,中央repo將永久保留兩個主要的branches:

  • master
  • develop

origin上的master分支對於每一個GIT使用者來說是非常熟悉的.和master branch平行的,有一個另外叫做develop的分支。

我們考慮orgin/master作為main branch,這個分支上的HEAD總是反映了一個production-ready的狀態;

我們將origin/develop視作一個HEAD永遠指示為了下一個release而最近遞交的開發變化。有些人比較喜歡將此稱為integration branch.在這個分支上,每日的build將從這裡構建。

當在develop分支上的程式碼達到一個穩定狀態點,達到可以釋出的狀態時,所有的變更都應該merge back到master,並且在master上tag一個release number。

這樣每次當變更merge back到master時,這就是一個新的production release by definition.我們更傾向於對此非常嚴格,因此理論上,我們可以使用一個Git hook sciprt實現每當有一個向master的commit,我們自動構建和部署master上的軟體到生產環境上去(因為master總是一個Production ready的狀態)

Supporting branches:

除了main branches: master和develop,我們的開發模型中也將使用一系列的supporting branches以便支援在team member之間實現並行開發,更加容易地實現feature的tracking,更方便地準備產品release,更好的輔助解決產品的問題。不像main branch(master,develop),這些branch總是有一個生命週期,因為他們最終將被刪除。

我們建議以下幾種型別的分支:

  • feature branches;
  • release branches
  • hotfix branches

每種非main branch都有一個特定的目的,並且有嚴格的定義規則:這個分支應該從那個分支來建立,以及該分支最終的merge targets都有明確的定義。下面我們將明確說明。

從技術角度來說,這些branch本身並無特別之處。這些branch的型別是由"how we use them"來分類的。他們就是簡單的普通的Git branch.

Feature branches:

May branch off from: develop;

Must merge back into: develop;

Branch naming convention: 任何除了master,develop,release-*,hotfix-*之外都可以

Feature branch(或者被叫做topic branch)被用來為了即將到來的(upcoming)或者長遠計劃的release而做開發之用的。當開始開發一個feature時,這個feature的target release(目標release)在那時可能根本還確定。feature branch的本質是:只要feature仍在開發中,它將一直存續,但是最終它將被merged back到dvelop分支(to definitely add the new feature to the upcoming release)或者被discarded(in case of a disappointing experiment)。

Feature branche典型地只存在於開發人員的repo中,並不在origin repo中存在。

當開始一個新的feature開發時,feature branch從develop branch中建立出來

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

結束的feature將被merged into the develop branch以便在即將到來的release中新增這個feature.

$ 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 flag使得這個merge動作即便是這個merge屬於fast-forward(no conflict)也總會產生一個新的commit object。這樣就避免了關於曾近為了開發一個新的feature而做了feature branch並遞交了多個檔案的歷史。

上圖中,後一個fast-forward merge的場景,你將無法得知哪些commit objects最終形成了那個feature---你可能必須手動的查閱所有的log資訊才能得知。同樣在後一個場景中,你想rollback一個feature也是一個非常頭疼的問題,然而如果我們增加了--no--ff flag則很容易做到這些。

當然,使用--no-ff flag確實會產生一些可能是空的commit objects,但是這個壞處相比其帶來的好處是可以忍受的。

Release branches:

May branch off from: develop

Must merge back into: develop and master

Branch naming convention: release-*

注意release branch一般命名只包含major+minor,而不包含patch。比如release-1.0,release-2.3,對於release-1.5.1這樣的tag只存在於hotfix生成的版本中

Release branches支援準備一個新的產品release而存在。They allow for last-minute dotting of i’s and crossing t’s.而且,他們允許曉得bug fix並且為那個release準備一些meta-data(比如version number, build dates,etc)。這些工作都在一個release branch上做,那麼develop分支總是很清晰地準備為下一個大的release接受新的feature.

開始從develop branch off一個新的release branch的時機往往是在當develop基本反映了新的release的可接受狀態時。至少所有的必須在這個release中deliver的feature必須被merge到了develop分支,這個時刻就是建立release branch的時機。所有為將來release計劃的feature都不能merge下來直到上一個release branch已經被建立。

通常直到將要到來的release被賦予一個版本號,這將是這個release branch的一個起點。直到那個時刻,develop分支反映了下一個release的變化,但是關於是否那個下一個release會最終變成0.3或者1.0並不是很清晰,直到release branch開始工作。這個決定在release branch 開始時決定下來,由專案的版本號命名規則來決定

Release branch由develop branch來建立。比如,版本v1.1.5是當前產品release,而我們將有一個大的release將被release。develop分支的狀態已經為下一個release做好了準備,而我們決定這個release將命名為v1.2(而不是v1.1.6或者v2.0)。那麼我們可以branch off並且給這個release branch一個1.2的名稱:

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

在建立一個新的release branch並且切換到該branch後,我們bump the version number。這裡bump-verison.sh是一個虛構的shell指令碼,該指令碼更改一些檔案以便反映新的版本號變更。(這當然也可以是一個手工的變更)然後,這個bumped version number is commited.

這個新的branch可能會存續一段時間,直到這個release被正式rollout.在這個時間段內,bugfix可能會被應用到這個release branch上去(而不是應用到develop branch上去)。在這裡增加大的新功能將被嚴格禁止。這些bugfix必須被merge到develop分支上去,這樣又開始等待下一個大的release.

當releasea branch的狀態真正達到實際能夠release質量時,也需要做一些工作。首先,release branch需要merge到master(可能包含一些bug fix)(since every commit on master is a new release by definition, remember!)。接著,那個對master的commit必須被打上tag,以便該歷史版本將來能夠容易地被引用。最後,在release branch上做的bugfix變更需要被merge到develop上去,以便將來的release中包含這些bugfix。

前兩步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

 

release現在已經結束了,並且被打了標籤1.2以便將來引用

為了將release branch上做的bugfix保持好,我們需要將這些fix merge到develop分支上:

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

在這一步,我們可能會碰到merge conflict(probably even, since we have changed the version number),如果發現conflict,則解決它並且commit掉

現在我們確實完成了這個release的工作,release branch可以被刪除了:

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

Hotfix branches:

May branch off from:

master

Must merge back into:

develop and master

Branch naming convention:

hotfix-*

 hotfix branch和release branch非常相似,因為他們本身也是用於為準備一個新的產品release而存在的(儘管是非計劃的,往往由某個release現場發現緊急問題而觸發的)。他們往往是由一個現場執行的產品release上報的問題來觸發產生的。當一個critical bug必須馬上解決時,一個hotfix branch將從master branch中的相應tag版本處branch off出來。這樣做的本質是:團隊其他成員可以在develop branch上繼續工作,而指定團隊一個人在這個hotfix branch上準備一個快速產品fix版本。

hotfix branch由master branch來建立。比如,假設v1.2是當前報嚴重問題的產品release版本,但是由於在develp branch上的變更還不穩定,所以我們開始從master上拉一個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 number after branching off~!

然後,fix the bug,commit the fix in one or more seperate commits:

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

Finishing a hotfix branch:

當完成該問題的fix時,bugfix需要merged back到master中去,同時也需要merge到develop branch中去,只有這樣才能保證bugfix本身將會在下一個release中自動包含。這和release branch結束的動作是一樣的。

首先,更新master並且tag the release:

$ 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

下一步,也需要同時包含bugfix到develop分支中去:

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

在這裡有一個例外的rule:當一個release branch當前存續,那麼這個hotfix change需要merge到那個release branch中去,而不是develop分支。Back-merging the bugfix into the release branch will eventually result in the bugfix being merged into the develop too, when the release branch is finished, (If work in develop immediately requires this bugfix and cannot wait for the release branch to be finished, you may safely merge the bugfix into the develop now already as well.)

最後,刪除這個臨時的branch

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

原創:在github上大型專案團隊branch策略:

Summary:

雖然本篇文章本身對於分支策略並無令人震驚之處,但是那個big picture圖片確實對我們的專案成功有非常重要的意義。它構建了一個優雅地心智模型,而該模型易於理解並且允許團隊充分理解整個產品的branching/releasing流程。

 兩個team member有兩個repo,互為remote,當git push時,有可能出現conflict,這時需要做的事情是必須先git pull操作,解決衝突以後,再做git push。這和在一個repo時merge衝突是類似的。

https://github.com/nvie/gitflow 是一個對git擴充的flow慣例,非常實用

相關文章