Git 分支的最佳實踐

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


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

image.png

在我的演示中咖祭,所有的操作都是通過 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)被包含在第三章(基礎(chǔ))里了厢绝。

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

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

分散但也集中

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

每一位開發(fā)者都向 origin 這個中心結(jié)點 pull 和 push泻轰。但是除此之外技肩,每一位開發(fā)者也可以向其它結(jié)點 pull 改變形成子團隊。比如浮声,對于兩個以上開發(fā)者同時開發(fā)一項大的新特性來說虚婿,為了不必過早向 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 自動化任務(wù)的來源软啼。

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

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

支持型分支

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

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

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

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

這些“特殊”的分支在技術(shù)上是沒有任何特殊的辆床。分支的類型取決于我們?nèi)绾芜\用它們。它們完完全全都是普通而又平凡的 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)了這一特性——你可能不得不查看每一筆提交日志蚂维。恢復(fù)一個完整的特性(比如通過一組提交)在右邊變成了一個頭疼事情路狮,而如果使用了 --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ù)以及準備發(fā)布時的meta數(shù)據(jù)(比如版本號、發(fā)布日期等)箕般。在 release 分支做了上述這些工作之后耐薯,develop 分支會被“翻篇兒”,開始接收下一次發(fā)布的新特性。

我們選擇(幾近)完成所有預(yù)期的開發(fā)的時候曲初,作為從 develop 派發(fā)出 release 分支的時機体谒。最起碼所有準備構(gòu)建發(fā)布的功能都已經(jīng)及時合并到了 develop 分支。而往后才會發(fā)布的功能則不應(yīng)該合并到 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 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 是一個虛構(gòu)的用來改變代碼庫中某些文件以反映新版本的 shell 腳本。(當然你也可以手動完成這些改變——重點是有些文件發(fā)生了改變)然后骄瓣,晉升了的版本號會被提交停巷。

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

完成一個 release 分支

當 release 分支真正發(fā)布成功之后,還有些事情需要收尾妨托。首先缸榛,release 分支會被合并到 master (別忘了,master 上的每一次提交都代表一個真正的新的發(fā)布)兰伤;然后内颗,為 master 上的這次提交打一個 tag,以便作為版本歷史的重要參考敦腔;最后均澳,還要把 release 分支產(chǎn)生的改動合并回 develop,以便后續(xù)的發(fā)布同樣包含對這些 bug 的修復(fù)。

前兩部在 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)

這一步有可能導(dǎo)致沖突的發(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)意外需要快速響應(yīng)時得糜,從 master 分支相應(yīng)的 tag 被派發(fā)敬扛。

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

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

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

$ 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ā)出分支之后晉升版本號侮邀!

然后坏怪,修復(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 的修復(fù)需要合并回 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。

然后况芒,把已修復(fù)的 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 的修復(fù)合并回 release 分支之后耐版,release 分支最終還是會合并回 develop 分支的祠够。(如果在 develop 分支中立刻需要對這個 bug 的修復(fù),且等不及 release 分支合并回來粪牲,則你還是可以直接合并回 develop 分支的古瓤,這是絕對沒問題的)

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

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

摘要
其實這個分支模型里沒有什么新奇的東西腺阳。文章開頭的那張大圖對我們的項目來說非常有用落君。它非常易于團隊成員理解這個優(yōu)雅有效的模型,并在團隊內(nèi)部達成共識亭引。
這里還有一份那張大圖的 高清PDF版本绎速,你可以把它當做手冊放在手邊快速瀏覽。
補充:還有焙蚓,如果你們需要的話纹冤,這里還有一份 Keynote 版本

Git 分支的最佳實踐

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市购公,隨后出現(xiàn)的幾起案子萌京,更是在濱河造成了極大的恐慌,老刑警劉巖宏浩,帶你破解...
    沈念sama閱讀 212,542評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件知残,死亡現(xiàn)場離奇詭異,居然都是意外死亡比庄,警方通過查閱死者的電腦和手機求妹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,596評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來印蔗,“玉大人扒最,你說我怎么就攤上這事丑勤』冢” “怎么了?”我有些...
    開封第一講書人閱讀 158,021評論 0 348
  • 文/不壞的土叔 我叫張陵法竞,是天一觀的道長耙厚。 經(jīng)常有香客問我,道長岔霸,這世上最難降的妖魔是什么薛躬? 我笑而不...
    開封第一講書人閱讀 56,682評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮呆细,結(jié)果婚禮上型宝,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好趴酣,可當我...
    茶點故事閱讀 65,792評論 6 386
  • 文/花漫 我一把揭開白布梨树。 她就那樣靜靜地躺著,像睡著了一般岖寞。 火紅的嫁衣襯著肌膚如雪抡四。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,985評論 1 291
  • 那天仗谆,我揣著相機與錄音指巡,去河邊找鬼。 笑死隶垮,一個胖子當著我的面吹牛藻雪,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播岁疼,決...
    沈念sama閱讀 39,107評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼阔涉,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了捷绒?” 一聲冷哼從身側(cè)響起瑰排,我...
    開封第一講書人閱讀 37,845評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎暖侨,沒想到半個月后椭住,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,299評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡字逗,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,612評論 2 327
  • 正文 我和宋清朗相戀三年京郑,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片葫掉。...
    茶點故事閱讀 38,747評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡些举,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出俭厚,到底是詐尸還是另有隱情户魏,我是刑警寧澤,帶...
    沈念sama閱讀 34,441評論 4 333
  • 正文 年R本政府宣布挪挤,位于F島的核電站叼丑,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏扛门。R本人自食惡果不足惜鸠信,卻給世界環(huán)境...
    茶點故事閱讀 40,072評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望论寨。 院中可真熱鬧星立,春花似錦爽茴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,828評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至辕坝,卻和暖如春窍奋,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酱畅。 一陣腳步聲響...
    開封第一講書人閱讀 32,069評論 1 267
  • 我被黑心中介騙來泰國打工琳袄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人纺酸。 一個月前我還...
    沈念sama閱讀 46,545評論 2 362
  • 正文 我出身青樓窖逗,卻偏偏與公主長得像,于是被迫代替她去往敵國和親餐蔬。 傳聞我的和親對象是個殘疾皇子碎紊,可洞房花燭夜當晚...
    茶點故事閱讀 43,658評論 2 350

推薦閱讀更多精彩內(nèi)容