如何選擇 Git 分支模式?
編寫代碼,是軟件開發交付過程的起點,發布上線,是開發工作完成的終點。代碼分支模式貫穿了開發、集成和發布的整個過程,是工程師們最親切的小伙伴。那如何根據自身的業務特點和團隊規模來選擇適合的分支模式呢?本文分享幾種主流 Git 分支模式的流程及特點,并給出選擇建議。
分支的目的是隔離,但多一個分支也意味著維護成本的增加。我們可以分別從開發和發布分支的多寡,做個簡單組合,即:
- 主干開發,主干發布。
- 分支開發,主干發布。
- 主干開發,分支發布。
- 分支開發,分支發布。
設想兩個不同的場景:
- 如果一個軟件,只有一個開發者,只需要一個發布版本,那他需要什么樣的分支模式?
- 如果一個軟件,有 10 位開發者,需要支持多個版本,那他們又需要什么樣的分支模式?
一個好的分支模式,可以大大提高軟件的開發、集成和發布效率。選擇什么樣的分支策略,是每一個開發團隊開始工作時面臨的第一個問題。那么,選擇什么樣的分支模式才適合我們呢?在回答這個題之前,我們先了解一下幾種常見的分支模式。
主流的分支模式
常見的分支模式有 TBD(即主干開發模式)、Git-Flow 模式、Github-Flow 模式及 Gitlab-Flow 模式。
TBD(主干開發模式)
即所有開發者,僅在一個開發分支(即主干)上進行協作開發的模式,在這種模式下,不允許新建任何長期存在的開發分支,有且僅保留主干分支進行開發協作。
因為沒有長期分離的其他開發分支,任何代碼變更持續地更新到主干上,在一定程度上避免了 merge 代碼帶來的困擾。同時,在這種開發模式下,建議采用發布分支的策略,根據軟件版本的發布節奏拉出發布分支。在 TBD 模式下,所有的修改都是在主干上,哪怕是缺陷的修改也是,修改完缺陷后,再 cherry pick 到發布分支上。
其特點總結一下就是:
- 有且僅有一個開發分支,即主干分支。
- 所有改動都發生在主干分支。
- 發布可以從主干拉發布分支。
- 主干上進行的修復需要根據缺陷的修復策略,確定是否 cherry pick 到對應版本的發布分支。
因為團隊共享一個開發分支,并且在開發分支上進行集成驗證,而每次代碼提交都會觸發集成驗證,這就要求每次代碼的變更在主干上都能快速地驗證,以確定是否接受下一次代碼變更(每次代碼變更都應該基于前一個穩定的版本進行),為了保證主干一直處在可工作狀態,這就需要:
- 每一次的變更要小,這樣在驗證的過程中才能控制范圍。
- 快速完成驗證,這就要求有相對完善的自動化檢查驗證機制。
所以,主干開發模式可以說是持續集成的關鍵推動者。主干開發模式非常利于持續集成,并且根據穩定和主干基線,做到隨時發布,以達到持續交付。但這些是建立在團隊成熟的協作能力和相對成熟的工程配套的基礎上,快速地對主干的變更提交完成編譯、檢查及驗證;同時,因為采取發布分支的實踐方式,在產品版本、分支、部署場景的對應關系需要梳理清楚,避免發布分支混亂,及缺陷修改在各分支上的修復策略。
因為主干開發要求每次變更提交都要小,并且要快速驗證完,保證主干是處在可發布狀態。對于一些處在開發過程中的特性,如每次變更提交,并非意味著完整特性的完成,為了隔離“特性半成品”對主干的影響,一般會采用特性開關(Feature Toggle)的方式進行隔離。即頻繁的代碼變更提交,可以先做集成及驗證,但是在發布的角度,通過(Feature Toggle)先隱藏相關特性,只有當特性都完成之后,才打開開關,特性完全透出。
但是,特性開關的引入也并不是沒有成本,因為特性開關是配置,本質上跟我們常常用到的宏定義(#if #else)沒啥區別,從本質上,它也是一種代碼的分支。特性開關的使用,在一定程度上讓你的代碼變得更脆弱。所以,特性開關的使用,是建立在良好的代碼設計基礎上。
為了彌補諸如特性開關這樣針對某個特性開發的需要,而且現在軟件開發中,越來越多的團隊共同協作在一起完成某一個特性這樣的場景,一種針對特性開發的分支模式就應運而生,這就是特性分支開發模式,最有代表性的就是 Git-Flow。
Git-Flow
Git-Flow 是為了解決多個不同特性之間并行開發需要的一種工作方式。當開始一個特性的開發工作的時候,從主干上拉出一個特性分支,所有的關于該特性的開發工作都發生在這個特性分支上,當完成該特性的工作之后,再把特性分支合并回代碼主路徑上,并準備發布。
Git-Flow 有以下幾種分支:
- feature 分支:開發者進行功能開發的分支。
- develop 分支:對開發的功能進行集成的分支。
- release 分支:負責版本發布的分支。
- hotfix 分支:對線上缺陷進行修改工作的分支。
- master:保存最新已發布版本基線的分支。
每個特性都有屬于自己的開發分支,即 feature 分支,當一個開發者需要在兩個特性上進行工作的時候,他需要做的是通過 check out 命令在兩個分支之間進行切換。這樣做的目的是防止開發過程中,兩個特性開發工作的相互干擾。
特性開發過程中,需要針對該特性進行單獨驗證,當該特性并驗證通過之后,merge 到一個叫做 develop 分支(大部分時間與 master 分支相近)的集成分支中,對整個軟件進行驗證。develop 分支永遠保存都是最近的未發布版本,當 develop 分支的代碼被驗證可發布之后,單獨從 develop 分支拉出 release 分支進行發布。
當拉出 release 分支進行發布過程中,如果發現缺陷,缺陷的修復發生在 release 分支上,所做的缺陷修改再持續同步到 develop 分支上。當 release 分支被發布完,其代碼的最終版本會再次分別同步給 develop 分支和 master 主干上。我們可以發現, master 上永遠保存的是可工作版本的基線。develop 分支保證的是開發集成中最新的版本。
Git-Flow 引入了一種叫做 hotfix 的分支,專門用于線上缺陷的修復。當缺陷修復完,再集成到 develop 分支,及同步到 master。其實,我們可以理解 hotfix 是一種特殊的 feature 分支,只是它的變更提交在集成到 develop 分支的同時需要同步到 master。
是不是覺得這個模式很專(fu)業(zha)的樣子,那我們通過開發者做一個特性開發,按 happy path 捋捋:
- 開發者接到一個開發需求,從 develop 分支拉一個 feature 分支。
- 大家完成自己本地的開發工作,完成本地驗證,提交代碼到 feature 分支。
- 基于 feature 分支進行驗證,并持續合并新的開發代碼。
- 完成特性的開發,并且 feature 分支驗證無誤,將 feature 分支的代碼合并到 develop 分支。
- 在 develop 分支進行集成驗證(此處,可能和其他合并進來的特性分支一起進行驗證),集成驗證完畢,feature 分支會被刪除。
- 當 develop 分支是一個成熟的發布版本時,如完成了徹底的測試及問題的修復,拉出 release 分支進行發布。
- 完成發布之后,將 release 分支合并入 develop 和 master(master 保存的永遠都是已發布的最新代碼),并刪除 release 分支。
Hotfix 的流程如下:
- 如果發布之后,發現了缺陷,基于 master 拉出一個 hotfix 分支。
- 在 hotfix 對問題進行修改及驗證。
- 問題的修復合并到 develop 和 master 上。
- 刪除 hotfix 分支。
Git-Flow 的分支模式,提供了相對完備的各種分支,以覆蓋軟件開發過程中的大部分場景,以致于在相當長的一段時間內,人們認為這就是標準的 Git 的分支模式(因為從它的名字上看,也很容易產生這樣的幻覺)。但是,Git-Flow 也存在著明顯的一些問題,如:
- 分支特別多,而且每類分支都有特定限定的用法,開發者很難記住什么分支是干什么的。
- 整個分支模式過于復雜,大大超出大部分團隊和項目的需求。
- feature 分支的生命周期過長導致的合并沖突。如果一個特性所在 feature 分支生命周期過長,它跟 develop 分支的差異就越大,這樣,在該特性集成到 develop 的過程中,潛在的代碼沖突將是集成的噩夢。
- 像 develop 分支,感覺其實存在的意義不是太大,完全通過 master 就可以替代集成的作用,額外為變更提交的集成引入 develop 分支,對分支模式來說,變得更加的復雜;同樣,如果取消 develop 分支,那么,hotfix 分支存在的意義也就沒有必要了,因為這個時候,hotfix 分支與 feature 分支就沒有任何的差別。
那么,有沒有一種分支模式,既包含開發任務對于主線的隔離,又相對 Git-Flow 輕量一點?我認為,真正的解決方案,應該是本質極簡單的。這里,我們就不得不給大家介紹另一種分支模式,叫做 GitHub-Flow。
GitHub-Flow
在 GitHub-Flow 上,第一步就是沒有 Git-Flow 中所介紹的 release 分支。對于 GitHub-Flow 來說,發布應該是持續地,當一個版本準備好,它就可以被部署。同樣,在 hotfix 上的處理,GitHub-Flow 認為,hotfix 與那些小的特性修改沒有任何區別,它的處理方式也應該與之相似。
在 GitHub-Flow 的整體流程是:
- 在 master 分支上的所有代碼都應該是最新可部署、可工作的版本。
- 如果要進行新的工作,從 master 分支上拉出一個新的分支,并以工作任務清晰命名,如 “new-scheduling-strategy”。
- 盡可能頻繁地提交代碼變更到本地分支,與此同時,盡可能頻繁地同步到服務端相同分支名的分支。
- 當準備合并代碼到 master 主干分支上,通過發起 Pull Request,提請代碼評審。
- 通過代碼評審后,或與此同時,需要將該分支部署到測試環境,進行驗證。
- 如果評審通過及驗證通過,代碼則合并到 master 主干分支上,應該立即部署到生產環境。
GitHub-Flow 相比 Git-Flow 來說,有個顯而易見的好處——簡單。另一個好處就是持續部署的要求,盡可能快速地發現 master 分支的問題,并能通過 rollback 等機制,快速恢復。將所有內容合到 master 分支中,并經常部署,意味著你可以最小化未發布的代碼量,這也是精益開發和持續交付所倡導的最佳實踐。部署原本是一件很繁瑣的事情,但是因為要頻繁做,我們就容易把這樣一件事情做簡單,以達到持續交付的目的。
雖然 GitHub-Flow 簡化了 Git-Flow 的分支模式,但是對于部署、環境、以及發布,該分支模式仍然存在許多未回答的問題,所以,我們希望通過 GitLab-Flow 來為這些問題提供更多的參考。
GitLab-Flow
GitLab-Flow 相比于 GitHub-Flow 來說,在開發側的區別不大,只是將 pull request 改成了 merge request,而 merge request 的用法與 pull request 類似,都可以做為代碼評審、獲取反饋意見的一種溝通方式。
最大的區別體現在發布側,即引入了對應生產環境的 production 分支和對應預發環境的 pre-production 分支(如果有預發環境的話)。這樣,master 分支反映的是部署在集成環境上的代碼,pre-production 分支反映的是部署在預發環境的代碼,production 分支反映的最新部署在生產環境的代碼。
當一個特性開發完成,提交 merge request,將特性開發的代碼合并到 master,并部署到集成環境進行驗證;當驗證通過之后,提交 merge reqeust,合并 master 到 pre-production 分支,并部署到預發環境,進行預發環境上驗證;當預發環境驗證成功之后,再提交 merge request,將 pre-production 分支上的代碼合并到 production 分支上。
除了以上這種按環境,將主干發布向下游合并,并依次部署發布的過程。GitLab-Flow 同樣支持不同版本的發布分支,即不同的版本會從 master 上拉出發布分支,不同的發布分支再走 pre-production 分支和 production 分支的方式進行發布。
從上面的介紹中,我們發現,GitLab-Flow 更多是在發布側做了更多的工作。同樣 GitLab-Flow 因為跟 GitLab 工具強依賴,所以 GitLab-Flow 與 GitLab 中的 Issue 系統也有很好的集成,在其推薦的工作模式中,每次新建一個新的 feature 分支,都是從一個 issue 上發起的,即建立 issue 與 feature 開發分支之間的映射。
pros vs cons
所以,如果我們還是按照開發和發布的分支多寡來分類的話,以上這些分支模式分別屬于:
- TBD 應該是主干開發,可以是分支發布,也可以是主干發布。
- Git-Flow 應該是分支開發,分支發布。
- GitHub-Flow 應該是分支開發,主干發布。
- GitLab-Flow 支持分支開發,但支持主干發布,也支持分支發布。
了解了常見的這些分支模式之后,我們在工作中就可以根據自身的業務特點和團隊規模來選擇適合的實踐,沒有絕對好的模式,只有適合的模式。
根據團隊自身和項目的特點來選擇最適合的分支實踐應該從哪些地方考量呢?
選擇合適的實踐
首先是項目的版本發布周期。如果發布周期較長,則 Git-Flow 是最好的選擇。Git-Flow 可以很好地解決新功能開發、版本發布、生產系統維護等問題;如果發布周期較短,則 TBD 和 GitHub-Flow 都是不錯的選擇。GitHub-flow 的特色在于集成了 pull request 和代碼審查。如果項目已經使用 GitHub,則 GitHub-Flow 是最佳的選擇。GitHub-Flow 和 TBD 對持續集成和自動化測試等基礎設施有比較高的要求。如果相關的基礎設施不完善,則不建議使用。
另外,從需要發布版本的多寡的角度來看:
- 支持一個產品多個發布版本,用 Git-Flow。
- 支持一個簡單產品單個發布版本,用 GitHub-Flow 或 TBD。
- 支持一個復雜產品單個發布版本,用 GitLab-Flow。
如果發現,現有主流的分支模式都無法滿足你的要求,那么,我們可以定義自己的分支模式,如我們有個團隊是基于主干開發的,于是定義了春夏秋冬分支模式,春天用“春分支”,夏天用“夏分支”......,我個人就蠻喜歡的,原因有二,一是做到了主干開發,持續發布,二是名字起得很有意思,開發工作一定要有樂趣,不是么?
參考
[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