在項(xiàng)目的開發(fā)過程中,分支與合并是很常見的操作。以我曾經(jīng)管理過的一個(gè)長(zhǎng)期的項(xiàng)目為例,由于項(xiàng)目的總體需求規(guī)模比較龐大,全部開發(fā)完成需要很長(zhǎng)的時(shí)間,而且隨著業(yè)務(wù)的增長(zhǎng)后續(xù)不斷會(huì)有需求的邊界也會(huì)逐步的擴(kuò)張。為了規(guī)避風(fēng)險(xiǎn)因此我們采用敏捷的開發(fā)模式,同時(shí)將需求按優(yōu)先級(jí)做迭代增量開發(fā)和交付,持續(xù)和盡早獲得反饋。短期內(nèi)(3個(gè)月)項(xiàng)目就上線了,隨后每個(gè)月都會(huì)發(fā)布一個(gè)包含了新需求的版本。在這中間可能也會(huì)有修復(fù)緊急feature、issue的臨時(shí)發(fā)布。
在上面提到的項(xiàng)目實(shí)例,假設(shè)新的需求開發(fā)、生產(chǎn)環(huán)境緊急的bug修復(fù)工作等都是直接在master分支(Git在新建一個(gè)倉庫時(shí),默認(rèn)只有一個(gè)名為master的分支)內(nèi)進(jìn)行,會(huì)發(fā)生什么樣的后果?
(注:圖-1 帶箭頭實(shí)線表示的不是提交方向、時(shí)間方向,而是指針,指向當(dāng)前提交的上一次提交(parent))
實(shí)例中最容易發(fā)生的后果之一,如圖-1所示,master最近一次的提交C2是發(fā)布到生產(chǎn)環(huán)境的版本。隨后開發(fā)人員提交了包含了新特性的代碼C3以及C4開始做集成測(cè)試。此時(shí)在生產(chǎn)環(huán)境發(fā)現(xiàn)了一個(gè)bug需要緊急修復(fù)并發(fā)布。但是現(xiàn)在master內(nèi)的代碼已經(jīng)包含了C3等不能發(fā)布到生產(chǎn)環(huán)境的代碼提交。這種情況下要么只有等C3測(cè)試通過可以發(fā)布到生產(chǎn)環(huán)境時(shí),和bugfix一起發(fā)布;又或者將版本回撤到C2,修復(fù)bug后將重新提交C3和C4。兩種方案都是非常差的選擇。
為了規(guī)避這類問題,就需要使用分支功能將新工作從開發(fā)的主線上分離開來,以免影響開發(fā)主線。例如生產(chǎn)環(huán)境單獨(dú)使用master分支,其它的工作如新需求開發(fā)、緊急修復(fù)等分別使用不同的分支,在工作完成后需要發(fā)布時(shí)再和并回master。可以根據(jù)項(xiàng)目的實(shí)際情況來制定分支策略和發(fā)布管理的工作流程。
1. 分支的創(chuàng)建
在git中創(chuàng)建一個(gè)分支很簡(jiǎn)單:git branch <newbranchname>。在上面的例子中,使用分支的簡(jiǎn)單方案就是,在C2階段,將開發(fā)C3等特性的工作放入新的分支去做。創(chuàng)建一個(gè)分支:
$ git branch feature-c3
檢出分支:
git checkout feature-c3
檢出分支時(shí)會(huì)將該分支的最后一次提交檢出到暫存區(qū)和工作區(qū)。另外,也可以使用git checkout -b feature-c3命令來創(chuàng)建并立即檢出這個(gè)新分支,這條命令等效于先后執(zhí)行上面的兩條命令。
接下來我們來模仿在feature-c3分支中增加新的特性:
- 修改README.md文件,新增一行內(nèi)容并保存:This line is demo for new feature c3.
- 新增一個(gè)featurec4.txt,文件內(nèi)容保存一行文字:This line is demo for new feature c4.
然后分別使用add、commit將修改提交到版本庫。
此時(shí)C2版本發(fā)布到生產(chǎn)環(huán)境的系統(tǒng)發(fā)現(xiàn)了嚴(yán)重的bug #07,需要緊急修復(fù)和發(fā)布。目前C3和C4都是在feature-c3上提交的,master仍然是c2的源碼版本。所以我們需要使用master分支的版本來修復(fù)bug并發(fā)布。
首先運(yùn)行git checkout master命令檢出master分支。
然后創(chuàng)建一個(gè)基于master的hotfix分支git checkout -b hotfix0.1_07,然后修改README.md文件,在第6行保存段文字“This is a hotfix for v0.1.0”,然后add、commit文件。
2. 分支的合并
假設(shè)上面修改了README.md文件后就修復(fù)了bug #07并通過了驗(yàn)收測(cè)試,現(xiàn)在要將hotfix分支的修改合并回master并發(fā)布到生產(chǎn)環(huán)境。
首先要將分支切換回master:git checkout master?,F(xiàn)在如果查看README.md文件會(huì)發(fā)現(xiàn)內(nèi)容仍然只有5行(C2時(shí)的版本)。然后運(yùn)行merge命令git merge hotfix0.1:
$ git merge hotfix0.1
Updating 4a567b9..fa7f53c
Fast-forward
README.md | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
打開gitsample工作區(qū)可以看到README.md文件已經(jīng)發(fā)生了改變,hotfix分支的修改已經(jīng)合并進(jìn)來了。
現(xiàn)在hotfix分支已經(jīng)完成了使命,我們可以使用git branch -d hotfix0.1命令將該分支刪除。
再來查看倉庫里的分支信息:git branch
$ git branch
feature-c3
* master
從命令返回結(jié)果可以看到,倉庫里只剩下feature-c3和master兩個(gè)分支。master左側(cè)有一個(gè)*,表示這是當(dāng)前檢出的分支。
feature-c3分支的內(nèi)容還沒有被合并到master,此時(shí)我們來試著刪除這個(gè)分支:
$ git branch -d feature-c3
error: The branch 'feature-c3' is not fully merged.
If you are sure you want to delete it, run 'git branch -D feature-c3'.
命令返回錯(cuò)誤信息:c3分支的內(nèi)容還沒有完全合并,不能刪除?,F(xiàn)在假設(shè)feature分支的開發(fā)已經(jīng)完成并通過測(cè)試驗(yàn)收,可以發(fā)布到生產(chǎn)環(huán)境了。讓我們?cè)俅魏喜⒎种В?/p>
$ git merge feature-c3
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.
命令提示README.md文件的內(nèi)容沖突,自動(dòng)合并失敗。需要人工干預(yù)重復(fù)然后再提交。
我們先用git log -1看下最后一次的提交日志:
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master|MERGING)
$ git log
commit fa7f53c462f5500d00f2968b7f7ab42d307cdbc0 (HEAD -> master)
Author: Efrey Kong <efreykong@outlook.com>
Date: Tue Aug 20 10:15:56 2019 +0800
hotfix 0.1.0
可以看到Git bash命令行提示符上方的顯示內(nèi)容由
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master)
變成了
efrey@EKStudio MINGW64 ~/Desktop/gitsample (master|MERGING)
并且日志中最后一次的提交仍然是合并hotfix分支操作。
接下來我們?cè)俅蜷_README.md看下文件沖突的內(nèi)容:
# gitsample
This line is the first time modified.
This line is the second time modified.
<<<<<<< HEAD
This is a hotfix for v0.1.0
=======
This line is a demo for feature-c3.
>>>>>>> feature-c3
<<<<<<< HEAD和=======中間的內(nèi)容是當(dāng)前master分支的內(nèi)容;
======= >>>>>>> feature-c3中間的內(nèi)容是feature-c3分支的內(nèi)容。
原來在hotfix分支中,我們?cè)赗EADME.md文件的第6行增加了一段“This is a hotfix for v0.1.0”。
而在feature-c3分支中,同樣的位置增加了一段“This line is a demo for feature-c3.”。我們來處理下這個(gè)沖突:
# gitsample
This line is the first time modified.
This line is the second time modified.
This is a hotfix for v0.1.0
This line is a demo for feature-c3.
保存修改,然后分別運(yùn)行git add .、git commit -m "feature-c3"提交。此時(shí)再來查看最后一次提交日志:
efrey@EKStudio-ZBook MINGW64 ~/Desktop/gitsample (master)
$ git log -1
commit 23b2190e6821953f8bba6935faef56cf3532b694 (HEAD -> master)
Merge: fa7f53c 49418b5
Author: Efrey Kong <efreykong@outlook.com>
Date: Tue Aug 20 10:55:25 2019 +0800
feature-c3
合并成功了。此時(shí)刪除feature-c3:
$ git branch -d feature-c3
Deleted branch feature-c3 (was 49418b5).
3. 分支工作區(qū)內(nèi)容的暫存
4. 分支工作流程模型
4.1. develop-master模型
A successful Git branching model一文提到了一種比較實(shí)用的Git工作流程模型,也被稱為develop-master模型。

