1. 程式人生 > >前端小微團隊的Gitlab實踐

前端小微團隊的Gitlab實踐

疫情期間我感覺整個人懶散了不少,慢慢有意識要振作起來了,恢復到正常的節奏。最近團隊程式碼庫從`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)