前端中小團(tuán)隊(duì) GitLab 最佳實(shí)踐(文末送書)
最近團(tuán)隊(duì)代碼庫從Gerrit遷移到了Gitlab,為了讓前端團(tuán)隊(duì)日常開發(fā)工作有條不紊,高效運(yùn)轉(zhuǎn),開發(fā)歷史可追溯,我也查閱和學(xué)習(xí)了不少資料。參考業(yè)界主流的Git工作流,結(jié)合公司業(yè)務(wù)特質(zhì),我也梳理了一套適合自己團(tuán)隊(duì)的Git工作流,在這里做下分享。
分支管理
首先要說的是分支管理,分支管理是git工作流的基礎(chǔ),好的分支設(shè)計(jì)有助于規(guī)范開發(fā)流程,也是CI/CD的基礎(chǔ)。
分支策略
業(yè)界主流的git工作流,一般會(huì)分為develop, release, master, hotfix/xxx, feature/xxx等分支。各個(gè)分支各司其職,貫穿了整個(gè)開發(fā),測(cè)試,部署流程。我這里也基于主流的分支策略做了一些定制,下面用一張表格簡(jiǎn)單概括下:
| 分支名 | 分支定位 | 描述 | 權(quán)限控制 |
|---|---|---|---|
| develop | 開發(fā)分支 | 不可以在develop分支push代碼,應(yīng)新建feature/xxx進(jìn)行需求開發(fā)。迭代功能開發(fā)完成后的代碼都會(huì)merge到develop分支。 | Develper不可直接push,可發(fā)起merge request |
| feature/xxx | 特性分支 | 針對(duì)每一項(xiàng)需求,新建feature分支,如feature/user_login,用于開發(fā)用戶登錄功能。 | Develper可直接push |
| release | 提測(cè)分支 | 由develop分支合入release分支。ps: 應(yīng)配置此分支觸發(fā)CI/CD,部署至測(cè)試環(huán)境。 | Maintainer可發(fā)起merge request |
| bug/xxx | 缺陷分支 | 提測(cè)后發(fā)現(xiàn)的bug,應(yīng)基于develop分支創(chuàng)建bug/xxx分支修復(fù)缺陷,修改完畢后應(yīng)合入develop分支等待回歸測(cè)試。 | |
| master | 發(fā)布分支 | master應(yīng)處于隨時(shí)可發(fā)布的狀態(tài),用于對(duì)外發(fā)布正式版本。ps: 應(yīng)配置此分支觸發(fā)CI/CD,部署至生產(chǎn)環(huán)境。 | Maintainer可發(fā)起merge request |
| hotfix/xxx | 熱修復(fù)分支 | 處理線上最新版本出現(xiàn)的bug | Develper可直接push |
| fix/xxx | 舊版本修復(fù)分支 | 處理線上舊版本的bug | Develper可直接push |
一般來說,develop, release, master分支是必備的。而feature/xxx, bug/xxx, hotfix/xxx, fix/xxx等分支純屬一種語義化的分支命名,如果要簡(jiǎn)單粗暴一點(diǎn),這些分支可以不分類,都命名為issue/issue號(hào),比如issue/1,但是要在issue中說明具體問題和待辦事項(xiàng),保證開發(fā)工作可追溯。
保護(hù)分支
利用Protected Branches,我們可以防止開發(fā)人員錯(cuò)誤地將代碼push到某些分支。對(duì)于普通開發(fā)人員,我們僅對(duì)develop分支提供merge權(quán)限。

具體操作案例請(qǐng)前往下面的實(shí)戰(zhàn)案例一節(jié)查看。
issue驅(qū)動(dòng)工作
我們團(tuán)隊(duì)采用的敏捷開發(fā)協(xié)作平臺(tái)是騰訊的TAPD[1],日常迭代需求,缺陷等都會(huì)在TAPD上記錄。為了讓Gitlab代碼庫能與迭代日常事務(wù)關(guān)聯(lián)上,我決定用Gitlab issues來做記錄,方便追溯問題。
里程碑
里程碑Milestone可以認(rèn)為是一個(gè)階段性的目標(biāo),比如是一輪迭代計(jì)劃。里程碑可以設(shè)定時(shí)間范圍,用來約束和提醒開發(fā)人員。

里程碑可以拆解為N個(gè)issue,新建issue時(shí)可以關(guān)聯(lián)里程碑。比如這輪迭代一共5個(gè)需求,那么就可以新建5個(gè)issue。在約定的時(shí)間范圍內(nèi),如果一個(gè)里程碑關(guān)聯(lián)的所有issue都Closed掉了,就意味著目標(biāo)順利達(dá)成。

標(biāo)簽
Gitlab提供了label來標(biāo)識(shí)和分類issue,我覺得這是一個(gè)非常好的功能。我這里列舉了幾種label,用來標(biāo)識(shí)issue的分類和緊急程度。

issue分類
所有的開發(fā)工作應(yīng)該通過issue記錄,包括但不限于需求,缺陷,開發(fā)自測(cè)試,用戶體驗(yàn)等范疇。
需求&缺陷
這里大概又分為兩種情況,一種是TAPD記錄在案的需求和缺陷,另一種是與產(chǎn)品或測(cè)試人員口頭溝通時(shí)傳達(dá)的簡(jiǎn)單需求或缺陷(小公司會(huì)有這種情況...)。
對(duì)于TAPD記錄的需求和缺陷,創(chuàng)建issue時(shí)應(yīng)附上鏈接,方便查閱(上文中已有提到)。
對(duì)于口頭溝通的需求和缺陷,我定了個(gè)規(guī)則,要求提出人本人在Gitlab上創(chuàng)建issue,并將需求或缺陷簡(jiǎn)單描述清楚,否則口頭溝通的開發(fā)工作我不接(也是為了避免事后扯皮)。
ps:其實(shí)要求產(chǎn)品或者測(cè)試提issue,還不如上Tapd記錄。我定這么個(gè)規(guī)則,其實(shí)就是借Gitlab找個(gè)說辭,杜絕口頭類需求或缺陷,哈哈。
開發(fā)自測(cè)試
開發(fā)者自己發(fā)現(xiàn)了系統(tǒng)缺陷或問題,此時(shí)應(yīng)該通過issue記錄問題,并創(chuàng)建相應(yīng)分支修改代碼。

實(shí)戰(zhàn)案例
我前面也說了,我的原則是issue驅(qū)動(dòng)開發(fā)工作。
下面用幾個(gè)例子來簡(jiǎn)單說明基本的開發(fā)流程。小公司里整個(gè)流程比較簡(jiǎn)單,沒有復(fù)雜的集成測(cè)試,多輪驗(yàn)收測(cè)試,灰度測(cè)試等。我甚至連單元測(cè)試都沒做(捂臉...)。
公共庫和公共組件其實(shí)是很有必要做單元測(cè)試的,這里立個(gè)flag,后面一定補(bǔ)上單元測(cè)試。
需求開發(fā)
feature/1,一個(gè)特性分支,對(duì)應(yīng)issue 1
創(chuàng)建需求
正常的需求當(dāng)然來源于產(chǎn)品經(jīng)理等需求提出方,由于是通過示例說明,這里我自己在TAPD上模擬著寫一個(gè)需求。

創(chuàng)建issue
創(chuàng)建Gitlab issue,鏈接到TAPD中的相關(guān)需求。


創(chuàng)建分支&功能開發(fā)
基于develop分支創(chuàng)建feature分支進(jìn)行功能開發(fā)(要保證本地git倉庫當(dāng)前處于develop分支,且與遠(yuǎn)程倉庫develop分支同步)。
git checkout -b feature/1
或者直接以遠(yuǎn)程倉庫的develop分支為基礎(chǔ)創(chuàng)建分支。
git checkout -b feature/1 origin/develop
ps:我這里用的feature/1作為分支名,其實(shí)這里的1是用的issue號(hào),并沒有用諸如feature/login_verify之類的名字,是因?yàn)槲矣X得用issue號(hào)可以更方便地找到對(duì)應(yīng)的issue,更容易追蹤代碼。
接著我們開始開發(fā)新功能......

commit & push
完成功能開發(fā)后,我們需要提交代碼并同步到遠(yuǎn)程倉庫。
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) 登錄校驗(yàn)功能
? 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。詳情請(qǐng)點(diǎn)擊前端自動(dòng)化部署的深度實(shí)踐深入了解。
fix #1用于關(guān)閉issue 1。
git push origin HEAD則代表推送到遠(yuǎn)程倉庫同名分支。
創(chuàng)建Merge Request
開發(fā)人員發(fā)起Merge Request,請(qǐng)求將自己開發(fā)的功能特性合入develop分支。

接著Maintainer需要Review代碼,確認(rèn)無誤后同意Merge。然后這部分代碼就在遠(yuǎn)程Git倉庫入庫了,其他開發(fā)同學(xué)拉取develop分支就能看到了。
版本提測(cè)
issue/2,處理更新日志,版本號(hào)等內(nèi)容,對(duì)應(yīng)issue 2
每個(gè)團(tuán)隊(duì)的開發(fā)節(jié)奏都不同,有的團(tuán)隊(duì)會(huì)每日持續(xù)集成發(fā)版本提測(cè),有的可能兩天一次,這個(gè)就不深入討論了......
那么當(dāng)我們準(zhǔn)備提測(cè)時(shí),應(yīng)該怎么做呢?
通過上節(jié)的了解,我們已經(jīng)知道,迭代內(nèi)的功能需求都會(huì)通過feature/xxx分支合入到develop分支。
提測(cè)前,一般來說,還是要更新下CHANGELOG.md和package.json的版本號(hào),可以由Maintainer或其他負(fù)責(zé)該項(xiàng)事務(wù)的同學(xué)執(zhí)行。
主要是執(zhí)行npm version major/minor/patch -m 'something done',具體操作可以參考前端自動(dòng)化部署的深度實(shí)踐一文。
git checkout -b issue/2 origin/develop
npm version minor -m '迭代1第一次提測(cè)'
git push origin HEAD
然后發(fā)起merge request合入develop分支
此時(shí),應(yīng)以最新的develop分支代碼在開發(fā)環(huán)境跑一遍功能,保證版本自測(cè)通過。
提測(cè)時(shí),由Maintainer發(fā)起Merge Request,將develop分支代碼合入release分支,此時(shí)自動(dòng)觸發(fā)Gitlab CI/CD,自動(dòng)構(gòu)建并發(fā)布至測(cè)試環(huán)境。
版本提測(cè)后,各責(zé)任人應(yīng)在TAPD上將相關(guān)需求和缺陷的狀態(tài)變更為**“測(cè)試中”**。
修復(fù)測(cè)試環(huán)境bug
bug/3,一個(gè)bug分支,對(duì)應(yīng)issue 3
這里說的是在迭代周期內(nèi)由測(cè)試工程師發(fā)現(xiàn)的測(cè)試環(huán)境中的系統(tǒng)bug,這些bug會(huì)被記錄在敏捷開發(fā)協(xié)作平臺(tái)TAPD上。修復(fù)測(cè)試環(huán)境bug的步驟與開發(fā)需求類似,這里簡(jiǎn)單說下步驟:
在Gitlab上創(chuàng)建issue
創(chuàng)建issue,并附上TAPD上的缺陷鏈接,方便追溯
創(chuàng)建分支&修復(fù)缺陷
基于
develop分支創(chuàng)建分支:// 3是issue號(hào)
git checkout -b bug/3 origin/develop接著改代碼......
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) 修復(fù)一個(gè)測(cè)試環(huán)境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發(fā)起Merge Request
開發(fā)人員發(fā)起
Merge Request,請(qǐng)求將自己修復(fù)缺陷引入的代碼合入develop分支。然后
Maintainer需要Review代碼,同意本次Merge Request。等待回歸測(cè)試
該
bug將在下一次CI/CD后,進(jìn)入回歸測(cè)試流程。級(jí)別高的測(cè)試環(huán)境Bug
如果是級(jí)別很高的
bug,比如影響了系統(tǒng)運(yùn)行,導(dǎo)致測(cè)試人員無法正常測(cè)試的,應(yīng)以release分支為基礎(chǔ)創(chuàng)建bug分支,修改完畢后合入release分支,再發(fā)起cherry pick合入develop分支。
發(fā)布至生產(chǎn)環(huán)境
經(jīng)過幾輪持續(xù)集成和回歸測(cè)試后,一個(gè)迭代版本也慢慢趨于穩(wěn)定,此時(shí)也迎來了激動(dòng)人心的上線時(shí)間。我們要做的就是把通過了測(cè)試的release分支合入master分支。

這一步相對(duì)簡(jiǎn)單,但是要特別注意權(quán)限控制(防止生產(chǎn)環(huán)境事故),具體權(quán)限控制可以回頭看第一章節(jié)分支管理。
與release分支類似,master分支自動(dòng)觸發(fā)Gitlab CI/CD,自動(dòng)構(gòu)建并發(fā)布至生產(chǎn)環(huán)境。
線上回滾
revert/4,一個(gè)回滾分支,對(duì)應(yīng)issue 4
代碼升級(jí)到線上,但是發(fā)現(xiàn)報(bào)錯(cuò),系統(tǒng)無法正常運(yùn)行。此時(shí)如果不能及時(shí)排查出問題,那么只能先進(jìn)行版本回退操作。
先說說慣性思維下,我的版本回退做法。
首先還是創(chuàng)建issue記錄下:

基于master分支創(chuàng)建revert/4分支
git checkout -b revert/4 origin/master
假設(shè)當(dāng)前版本是1.1.0,我們想回退到上個(gè)版本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 <[email protected]>
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版本還是有個(gè)版本,再修復(fù)下
+
特性2提交
特性3提交
主要是取到1.0.3版本對(duì)應(yīng)的commit id,其值為90c9170a499c2c5f8f8cf4e97fc49a91d714be50。
接著,我們根據(jù)commit id進(jìn)行reset操作,再推送到遠(yuǎn)程同名分支。
git reset --hard 90c9170a499c2c5f8f8cf4e97fc49a91d714be50
git push origin HEAD
接著發(fā)起Merge Request把revert/4分支合入master分支。

一般來說,這波操作沒什么問題,能解決常規(guī)的回滾問題。
臨時(shí)變通
由于master分支是保護(hù)分支,設(shè)置了不可push。如果不想通過merge的方式回滾,所以只能先臨時(shí)設(shè)置Maintainer擁有push權(quán)限,然后由Maintainer進(jìn)行回滾操作。
git checkout master
git pull
git show 1.0.3
git reset --hard 90c9170a499c2c5f8f8cf4e97fc49a91d714be50
git push origin HEAD
完事后,還需要記得把master設(shè)置為不可push。
Q: 為什么不讓
Maintainer一直擁有master的push權(quán)限?A: 主要還是為了防止出現(xiàn)生產(chǎn)環(huán)境事故,給予臨時(shí)性權(quán)限更穩(wěn)妥!
git reset --hard存在什么問題?
如題,git reset --hard確實(shí)是存在問題的。git reset --hard屬于霸道玩法,直接移動(dòng)HEAD指針,會(huì)丟棄之后的提交記錄,如果不慎誤操作了也別慌,還是可以通過查詢git reflog找到commitId搶救回來的;git reset后還存在一個(gè)隱性的問題,如果與舊的branch進(jìn)行merge操作時(shí),會(huì)把git reset回滾的代碼重新引入。那么怎么解決這些問題呢?

別慌,這個(gè)時(shí)候你必須掏出git revert了。
Q:
git revert的優(yōu)勢(shì)在哪?A: 首先,
git revert是通過一次新的commit來進(jìn)行回滾操作的,HEAD指針向前移動(dòng),這樣就不會(huì)丟失記錄;另外,git revert也不會(huì)引起merge舊分支時(shí)誤引入回滾的代碼。最重要的是,git revert在回滾的細(xì)節(jié)控制上更加優(yōu)秀,可解決部分回滾的需求。
舉個(gè)栗子,本輪迭代團(tuán)隊(duì)共完成需求2項(xiàng),而上線后發(fā)現(xiàn)其中1項(xiàng)需求有致命性缺陷,需要回滾這個(gè)需求相關(guān)的代碼,同時(shí)要保留另一個(gè)需求的代碼。

