博主說:本文借鑑了很多「 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
,其中每一部分都有不同的含義,如-
hotfix
為固定詞,表示這是一個修復 BUG 的分支; -
1xx
表示 BUG 的描述,為防止分支名過長,可以用縮寫; -
2xx
表示新分支創建的時間,格式為YYYYMMDD
; -
3xx
表示新分支的創建者,姓名拼音或者英文名均可。
-
給出一個 BUG 修復分支命名示例,hotfix-messageRepeat-20200711-chariesgavin
,整個分支名稱的含義就是,“某人在某時創建了修復某個 BUG 的新分支”。理論上來說,BUG 修復的開發、測試及代碼合併的流程應該和上述的開發需求是一致的,畢竟如果生產環境出現了問題,其他前置環境肯定也是跑不掉的,修復已知問題終歸是值得提倡的;但在比較緊急的情況下,沒有足夠的時間讓我們在不同的環境進行測試,該流程也是可以簡化的,大致如下:
- 從
master
分支拉取新的開發分支,進行編碼,自測; - 自測完成後,將代碼直接合併到
beta
分支,上線到內測環境進行測試; - 內測環境通過後,再將代碼合併到
master
分支,同步到生產環境,並從master
分支打一個tag
,備份穩定代碼; - 最後,再將修復 BUG 的代碼同步到不同環境的穩定分支。
在這裡,有一點可能讓我們詬病,那就是分支名稱太長了。確實,當我們想把更多的信息都揉進一個名稱的時候,難免會遇到這樣的問題!但如果是feature-1.0
或者hotfix-20200710
這類名稱,可能開發週期稍微長一些的時候,大家都容易忘了這樣的分支到底是誰創建的、實現了什麼功能吧?因此,與之相比,我感覺分支名稱稍微長一些還是可以接受的。
當然,就如 Git Flow 一樣,任何工作流想要起作用,都需要我們認同它、打心裡接受它,然後才能自覺的遵守其規範,畢竟,公司總不至於因為我們不遵守分支命名規範而開除我們吧?公司採取硬性規定的另算。但這些工作流之所以能得到大家廣泛的認同,並且流傳之廣,自然還是尤其魅力的,或多或少還是能夠提高團隊協作效率的。採取與否,您來決定!
參考資料: