如何選擇 Git 分支模式?
編寫代碼,是軟件開發(fā)交付過程的起點(diǎn),發(fā)布上線,是開發(fā)工作完成的終點(diǎn)。代碼分支模式貫穿了開發(fā)、集成和發(fā)布的整個(gè)過程,是工程師們最親切的小伙伴。那如何根據(jù)自身的業(yè)務(wù)特點(diǎn)和團(tuán)隊(duì)規(guī)模來選擇適合的分支模式呢?本文分享幾種主流 Git 分支模式的流程及特點(diǎn),并給出選擇建議。
分支的目的是隔離,但多一個(gè)分支也意味著維護(hù)成本的增加。我們可以分別從開發(fā)和發(fā)布分支的多寡,做個(gè)簡單組合,即:
- 主干開發(fā),主干發(fā)布。
- 分支開發(fā),主干發(fā)布。
- 主干開發(fā),分支發(fā)布。
- 分支開發(fā),分支發(fā)布。
設(shè)想兩個(gè)不同的場景:
- 如果一個(gè)軟件,只有一個(gè)開發(fā)者,只需要一個(gè)發(fā)布版本,那他需要什么樣的分支模式?
- 如果一個(gè)軟件,有 10 位開發(fā)者,需要支持多個(gè)版本,那他們又需要什么樣的分支模式?
一個(gè)好的分支模式,可以大大提高軟件的開發(fā)、集成和發(fā)布效率。選擇什么樣的分支策略,是每一個(gè)開發(fā)團(tuán)隊(duì)開始工作時(shí)面臨的第一個(gè)問題。那么,選擇什么樣的分支模式才適合我們呢?在回答這個(gè)題之前,我們先了解一下幾種常見的分支模式。
主流的分支模式
常見的分支模式有 TBD(即主干開發(fā)模式)、Git-Flow 模式、Github-Flow 模式及 Gitlab-Flow 模式。
TBD(主干開發(fā)模式)
即所有開發(fā)者,僅在一個(gè)開發(fā)分支(即主干)上進(jìn)行協(xié)作開發(fā)的模式,在這種模式下,不允許新建任何長期存在的開發(fā)分支,有且僅保留主干分支進(jìn)行開發(fā)協(xié)作。
因?yàn)闆]有長期分離的其他開發(fā)分支,任何代碼變更持續(xù)地更新到主干上,在一定程度上避免了 merge 代碼帶來的困擾。同時(shí),在這種開發(fā)模式下,建議采用發(fā)布分支的策略,根據(jù)軟件版本的發(fā)布節(jié)奏拉出發(fā)布分支。在 TBD 模式下,所有的修改都是在主干上,哪怕是缺陷的修改也是,修改完缺陷后,再 cherry pick 到發(fā)布分支上。
其特點(diǎn)總結(jié)一下就是:
- 有且僅有一個(gè)開發(fā)分支,即主干分支。
- 所有改動都發(fā)生在主干分支。
- 發(fā)布可以從主干拉發(fā)布分支。
- 主干上進(jìn)行的修復(fù)需要根據(jù)缺陷的修復(fù)策略,確定是否 cherry pick 到對應(yīng)版本的發(fā)布分支。
因?yàn)閳F(tuán)隊(duì)共享一個(gè)開發(fā)分支,并且在開發(fā)分支上進(jìn)行集成驗(yàn)證,而每次代碼提交都會觸發(fā)集成驗(yàn)證,這就要求每次代碼的變更在主干上都能快速地驗(yàn)證,以確定是否接受下一次代碼變更(每次代碼變更都應(yīng)該基于前一個(gè)穩(wěn)定的版本進(jìn)行),為了保證主干一直處在可工作狀態(tài),這就需要:
- 每一次的變更要小,這樣在驗(yàn)證的過程中才能控制范圍。
- 快速完成驗(yàn)證,這就要求有相對完善的自動化檢查驗(yàn)證機(jī)制。
所以,主干開發(fā)模式可以說是持續(xù)集成的關(guān)鍵推動者。主干開發(fā)模式非常利于持續(xù)集成,并且根據(jù)穩(wěn)定和主干基線,做到隨時(shí)發(fā)布,以達(dá)到持續(xù)交付。但這些是建立在團(tuán)隊(duì)成熟的協(xié)作能力和相對成熟的工程配套的基礎(chǔ)上,快速地對主干的變更提交完成編譯、檢查及驗(yàn)證;同時(shí),因?yàn)椴扇“l(fā)布分支的實(shí)踐方式,在產(chǎn)品版本、分支、部署場景的對應(yīng)關(guān)系需要梳理清楚,避免發(fā)布分支混亂,及缺陷修改在各分支上的修復(fù)策略。
因?yàn)橹鞲砷_發(fā)要求每次變更提交都要小,并且要快速驗(yàn)證完,保證主干是處在可發(fā)布狀態(tài)。對于一些處在開發(fā)過程中的特性,如每次變更提交,并非意味著完整特性的完成,為了隔離“特性半成品”對主干的影響,一般會采用特性開關(guān)(Feature Toggle)的方式進(jìn)行隔離。即頻繁的代碼變更提交,可以先做集成及驗(yàn)證,但是在發(fā)布的角度,通過(Feature Toggle)先隱藏相關(guān)特性,只有當(dāng)特性都完成之后,才打開開關(guān),特性完全透出。
但是,特性開關(guān)的引入也并不是沒有成本,因?yàn)樘匦蚤_關(guān)是配置,本質(zhì)上跟我們常常用到的宏定義(#if #else)沒啥區(qū)別,從本質(zhì)上,它也是一種代碼的分支。特性開關(guān)的使用,在一定程度上讓你的代碼變得更脆弱。所以,特性開關(guān)的使用,是建立在良好的代碼設(shè)計(jì)基礎(chǔ)上。
為了彌補(bǔ)諸如特性開關(guān)這樣針對某個(gè)特性開發(fā)的需要,而且現(xiàn)在軟件開發(fā)中,越來越多的團(tuán)隊(duì)共同協(xié)作在一起完成某一個(gè)特性這樣的場景,一種針對特性開發(fā)的分支模式就應(yīng)運(yùn)而生,這就是特性分支開發(fā)模式,最有代表性的就是 Git-Flow。
Git-Flow
Git-Flow 是為了解決多個(gè)不同特性之間并行開發(fā)需要的一種工作方式。當(dāng)開始一個(gè)特性的開發(fā)工作的時(shí)候,從主干上拉出一個(gè)特性分支,所有的關(guān)于該特性的開發(fā)工作都發(fā)生在這個(gè)特性分支上,當(dāng)完成該特性的工作之后,再把特性分支合并回代碼主路徑上,并準(zhǔn)備發(fā)布。
Git-Flow 有以下幾種分支:
- feature 分支:開發(fā)者進(jìn)行功能開發(fā)的分支。
- develop 分支:對開發(fā)的功能進(jìn)行集成的分支。
- release 分支:負(fù)責(zé)版本發(fā)布的分支。
- hotfix 分支:對線上缺陷進(jìn)行修改工作的分支。
- master:保存最新已發(fā)布版本基線的分支。
每個(gè)特性都有屬于自己的開發(fā)分支,即 feature 分支,當(dāng)一個(gè)開發(fā)者需要在兩個(gè)特性上進(jìn)行工作的時(shí)候,他需要做的是通過 check out 命令在兩個(gè)分支之間進(jìn)行切換。這樣做的目的是防止開發(fā)過程中,兩個(gè)特性開發(fā)工作的相互干擾。
特性開發(fā)過程中,需要針對該特性進(jìn)行單獨(dú)驗(yàn)證,當(dāng)該特性并驗(yàn)證通過之后,merge 到一個(gè)叫做 develop 分支(大部分時(shí)間與 master 分支相近)的集成分支中,對整個(gè)軟件進(jìn)行驗(yàn)證。develop 分支永遠(yuǎn)保存都是最近的未發(fā)布版本,當(dāng) develop 分支的代碼被驗(yàn)證可發(fā)布之后,單獨(dú)從 develop 分支拉出 release 分支進(jìn)行發(fā)布。
當(dāng)拉出 release 分支進(jìn)行發(fā)布過程中,如果發(fā)現(xiàn)缺陷,缺陷的修復(fù)發(fā)生在 release 分支上,所做的缺陷修改再持續(xù)同步到 develop 分支上。當(dāng) release 分支被發(fā)布完,其代碼的最終版本會再次分別同步給 develop 分支和 master 主干上。我們可以發(fā)現(xiàn), master 上永遠(yuǎn)保存的是可工作版本的基線。develop 分支保證的是開發(fā)集成中最新的版本。
Git-Flow 引入了一種叫做 hotfix 的分支,專門用于線上缺陷的修復(fù)。當(dāng)缺陷修復(fù)完,再集成到 develop 分支,及同步到 master。其實(shí),我們可以理解 hotfix 是一種特殊的 feature 分支,只是它的變更提交在集成到 develop 分支的同時(shí)需要同步到 master。
是不是覺得這個(gè)模式很專(fu)業(yè)(zha)的樣子,那我們通過開發(fā)者做一個(gè)特性開發(fā),按 happy path 捋捋:
- 開發(fā)者接到一個(gè)開發(fā)需求,從 develop 分支拉一個(gè) feature 分支。
- 大家完成自己本地的開發(fā)工作,完成本地驗(yàn)證,提交代碼到 feature 分支。
- 基于 feature 分支進(jìn)行驗(yàn)證,并持續(xù)合并新的開發(fā)代碼。
- 完成特性的開發(fā),并且 feature 分支驗(yàn)證無誤,將 feature 分支的代碼合并到 develop 分支。
- 在 develop 分支進(jìn)行集成驗(yàn)證(此處,可能和其他合并進(jìn)來的特性分支一起進(jìn)行驗(yàn)證),集成驗(yàn)證完畢,feature 分支會被刪除。
- 當(dāng) develop 分支是一個(gè)成熟的發(fā)布版本時(shí),如完成了徹底的測試及問題的修復(fù),拉出 release 分支進(jìn)行發(fā)布。
- 完成發(fā)布之后,將 release 分支合并入 develop 和 master(master 保存的永遠(yuǎn)都是已發(fā)布的最新代碼),并刪除 release 分支。
Hotfix 的流程如下:
- 如果發(fā)布之后,發(fā)現(xiàn)了缺陷,基于 master 拉出一個(gè) hotfix 分支。
- 在 hotfix 對問題進(jìn)行修改及驗(yàn)證。
- 問題的修復(fù)合并到 develop 和 master 上。
- 刪除 hotfix 分支。
Git-Flow 的分支模式,提供了相對完備的各種分支,以覆蓋軟件開發(fā)過程中的大部分場景,以致于在相當(dāng)長的一段時(shí)間內(nèi),人們認(rèn)為這就是標(biāo)準(zhǔn)的 Git 的分支模式(因?yàn)閺乃拿稚峡?,也很容易產(chǎn)生這樣的幻覺)。但是,Git-Flow 也存在著明顯的一些問題,如:
- 分支特別多,而且每類分支都有特定限定的用法,開發(fā)者很難記住什么分支是干什么的。
- 整個(gè)分支模式過于復(fù)雜,大大超出大部分團(tuán)隊(duì)和項(xiàng)目的需求。
- feature 分支的生命周期過長導(dǎo)致的合并沖突。如果一個(gè)特性所在 feature 分支生命周期過長,它跟 develop 分支的差異就越大,這樣,在該特性集成到 develop 的過程中,潛在的代碼沖突將是集成的噩夢。
- 像 develop 分支,感覺其實(shí)存在的意義不是太大,完全通過 master 就可以替代集成的作用,額外為變更提交的集成引入 develop 分支,對分支模式來說,變得更加的復(fù)雜;同樣,如果取消 develop 分支,那么,hotfix 分支存在的意義也就沒有必要了,因?yàn)檫@個(gè)時(shí)候,hotfix 分支與 feature 分支就沒有任何的差別。
那么,有沒有一種分支模式,既包含開發(fā)任務(wù)對于主線的隔離,又相對 Git-Flow 輕量一點(diǎn)?我認(rèn)為,真正的解決方案,應(yīng)該是本質(zhì)極簡單的。這里,我們就不得不給大家介紹另一種分支模式,叫做 GitHub-Flow。
GitHub-Flow
在 GitHub-Flow 上,第一步就是沒有 Git-Flow 中所介紹的 release 分支。對于 GitHub-Flow 來說,發(fā)布應(yīng)該是持續(xù)地,當(dāng)一個(gè)版本準(zhǔn)備好,它就可以被部署。同樣,在 hotfix 上的處理,GitHub-Flow 認(rèn)為,hotfix 與那些小的特性修改沒有任何區(qū)別,它的處理方式也應(yīng)該與之相似。
在 GitHub-Flow 的整體流程是:
- 在 master 分支上的所有代碼都應(yīng)該是最新可部署、可工作的版本。
- 如果要進(jìn)行新的工作,從 master 分支上拉出一個(gè)新的分支,并以工作任務(wù)清晰命名,如 “new-scheduling-strategy”。
- 盡可能頻繁地提交代碼變更到本地分支,與此同時(shí),盡可能頻繁地同步到服務(wù)端相同分支名的分支。
- 當(dāng)準(zhǔn)備合并代碼到 master 主干分支上,通過發(fā)起 Pull Request,提請代碼評審。
- 通過代碼評審后,或與此同時(shí),需要將該分支部署到測試環(huán)境,進(jìn)行驗(yàn)證。
- 如果評審?fù)ㄟ^及驗(yàn)證通過,代碼則合并到 master 主干分支上,應(yīng)該立即部署到生產(chǎn)環(huán)境。
GitHub-Flow 相比 Git-Flow 來說,有個(gè)顯而易見的好處——簡單。另一個(gè)好處就是持續(xù)部署的要求,盡可能快速地發(fā)現(xiàn) master 分支的問題,并能通過 rollback 等機(jī)制,快速恢復(fù)。將所有內(nèi)容合到 master 分支中,并經(jīng)常部署,意味著你可以最小化未發(fā)布的代碼量,這也是精益開發(fā)和持續(xù)交付所倡導(dǎo)的最佳實(shí)踐。部署原本是一件很繁瑣的事情,但是因?yàn)橐l繁做,我們就容易把這樣一件事情做簡單,以達(dá)到持續(xù)交付的目的。
雖然 GitHub-Flow 簡化了 Git-Flow 的分支模式,但是對于部署、環(huán)境、以及發(fā)布,該分支模式仍然存在許多未回答的問題,所以,我們希望通過 GitLab-Flow 來為這些問題提供更多的參考。
GitLab-Flow
GitLab-Flow 相比于 GitHub-Flow 來說,在開發(fā)側(cè)的區(qū)別不大,只是將 pull request 改成了 merge request,而 merge request 的用法與 pull request 類似,都可以做為代碼評審、獲取反饋意見的一種溝通方式。
最大的區(qū)別體現(xiàn)在發(fā)布側(cè),即引入了對應(yīng)生產(chǎn)環(huán)境的 production 分支和對應(yīng)預(yù)發(fā)環(huán)境的 pre-production 分支(如果有預(yù)發(fā)環(huán)境的話)。這樣,master 分支反映的是部署在集成環(huán)境上的代碼,pre-production 分支反映的是部署在預(yù)發(fā)環(huán)境的代碼,production 分支反映的最新部署在生產(chǎn)環(huán)境的代碼。
當(dāng)一個(gè)特性開發(fā)完成,提交 merge request,將特性開發(fā)的代碼合并到 master,并部署到集成環(huán)境進(jìn)行驗(yàn)證;當(dāng)驗(yàn)證通過之后,提交 merge reqeust,合并 master 到 pre-production 分支,并部署到預(yù)發(fā)環(huán)境,進(jìn)行預(yù)發(fā)環(huán)境上驗(yàn)證;當(dāng)預(yù)發(fā)環(huán)境驗(yàn)證成功之后,再提交 merge request,將 pre-production 分支上的代碼合并到 production 分支上。
除了以上這種按環(huán)境,將主干發(fā)布向下游合并,并依次部署發(fā)布的過程。GitLab-Flow 同樣支持不同版本的發(fā)布分支,即不同的版本會從 master 上拉出發(fā)布分支,不同的發(fā)布分支再走 pre-production 分支和 production 分支的方式進(jìn)行發(fā)布。
從上面的介紹中,我們發(fā)現(xiàn),GitLab-Flow 更多是在發(fā)布側(cè)做了更多的工作。同樣 GitLab-Flow 因?yàn)楦?GitLab 工具強(qiáng)依賴,所以 GitLab-Flow 與 GitLab 中的 Issue 系統(tǒng)也有很好的集成,在其推薦的工作模式中,每次新建一個(gè)新的 feature 分支,都是從一個(gè) issue 上發(fā)起的,即建立 issue 與 feature 開發(fā)分支之間的映射。
pros vs cons
所以,如果我們還是按照開發(fā)和發(fā)布的分支多寡來分類的話,以上這些分支模式分別屬于:
- TBD 應(yīng)該是主干開發(fā),可以是分支發(fā)布,也可以是主干發(fā)布。
- Git-Flow 應(yīng)該是分支開發(fā),分支發(fā)布。
- GitHub-Flow 應(yīng)該是分支開發(fā),主干發(fā)布。
- GitLab-Flow 支持分支開發(fā),但支持主干發(fā)布,也支持分支發(fā)布。
了解了常見的這些分支模式之后,我們在工作中就可以根據(jù)自身的業(yè)務(wù)特點(diǎn)和團(tuán)隊(duì)規(guī)模來選擇適合的實(shí)踐,沒有絕對好的模式,只有適合的模式。
根據(jù)團(tuán)隊(duì)自身和項(xiàng)目的特點(diǎn)來選擇最適合的分支實(shí)踐應(yīng)該從哪些地方考量呢?
選擇合適的實(shí)踐
首先是項(xiàng)目的版本發(fā)布周期。如果發(fā)布周期較長,則 Git-Flow 是最好的選擇。Git-Flow 可以很好地解決新功能開發(fā)、版本發(fā)布、生產(chǎn)系統(tǒng)維護(hù)等問題;如果發(fā)布周期較短,則 TBD 和 GitHub-Flow 都是不錯(cuò)的選擇。GitHub-flow 的特色在于集成了 pull request 和代碼審查。如果項(xiàng)目已經(jīng)使用 GitHub,則 GitHub-Flow 是最佳的選擇。GitHub-Flow 和 TBD 對持續(xù)集成和自動化測試等基礎(chǔ)設(shè)施有比較高的要求。如果相關(guān)的基礎(chǔ)設(shè)施不完善,則不建議使用。
另外,從需要發(fā)布版本的多寡的角度來看:
- 支持一個(gè)產(chǎn)品多個(gè)發(fā)布版本,用 Git-Flow。
- 支持一個(gè)簡單產(chǎn)品單個(gè)發(fā)布版本,用 GitHub-Flow 或 TBD。
- 支持一個(gè)復(fù)雜產(chǎn)品單個(gè)發(fā)布版本,用 GitLab-Flow。
如果發(fā)現(xiàn),現(xiàn)有主流的分支模式都無法滿足你的要求,那么,我們可以定義自己的分支模式,如我們有個(gè)團(tuán)隊(duì)是基于主干開發(fā)的,于是定義了春夏秋冬分支模式,春天用“春分支”,夏天用“夏分支”......,我個(gè)人就蠻喜歡的,原因有二,一是做到了主干開發(fā),持續(xù)發(fā)布,二是名字起得很有意思,開發(fā)工作一定要有樂趣,不是么?
參考
[1]TBD: https://trunkbaseddevelopment.com/[2]A successful Git branching model:https://nvie.com/posts/a-successful-git-branching-model/[3]Learn Version Control with Git:https://www.git-tower.com/learn/git/ebook/cn/command-line/advanced-topics/git-flow[4]Branching Models and Best Practices for Abstract - Design Version Control:https://projekt202.com/blog/2018/branching-models-and-abstract[5]Understand the GitHub-Flow: https://guides.github.com/introduction/flow/[6]GitHub Flow: http://scottchacon.com/2011/08/31/github-flow.html[7]Introduction to GitLabFlow: https://docs.gitlab.com/ee/workflow/gitlab_flow.html