功能分支工作流的核心思想就是所有功能的開發(fā)都應(yīng)隔離在專有分支之內(nèi),而不應(yīng)該在主分支內(nèi)進(jìn)行任何功能開發(fā)。進(jìn)行如此封裝可以避免多個開發(fā)者在開發(fā)特定功能的同時對主代碼庫產(chǎn)生負(fù)面影響。這也同時意味著主分支內(nèi)永遠(yuǎn)沒有未開發(fā)完成的代碼,對于持續(xù)集成來說非常具有優(yōu)勢。
將功能開發(fā)封裝在單獨(dú)的功能分支中同時也使得pull request產(chǎn)生更多價值。在Git工作流程中,開發(fā)者之間可以通過pull request開展一系列對于開發(fā)內(nèi)容的討論。在pull request的過程中,開發(fā)者們可以在新代碼被集成進(jìn)主分支之前,有機(jī)會對具體代碼進(jìn)行審核?;蛘咴诹硗庖环N場景中,當(dāng)你開發(fā)受阻時,可以新開一個pull request,請求其他團(tuán)隊(duì)成員對具體代碼提出建議和啟發(fā)。重點(diǎn)在于,由于pull request流程的存在,讓團(tuán)隊(duì)成員之間互相審視對方的代碼變得簡單到不可思議。
工作方式
功能分支工作流基于一個中心化的倉庫,其中的主分支代表正式項(xiàng)目代碼。開發(fā)者們每次開啟一個新功能的開發(fā)時,需要新建一個分支,而不是直接向主分支提交開發(fā)代碼。功能分支應(yīng)該以有具體含義的名稱命名,比如 animated-menu 或者issue-#1061這樣的名字。這么做的目的是為了讓分支承載的開發(fā)任務(wù)足夠明確,足夠聚焦。Git本身并不會對功能分支和主分支區(qū)別對待,為其提供不同的特性支持,所以開發(fā)者可以在功能分支上執(zhí)行同樣的操作:編輯,暫存,然后提交變更。
此外,功能分支可以(也應(yīng)該)推送到中心倉庫。推送到中心倉庫的代碼可以共享給其他開發(fā)者。另外,由于只有主分支是一個比較特別的分支,所以在中心倉庫中存儲若干個功能分支并不會產(chǎn)生什么問題。當(dāng)然這也是一種用來備份本地提交的方法。下面我們就來演示一個功能分支的使用流程。
從主分支開發(fā)
所有的功能分支都應(yīng)該創(chuàng)建自最新的主分支代碼。在接下來的演示中,我們假設(shè)主分支名為main
git checkout main
git fetch origin
git reset --hard origin/main
以上命令先將本地分支切換到main分支,然后更新最新的提交記錄,并且最后使用reset命令把本地的main分支指向origin/main遠(yuǎn)程分支的最新提交。當(dāng)然如果你對于reset不太熟悉,也可以使用如下更加常見的命令
git checkout main
git fetch origin
git pull origin main
創(chuàng)建新分支
基于本文最開始所說的關(guān)于功能分支的核心思想,當(dāng)開發(fā)一個新功能時,需要為其專門創(chuàng)建一個獨(dú)有分支。創(chuàng)建完成之后,別忘了切換到新創(chuàng)建的分支上。
git checkout -b new-feature
以上命令通過-b選項(xiàng)基于當(dāng)前所在分支(也就是main分支),創(chuàng)建一個之前并未存在的new-feature分支。
更新,添加,提交以及推送
在功能分支上,編輯、暫存并且提交的方式與其他任何分支完全一樣。所以就像你正常使用Git一樣,在該分支上進(jìn)行任何你認(rèn)為需要進(jìn)行的操作。最后在新功能開發(fā)完成時提交變更。
git status
git add <some-file>
git commit
向遠(yuǎn)程倉庫推送分支
時不時地把本地分支推送到遠(yuǎn)程倉庫是個好習(xí)慣。這一方面可以隨時將本地變更備份在遠(yuǎn)程倉庫中,另外在與其他開發(fā)者合作方面,允許他人查看你分支上的最新代碼。
git push -u origin new-feature
以上命令會把new-feature分支推送到中心倉庫(origin),添加的 -u 選項(xiàng)將其添加為遠(yuǎn)程追蹤分支,以便未來執(zhí)行git push操作時git自動判斷需要推送到哪個遠(yuǎn)程倉庫,就不再需要每次都添加origin之類的參數(shù)了。推送之后,就可以通過倉庫托管軟件新建pull request / merge request (github / gitlab / bitbucket etc.)。在這里可以指定其他開發(fā)者作為代碼評審人,在合并之前對新提交的代碼進(jìn)行評審。
關(guān)于反饋
如果在pull request或者merge request頁面,團(tuán)隊(duì)成員進(jìn)行了代碼評審并且通過了這次提交,那么就可以進(jìn)行代碼合并。但還有另外一種情況,也就是審查人員可能會提出一些修改建議,此時開發(fā)者可以在本地針對這些建議進(jìn)行修改,然后再次執(zhí)行一系列編輯,暫存,提交,推送的操作。此時針對這些建議的新修改會自動在pull request/merge request中呈現(xiàn)出來。
合并pull request
如果其他成員提交了同樣段落的修改,pull request頁面會告訴你此次提交有沖突發(fā)生,此時你需要先解決這些沖突。如果沒有沖突或者沖突已經(jīng)解決,并且通過了代碼評審,那么就可以將分支合并進(jìn)main分支。
Pull requests
除了可以隔離功能分支與主分支,分支還通過pull request提供了在合并之前針對開發(fā)內(nèi)容進(jìn)行討論的能力。一旦有人完成了某個功能的開發(fā),不要直接馬上合并進(jìn)main分支,請?zhí)峤灰粋€pull request并請求其他人幫忙,然后再合并進(jìn)main分支。這樣在新開發(fā)的代碼融入主代碼庫之前,其他開發(fā)者有機(jī)會對其進(jìn)行審查。
進(jìn)行代碼評審是pull request過程所提供的一個重要收益。雖然在pull request中進(jìn)行的代碼評審?fù)ǔJ轻槍σ淮魏喜⑺婕暗降男略龃a,但代碼評審本身畢竟是一種通用的關(guān)于代碼本身的討論。這也就意味著你可以在開發(fā)過程中的任何節(jié)點(diǎn)進(jìn)行代碼評審。比如在開發(fā)過程中需要其他人對正在開發(fā)的功能提供一些具體的幫助,直接發(fā)起一個pull request,讓其他合作者深入到具體代碼中來提供幫助吧。
一旦一個pull request被接受,接下來的發(fā)布流程就與我們在其他文章中討論到的中心化工作流一致了。首先你需要確保本地的main分支與遠(yuǎn)程倉庫的main分支同步,然后合并功能分支到main分支內(nèi),再推送回中心倉庫即可。
示例
接下來的示例展示了使用功能分支工作流的一種場景。該場景描述了在團(tuán)隊(duì)內(nèi)部,針對一個新功能的pull request進(jìn)行代碼評審的場景。
韓梅梅開始開發(fā)一個新的功能

在進(jìn)行任何代碼開發(fā)之前,先創(chuàng)建一個獨(dú)立的分支。使用如下命令:
git checkout -b hanmeimeis-feature main
通過上面的命令,韓梅梅基于main分支創(chuàng)建了一個新分支,名叫hanmeimeis-feature,-b 選項(xiàng)意味如果該分支不存在就創(chuàng)建它。在此分支上韓梅梅進(jìn)行開發(fā):
git status
git add <some-file>
git commit
韓梅梅開發(fā)告一段落,去吃午飯

韓梅梅在早上進(jìn)行了幾次提交。在中午吃飯之前,是個好時間點(diǎn)推送本地修改到遠(yuǎn)程倉庫。這可以備份韓梅梅早上的工作,也可以讓其他團(tuán)隊(duì)成員獲取到韓梅梅早上提交的代碼。
git push -u origin hanmeimeis-feature
上面的命令把hanmeimeis-feature分支推送到遠(yuǎn)程倉庫。-u 選項(xiàng)會自動把這個分支添加為遠(yuǎn)程追蹤分支,這樣以后再進(jìn)行git push操作時就不需要通過參數(shù)指定推送的具體遠(yuǎn)程倉庫了。
韓梅梅開發(fā)完成

午飯歸來,韓梅梅又做了幾次提交,新功能終于開發(fā)完了。但是在合并到main分支之前,她需要先發(fā)起一個pull request讓其他人知道她已經(jīng)開發(fā)完成。首先,她需要推送本地代碼,保證遠(yuǎn)程倉庫的分支是最新的。
git push
接下來,韓梅梅在倉庫托管軟件的UI界面上發(fā)起一個pull request,請求將hanmeimeis-feature分支合并到main分支,此時團(tuán)隊(duì)其他成員就會自動收到托管軟件發(fā)出的通知。在pull request的頁面上,對于指定代碼段落的評論會直接顯示在這段代碼旁邊,所以可以很方便的就具體代碼提出問題。
李雷收到pull request通知

李雷收到了通知,然后打算看看hanmeimeis-feature里都有些啥內(nèi)容??戳酥?,李雷認(rèn)為在合并之前應(yīng)該再做一點(diǎn)點(diǎn)修改,于是他倆在pull request中進(jìn)行了一番討論。
韓梅梅進(jìn)行了修改

韓梅梅又一次進(jìn)入了經(jīng)典流程:編輯,暫存,提交,推送。之后她所有的更新就出現(xiàn)在了pull request頁面上,于是李雷又開啟了評論模式。
其實(shí)如果李雷愿意的話,他完全可以自己把hanmeimeis-feature分支拉到本地,然后自行修改。李雷進(jìn)入經(jīng)典流程之后作出的修改也同樣會自動出現(xiàn)在pull request頁面。但他沒有這么做,可能因?yàn)樗X得自己是領(lǐng)導(dǎo)吧。
韓梅梅發(fā)布新功能

當(dāng)李雷覺得韓梅梅的代碼可以進(jìn)行合并了,就需要有人去執(zhí)行這個合并操作,然后新的代碼就進(jìn)入主分支里(這個操作誰都可以做,韓梅梅可以,李雷也可以,甚至是Jim或者Lily和Lucy)
git checkout main
git pull
git pull origin hanmeimeis-feature
git push
以上流程會在提交歷史上留下一個合并提交的節(jié)點(diǎn)。有些人喜歡這樣,因?yàn)檫@種合并提交歷史可以明確的顯示出何時何地進(jìn)行了兩個分支的合并。也有一些人更喜歡線性的提交歷史,如果你是這樣的人可以在功能分支執(zhí)行rebase main分支的操作,這樣功能分支的所有提交就會位于main分支的頂端,這樣就可以進(jìn)行一次快進(jìn)合并。
與此同時,Tom也在經(jīng)歷著韓梅梅同樣的事情:
當(dāng)韓梅梅和李雷一直在討論和合并hanmeimeis-feature時,Tom也在對自己的功能分支進(jìn)行同樣的操作。所以可以看到通過隔離功能分支的開發(fā),每個人都可以獨(dú)立進(jìn)行開發(fā)工作,而且當(dāng)確實(shí)需要與其他人共享代碼時,也很容易辦到。
總結(jié)
在本文檔中我們討論了git功能分支工作流。這個工作流聚焦于處理那些針對不同業(yè)務(wù)功能進(jìn)行獨(dú)立開發(fā)的分支。我們還以一個具體的例子展示了功能分支工作流是如何一步一步進(jìn)行的。關(guān)于功能分支工作流我們得到以下結(jié)論:
- 專注于分支模型
- 同樣可以被用于其他面向倉庫的工作流程
- 通過pull request和合并評審提升協(xié)作效果
在合并功能分支操作中使用git rebase可以避免讓過多不相關(guān)的提交記錄出現(xiàn)在git歷史中。在團(tuán)隊(duì)中使用功能分支模型可以提升團(tuán)隊(duì)的協(xié)作表現(xiàn)。