在這篇文章中介紹的開發模型在大約一年前已經在我的私有專案和工作引入的,而且已經被證明是非常成功的。我想寫一些關於這個模型的東西已經好一段時間了,但是一直苦於沒有時間,不過現在可以了。我不想探討任何專案細節,只討論分支策略和釋出管理。
這篇文章圍繞著Git做為我們所有的原始碼版本控制工具而展開的。
為什麼是Git
為了深入探討git和集中式原始碼版本控制系統的利弊,參見這些文章。這方面有太多的激烈爭論。作為一個開發者,相比其他工具,當前我更喜歡Git。Git的確改變了開發者關於合併與分支的思考方式。在那些經典的CVS/Subversion管理工具的世界中,合併/分支被認為是有些嚇人的(“當心合併衝突,它們咬你!”),而且偶爾你得做些操作解決一些問題。
但是使用Git,這些操作都變得極度簡單,這些操作被認為是你日常工作流程核心部分之一。例如,在CVS/Subversion 這本書中,分支與合併在很後的章節中才被第一次討論(針對高階使用者)。但是在每一本Git書籍中,在第三章就講到了(基礎部分)。
由於它的簡單性和操作命令的重複性,分支與合併操作變得不再可怕。版本控制工具被認為在分支/合併方面提供操作便利性比什麼都重要
關於工具本身,已經討論的足夠多了,下面針對開發模型進行展開。我將要介紹的這個模型不會比任何一套流程內容多,每個團隊成員都必須遵守,這樣便於管理軟體開發過程。
既分散又集中
我們使用的,且與這個分支模型配合的非常好的庫,他有一個“真正”的中央倉庫。注意,這個庫只是被認為是中央倉庫(因為Git是一個分散式的版本控制工具,在技術層面沒有所謂的中央倉庫)。我們將會為這個倉庫起名為origin
,因為所有的Git使用者對這個名字都比較熟悉。
每個開發者從origin拉取和推送程式碼。除了集中式的推送拉取關係,每個開發者也有可能從別的開發者處拉取程式碼,形成自己的團隊。例如當與兩個或者更多的人開發一個大的特性時,或者在將程式碼推送到origin
之前,這種程式碼管理模式可能有用。在上圖中,存在Alice和Bob,Alice和David,Clair 和David三個子團隊
技術上而言,這只不過意味著Alice定義了一個遠端Git倉庫,起名為bob,實際上指向Bob的版本庫,反之亦然(Bob定義了一個遠端Git倉庫,起名為alice,實際上指向Alice的版本庫)。
主分支
老實說,我們討論的開發模型受到了當前已存在模型的很大啟發。集中式的版本庫有兩個永久存在的主分支:
master分支
develop分支
分支每個Git使用者都很熟悉。平行的另外一個分支叫做origin
的masterdevelop
分支。
我們認為origin/master
這個分支上HEAD
引用所指向的程式碼都是可釋出的。
我們認為origin/develop
這個分支上HEAD
引用所指向的程式碼總是反應了下一個版本所要交付特性的最新的程式碼變更。一些人管它叫“整合分支”。它也是自動構建系統執行構建命令的分支。
當develop
分支上的程式碼達到了一個穩定狀態,並且準備釋出時,所有的程式碼變更都應該合併到master分支,然後打上釋出版本號的tag。具體如何進行這些操作,我們將會討論
因此,每次程式碼合併到master分支時,它就是一個人為定義的新的釋出產品。理論上而言,在這我們應該非常嚴格,當master分支有新的提交時,我們應該使用Git的鉤子指令碼執行自動構建命令,然後將軟體推送到生產環境的伺服器中進行釋出。
輔助性分支
緊鄰master
和develop
分支,我們的開發模型採用了另外一種輔助性的分支,以幫助團隊成員間的並行開發,特性的簡單跟蹤,產品的釋出準備事宜,以及快速的解決線上問題。不同於主分支,這些輔助性分支往往只要有限的生命週期,因為他們最終會被刪除。
我們使用的不同型別分支包括:
- 特性分支
- Release分支
- Hotfix 分支
上述的每一個分支都有其特殊目的,也繫結了嚴格的規則:哪些分支是自己的拉取分支,哪些分支是自己的目標合併分支。
從技術角度看,這些分支的特殊性沒有更多的含義。只是按照我們的使用方式對這些分支進行了歸類。他們依舊是原Git分支的樣子。
特性分支
特性分支可以從develop分支拉取建立,最終必須合併會develop分支。特性分支的命名,除了 master
, develop
, release-*
,或hotfix-*
以外,可以隨便起名。
特性分支(有時候也成主題分支)用於開發未來某個版本新的特性。當開始一個新特性的開發時,這個特性未來將釋出於哪個目標版本,此刻我們是不得而知的。特性分支的本質特徵就是隻要特性還在開發,他就應該存在,但最終這些特性分支會被合併到develop分支(目的是在新版本中新增新的功能)或者被丟棄(它只是一個令人失望的試驗)
特性分支只存在開發者本地版本庫,不在遠端版本庫。
建立特性分支
當開始開發一個新特性時,從develop分支中建立特性分支
1 2 |
$ git checkout -b myfeature develop Switched to a new branch "myfeature" |
在develop分支整合已經開發完成的特性
開發完成的特性必須合併到develop分支,即新增到即將釋出的版本中。
1 2 3 4 5 6 7 8 |
$ 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
引數的作用是在合併的時候,會建立一個新的提交物件,即使是fast-forward方式的合併。這就避免了丟失特性分支的歷史記錄資訊以及提交記錄資訊。比較一下
在右面的例子中,是不可能從Git歷史記錄中看到一個已經實現了的特性的所有提交物件-除非你去檢視所有的日誌資訊。要想獲取整個特性分支資訊,在右面的例子中的確是一個頭疼的問題,但是如果使用--no-ff
引數就沒有這個問題。
使用這個引數後,的確建立了一些新的提交物件(那怕是空提交物件),但是很值得。
不幸的是,我還沒有找到一種方法使Git預設的merge操作帶著--no-ff
引數,但的確應該這樣。
釋出分支
從develop
分支去建立Release分支,Release分支必須合併到develop分支和master分支,Release分支名可以這樣起名:release-*。
Release分支用於支援一個新版本的釋出。他們允許在最後時刻進行一些小修小改。甚至允許進行一些小bug的修改,為新版本的釋出準要一些後設資料(版本號,構建時間等)。通過在release分支完成這些工作,develop
分支將會合並這些特性以備下一個大版本的釋出。
從develop
分支拉取新的release分支的時間點是當開發工作已經達到了新版本的期望值。至少在這個時間點,下一版本準備釋出的所有目標特性必須已經合併到了develop
分支。更遠版本的目標特性不必合併會develop分支。這些特性必須等到個性分支建立後,才能合併回develop分支
在release分支建立好後,就會獲取到一個分配好即將釋出的版本號,不能更早,就在這個時間點。在此之前,develop分支程式碼反應出了下一版本的程式碼變更,但是到底下一版本是 0.3 還是 1.0,不是很明確,直到release分支被建立後一切都確定了。這些決定在release分支開始建立,專案版本號等專案規則出來後就會做出。
建立release分支
從develop
分支建立release分支。例如1.1.5版本是當前產品的釋出版本,我們即將釋出一個更大的版本。develop
分支此時已經為下一版本準備好了,我們決定下一版的版本號是1.2(1.1.6或者2.0也可以)。所以我們建立release分支,並給分支賦予新的版本號:
1 2 3 4 5 6 7 |
$ 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(-) |
建立好分支並切到這個分支後,我們給分支打上版本號。bump-version.sh是一個虛構的shell指令碼,它更改了工作空間的
某些檔案來
反映新版本特徵。(當然也可以手動改變這些檔案),然後版本就被提交了。
新的分支會存在一段時間,直到新版本最終釋出。在這段時間裡,bug的解決可以在這個分支進行(不要在develop
分支進行)。此時是嚴禁新增新的大特性。這些修改必須合併回develop分支,之後就等待新版本的釋出。
結束一個release分支
當release分支的準備成為一個真正的釋出版本時,一些操作必須需要執行。首先,將release分支合併回master
分支(因為master
分支的每一次提交都是預先定義好的一個新版本,謹記)。然後為這次提交打tag,為將來去檢視歷史版本。最後在release分支做的更改也合併到develop分支,這樣的話,將來的其他版本也會包含這些已經解決了的bug。
在Git中需要兩步完成:
1 2 3 4 5 6 |
$ 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分支已經完成工作,tag也已經打了。
備註:你可以使用-s
or -u <key>
引數為你的tag設定標籤簽名。
為了儲存這些在release分支所做的變更,我們需要將這些變更合併回develop分支。執行如下Git命令:
1 2 3 4 5 |
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff release-1.2 Merge made by recursive. (Summary of changes) |
這步有可能會有合併衝突(極有可能,因為我們已經改變了版本號)。如果有衝突,解決掉他,然後提交。
現在我們已經完成了工作,release分支可以刪除了,因為我們不在需要他:
1 2 |
$ git branch -d release-1.2 Deleted branch release-1.2 (was ff452fe). |
Hotfix分支
Hotfix分支從master分支建立,必須合併回develop
分支和master
分支,為Hotfix分支可以這樣起名:hotfix-*
Hotfix分支在某種程度上非常像release分支,他們都意味著為某個新版本釋出做準備,並且都是預先不可知的。Hotfix分支是基於當前生產環境的產品的一個bug急需解決而必須建立的。當某個版本的產品有一個嚴重bug需要立即解決,Hotfix分支需要從master分支上該版本對應的tag上進行建立,因為這個tag標記了產品版本
建立hotfix分支
Hotfix分支從master
分支進行建立。例如當前線上1.2版本產品因為server端的一個Bug導致系統有問題。但是在develop分支進行
更改是不靠譜的,所以我們需要建立hotfix分支,然後開始解決問題:
1 2 3 4 5 6 7 |
$ 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,提交一次或多次。
1 2 3 |
$ 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
分支,目的是保證在下一版中該bug已經被解決。這多麼像release分支啊。
首先,對master分支進行合併更新,然後打tag
1 2 3 4 5 6 |
$ 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
or -u <key>
引數為你的tag設定標籤簽名。
緊接著,在develop分支合併bugfix程式碼
1 2 3 4 5 |
$ git checkout develop Switched to branch 'develop' $ git merge --no-ff hotfix-1.2.1 Merge made by recursive. (Summary of changes) |
這裡可能會有一些異常情況,當一個release分支存在時,hotfix 分支需要合併到release 分支,而不是develop
分支。當release分支的使命完成後,合併回release分支的bugfix程式碼最終也會被合併到develop分支。(當develop
分支急需解決這些bug,而等不到release分支的結束,你可以安全的將這些bugfix程式碼合併到develop分支,這樣做也是可以的)。
最後刪除這些臨時分支
1 2 |
$ git branch -d hotfix-1.2.1 Deleted branch hotfix-1.2.1 (was abbe5d6). |
總結
這個分支模型其實沒有什麼震撼人心的新東西,這篇文章開始的那個“最大圖片”已經證明了他在我們工程專案中的巨大作用。它會形成一種優雅的理想模型,而且很容易理解,該模型也允許團隊成員形成一個關於分支和版本釋出過程的相同理念。
這裡有提供一個高質量的分支模型圖的PDF版本。去吧,把它掛在牆上隨時快速參考。
更新:任何需要他的人,這裡有一個主圖的gitflow-model.src.key檔案
如果想和我取得聯絡,在推特上@nvie