前端小微團隊的Gitlab實踐
阿新 • • 發佈:2020-03-09
疫情期間我感覺整個人懶散了不少,慢慢有意識要振作起來了,恢復到正常的節奏。最近團隊程式碼庫從`Gerrit`遷移到了`Gitlab`,為了讓前端團隊日常開發工作**有條不紊**,**高效運轉**,開發歷史**可追溯**,我也查閱和學習了不少資料。參考業界主流的**Git工作流**,結合公司業務特質,我也梳理了一套**適合自己團隊的Git工作流**,在這裡做下分享。
# 分支管理
首先要說的是分支管理,分支管理是`git`工作流的基礎,好的分支設計有助於規範開發流程,也是`CI/CD`的基礎。
## 分支策略
業界主流的`git`工作流,一般會分為`develop`, `release`, `master`, `hotfix/xxx`, `feature/xxx`等分支。各個分支各司其職,貫穿了整個**開發,測試,部署**流程。我這裡也基於主流的分支策略做了一些定製,下面用一張表格簡單概括下:
| 分支名 | 分支定位 | 描述 | 許可權控制 |
| ----------- | :------------- | ------------------------------------------------------------ | ----------------------------------------- |
| develop | 開發分支 | 不可以在develop分支push程式碼,應新建feature/xxx進行需求開發。迭代功能開發完成後的程式碼都會merge到develop分支。 | Develper不可直接push,可發起merge request |
| feature/xxx | 特性分支 | 針對每一項需求,新建feature分支,如feature/user_login,用於開發使用者登入功能。 | Develper可直接push |
| release | 提測分支 | 由develop分支合入release分支。ps: 應配置此分支觸發CI/CD,部署至測試環境。 | Maintainer可發起merge request |
| bug/xxx | 缺陷分支 | 提測後發現的bug,應基於`develop`分支建立`bug/xxx`分支修復缺陷,修改完畢後應合入develop分支等待迴歸測試。 | |
| master | 釋出分支 | master應處於隨時可釋出的狀態,用於對外發布正式版本。ps: 應配置此分支觸發CI/CD,部署至生產環境。 | Maintainer可發起merge request |
| hotfix/xxx | 熱修復分支 | 處理線上最新版本出現的bug | Develper可直接push |
| fix/xxx | 舊版本修復分支 | 處理線上舊版本的bug | Develper可直接push |
一般來說,`develop`, `release`, `master`分支是必備的。而`feature/xxx`, `bug/xxx`, `hotfix/xxx`, `fix/xxx`等分支純屬一種語義化的分支命名,如果要簡單粗暴一點,這些分支可以不分類,都命名為`issue/issue號`,比如`issue/1`,但是要在`issue`中說明具體問題和待辦事項,保證開發工作可追溯。
## 保護分支
利用`Protected Branches`,我們可以防止開發人員錯誤地將程式碼`push`到某些分支。對於普通開發人員,我們僅對`develop`分支提供`merge`許可權。
![保護分支](https://qncdn.wbjiang.cn/%E4%BF%9D%E6%8A%A4%E5%88%86%E6%94%AF.png)
具體操作案例請前往下面的**實戰案例**一節檢視。
# issue驅動工作
我們團隊採用的**敏捷開發**協作平臺是騰訊的[TAPD](https://www.tapd.cn/ 'TAPD'),日常迭代需求,缺陷等都會在`TAPD`上記錄。為了讓`Gitlab`程式碼庫能與迭代日常事務關聯上,我決定用`Gitlab issues`來做記錄,方便追溯問題。
## 里程碑
**里程碑Milestone**可以認為是一個**階段性的目標**,比如是一輪迭代計劃。里程碑可以設定時間範圍,用來約束和提醒開發人員。
![milestones](https://qncdn.wbjiang.cn/%E9%87%8C%E7%A8%8B%E7%A2%91.png)
里程碑可以**拆解為N個issue**,新建`issue`時可以**關聯里程碑**。比如這輪迭代一共5個需求,那麼就可以新建5個`issue`。在約定的時間範圍內,如果一個里程碑關聯的所有`issue`都`Closed`掉了,就意味著目標順利達成。
![建立issue](https://qncdn.wbjiang.cn/%E5%88%9B%E5%BB%BAissue.png)
## 標籤
`Gitlab`提供了`label`來標識和分類`issue`,我覺得這是一個非常好的功能。我這裡列舉了幾種`label`,用來標識`issue`的**分類**和**緊急程度**。
![標籤管理](https://qncdn.wbjiang.cn/%E6%A0%87%E7%AD%BE%E7%AE%A1%E7%90%86.png)
## issue分類
所有的開發工作應該通過`issue`記錄,包括但不限於**需求**,**缺陷**,**開發自測試**,**使用者體驗**等範疇。
### 需求&缺陷
這裡大概又分為兩種情況,一種是`TAPD`記錄在案的需求和缺陷,另一種是與產品或測試人員口頭溝通時傳達的簡單需求或缺陷(小公司會有這種情況...)。
對於`TAPD`記錄的需求和缺陷,建立`issue`時應附上鍊接,方便查閱(上文中已有提到)。
對於口頭溝通的需求和缺陷,我定了個規則,要求提出人本人在`Gitlab`上建立`issue`,並將需求或缺陷簡單描述清楚,否則口頭溝通的開發工作我不接(也是為了避免事後扯皮)。
**ps**:其實要求產品或者測試提`issue`,還不如上`Tapd`記錄。我定這麼個規則,其實就是借`Gitlab`找個說辭,**杜絕口頭類需求或缺陷**,哈哈。
### 開發自測試
開發者自己發現了系統缺陷或問題,此時應該通過`issue`記錄問題,並建立相應分支修改程式碼。
![自測試issue](https://qncdn.wbjiang.cn/%E8%87%AA%E6%B5%8B%E8%AF%95issue.png)
# 實戰案例
我前面也說了,我的原則是`issue`驅動開發工作。
下面用幾個例子來簡單說明基本的開發流程。小公司裡整個流程比較簡單,沒有複雜的整合測試,多輪驗收測試,灰度測試等。我甚至連單元測試都沒做(捂臉...)。
> 公共庫和公共元件其實是很有必要做單元測試的,這裡立個flag,後面一定補上單元測試。
## 需求開發
> feature/1,一個特性分支,對應issue 1
### 建立需求
正常的需求當然來源於產品經理等需求提出方,由於是通過示例說明,這裡我自己在`TAPD`上模擬著寫一個需求。
![TAPD建立需求](https://qncdn.wbjiang.cn/TAPD%E5%88%9B%E5%BB%BA%E9%9C%80%E6%B1%82.png)
### 建立issue
建立`Gitlab issue`,連結到`TAPD`中的相關需求。
![建立issue](https://qncdn.wbjiang.cn/%E5%88%9B%E5%BB%BAissue.png)
![一個issue](https://qncdn.wbjiang.cn/%E4%B8%80%E4%B8%AAissue.png)
### 建立分支&功能開發
基於`develop`分支建立`feature`分支進行功能開發(要保證本地git倉庫當前處於develop分支,且與遠端倉庫develop分支同步)。
```shell
git checkout -b feature/1
```
或者直接以遠端倉庫的`develop`分支為基礎建立分支。
```
git checkout -b feature/1 origin/develop
```
ps:我這裡用的`feature/1`作為分支名,其實這裡的`1`是用的`issue`號,並沒有用諸如`feature/login_verify`之類的名字,是因為我覺得用`issue`號可以更方便地找到對應的`issue`,更容易追蹤程式碼。
接著我們開始開發新功能......
![快樂地擼程式碼](https://qncdn.wbjiang.cn/%E5%BF%AB%E4%B9%90%E5%9C%B0%E6%92%B8%E4%BB%A3%E7%A0%81.gif)
### commit & push
完成功能開發後,我們需要提交程式碼並同步到遠端倉庫。
```
PS D:\projects\gitlab\project_xxx> git add .
PS D:\projects\gitlab\project_xxx> git cz
[email protected], [email protected]
? Select the type of change that you're committing: feat: A new feature
? What is the scope of this change (e.g. component or file name): (press enter to skip)
? Write a short, imperative tense description of the change (max 94 chars):
(9) 登入校驗功能
? Provide a longer description of the change: (press enter to skip)
? Are there any breaking changes? No
? Does this change affect any open issues? Yes
? If issues are closed, the commit requires a body. Please enter a longer description of the commit itself:
-
? Add issue references (e.g. "fix #123", "re #123".):
fix #1
git push origin HEAD
```
`git cz`是利用了`commitizen`來替代`git commit`。詳情請點選[前端自動化部署的深度實踐](https://juejin.im/post/5e38ec1ce51d4526c932a4fb)深入瞭解。
`fix #1`用於關閉`issue 1`。
`git push origin HEAD`則代表推送到遠端倉庫同名分支。
### 建立Merge Request
開發人員發起`Merge Request`,請求將自己開發的功能特性合入`develop`分支。
![建立Merge Request](https://qncdn.wbjiang.cn/%E5%88%9B%E5%BB%BAmerge%20request.png)
接著`Maintainer`需要**Review程式碼**,確認無誤後**同意Merge**。然後這部分程式碼就在遠端`Git`倉庫入庫了,其他開發同學拉取`develop`分支就能看到了。
## 版本提測
> issue/2,處理更新日誌,版本號等內容,對應issue 2
每個團隊的開發節奏都不同,有的團隊會每日**持續整合**發版本提測,有的可能兩天一次,這個就不深入討論了......
那麼當我們準備提測時,應該怎麼做呢?
通過上節的瞭解,我們已經知道,迭代內的功能需求都會通過`feature/xxx`分支合入到`develop`分支。
提測前,一般來說,還是要更新下`CHANGELOG.md`和`package.json`的版本號,可以由`Maintainer`或其他負責該項事務的同學執行。
> 主要是執行npm version major/minor/patch -m 'something done',具體操作可以參考[前端自動化部署的深度實踐](https://juejin.im/post/5e38ec1ce51d4526c932a4fb#heading-7)一文。
```
git checkout -b issue/2 origin/develop
npm version minor -m '迭代1第一次提測'
git push origin HEAD
然後發起merge request合入develop分支
```
此時,應以最新的`develop`分支程式碼在開發環境跑一遍功能,保證版本自測通過。
提測時,由`Maintainer`發起`Merge Request`,將`develop`分支程式碼合入`release`分支,此時自動觸發`Gitlab CI/CD`,自動構建併發布至**測試環境**。
版本提測後,各責任人應在`TAPD`上將相關需求和缺陷的狀態變更為**“測試中”**。
## 修復測試環境bug
> bug/3,一個bug分支,對應issue 3
這裡說的是在迭代週期內由測試工程師發現的測試環境中的系統`bug`,這些`bug`會被記錄在敏捷開發協作平臺`TAPD`上。修復測試環境`bug`的步驟與開發需求類似,這裡簡單說下步驟:
1. **在Gitlab上建立issue**
> 建立issue,並附上TAPD上的缺陷連結,方便追溯
2. **建立分支&修復缺陷**
基於`develop`分支建立分支:
```
// 3是issue號
git checkout -b bug/3 origin/develop
```
接著改程式碼......
3. **commit & push**
```
PS D:\projects\gitlab\project_xxx> git cz
[email protected], [email protected]
? Select the type of change that you're committing: fix: A bug fix
? What is the scope of this change (e.g. component or file name): (press enter to skip)
? Write a short, imperative tense description of the change (max 95 chars):
(11) 修復一個測試環境bug
? Provide a longer description of the change: (press enter to skip)
? Are there any breaking changes? No
? Does this change affect any open issues? Yes
? If issues are closed, the commit requires a body. Please enter a longer description of the commit itself:
-
? Add issue references (e.g. "fix #123", "re #123".):
fix #3
git push origin HEAD
```
4. **發起Merge Request**
開發人員發起`Merge Request`,請求將自己修復缺陷引入的程式碼合入`develop`分支。
然後`Maintainer`需要**Review程式碼**,同意本次`Merge Request`。
5. **等待迴歸測試**
該`bug`將在下一次`CI/CD`後,進入迴歸測試流程。
6. **級別高的測試環境Bug**
如果是級別很高的`bug`,比如影響了系統執行,導致測試人員無法正常測試的,應以`release`分支為基礎建立`bug`分支,修改完畢後合入`release`分支,再發起`cherry pick`合入`develop`分支。
## 釋出至生產環境
經過幾輪持續整合和迴歸測試後,一個迭代版本也慢慢趨於穩定,此時也迎來了激動人心的上線時間。我們要做的就是把通過了測試的`release`分支合入`master`分支。
![release合入master](https://qncdn.wbjiang.cn/release%E5%90%88%E5%85%A5master.png)
這一步相對簡單,但是要特別注意許可權控制(**防止生產環境事故**),具體許可權控制可以回頭看第一章節**分支管理**。
與`release`分支類似,`master`分支自動觸發`Gitlab CI/CD`,自動構建併發布至**生產環境**。
## 線上回滾
> revert/4,一個回滾分支,對應issue 4
程式碼升級到線上,但是發現報錯,系統無法正常執行。此時如果不能及時排查出問題,那麼只能先進行版本回退操作。
先說說**慣性思維**下,我的版本回退做法。
首先還是建立`issue`記錄下:
![建立記錄回滾的issue](https://qncdn.wbjiang.cn/%E5%88%9B%E5%BB%BA%E5%9B%9E%E6%BB%9A%E7%9A%84issue.png)
基於`master`分支建立`revert/4`分支
```
git checkout -b revert/4 origin/master
```
假設當前版本是`1.1.0`,我們想回退到上個版本`1.0.3`。那麼我們需要先檢視下`1.0.3`版本的資訊。
```
PS D:\tusi\projects\gitlab\projectname> git show 1.0.3
commit 90c9170a499c2c5f8f8cf4e97fc49a91d714be50 (tag: 1.0.3, fix/1.0.2_has_bug)
Author: tusi
Date: Thu Feb 20 13:29:31 2020 +0800
fix:1.0.2
diff --git a/README.md b/README.md
index ac831d0..2ee623b 100644
--- a/README.md
+++ b/README.md
@@ -10,6 +10,8 @@
只想修改舊版本的bug,不改最新的master
+1.0.2版本還是有個版本,再修復下
+
特性2提交
特性3提交
```
主要是取到`1.0.3`版本對應的`commit id`,其值為`90c9170a499c2c5f8f8cf4e97fc49a91d714be50`。
接著,我們根據`commit id`進行`reset`操作,再推送到遠端同名分支。
```
git reset --hard 90c9170a499c2c5f8f8cf4e97fc49a91d714be50
git push origin HEAD
```
接著發起`Merge Request`把`revert/4`分支合入`master`分支。
![回滾分支合入master](https://qncdn.wbjiang.cn/%E5%9B%9E%E6%BB%9A%E5%88%86%E6%94%AF%E5%90%88%E5%85%A5master.png)
一般來說,這波操作沒什麼問題,能解決常規的回滾問題。
### 臨時變通
由於`master`分支是保護分支,設定了不可`push`。如果不想通過`merge`的方式回滾,所以只能先臨時設定`Maintainer`擁有`push`許可權,然後由`Maintainer`進行回滾操作。
```
git checkout master
git pull
git show 1.0.3
git reset --hard 90c9170a499c2c5f8f8cf4e97fc49a91d714be50
git push origin HEAD
```
完事後,還需要記得把`master`設定為不可`push`。
> Q: 為什麼不讓`Maintainer`一直擁有`master`的`push`許可權?
>
> A: 主要還是為了防止出現生產環境事故,給予臨時性許可權更穩妥!
### git reset --hard存在什麼問題?
如題,`git reset --hard`確實是存在問題的。`git reset --hard`屬於霸道玩法,直接移動`HEAD`指標,會丟棄之後的提交記錄,如果不慎誤操作了也別慌,還是可以通過查詢`git reflog`找到`commitId`搶救回來的;`git reset`後還存在一個隱性的問題,如果與舊的`branch`進行`merge`操作時,會把`git reset`回滾的程式碼重新引入。那麼怎麼解決這些問題呢?
![一籌莫展](https://qncdn.wbjiang.cn/%E7%A8%8B%E5%BA%8F%E5%91%98%E4%BD%95%E8%8B%A6%E4%B8%BA%E9%9A%BE%E7%A8%8B%E5%BA%8F%E5%91%98.gif)
別慌,這個時候你必須掏出`git revert`了。
> Q: `git revert`的優勢在哪?
>
> A: 首先,`git revert`是通過一次新的commit來進行回滾操作的,HEAD指標向前移動,這樣就不會丟失記錄;另外,`git revert`也不會引起`merge`舊分支時誤引入回滾的程式碼。最重要的是,`git revert`在回滾的細節控制上更加優秀,可解決部分回滾的需求。
舉個栗子,本輪迭代團隊共完成需求`2`項,而上線後發現其中`1`項需求有致命性缺陷,需要回滾這個需求相關的程式碼,同時要保留另一個需求的程式碼。
![我太難了](https://qncdn.wbjiang.cn/%E6%88%91%E5%A4%AA%E9%9A%BE%E4%BA%86.jpg)
首先我們檢視下日誌,找下這兩個需求的`commitId`
```shell
PS D:\tusi\projects\test\git_test> git log --oneline
86252da (HEAD -> master, origin/master, origin/HEAD) 解決衝突
e3cef4e (origin/release, release) Merge branch 'develop' into 'release'
f247f38 (origin/develop, develop) 需求2
89502c2 需求1
```
我們利用`git revert`回滾需求1相關的程式碼
```shell
git revert -n 89502c2
```
這時可能要解決衝突,解決完衝突後就可以`push`到遠端`master`分支了。
```shell
git add .
git commit -m '回滾需求1的相關程式碼,並解決衝突'
git push origin master
```
感覺還是菜菜的,如果大佬們有更優雅的解決方案,求指導啊!
## 修復線上bug
> hotfix/5,一個線上熱修復分支,對應issue 5
比如線上出現了系統無法登入的`bug`,測試工程師也在`TAPD`提交了缺陷記錄。那麼修復線上`bug`的步驟是什麼呢?
1. 建立`issue`,標題可以從`TAPD`中的`Bug`單中`copy`過來,而描述就貼上`Bug`單的連結即可。
2. 基於`master`分支建立分支`hotfix/5`。
```
git checkout -b hotfix/5 origin/master
```
3. 擼程式碼,修復此bug......
4. 修復完此`bug`後,提交該分支程式碼到遠端倉庫同名分支
```
git push origin HEAD
```
5. 然後發起`cherry pick`合入到`master`和`develop`分支,並在`master`分支打上新的版本`tag`(假設當前最大的`tag`是`1.0.0`,那麼新的版本`tag`應為`1.0.1`)。
## 修復線上舊版本bug
> fix/6,一個線上舊版本修復分支,對應issue 6
某些專案產品可能會有多個線上版本同時在執行,那麼不可避免要解決舊版本的`bug`。針對線上舊版本出現的`bug`,修復步驟與上節類似。
1. 建立`issue`,描述清楚問題
2. 假設當前版本是`1.0.0`,而`0.9.0`版本出了一個`bug`,應基於`tag 0.9.0`建立`fix`分支。
```
git checkout -b fix/6 0.9.0
```
3. 修復缺陷後,應打上新的`tag 0.9.1`,並推送到遠端。
```
git tag 0.9.1
git push origin tag 0.9.1
```
4. 如果此`bug`也存在於最新的`master`分支,則需要`git push origin HEAD`提交該`fix`分支程式碼到遠端倉庫同名分支,然後發起`cherry pick`合入到`master`,此時很大可能存在衝突,需要解決衝突。
![cherry pick](https://qncdn.wbjiang.cn/cherry%20pick.png)
## cherry pick
在瞭解到`cherry pick`之前,我一直認為只有`git merge`可以合併程式碼,也好幾次遇到合入了不想要的程式碼的問題。有了`cherry pick`,我們就可以合併單次提交記錄,解決`git merge`時合併太多不想要的內容的煩惱,在解決`bug`時特別有用。
## git rebase
經過這段時間的使用,我發現使用`git merge`合併分支時,會讓`git log`的`Graph`圖看起來有點吃力。
```
PS D:\tusi\projects\gitlab\projectname> git log --pretty --oneline --graph
* 7f513b0 (HEAD -> develop) Merge branch 'issue/55' into 'release'
|\
| * 1c94437 (origin/issue/55, issue/55) fix: 【bug】XXX1
| * c84edd6 Merge branch 'release' of host:project_repository into release
| |\
| |/
|/|
* | 115a26c Merge branch 'develop' into 'release'
|\ \
| * \ 60d7de6 Merge branch 'issue/30' into 'develop'
| |\ \
| | * | 27c59e8 (origin/issue/30, issue/30) fix: 【bug】XXX2
| | | * ea17250 Merge branch 'release' of host:project_repository into release
| | | |\
| |_|_|/
|/| | |
* | | | 9fd704b Merge branch 'develop' into 'release'
|\ \ \ \
| |/ / /
| * | | a774d26 Merge branch 'issue/30' into 'develop'
| |\ \ \
| | |/ /
```
接著我就瞭解到了`git rebase`,變基,哈哈哈。由於對`rebase`瞭解不深,目前也不敢輕易改用`rebase`,畢竟`rebase`還是有很多隱藏的坑的,使用起來要慎重!在這裡先挖個坑吧,後面搞懂了再填坑......
# 注意事項
1. 一般而言,自己發起的`Merge Request`必須由別的同事`Review`並同意合入,這樣更有利於發現程式碼問題。
2. 對了,`TAPD`還支援與`Gitlab`協同的。詳情見[原始碼關聯指引](https://www.tapd.cn/help/view#1120003271001001346 '原始碼關聯指引')。
# 結語
實踐證明,這套`Git`工作流目前能覆蓋我專案開發過程中的絕大部分場景。不過要注意的是,適合自己的才是最好的,盲目採用別人的方案有時候是會水土不服的。
以上所述純屬前端小微團隊內部的`Gitlab`實踐,必然存在著很多不足之處,如有錯誤之處還請指正,歡迎交流。
![歡迎關注&交流](https://qncdn.wbjiang.cn/%E5%A4%A7%E5%89%8D%E7%AB%AF%E5%85%AC%E4%BC%97%E5%8F%B7%E5%90%8D%E7%89%87.png)