譯自:A successful Git branching model ? nvie.com
本文將展示我一年前在自己的項目中成功運用的開發(fā)模型荆忍。我一直打算把這些東西寫出來导而,但總是沒有抽出時間辕录,現(xiàn)在終于寫好了玫鸟。這里介紹的不是任何項目的細節(jié)退渗,而是有關(guān)分支的策略以及對發(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伴嗡,這些內(nèi)容已經(jīng)被包含在第三章(基礎)里了急波。
因為它的簡單直接和重復性,分支和合并不再令人害怕瘪校。版本控制工具比其它任何東西都支持分支/合并澄暮。
有關(guān)工具就介紹到這里名段,我們現(xiàn)在進入開發(fā)模型這個正題。我要展現(xiàn)的模型本質(zhì)上無外乎是一個流程的集合泣懊,每個團隊成員都有必要遵守這些流程伸辟,來達到管理軟件開發(fā)流程的目的。
分散但也集中
我們的分支模型中使用良好的代碼庫的設置方式馍刮,是圍繞一個真實的中心代碼庫的信夫。注意,這里的代碼庫僅僅被看做是一個中心代碼庫(因為 git 是 DVCS渠退,即分散版本控制系統(tǒng)忙迁,從技術(shù)層面看脐彩,是沒有所謂的中心代碼庫的)碎乃。我們習慣于把這個中心代碼庫命名為 origin
,這同時也是所有 git 用戶的習慣惠奸。
每一位開發(fā)者都向 origin這個中心結(jié)點 pull 和 push梅誓。但是除此之外,每一位開發(fā)者也可以向其它結(jié)點 pull 改變形成子團隊佛南。比如梗掰,對于兩個以上開發(fā)者同時開發(fā)一項大的新Feature來說,為了不必過早向 origin推送開發(fā)進度嗅回,這就非常有用及穗。在上面的這個例子中,Alice 和 Bob绵载、Alice 和 David埂陆、Clair 和 David 都是這樣的子團隊。
從技術(shù)角度娃豹,這無非意味著 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)境自動化構(gòu)建并發(fā)布我們的軟件。
支持型分支
我們的開發(fā)模型里着帽,緊接著 master和 develop主分支的杂伟,是多種多樣的支持型分支。它們的目的是幫助團隊成員并行處理每次追蹤Feature仍翰、準備發(fā)布赫粥、快速修復線上問題等開發(fā)任務。和之前的主分支不同予借,這些分支的生命周期都是有限的越平,它們最終都會被刪除掉。
我們可能會用到的不同類型的分支有:
- feature 分支
- release 分支
- hotfix 分支
每一種分支都有一個特別的目的灵迫,并且有嚴格的規(guī)則秦叛,諸如哪些分支是它們的起始分支、哪些分支必須是它們合并的目標等瀑粥。我們快速把它們過一遍挣跋。
這些“特殊”的分支在技術(shù)上是沒有任何特殊的。分支的類型取決于我們?nèi)绾?em>運用它們狞换。它們完完全全都是普通而又平凡的 git 分支避咆。
feature 分支
- 可能派發(fā)自:develop
- 必須合并回:develop
- 分支命名規(guī)范:除了 master、develop哀澈、release-或 hotfix-的任何名字
Feature 分支(有時也被稱作 topic 分支)用來開發(fā)包括即將發(fā)布或遠期發(fā)布的新的Feature牌借。當我們開始開發(fā)一個Feature的時候,發(fā)布合并的目標可能還不太確定割按。Feature 分支的生命周期會和新Feature的開發(fā)周期保持同步膨报,但是最終會合并回 develop (恩,下次發(fā)布的時候把這個新Feature帶上)或被拋棄(真是一次杯具的嘗試啊)适荣。
Feature 分支通常僅存在于開發(fā)者的代碼庫中现柠,并不出現(xiàn)在 origin里。
創(chuàng)建一個 feature 分支
當開始一個新feature的時候弛矛,從 develop分支派發(fā)出一個分支
$ git checkout -b myfeature develop
Switched to a new branch "myfeature"
把完成的feature合并回 develop
完成的feature可以合并回 develop
分支并趕上下一次發(fā)布:
$ git checkout developSwitched to a new branch "develop"
$ git merge --no-ff myfeatureUpdating ea1b82a..05e9557(Summary of changes)
$ git branch -d myfeatureDeleted branch myfeature (was 05e9557)$ git push origin develop
-no-ff 標記使得合并操作總是產(chǎn)生一次新的提交够吩,哪怕合并操作可以快速完成。這個標記避免將 feature 分支和團隊協(xié)作的所有提交的歷史信息混在主分支的其它提交之后丈氓。比較一下:
在右邊的例子里周循,我們不可能從 git 的歷史記錄中看出來哪些提交實現(xiàn)了這一Feature——你可能不得不查看每一筆提交日志强法。恢復一個完整的Feature(比如通過一組提交)在右邊變成了一個頭疼事情湾笛,而如果使用了 --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ā)布的新Feature关霸。
我們選擇(幾近)完成所有預期的開發(fā)的時候,作為從 develop
派發(fā)出 release分支的時機杰扫。最起碼所有準備構(gòu)建發(fā)布的功能都已經(jīng)及時合并到了 develop分支队寇。而往后才會發(fā)布的功能則不應該合并到 develop分支——他們必須等到 release分支派發(fā)出去之后再做合并。
在一個 release分支的開始章姓,我們就賦予其一個明確的版本號佳遣。直到該分支創(chuàng)建之前,develop分支上的描述都是“下一次”release 的改動凡伊,但這個“下一次”release 其實也沒說清楚是 0.3 release 還是 1.0 release零渐。而在一個 release 分支的開始時這一點就會確定。這將成為有關(guān)項目版本號晉升的一個守則系忙。
創(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 developSwitched to a new branch "release-1.2"
$ ./bump-version.sh 1.2Files 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.21 files changed, 1 insertions(+), 1 deletions(-)
創(chuàng)建好并切換到新的分支之后戒财,我們完成對版本號的晉升。這里的 bump-version.sh是一個虛構(gòu)的用來改變代碼庫中某些文件以反映新版本的 shell 腳本捺弦。(當然你也可以手動完成這些改變——重點是有些文件發(fā)生了改變)然后饮寞,晉升了的版本號會被提交孝扛。
這個新的分支會存在一段時間,直到它確實發(fā)布出去了為止幽崩。期間可能會有 bug 修復(這比在 develop做更合理)疗琉。但我們嚴格禁止在此開發(fā)龐大的新Feature,它們應該合并到 develop分支歉铝,并放入下次發(fā)布盈简。
完成一個 release 分支
當 release 分支真正發(fā)布成功之后,還有些事情需要收尾太示。首先柠贤,release 分支會被合并到 master(別忘了,master上的每一次提交都代表一個真正的新的發(fā)布)类缤;然后臼勉,為 master上的這次提交打一個 tag,以便作為版本歷史的重要參考餐弱;最后宴霸,還要把 release 分支產(chǎn)生的改動合并回 develop,以便后續(xù)的發(fā)布同樣包含對這些 bug 的修復膏蚓。
前兩部在 git 下是這樣操作的:
$ git checkout masterSwitched to branch 'master'
$ git merge --no-ff release-1.2Merge 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 developSwitched to branch 'develop'
$ git merge --no-ff release-1.2Merge made by recursive.(Summary of changes)
這一步有可能導致沖突的發(fā)生(只是有理論上的可能性采郎,因為我們已經(jīng)改變了版本號),一旦發(fā)現(xiàn)狂魔,解決沖突然后提交就好了蒜埋。
現(xiàn)在我們真正完成了一個 release 分支,該把它刪掉了最楷,因為它的使命已經(jīng)完成了:
$ git branch -d release-1.2Deleted 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 masterSwitched to a new branch "hotfix-1.2.1"
$ ./bump-version.sh 1.2.1Files 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.11 files changed, 1 insertions(+), 1 deletions(-)
別忘了在派發(fā)出分支之后晉升版本號德迹!
然后芽卿,修復 bug,提交改動胳搞。通過一個或多個提交都可以卸例。
$ git commit -m "Fixed severe production problem"[hotfix-1.2.1 abbe5d6] Fixed severe production problem5 files changed, 32 insertions(+), 17 deletions(-)
完成一個 hotfix 分支
當我們完成之后,對 bug 的修復需要合并回 master肌毅,同時也需要合并回 develop筷转,以保證接下來的發(fā)布也都已經(jīng)解決了這個 bug。這和 release 分支的完成方式是完全一樣的悬而。
首先呜舒,更新 master并為本次發(fā)布打一個 tag:
$ git checkout masterSwitched to branch 'master'
$ git merge --no-ff hotfix-1.2.1Merge made by recursive(Summary of changes)
$ git tag -a 1.2.1
補充:你也可以通過 -s或 -u <key> 標記打 tag。
然后笨奠,把已修復的 bug 合并到 develop:
$ git checkout developSwitched to branch 'develop'
$ git merge --no-ff hotfix-1.2.1Merge 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.1Deleted branch hotfix-1.2.1 (was abbe5d6).
摘要
其實這個分支模型里沒有什么新奇的東西。文章開頭的那張大圖對我們的項目來說非常有用段誊。它非常易于團隊成員理解這個優(yōu)雅有效的模型闰蚕,并在團隊內(nèi)部達成共識。
這里還有一份那張大圖的 高清PDF版本连舍,你可以把它當做手冊放在手邊快速瀏覽没陡。
補充:還有,如果你們需要的話索赏,這里還有一份 Keynote 版本