本文中我會展示一種開發模型,一年前該模型就已經被我用在所有的項目中(包括工作中的項目和私有項目),結果是非常成功的。我早就想為此寫點東西,可直到現在才有時間。本文不會講述任何項目的細節,隻會涉及到分支政策和釋出管理。

git-branch-1
<a target="_blank"></a>
簡單及易重複性帶來的好處就是,分支及合并變得不再可怕。版本控制工具本該幫助我們友善的進行和分支及合并操作。
簡單介紹下工具後,讓我們來看開發模型。我講介紹的模型本質上隻是一組步驟,每個團隊成員都必須遵循這些步驟以形成一個可靠管理的軟體開發過程。
在這個分支模型中我們使用的,且被證明工作得很好的倉庫配置,其核心是一個中心“真理”倉庫。注意隻有該倉庫才被認為是中心庫(由于git是dvcs [分布式版本控制系統],在技術層面沒有中心庫這一東西)。之後我們用origin指代該倉庫,因為大多數git使用者都熟悉這個名稱。
git-branch-2
每個開發者都對origin做push和pull操作。不過除了這種中心化的push-pull關系外,每個開發者還可以從其他開發者或者小組處pull變更。例如,可能兩個或更多的開發者一起開發一個大的特性,在往origin永久性的push工作代碼之前,他們之間可以執行一些去中心化的操作。在上圖中,分别有alice和bob、alice和david、clair和david這些小組。
從技術上來說,這僅僅是alice定義一個git remote,名字為bob,指向bob的倉庫,反過來也一樣。
git-branch-3
此開發模型的核心主要受現有的模型啟發。中心倉庫包含了兩個主要分支,這兩個分支的壽命是無限的:
master
develop
每個git用于都應該熟悉origin上的master分支。與master分支平行存在的,是另外一個名為develop的分支。
我們認為origin/develop分支上的head源碼反映了開發過程中最新的送出變更。有人會稱之為“內建分支”。該分支是自動化每日建構的代碼源。
當develop分支上的源碼到達一個穩定的狀态時,就可以釋出版本。所有develop上的變更都應該以某種方式合并回master分支,并且使用釋出版本号打上标簽。稍後我們會讨論具體操作細節。
是以,每次有變化被合并到master分支時,根據定義這就是一次新的産品版本釋出。我們趨向于嚴格遵守該規範,是以理論上來說,每次master有送出時,我們都可以使用一個git鈎子(hook)腳本來自動建構并部署軟體至産品環境伺服器。
緊接着主要分支master和develop,我們的開發模型使用多種支援性分支來幫助團隊成員間實作并行開發、追蹤産品特性、準備産品版本釋出、以及快速修複産品問題。與主要分支不同的是,這些分支的壽命是有限的,它們最終都會被删除。
我們會用到的分支有這幾類:
特性分支(feature branch)
釋出分支(release branch)
熱更新檔分支(hotfix branch)
上述每種分支都有特定的用途,它們各自關于源自什麼分支、合并回什麼分支,都有嚴格的規定。稍後我們逐個進行介紹。
從技術角度來說,這些分支一點都不“特殊”。分支按照我們對其的使用方式進行分類。技術角度它們都一樣是平常的git分支。
git-branch-4
可能的分支來源:develop
必須合并回:develop
分支指令約定:任何除master, develop, release-*, 或 hotfix-*以外的名稱
特性分支(有時也被稱作topic分支)是用來為下一釋出版本開發新特性。當開始開發一個特性的時候,該特性會成為哪個釋出版本的一部分,往往還不知道。特性分支的重點是,隻要特性還在開發,該分支就會一直存在,不過它最終會被合并回develop分支(将該特性加入到釋出版本中),或者被丢棄(如果試驗的結果令人失望)。
特性分支往往隻存在于開發者的倉庫中,而不會出現在origin。
開始開發新特性的時候,從develop分支建立特性分支。
$ git checkout -b myfeature develop switch to a new branch “myfeature”
完成的特性應該被合并回develop分支以将特性加入到下一個釋出版本中:
$ git checkout develop switch 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标記會使合并永遠建立一個新的commit對象,即使該合并能以fast-forward的方式進行。這麼做可以避免丢失特性分支存在的曆史資訊,同時也能清晰的展現一組commit一起構成一個特性。比較下面的圖:
git-branch-5
在第2張圖中,已經無法一眼從git曆史中看到哪些commit對象構成了一個特性——你需要閱讀日志以獲得該資訊。在這種情況下,回退(revert)整個特性(一組commit)就會比較麻煩,而如果使用了–no-diff就會簡單很多。
是的,這麼做會造成一些(空的)commit對象,但這麼做是利大于弊的。
可惜的是,我沒能找到方法讓–no-diff成為預設的git merge行為參數,但其實應該這麼做。
必須合并回:develop和master
分支命名約定:release-*
釋出分支為準備新的産品版本釋出做支援。它允許你在最後時刻檢查所有的細節。此外,它還允許你修複小bug以及準備版本釋出的中繼資料(例如版本号,建構日期等等)。在釋出分支做這些事情之後,develop分支就會顯得比較幹淨,也友善為下一大版本釋出接受特性。
從develop分支建立釋出分支的時間通常是develop分支(差不多)能反映新版本所期望狀态的時候。至少說,這是時候版本釋出所計劃的特性都已經合并回了develop分支。而未來其它版本釋出計劃的特性則不應該合并,它們必須等到目前的版本分支建立好之後才能合并。
正是在釋出分支建立的時候,對應的版本釋出才獲得一個版本号——不能更早。在該時刻之前,develop分支反映的是“下一版本”的相關變更,但不知道這“下一版本”到底會成為0.3還是1.0,直到釋出分支被建立。版本号是在釋出分支建立時,基于項目版本号規則确定的。
釋出分支從develop分支建立。例如,假設1.1.5是目前的産品版本,同時我們即将釋出下個版本。develop分支的狀态已經是準備好“下一版本”釋出了,我們也決定下個版本是1.2(而不是1.1.6或者2.0)。是以我們建立釋出分支,并且為其賦予一個能展現新版本号的名稱:
$ git checkout -b releases-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是一個虛構的shell腳本,它修改一些本地工作區的檔案以展現新的版本号。(當然這也可以手動完成——這裡隻是說要有一些檔案變更)接着,送出新版本号。
新的釋出分支可能存在一段時間,直到該版本明确對外傳遞。這段時間内,該分支上可能會有一些bug的修複(而不是在develop分支上)。在該分支上添加新特性是嚴格禁止的。新特性必須合并到develop分支,然後等待下一個版本釋出。
當釋出分支達到一個可以正式釋出的狀态時,我們就需要執行一些操作。首先,将釋出分支合并至master(記住,我們之前定義master分支上的每一個commit都對應一個新版本)。接着,master分支上的commit必須被打上标簽(tag),以友善将來尋找曆史版本。最後,釋出分支上的變更需要合并回develop,這樣将來的版本也能包含相關的bug修複。
前兩步在git中的操作:
$ git checkout master switched to branch ‘master’ $ git merge –no-ff release-1.2 merge made by recursive. $ git tag -a 1.2
現在版本釋出完成了,而且為未來的查閱提供了标簽。
提醒:你可能同時也會想要用 -s 或者 -u 來對标簽進行簽名。
為了能保留釋出分支上的變更,我們還需要将分支合并回develop。在git中:
$ git checkout develop switched to branch ‘develop’
這一操作可能會導緻合并沖突(可能性還很大,因為我們改變了版本号)。如果發現,則修複之并送出。
現在工作才算真正完成了,最後一步是删除釋出分支,因為我們已不再需要它:
$ git branch -d release-1.2 deleted branch release-1.2 (was ff452fe).
git-branch-6
可能的分支來源:master
分支命名約定:hotfix-*
熱更新檔分支和釋出分支十分類似,它的目的也是釋出一個新的産品版本,盡管是不在計劃中的版本釋出。當産品版本發現未預期的問題的時候,就需要了解着手處理,這個時候就要用到熱更新檔分支。當産品版本的重大bug需要立即解決的時候,我們從對應版本的标簽建立出一個熱更新檔分支。
使用熱更新檔分支的主要作用是(develop分支上的)團隊成員可以繼續工作,而另外的人可以在熱更新檔分支上進行快速的産品bug修複。
熱更新檔分支從master分支建立。例如,假設1.2是目前正在被使用的産品版本,由于一個嚴重的bug,産品引起了很多問題。同時,develop分支還處于不穩定狀态,無法釋出新的版本。這時我們可以建立一個熱更新檔分支,并在該分支上修複問題:
$ 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并使用一個或者多個單獨的commit送出。
$ git commit -m “fixed severe production problem” [hotfix-1.2.1 abbe5d6] fixed severe production problem 5 files changed, 32 insertions(+), 17 deletions(-)
修複完成後,熱更新檔分支需要合并回master,但同時它還需要被合并回develop,這樣相關的修複代碼才會同時被包含在下個版本中。這與我們完成釋出分支很類似。
首先,更新master分支并打上标簽。
$ git merge –no-ff hotfix-1.2.1 $ git tag -a 1.2.1
接着,将修複代碼合并到develop:
這裡還有個例外情況,如果這個時候有釋出分支存在,熱更新檔分支的變更則應該合并至釋出分支,而不是develop。将熱更新檔合并到釋出分支,也意味着當釋出分支結束的時候,變更最終會被合并到develop。(如果develop上的開發工作急需熱更新檔并無法等待釋出分支完成,這時你也已經可以安全地将熱更新檔合并到develop分支。)
最後,删除臨時的熱更新檔分支:
$ git branch -d hotfix-1.2.1 deleted branch hotfix-1.2.1 (was abbe5d6).
雖然這個分支模型中沒有什麼特别新鮮的東西,但本文起始處的“全景圖”事實上在我們的項目中起到了非常大的作用。它幫助建立了優雅的,易了解的概念模型,使得團隊成員能夠快速建立并了解一個公用的分支和釋出過程。
原文釋出時間為:2013-10-07
本文來自雲栖社群合作夥伴“linux中國”