首先我們查看下日志,找下這兩個(gè)需求的commitId
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相關(guān)的代碼
git revert -n 89502c2
這時(shí)可能要解決沖突,解決完沖突后就可以push到遠(yuǎn)程master分支了。
git add .
git commit -m '回滾需求1的相關(guān)代碼,并解決沖突'
git push origin master
感覺還是菜菜的,如果大佬們有更優(yōu)雅的解決方案,求指導(dǎo)啊!
修復(fù)線上bug
hotfix/5,一個(gè)線上熱修復(fù)分支,對(duì)應(yīng)issue 5
比如線上出現(xiàn)了系統(tǒng)無法登錄的bug,測(cè)試工程師也在TAPD提交了缺陷記錄。那么修復(fù)線上bug的步驟是什么呢?
創(chuàng)建
issue,標(biāo)題可以從TAPD中的Bug單中copy過來,而描述就貼上Bug單的鏈接即可。基于
master分支創(chuàng)建分支hotfix/5。git checkout -b hotfix/5 origin/master擼代碼,修復(fù)此bug......
修復(fù)完此
bug后,提交該分支代碼到遠(yuǎn)程倉庫同名分支git push origin HEAD然后發(fā)起
cherry pick合入到master和develop分支,并在master分支打上新的版本tag(假設(shè)當(dāng)前最大的tag是1.0.0,那么新的版本tag應(yīng)為1.0.1)。
修復(fù)線上舊版本bug
fix/6,一個(gè)線上舊版本修復(fù)分支,對(duì)應(yīng)issue 6
某些項(xiàng)目產(chǎn)品可能會(huì)有多個(gè)線上版本同時(shí)在運(yùn)行,那么不可避免要解決舊版本的bug。針對(duì)線上舊版本出現(xiàn)的bug,修復(fù)步驟與上節(jié)類似。
創(chuàng)建
issue,描述清楚問題假設(shè)當(dāng)前版本是
1.0.0,而0.9.0版本出了一個(gè)bug,應(yīng)基于tag 0.9.0創(chuàng)建fix分支。git checkout -b fix/6 0.9.0修復(fù)缺陷后,應(yīng)打上新的
tag 0.9.1,并推送到遠(yuǎn)程。git tag 0.9.1
git push origin tag 0.9.1如果此
bug也存在于最新的master分支,則需要git push origin HEAD提交該fix分支代碼到遠(yuǎn)程倉庫同名分支,然后發(fā)起cherry pick合入到master,此時(shí)很大可能存在沖突,需要解決沖突。
cherry pick
cherry pick
在了解到cherry pick之前,我一直認(rèn)為只有git merge可以合并代碼,也好幾次遇到合入了不想要的代碼的問題。有了cherry pick,我們就可以合并單次提交記錄,解決git merge時(shí)合并太多不想要的內(nèi)容的煩惱,在解決bug時(shí)特別有用。
git rebase
經(jīng)過這段時(shí)間的使用,我發(fā)現(xiàn)使用git merge合并分支時(shí),會(huì)讓git log的Graph圖看起來有點(diǎn)吃力。
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,變基,哈哈哈。由于對(duì)rebase了解不深,目前也不敢輕易改用rebase,畢竟rebase還是有很多隱藏的坑的,使用起來要慎重!在這里先挖個(gè)坑吧,后面搞懂了再填坑......
注意事項(xiàng)
一般而言,自己發(fā)起的 Merge Request必須由別的同事Review并同意合入,這樣更有利于發(fā)現(xiàn)代碼問題。對(duì)了, TAPD還支持與Gitlab協(xié)同的。詳情見源碼關(guān)聯(lián)指引[2]。
結(jié)語
實(shí)踐證明,這套Git工作流目前能覆蓋我項(xiàng)目開發(fā)過程中的絕大部分場(chǎng)景。不過要注意的是,適合自己的才是最好的,盲目采用別人的方案有時(shí)候是會(huì)水土不服的。
以上所述純屬前端小微團(tuán)隊(duì)內(nèi)部的Gitlab實(shí)踐,必然存在著很多不足之處,如有錯(cuò)誤之處還請(qǐng)指正,歡迎交流。
參考資料
TAPD: https://www.tapd.cn/
[2]源碼關(guān)聯(lián)指引: https://www.tapd.cn/help/view#1120003271001001346
最近上市的新書《前端函數(shù)式演進(jìn)》是一部寫給前端工程師的函數(shù)式編程實(shí)戰(zhàn)指南,由阿里本地生活企業(yè)訂餐前端負(fù)責(zé)人撰寫。
作者結(jié)合自己在前端領(lǐng)域的大量工程實(shí)踐經(jīng)驗(yàn),首先從前端開發(fā)者的角度介紹了函數(shù)式編程在前端領(lǐng)域的演進(jìn),以及前端必備的函數(shù)式編程知識(shí);然后對(duì)React和RxJS等流行前端工具的函數(shù)式編程功能和思想進(jìn)行了深入剖析,既可以幫助讀者更好地理解和使用這些工具,又能給予讀者技術(shù)選型方面的建議。此外,書中提供了平臺(tái)級(jí)別的前端開發(fā)示例的多種實(shí)現(xiàn),方便讀者結(jié)合書中的理論進(jìn)行印證和思考。
這本書還是挺不錯(cuò)的,推介一下 ,最后給本公眾號(hào)粉絲送出 12 個(gè)小禮物(在下大出血),包括 3 本前端函數(shù)式演進(jìn)。
廢話少說,抽獎(jiǎng)在下面:
