部落客說:本文借鑒了很多「 DRPrincess 」部落客的文章内容,在此對其表示感謝。
為了更好的了解基于 Git 的版本控制工作流,我們不妨先來回答幾個問題?
- 什麼是版本控制?
- 什麼是版本控制系統?
- 為什麼要做版本控制?
- 為什麼選擇基于 Git 的版本控制?
要回答這些問題,最好的方法,莫過于回顧一下版本控制的發展曆史。
是以,在本文中,我們就從「
[版本控制簡史」出發,揭開「
基于 Git 的版本控制工作流」的神秘面紗。
版本控制簡史
版本控制,是指對軟體開發過程中各種程式代碼、配置檔案及說明文檔等檔案變更的管理。版本控制最主要的目的就是追蹤檔案的變更。它将什麼時候、什麼人更改了檔案的什麼内容等資訊忠實地了記錄下來。每一次檔案的改變,檔案的版本号都将增加。
除了記錄版本變更外,版本控制的另一個重要功能是并行開發。軟體開發往往是多人協同作業,版本控制可以有效地解決版本的同步以及不同開發者之間的開發通信問題,提高協同開發的效率。并行開發中最常見的不同版本軟體的錯誤修正問題也可以通過版本控制中分支與合并的方法有效地解決。
但版本控制是目的而不是實作的工具,是以我們還需要通過某種工具來實作版本控制的目的,我們将這樣的工具稱之為 Version Controll System,縮寫為 VCS,即版本控制系統。我們可以把一個版本控制系統簡單的了解為一個“資料庫”,在需要的時候,它可以幫我們完整地儲存一個項目的快照。當我們需要檢視一個之前的快照(稱之為“版本”)時,版本控制系統可以顯示出目前版本與上一個版本之間的所有改動的細節。
早在 1986 年 12 月,Dick Grune 就以 shell 腳本的形式釋出了第一個流行的版本控制系統 CVS 的雛形。1989 年 4 月,Brian Berliner 設計了 CVS 并編寫了代碼。CVS 是一個 C/S 系統,其設計思路為,在一台伺服器上建立一個源代碼庫,庫裡可以存放許多不同項目的源程式,由源代碼庫管理者統一管理這些源程式。每個使用者在使用源代碼庫之前,首先要把源代碼庫裡的項目檔案下載下傳到本地,然後使用者可以在本地任意修改,最後用 CVS 指令進行送出,由 CVS 源代碼庫統一管理修改。這樣,就好像隻有一個人在修改檔案一樣,既避免了沖突,又可以做到跟蹤檔案變化等。
2000 年,CollabNet Inc 開發了 Subversion,縮寫為 SVN,是一個開放源代碼的版本控制系統,現已發展成了 Apache 基金會的項目。相對于 CVS,SVN 采用了分支管理系統,它的設計目标就是取代 CVS,但與 CVS 相同的是,SVN 也采用了 C/S 體系,項目的各種版本都存儲在伺服器上,程式開發人員首先将從伺服器上獲得一份項目的最新版本,并将其複制到本機,然後在此基礎上,每個開發人員可以在自己的用戶端進行獨立的開發工作,并且可以随時将新代碼送出給伺服器。當然也可以通過更新操作擷取伺服器上的最新代碼,進而保持與其他開發者所使用版本的一緻性。
2005 年,Linux 之父 Linus Torvalds 為了幫助管理 Linux 核心開發而開發了一個開放源碼的版本控制軟體 Git。說起來,Git 的誕生還有一些戲劇性,Linus 最初使用 BitKeeper 作為版本控制系統,但在 2005 年,Andrew Tridgell 寫了一個程式,可以連接配接 BitKeeper 的存儲庫,BitKeeper 著作權擁有者 Larry McVoy 認為 Andrew Tridgell 對 BitKeeper 内部使用的協定進行逆向工程,決定收回無償使用 BitKeeper 的許可。Linux 核心開發團隊與 BitMover 公司進行磋商,但無法解決他們之間的歧見。最終,Linus Torvalds 決定自行開發版本控制系統替代 BitKeeper,就用十天的時間編寫出了 Git 的第一個版本。
如上所述,從 CVS、到 SVN、再到 Git 的變化,也是版本控制系統演進的過程。我們可以将 CVS、SVN 和 Git 大緻分為兩類:
- 集中式版本控制系統:CVS 和 SVN 屬于這一類。它們用集中管理的單一伺服器,來儲存所有檔案修訂版本,而協同工作的人們都通過用戶端連到這台伺服器,下載下傳最新的代碼或者是更新送出。但是如果中央伺服器當機了,那當機的這一段時間,大家都無法更新送出更新,沒法協同工作;更糟糕的情況下,如果中央伺服器的資料沒有做備份而且損壞,那麼所有記錄就都丢失了。
- 分布式版本控制系統:Git 屬于這一類。分布式版本控制系統最大的特點就是用戶端并不隻是提取最新版本的檔案快照,而是把代碼倉庫完整地鏡像下來,每個用戶端其實都可以當做是中央伺服器,當中央伺服器資料損壞了,從任何一個本地用戶端都可以重新恢複。而且我們可以随時随地送出代碼,因為我們送出代碼是送出到本地的伺服器,是以效率大大提高。
現如今,Git 應該算是最受歡迎的版本控制工具了。例如現在世界上最大的兩個代碼托管平台 GitHub 和 GitLab,都是基于 Git 進行版本控制的;在國内,大家使用較多的中文代碼托管平台 Gitee,也是基于 Git 進行版本控制的。由此可見,Git 作為版本控制工具,其速度快、分布式等特性,深受大家喜愛的。是以,了解基于 Git 的版本控制工作流,還是與我們有益的!
什麼是工作流?
工作流,即工作流程。在項目開發過程中,多人協作是很常見的現象,每個人拉取自己分支、實作自己的業務邏輯,雖然各自在分支上互不幹擾,但是我們總歸需要把分支合并到一起,而且真實項目中涉及到很多問題,例如版本疊代,版本釋出,bug 修複等,為了更好的管理代碼,需要制定一個工作流程,這就是我們說的工作流,也有人叫它分支管理政策。
工作流不涉及任何指令,因為它就是一個規則,完全由開發者自定義,并且自行遵守,正所謂無規矩不成方圓,就是這個道理。其中,Git Flow 出現的最早,GitHub Flow 在 Git Flow 的基礎上,做了一些優化,适用于持續版本的釋出,而 GitLab Flow 出現的時間比較晚,是以綜合了前面兩種工作流的優點,制定而成的一種工作流。接下來,我們就詳細了解這三個工作流。
Git Flow
Git Flow 是 Vincent Driessen 2010 年釋出出來的他自己的分支管理模型,到現在為止,使用度非常高,可以說是一個非常成熟的 Git 工作流。Git Flow 的分支結構,按功能來說,可以分為 5 種分支,從 5 種分支的生命周期上,又可以分為長期分支和短期分支,或者更貼切的描述為,主要分支和輔助分支。
主要分支
在采用 Git Flow 工作流的項目中,代碼的中央倉庫會一直存在以下兩個長期分支:
- master
- develop
其中,
origin/master
分支上的最新代碼永遠是版本釋出狀态,
origin/develop
分支則是最新的開發進度。當
develop
上的代碼達到一個穩定的狀态,可以釋出版本的時候,
develop
上這些修改會以某種特别方式被合并到
master
分支上,然後标記上對應的版本标簽。
輔助分支
除了主要分支,Git Flow 的開發模式還需要一系列的輔助分支,來幫助更好的并行開發,簡化功能開發和問題修複。輔助分支不需要一直存在,僅當我們需要的時候,建立輔助分支就可以,當我們不需要的時候,也可以删除輔助分支。輔助分支分為以下幾類:
- Feature Branch
- Release Branch
- Hotfix Branch
Feature 分支用來做分子產品功能開發,命名看開發者喜好,不要和其他類型的分支命名弄混淆就好,舉個壞例子,命名為
master
就是一個非常不妥當的舉動。子產品完成之後,會合并到
develop
分支,然後删除自己。
Release 分支用來做版本釋出的預釋出分支,建議命名為
release-xxx
。例如在軟體
1.0.0
版本的功能全部開發完成,送出測試之後,從
develop
檢出
release-1.0.0
,測試中出現的小問題,在
release
分支進行修改送出,測試完畢準備釋出的時候,代碼會合并到
master
和
develop
,
master
分支合并後會打上對應版本标簽
v1.0.0
,合并後删除自己,這樣做的好處是,在測試的時候,不影響下一個版本功能并行開發。
Hotfix 分支是用來做線上的緊急 bug 修複的分支,建議命名為
hotfix-xxx
。當線上某個版本出現了問題,将檢出對應版本的代碼,建立 Hotfix 分支,問題修複後,合并回
master
develop
,然後删除自己。這裡注意,合并到
master
的時候,也要打上修複後的版本标簽。
Merge 加上 --no-ff 參數
需要說明的是,Git Flow 的作者 Vincent Driessen 非常建議,合并分支的時候,加上
--no-ff
參數,這個參數的意思是不要選擇 Fast-Forward 合并方式,而是政策合并,政策合并會讓我們多一個合并送出。這樣做的好處是保證一個非常清晰的送出曆史,可以看到被合并分支的存在。下面是對比圖,左側是加上參數的,後者是普通的送出:

示意圖
如上圖所示,這是 Vincent Driessen 于 2010 年給出的 Git Flow 示意圖,也是我們所有想要學習 Git Flow 的人都應該了解的一張圖。圖中畫了 Git Flow 的五種分支,
master
、
develop
feature
release
hoxfixes
,其中
master
develop
字型被加粗代表主要分支。
master
分支每合并一個分支,無論是
hotfix
還是
release
,都會打一個版本标簽。通過箭頭可以清楚的看到分支的開始和結束走向,例如
feature
分支從
develop
開始,最終合并回
develop
;
hoxfixes
從
master
檢出建立,最後合并回
develop
master
master
也打上了标簽。
GitHub Flow
GitHub Flow 是世界上最大的代碼托管平台,也稱為“世界上最大的同性交友網站” GitHub 制定并使用的工作流,其是一個輕量級,基于分支的工作流,支援團隊和項目的定期部署,由 Scott Chacon 在 2011 年 8月 31 号正式釋出。
模型說明
在 GitHub Flow 中,隻有一個長期分支
master
,而且
master
分支上的代碼永遠是可釋出狀态。一般來說,
master
會設定為受保護狀态,隻有有權限的人才能推送代碼到
master
分支。以 GitHub 官方教程為準,遵循 GitHub Flow 需要經曆以下幾個步驟:
- 建立分支
- 添加送出
- 提出 PR 請求
- 讨論和評估你的代碼
- 部署
- 合并
簡單解釋一下,其大緻流程為:如果有新功能開發、版本疊代或者 bug 修複等需求,我們就從
master
分支上檢出新的分支;将檢出的新分支代碼拉取到本地,在本地環境中進行編碼,完成後,向遠端新分支倉庫推送代碼;當我們需要回報問題、取得幫助,或者想合并分支代碼時,可以發起一個 Pull Request,常簡稱為 PR;當我們的代碼通過項目維護者(有權限向
master
分支合并代碼的人)讨論和評估後,就可以部署代碼;待部署完成、驗證通過後,代碼就應該被合并到目标分支。
與 Git Flow 的示意圖相比,GitHub Flow 的示意圖可以稱得上簡單明了,因為 GitHub Flow 推薦做法就是隻有一個主分支
master
,團隊成員們的分支代碼通過 PR 來合并到主分支上。實際上,上面的圖僅是建立分支的示意圖,但無論是建立分支還是添加送出、提出 PR 請求等,都不過是圍繞着主分支按照上述的流程推進而已,如果大家感興趣,可以通過「
深入了解 GitHub Flow」檢視全部示意圖。
特色功能
因為 GItHub Flow 的初衷就是用于在 GitHub 上進行團隊協作,是以借助于 GitHub 平台的功能,GItHub Flow 中也引入了一些比較實用的工作流程,其中最出色的兩個功能莫過于 PR 與問題追蹤了。
PR
在工作流中引入 PR,是 GItHub Flow 的一個特色,它的用處并不僅僅是合并分支,還有以下功能:
- 控制分支合并權限
- 問題讨論或者尋求其他小夥伴們的幫助
- Code Review
有了 PR 功能之後,相信我們再送出代碼的時候,就得慎之又慎了。否則的話,代碼寫的太爛,就等着被噴吧!
問題追蹤
在日常開發中,我們可能會用到很多第三方的開源庫,如果使用過程中遇到了問題,我們可以去其 GitHub 倉庫上搜尋一下 Issue 清單,看看有沒有人遇到過、項目維護者修複了沒有,一般未解決的 Issue 是
Open
狀态,已解決的 Issue 是
Closed
狀态,這就是問題追蹤。
如果你是一個項目維護者,除了标記 Issue 的開啟和關閉,還可以給它标記上不同的标簽。當送出的時候,如果送出資訊中有
fix #1
等字段,可以自動關閉對應編号的 Issue。
GitLab Flow
這個工作流十分地年輕,是 GitLab 的 CEO Sytse Sijbrandij 在 2014 年 9月 29 正式釋出出來的。因為出現的比前面兩種工作流稍微晚一些,是以它有個非常大的優勢,集百家之長,補百家之短。GitLab 既支援 Git Flow 的分支政策,也支援 GitHub Flow 的 PR 和問題追蹤。
Git Flow & GitHub Flow 的瑕疵
當 Git Flow 出現後,它解決了之前項目管理的很讓人頭疼的分支管理,但是實際使用過程中,也暴露了很多問題:
- 預設工作分支是
,但是大部分版本管理工具預設分支都是develop
,開始的時候總是需要切換很麻煩。master
- Hotfix 和 Release 分支在需要版本快速疊代的項目中,幾乎用不到,因為剛開發完就直接合并到
發版,出現問題master
就直接修複釋出下個版本了。develop
- Hotfix 和 Release 分支,一個從
建立,一個從master
建立,使用完畢,需要合并回develop
develop
。而且在實際項目管理中,很多開發者會忘記合并回master
或者develop
。master
GitHub Flow 的出現,非常大程度上簡化了 Git Flow ,因為隻有一個長期分支
master
,并且提供 GUI 操作工具,一定程度上避免了上述的幾個問題,然而在一些實際問題面前,僅僅使用
master
分支顯然有點力不從心,例如:
- 版本的延遲釋出(例如 iOS 應用稽核到通過中間,可能也要在
上推送代碼)master
- 不同環境的部署 (例如:測試環境,預發環境,正式環境)
- 不同版本釋出與修複 (是的,隻有一個
分支真的不夠用)master
GitLab Flow 解決方案
為了解決上面提到的那些問題,GitLab Flow 給出了以下的解決方法。
版本的延遲釋出 Prodution Branch
master
分支不夠,于是添加了一個
prodution
分支,專門用來釋出版本。
不同環境的部署 Environment Branches & Upstream First
每個環境,都對應一個分支,例如下圖中的
pre-production
prodution
分支都對應不同的環境,這個工作流模型比較适用服務端,測試環境,預發環境,正式環境,一個環境建一個分支。
這裡要注意,代碼合并的順序,要按環境依次推送,確定代碼被充分測試過,才會從上遊分支合并到下遊分支。除非是很緊急的情況,才允許跳過上遊分支,直接合并到下遊分支。這個被定義為一個規則,名字叫 “upstream first”,翻譯過來是 “上遊優先”。
版本釋出分支 Release Branches & Upstream First
隻有當對外釋出軟體的時候,才需要建立
release
分支。對外釋出版本的記錄是非常重要的,如果線上出現了一個問題,需要拿到問題出現對應版本的代碼,才能準确定位問題。
在 Git Flow 中,版本記錄是通過
master
上的
tag
來記錄的。發現問題,建立
hotfix
分支,完成之後合并到
master
develop
在 GitLab Flow 中,建議的做法是每一個穩定版本,都要從
master
分支拉出一個分支,比如
2-3-stable
2-4-stable
等等。發現問題,就從對應版本分支建立修複分支,完成之後,先合并到
master
,然後才能再合并到
release
分支,遵循 “上遊優先” 原則。
分支命名實踐
現如今,越來越多的公司都會利用 GitLab 來搭建自己的代碼托管平台,是以就以 GitLab Flow 為例,給出一個較好的分支命名實踐。
如果存在多個環境,則為每個環境建立一個長期分支,可以命名為:
-
,表示主分支,用于生産環境;master
-
,表示内測分支,用于内測環境;beta
-
,表示測試分支,用于測試環境。test
在此,着重解釋一下“内測環境”吧,實際上,内測環境應該是生産環境的一部分,是從生産環境隔離出來一部分用于内測,以保證線上回歸測試時不影響真實的使用者,是以兩者共用一套生産資料庫,僅是通過流量入口做區分。
接下來,根據不同的目的,為新拉取的分支取不同的名稱:
- 如果是開發需求,則從
拉取新分支,命名為master
,其中每一部分都有不同的含義,如feature-1xx-2xx-3xx
-
為固定詞,表示這是一個新特性分支;feature
-
表示新特性的描述,為防止分支名過長,可以用縮寫;1xx
-
表示新分支建立的時間,格式為2xx
YYYYMMDD
-
表示新分支的建立者,姓名拼音或者英文名均可。3xx
-
給出一個開發需求的分支命名示例,
feature-SupportIM-20200711-chariesgavin
,整個分支名稱的含義就是,“某人在某時建立了某個功能的新特性分支”。開發、測試及代碼合并的流程,大緻如下:
-
分支拉取新的開發分支,進行編碼,自測;master
- 自測完成後,将代碼合并到
分支,并且在test
環境進行測試;test
-
環境測試通過後,将代碼合并到test
beta
環境進行線上回歸測試;beta
-
beta
分支,并且将代碼同步到生産環境;master
- 生産環境上線後,就再從
分支打一個master
,其作用和穩定分支tag
、釋出分支stable
一樣,用于復原代碼,命名為release
tag-xxx
自定義即可,如版本号。xxx
如果線上的代碼一直沒問題,自然是萬事大吉,但難免會遇到各種各樣的問題。這時,我們就遇到了另一種場景,即 BUG 修複。
- 如果是 BUG 修複,則從
master
hotfix-1xx-2xx-3xx
-
為固定詞,表示這是一個修複 BUG 的分支;hotfix
-
表示 BUG 的描述,為防止分支名過長,可以用縮寫;1xx
-
2xx
YYYYMMDD
-
3xx
-
給出一個 BUG 修複分支命名示例,
hotfix-messageRepeat-20200711-chariesgavin
,整個分支名稱的含義就是,“某人在某時建立了修複某個 BUG 的新分支”。理論上來說,BUG 修複的開發、測試及代碼合并的流程應該和上述的開發需求是一緻的,畢竟如果生産環境出現了問題,其他前置環境肯定也是跑不掉的,修複已知問題終歸是值得提倡的;但在比較緊急的情況下,沒有足夠的時間讓我們在不同的環境進行測試,該流程也是可以簡化的,大緻如下:
-
master
- 自測完成後,将代碼直接合并到
分支,上線到内測環境進行測試;beta
- 内測環境通過後,再将代碼合并到
分支,同步到生産環境,并從master
master
,備份穩定代碼;tag
- 最後,再将修複 BUG 的代碼同步到不同環境的穩定分支。
在這裡,有一點可能讓我們诟病,那就是分支名稱太長了。确實,當我們想把更多的資訊都揉進一個名稱的時候,難免會遇到這樣的問題!但如果是
feature-1.0
hotfix-20200710
這類名稱,可能開發周期稍微長一些的時候,大家都容易忘了這樣的分支到底是誰建立的、實作了什麼功能吧?是以,與之相比,我感覺分支名稱稍微長一些還是可以接受的。
當然,就如 Git Flow 一樣,任何工作流想要起作用,都需要我們認同它、打心裡接受它,然後才能自覺的遵守其規範,畢竟,公司總不至于因為我們不遵守分支命名規範而開除我們吧?公司采取硬性規定的另算。但這些工作流之是以能得到大家廣泛的認同,并且流傳之廣,自然還是尤其魅力的,或多或少還是能夠提高團隊協作效率的。采取與否,您來決定!
參考資料: