對於專案版本管理,你是否存在這樣的痛點:專案分支多而雜不好管理,git log介面commit資訊錯亂複雜無規範,版本回退不知道選擇什麼版本合適……。
專案版本管理的最佳實踐系列,筆者將以兩篇文章的形式展開介紹(即基礎篇與進階篇)。本文為gitflow版本管理的最佳實踐-基礎篇。基礎篇主要介紹git應用於生產的基本流程與怎麼使用gitflow管理你的專案版本線(適用於敏捷迭代的專案管理場景下)。進階篇 將著重介紹gitflow+jenkins+docker+DevOps+敏捷Scrum 完成專案持續構建與持續交付(CI/CD)。閱讀本文需要有一定git基礎,基礎知識則不在本文展開,善用網上衝浪工具便可學習到許多Git的基礎知識。實際上,本文介紹的並不是純粹的gitflow,而是結合實際生產對gitflow的改造與最佳實踐。
目錄
術語 | 解釋 |
PR | 即pull request,拉取請求。請求git程式碼管理員將你的程式碼合併到倉庫的分支中。一般的PR由標題部分,描述部分與程式碼部分組成。 |
code review | 在PR過程中程式碼管理員對你提交的程式碼進行程式碼審查,即你的程式碼是否符合規範、是否存在風格問題、安全問題等,對你程式碼進行cr的同學並不一定是程式碼管理員,成熟的敏捷團隊,每一個成員都是code owner,都可以對pr進行審閱。 |
squash | PR過程中,會將你的所有commit合併(榨取)成一個commit並提交到目標分支中,目的是減少冗餘提交、規範主分支提交資訊(實際上是rebase的一種操作)。 |
LGTM | look good to me(看起來很吊),一般存在於PR評論中,即對pr的內容沒有問題,同意合併到版本庫。 |
在我們的最佳實踐中,遠端版本庫永遠只存在三條長期且相互獨立的分支,他們分別為develop、release與master,三條分支對應三個環境,分別為開發環境(整合式開發環境)、測試環境(預發環境)與生產環境,三個分支分別都上許可權,不可直接對其進行push與commit操作,即所有的修改均通過PR進行,以保證分支對應環境的安全與穩定。本地環境對應的遠端分支均會在PR通過之後,自動進行刪除,以保證版本線的簡單。
環境 | 分支名 |
開發環境 | origin/develop |
測試環境(預發環境) | origin/release |
生產環境 | origin/master |
本地環境功能分支 | develop_xxx (xxx為具體的開發成員或具體的功能描述, origin/develop_xxx,即feature分支下沉到本地,生命週期短,只存在於pr過程)。 |
在正式介紹gitflow之前,我們需要對版本號進行規範,方便接下來的行文展開。
在生產中,我們常用的版本號為三位數版本號(偶爾帶四位熱修復號),其構成如下:
V主版本號.次版本號.功能號(.${熱修復版本號}).環境
eg:V1.0.0.1.RELEASE、V1.1.0.DEVELOP、V1.0.0。(版本號並不以十進位制,而是按照迭代規劃推播)
主版本號,也叫首位版本號、頂位版本號,即V後第一個版本號。主版本號一般代表專案的期數與產品方向。除非專案合同改變、大規模api不相容、產品方向改變、底層架構升級等情況外不輕易更新。
另外,專案未正式釋出、未正式孵化、未正式上線,則首位版本號為0,一期釋出,則為V1。
次版本號,也叫迭代號,一般代表某個迭代釋出的功能集合(一個迭代釋出會包含若干個功能更新)。
如V1.1.0:第一期專案第一迭代釋出版本、V1.2.0:第一期第二迭代釋出版本。
一般來說,提交到專案分支內的程式碼均需要經過PR,而為了保證單個PR的簡潔性與純粹性,建議一個PR描述一個功能。因此第三位數的版本號也叫做PR號或功能號,用來描述單個提交到主分支內的功能或程式碼修改。
如V0.0.1:第一迭代的第一個提交、V0.0.98:第一迭代的第98個PR。
四位數版本號是可選版本號,為熱修復版本號(也叫老爺保號hh),常規迭代與develop分支下並不會出現,而常出現在測試環境對應的release分支與生產環境對應的master分支(develop分支對應的開發環境出現bug直接提交pr修復並在原來的版本號上+1便可)。這個版本號常用於線上熱修復,測試環境(預發環境)的熱修復。
值得注意的是,四位數版本號經過線上熱修復之後,要同步到本地develop環境的情況下,應當在develop分支下的三位數版本號上加一。
如:master的熱修復號為V1.0.0.4,develop分支當前版本為V1.1.8.DEVELOP,那麼這個修復要同步回develop分支保證bug不重現,那麼在develop上面的版本則為V1.1.9.DEVELOP
因為在git中的tag名稱是唯一的,那麼在develop分支下出現了V1.0.0的tag,那麼在release和master下便不可以再打一個tag叫V1.0.0。因此出現環境號來對分支版本進行區分(生產環境不加環境號)。
環境 | 環境名 | 版本號(範例) |
開發環境 | DEVELOP | V1.0.0.DEVELOP |
測試環境(預發環境) | RELEASE | V1.0.0.RELEASE |
生產環境 | MASTER | V1.0.0 |
這裡要搬出兩位同學進行接下來的講解,他們是【弓行】同學與【阿康】同學。
版本的最開始(指V0.0.0),程式碼管理員會初始化遠端倉庫,並基於master的初始版本建立三條分支,他們是
origin/master(對應生產),origin/release(對應測試環境),origin/develop(對應開發環境) 併為這三條分支設定保護策略,三條分支均不允許直接的commit與push修改。
程式碼管理員將三個初始版本打上相應的TAG:(V0.0.0.DEVELOP、V0.0.0.RELEASE與V0.0.0)。
完成迭代計劃會議(迭代版本號為V0.1.0)之後,弓行與阿康他們分別認領了兩個任務:【開發功能】弓行,【開發功能2】阿康。
此時,弓行與阿康會將遠端倉庫克隆下來,並基於origin/develop 建立本地develop_gx分支與develop_kang分支。
兩人認領任務後進行同步開發,一段時間後,弓行率先完成【開發功能1】的工作,因此他需要將當前開發版本提交到開發環境中進行自測與前後端聯調。但此時【origin/develop】是被保護的狀態無法被直接提交。因此,弓行需要對當前的開發的版本進行PR申請,即建立拉取請求,請求程式碼管理員對程式碼進行code review,通過後進行合併。
此處涉及的步驟大致如下:
1、push當前本地分支到origin,得到origin/develop_gx。
2、建立PR:即:origin/develop_gx 合併到 origin/develop 的拉取請求
3、等待程式碼管理員(或小組內同學)進行code review,若需要修改,則直接在pr中提出註釋,作者修改後直接push到遠端分支中,繼續等待程式碼管理員進行code review。
4、通過後,將當前commit list以squash的形式合併到origin/develop中,得到V0.0.1.DEVELOP 的commit
5、最後選擇刪除origin/develop_gx的遠端分支
此時,弓行同學完成了第一個功能的開發,並在【origin/develop】分支上對自己的pr commit 進行tag操作:將此commit記錄為【V0.0.1.DEVELOP】
不久後,阿康同學也完成了【開發功能2】的開發,他也需要將程式碼提交到origin/develop分支進行測試與聯調。但此時,origin/develop已經與他的基版本不一樣了(基版本為V0.0.0.DEVELOP,遠端版本為V0.0.1.DEVELOP,領先一個版本)如果直接建立PR,可能因為程式碼衝突的問題無法完成版本合併,如下圖。
此時阿康需要將origin/develop版本拉取到本地,並執行以下操作(推薦直接使用ide自帶的git工具,會方便不少)
//檢查遠端倉庫是否有新版本
git fetch origin
//發現新版本,需要拉取到本地解決衝突後進行程式碼合併
//暫存本地修改
git stash save 「stash changes」
//拉取遠端版本
git pull origin/develop
//取出本地修改
git stash apply
//手工解決衝突(推薦直接使用idea)
//提交修改
git commit -m'1、解決衝突合併版本'
使用ide自帶的衝突解決工具則如下圖
提交修改後(注意一定要和衝突程式碼的作者商量程式碼的變更),便可以建立PR,等待團隊內同學進行code review。團隊成員通過之後,阿康的修改便可以成功被合併到origin/develop中進行聯調與測試了。阿康此時需要將改commit打上tag【V0.0.2.DEVELOP】,如下圖。
至此,V0.1.0所規劃的開發工作全部完成。
完成V0.1.0版本開發工作後,弓行同學認領了一個新任務:【V0.1.0版本提測】。正在其他進行其他功能開發工作的弓行同學此時需要將原生程式碼stash起來,並將origin/develop分支的程式碼與原生程式碼進行合併(即git pull origin develop操作),並進行程式碼衝突的解決工作。
因為要將程式碼釋出到origin/release分支進行版本提測,所以弓行同學需要同時將origin/release上的程式碼與原生程式碼進行合併操作(即git pull origin release操作) 並進行程式碼衝突的解決工作。
完成git pull origin develop 與git pull origin release 之後,本地會形成一個新的commit版本。弓行同學需要將此commit版本通過pr的方式合併到 origin/release 上,方可完成release分支的測試版本釋出工作。因此弓行同學需要重複 3.2.3 步驟的PR建立過程,並通過release分支的分支管理員審批後,方可將版本釋出到測試環境。
將commit通過pr的形式提交到release後,接下來就是對版本進行標記的過程,因為此release已經完成了版本的開發工作,因此,當前版本在release分支上會被標記為【V0.1.0.RELEASE】。又因為在develop分支上,V0.0.2.DEVELOP版本對應著release的V0.1.0.RELEASE版本,針對origin/develop的分支上的該commit,會被打上第二個tag:【V0.1.0.DEVELOP】。
而後,對於develop分支的tag處理,將會直接從V0.1.0.DEVELOP繼續往下走(如V0.1.1.DEVELOP等)
origin/release分支對應著測試環境,對於某些情況而言,測試環境相當於專案的beta版本,有可能直接面對客戶。
那麼版本提測之後,測試同學針對該【V0.1.0.RELEASE】版本進行各種測試後發現當前版本存在BUG,那麼開發的同學就要針對改bug進行熱修復。
假設現在在測試環境出現一個BUG,該BUG的修復工作依舊由弓行同學認領解決,那麼此時弓行同學就需要將手頭上的開發工作暫停(git stash),然後拉取最新版本的origin/release分支到本地,然後進行bug修復工作。完成修復後,提交原生程式碼到 origin/release_hotfix_gx 分支,對該分支進行PR操作,由release管理員進行code review併合併到release中,並將該修復版本記錄為【V.0.1.0.1.RELEASE】。
當然了,因為分支commit存在對映關係,出現在V0.1.0.RELEASE上的BUG,也一定會出現在V0.1.0.DEVELOP。那麼此時修復了測試環境的版本仍不夠,弓行需要將該修復合併到origin/develop上。因此弓行同學需要將新發的版本【V0.1.0.1.RELEASE】拉取到本地,然後對origin/develop進行版本提交工作,形成【V.0.1.1.DEVELOP】
至此完成熱修復的過程(master的熱修復也是同理,不過是將修復版本根據實際情況合併到release和develop上的不同罷了)。
完成release版本的提測工作、BUG修復工作後,弓行同學需要將release分支的版本釋出到master上,完成生產環境版本的釋出,實際上這個過程也與 3.2.5 並無太大差異。同學們可以結合自己實際情況,在這一步增加團隊code review、checklist檢查,釋出風險控制等操作,對生產釋出進行安全保障。
在完成origin/master的釋出工作後,將master的tag更新到 V0.1.0 便完成了整個迭代的釋出工作。
細心的同學讀到這裡可能已經發現了,origin/develop、origin/release、origin/master 這三條分支在整個過程中都互相獨立,互不影響,因此本工作流程屬於三獨立分支模式的gitflow,同學們若為減少流程,release分支可優化掉,直接在develop分支上進行測試,(也符合測試驅動開發)
試想一個場景:產品沒多少時間規劃特性;專案草草規劃衝刺backlog;開發在迭代最後一天完成開發工作,測試只有最後2小時進行測試;上線後一堆BUG便令人十分抓狂,因此雙週迭代制應運而生。
以下圖為例,一個敏捷團隊中有三種垂直的職能角色(暫不考慮產品):開發、測試、與專案(Scrum master),我們假定當前迭代為N,下個迭代為N+1,上個迭代為N-1。
雙週迭代制,即一個衝刺迭代設定為兩週(或若干周),在這兩週中的第一週,這幾位垂直職能角色可以如下分工:
- 開發:進行N迭代 (當前迭代) 的開發與N-1版本 (上個版本) 的hotfix工作,並在每週五進行統一提測;
- 測試:進行N迭代 (當前迭代) 的develop環境測試與N-1迭代 (上個迭代) 的 release 環境測試,在每週五前完成N-1版本的測試工作;
- 專案:進行N+1 (下個迭代) 的迭代規劃工作與上兩個迭代(N-2)的迭代釋出工作
如此一來,N-1版本,N版本,N+1版本便可實現交錯進行,有條不紊(需求源源不斷地來hhh)當然了,迭代開發時間與測試時間可以適當變動(如 開發:測試 = 6:4 或7:3)。
採用雙週迭代的好處在於:
- 開發同學有充足和彈性的時間進行迭代的開發工作與bug修復工作與需求理解
- 測試同學有充足的時間進行測試工作以保證專案品質 (在develop環境上一個功能測一個功能,並在release環境可以完成充分的功能測試)
- 專案有更多時間去規劃專案的迭代與分解具體需求做更完善的設計(瘋狂規劃迭代)。
基於雙週迭代制的gitflow版本管理,即在迭代中:
- 開發在origin/develop上進行開發,進行 3.2.4步驟的開發工作,與上個release版本的hotfix工作;
- 測試緊跟開發進行develop分支的測試與上個release版本的測試;
- 第一週結束,統一發版本到origin/release,測試在第二週開始當前版本release環境的功能測試;
- 第三週的週一,專案進行版本發版工作(即釋出到origin/master)
Q1 : 微服務架構下,每個專案獨立一個版本庫怎麼做到版本號統一,是每個微服務單獨編制版本號還是全域性統一版本號?
A1 : 對於微服務架構下(或者分散式架構專案)的每個微服務獨立版本庫的情況,建議全域性編制版本號,即同一個釋出視窗,對所有的目標釋出分支與的二位數版本號進行編制(即全域性統一迭代號或者產品號)。對於沒有更新的微服務,可直接在原release的commit上進行Tag釋出。
Q2 : 三分支版本線相對獨立,對於版本合併比較痛苦。
A2 : 這個問題是切實存在的,建議固定釋出人,在本地分支保留release分支、master分支與develop分支的合併記錄,防止衝突過多。
Q3 : 對於目標釋出的功能,若功能在釋出前存在風險,則無法下有風險的分支。
A3 : 問題切實存在,可以配合開關設定與雙週迭代做釋出。
Q4 : 專案處於快速開發階段,大家一直往develop分支上面提PR,但是沒有人做code review。
A4 : 可以嘗試PR標題帶上tag資訊作為commit title,即:V0.0.1 xxx功能開發 V0.0.2 XXX功能開發,這樣一來,就相當於做了資源鎖,大家都想往develop上面提pr,但是上一個人把三位數版本號佔用了,那麼就需要有人把這個pr處理掉,自己才能使用下一個版本號,直到團隊code review習慣成熟,如下圖(develop的版本線是不是很清晰)。
Q5 : 允許origin/develop、origin/release與origin/master三條分支之間互相合並嗎?
A5 : 不允許,只能通過PR形式進行分支版本合併
Q6 : 使用此種gitflow後,三個分支的版本線會是什麼樣子的?
A6 : 如下圖,無論是develop分支還是release分支與master分支,分支永遠只有一條直線,不會有分支之間進行合併的情況,所以顯得版本線十分乾淨整潔。
Q7 : 好像整個過程都沒有看到feature分支
A7 : 是的,在此種版本管理中,feature分支其實已經下沉到了每個人的本地版本庫中,不直接在origin庫中體現。
Q8 : 遠端feature分支可以不刪除嗎?
A8 : 為了保證git log乾淨,建議個人分支合併到develop分支後便執行刪除,但不刪除遠端個人分支也是可以的,可以嘗試完成develop的pr後使用同一個個人分支合併到release_${version}中,然後執行release_${version}->release的PR完成release的釋出。
以上便是專案版本管理的最佳實踐:gitflow基礎篇的所有內容,歡迎在評論區討論與提出改進意見!