在這種模型中,git中存在兩個(gè)長(zhǎng)期分支:master和develop。其它的分支如hotfix、feature、release等屬于輔助分支,按需要?jiǎng)?chuàng)建,在工作完成后合并回長(zhǎng)期分支并刪除輔助分支。
master作為系統(tǒng)穩(wěn)定版的主干,最近一次提交是永遠(yuǎn)和生產(chǎn)環(huán)境保持一致的。dev分支在項(xiàng)目的一開始就和master分離,作為持續(xù)開發(fā)的主干。
當(dāng)生產(chǎn)環(huán)境系統(tǒng)發(fā)現(xiàn)了bug,則在master的主干上創(chuàng)建一個(gè)hotfix的分支用于修復(fù)bug。bug修復(fù)測(cè)試通過后,再將這個(gè)hotfix分支合并回master,同時(shí)dev也會(huì)吸收hotfix分支中的修復(fù)。合并后刪除hotfix分支。
在開發(fā)下一次發(fā)布的主要需求功能,或者要開發(fā)新的特性時(shí),則從dev分支創(chuàng)建新的分支,開發(fā)完成后合并回dev分支,并刪除這個(gè)新的分支。當(dāng)dev分支中的可以發(fā)布時(shí),從dev創(chuàng)建release分支,release分支只接受bug fix的提交(bug fix的提交也需要持續(xù)的合并回dev分支)。測(cè)試完成后,將release分支合并到master發(fā)布版本到生產(chǎn)環(huán)境。合并后刪除release分支。