博客 / 詳情

返回

Git開發模式及代碼提交規範

1、Git開發模式

在使用 Git 開發時,有 4 種常用的工作流,也叫開發模式,按演進順序分為集中式工作流、功能分支工作流、Git Flow 工作流和 Forking 工作流。接下來,我會按演進順序分別介紹這 4 種工作流

1.1、集中式工作流

我們先來看看集中式工作流,它是最簡單的一種開發方式。集中式工作流的工作模式如下圖所示 :
image.png
A、B、C 為 3 位開發者,每位開發者都在本地有一份遠程倉庫的拷貝:本地倉庫。A、B、C 在本地的 master 分支開發完代碼之後,將修改後的代碼 commit 到遠程倉庫,如果有衝突就先解決本地的衝突再提交。在進行了一段時間的開發之後,遠程倉庫 master 分支的日誌可能如下圖所示:
image.png
和其他工作流相比,集中式工作流程的代碼管理較混亂,容易出問題,因此適合用在團隊人數少、開發不頻繁、不需要同時維護多個版本的小項目中。當我們想要並行開發多個功能時,這種工作流就不適用了,這時候怎麼辦呢?我們接下來看功能分支工作流

1.2、功能分支工作流

功能分支工作流基於集中式工作流演進而來。在開發新功能時,基於 master 分支新建一個功能分支,在功能分支上進行開發,而不是直接在本地的 master 分支開發,開發完成之後合併到 master 分支,如下圖所示:
image.png
相較於集中式工作流,這種工作流讓不同功能在不同的分支進行開發,只在最後一步合併到 master 分支,不僅可以避免不同功能之間的相互影響,還可以使提交歷史看起來更加簡潔

還有,在合併到 master 分支時,需要提交 PR(pull request),而不是直接將代碼 merge 到 master 分支。PR 流程不僅可以把分支代碼提供給團隊其他開發人員進行 CR(Code Review),還可以在 PR 頁面討論代碼。通過 CR ,我們可以確保合併到 master 的代碼是健壯的;通過 PR 頁面的討論,可以使開發者充分參與到代碼的討論中,有助於提高代碼的質量,並且提供了一個代碼變更的歷史回顧途徑

那麼,功能分支工作流具體的開發流程是什麼呢?我們一起來看下
1.基於 master 分支新建一個功能分支,功能分支可以取一些有意義的名字,便於理解,例如 feature/rate-limiting

git checkout -b feature/rate-limiting

2.在功能分支上進行代碼開發,開發完成後 commit 到功能分支

git add limit.go
git commit -m "add rate limiting"

3.將本地功能分支代碼 push 到遠程倉庫

git push origin feature/rate-limiting

4.在遠程倉庫上創建 PR(例如:GitHub)
進入 GitHub 平台上的項目主頁,點擊 Compare & pull request 提交 PR,如下圖所示 :
image.png
點擊 Compare & pull request 後會進入 PR 頁面,在該頁面中可以根據需要填寫評論,最後點擊 Create pull request 提交 PR

5.代碼管理員收到 PR 後,可以 CR 代碼,CR 通過後,再點擊 Merge pull request 將 PR 合併到 master,如下圖所示 :
image.png

圖中的“Merge pull request” 提供了 3 種 merge 方法:

  • Create a merge commit:GitHub 的底層操作是 git merge --no-ff。feature 分支上所有的 commit 都會加到 master 分支上,並且會生成一個 merge commit。這種方式可以讓我們清晰地知道是誰做了提交,做了哪些提交,回溯歷史的時候也會更加方便
  • Squash and merge:GitHub 的底層操作是 git merge --squash。Squash and merge 會使該 pull request 上的所有 commit 都合併成一個 commit ,然後加到 master 分支上,但原來的 commit 歷史會丟失。如果開發人員在 feature 分支上提交的 commit 非常隨意,沒有規範,那麼我們可以選擇這種方法來丟棄無意義的 commit。但是在大型項目中,每個開發人員都應該是遵循 commit 規範的,因此我不建議你在團隊開發中使用 Squash and merge
  • Rebase and merge:GitHub 的底層操作是 git rebase。這種方式會將 pull request 上的所有提交歷史按照原有順序依次添加到 master 分支的頭部(HEAD)。因為 git rebase 有風險,在你不完全熟悉 Git 工作流時,我不建議 merge 時選擇這個

通過分析每個方法的優缺點,在實際的項目開發中,我比較推薦你使用 Create a merge commit 方式

從剛才講完的具體開發流程中,我們可以感受到,功能分支工作流上手比較簡單,不僅能使你並行開發多個功能,還可以添加 code review,從而保障代碼質量。當然它也有缺點,就是無法給分支分配明確的目的,不利於團隊配合。它適合用在開發團隊相對固定、規模較小的項目中。接下來我們要講的 Git Flow 工作流以功能分支工作流為基礎,較好地解決了上述問題

1.3、Git Flow 工作流

Git Flow 工作流是一個非常成熟的方案,也是非開源項目中最常用到的工作流。它定義了一個圍繞項目發佈的嚴格分支模型,通過為代碼開發、發佈和維護分配獨立的分支來讓項目的迭代流程更加順暢,比較適合大型的項目或者迭代速度快的項目。接下來,我會通過介紹 Git Flow 的 5 種分支和工作流程,來給你講解 GIt Flow 是如何工作的

Git Flow 的 5 種分支
Git Flow 中定義了 5 種分支,分別是 master、develop、feature、release 和 hotfix。其中,master 和 develop 為常駐分支,其他為非常駐分支,不同的研發階段會用到不同的分支。這 5 種分支的詳細介紹見下表

分支名 描述
master 該分支上的最新代碼永遠是發佈狀態,不能直接在該分支上開發。master分支每合併一個hotfix/release分支,都會打一個版本標籤
develop 該分支上的代碼是開發中的最新代碼,該分支只做合併操作,不做直接在該分支上開發
feature 在研發階段用來做功能開發。一個新功能會基於 develop 分支新建一個 feature 分支,分支命名建議命名為 : feature/xxx-xxx,功能開發完成之後,會合併到 develop 分支並刪除,還有一個點需要你注意,feature分支在申請合併之前,最好是先pull一下develop分支,看一下有沒有衝突,如果有就先解決衝突再申請合併
release 在發佈節點用作版本發佈的預發佈分支,基於 develop 分支創建,分支名建議命名為 : release/xxx-xxx。例如 : v1.0.0 版本的功能全部開發測試完成後,提交到develop 分支,然後基於develop分支創建 release/1.0.0 分支,並提交測試,測試中遇到的問題在release分支修改,最終通過測試後,將release分支合併到master和develop,並在master分支打上v1.0.0的版本標籤,最後刪除release/1.0.0分支
hotfix 在維護階段用作緊急bug修復分支,在master分支上創建,修復完成後合併到master。分支名建議命名為hotfix/xxx-xxx。例如 : 當線上某個版本出現Bug後,從master檢出對應版本的代碼,創建hotfix分支,並在hotfix分支修復問題。問題修復後,將hotfix分支合併到master和develop分支,並在master分支上修復後的版本標籤,最後刪除hotfix分支

Git Flow 開發流程
這裏我們用一個實際的例子來演示下 Git Flow 的開發流程。場景如下:
a. 當前版本為:0.9.0
b. 需要新開發一個功能,使程序執行時向標準輸出輸出“hello world”字符串
c. 在開發階段,線上代碼有 Bug 需要緊急修復

假設我們的 Git 項目名為 gitflow-demo,項目目錄下有 2 個文件,分別是 README.md 和 main.go,內容如下 :

package main

import "fmt"

func main() {
  fmt.Println("callmainfunction")
}

具體的開發流程有 12 步,你可以跟着以下步驟操作練習
1.創建一個常駐的分支:develop

git checkout -b develop master

2.基於 develop 分支,新建一個功能分支:feature/print-hello-world

git checkout -b feature/print-hello-world develop

3.feature/print-hello-world 分支中,在 main.go 文件中添加一行代碼
fmt.Println("Hello"),添加後的代碼如下

package main

import "fmt"

func main() {
  fmt.Println("callmainfunction")
  fmt.Println("Hello")
}

4.緊急修復 Bug
我們正處在新功能的開發中(只完成了 fmt.Println("Hello")而非 fmt.Println("Hello World"))突然線上代碼發現了一個 Bug,我們要立即停止手上的工作,修復線上的 Bug,步驟如下 :

git stash # 1. 開發工作只完成了一半,還不想提交,可以臨時保存修改至堆棧區
git checkout -b hotfix/print-error master # 2. 從 master 建立 hotfix 分支
vi main.go # 3. 修復 bug,callmainfunction -> call main function
git commit -a -m 'fix print message error bug' # 4. 提交修復
git checkout develop # 5. 切換到 develop 分支
git merge --no-ff hotfix/print-error # 6. 把 hotfix 分支合併到 develop 分支
git checkout master # 7. 切換到 master 分支
git merge --no-ff hotfix/print-error # 8. 把 hotfix 分支合併到 master
git tag -a v0.9.1 -m "fix log bug" # 9. master 分支打 tag
go build -v . # 10. 編譯代碼,並將編譯好的二進制更新到生產環境
git branch -d hotfix/print-error # 11. 修復好後,刪除 hotfix/xxx 分支
git checkout feature/print-hello-world # 12. 切換到開發分支下
git merge --no-ff develop # 13. 因為 develop 有更新,這裏最好同步更新下
git stash pop # 14. 恢復到修復前的工作狀態

5.繼續開發
在 main.go 中加入 fmt.Println("Hello World")

6.提交代碼到 feature/print-hello-world 分支。

git commit -a -m "print 'hello world'"

7.在 feature/print-hello-world 分支上做 code review
首先,我們需要將 feature/print-hello-world push 到代碼託管平台,例如 GitHub 上

git push origin feature/print-hello-world

然後,我們在 GitHub 上,基於 feature/print-hello-world 創建 pull request,如下圖所示 :
image.png
創建完 pull request 之後,我們就可以指定 Reviewers 進行 code review,如下圖所示 :
image.png

8.code review 通過後,由代碼倉庫 matainer 將功能分支合併到 develop 分支

git checkout develop
git merge --no-ff feature/print-hello-world

9.基於 develop 分支,創建 release 分支,測試代碼

git checkout -b release/1.0.0 develop
go build -v . # 構建後,部署二進制文件,並測試

10.測試失敗,因為我們要求打印“hello world”,但打印的是“Hello World”,修復的時候
我們直接在 release/1.0.0 分支修改代碼,修改完成後,提交併編譯部署

git commit -a -m "fix bug"
go build -v .

11.測試通過後,將功能分支合併到 master 分支和 develop 分支

git checkout develop
git merge --no-ff release/1.0.0
git checkout master
git merge --no-ff release/1.0.0
git tag -a v1.0.0 -m "add print hello world" # master 分支打 tag

12.刪除 feature/print-hello-world 分支,也可以選擇性刪除 release/1.0.0 分支

git branch -d feature/print-hello-world

親自操作一遍之後,你應該會更瞭解這種模式的優缺點。它的缺點,就是你剛才已經體會到的,它有一定的上手難度。不過 Git Flow 工作流還是有很多優點的:Git Flow 工作流的每個分支分工明確,這可以最大程度減少它們之間的相互影響。因為可以創建多個分支,所以也可以並行開發多個功能。另外,和功能分支工作流一樣,它也可以添加 code review,保障代碼質量
因此,Git Flow 工作流比較適合開發團隊相對固定,規模較大的項目

1.4、Forking 工作流

上面講的 Git Flow 是非開源項目中最常用的,而在開源項目中,最常用到的是 Forking 工作流,例如 Kubernetes、Docker 等項目用的就是這種工作流。這裏,我們先來了解下 fork 操作

fork 操作是在個人遠程倉庫新建一份目標遠程倉庫的副本,比如在 GitHub 上操作時,在項目的主頁點擊 fork 按鈕(頁面右上角),即可拷貝該目標遠程倉庫。Forking 工作流的流程如下圖所示
image.png

假設開發者 A 擁有一個遠程倉庫,如果開發者 B 也想參與 A 項目的開發,B 可以 fork 一份 A 的遠程倉庫到自己的 GitHub 賬號下。後續 B 可以在自己的項目進行開發,開發完成後,B 可以給 A 提交一個 PR。這時候 A 會收到通知,得知有新的 PR 被提交,A 會去查看 PR 並 code review。如果有問題,A 會直接在 PR 頁面提交評論,B 看到評論後會做進一步的修改。最後 A 通過 B 的 PR 請求,將代碼合併進了 A 的倉庫。這樣就完成了 A 代碼倉庫新特性的開發。如果有其他開發者想給 A 貢獻代碼,也會執行相同的操作

GitHub 中的 Forking 工作流詳細步驟共有 6 步(假設目標倉庫為 gitflow-demo),你可以跟着以下步驟操作練習

1.Fork 遠程倉庫到自己的賬號下
訪問 https://github.com/dataease/dataease,點擊 fork 按鈕。fork 後的倉庫地址為 https://github.com/qiaozhanwei/dataease

2.克隆 fork 的倉庫到本地

git clone https://github.com/qiaozhanwei/dataease
cd dataease
git remote add upstream https://github.com/dataease/dataease
git remote set-url --push upstream no_push # Never push to upstream master
git remote -v # Confirm that your remotes make sense
origin  https://github.com/qiaozhanwei/dataease (fetch)
origin  https://github.com/qiaozhanwei/dataease (push)
upstream  https://github.com/qiaozhanwei/dataease (fetch)
upstream  https://github.com/qiaozhanwei/dataease (push)

3.創建功能分支
首先,要同步本地倉庫的 master 分支為最新的狀態(跟 upstream master 分支一致)

git fetch upstream
git checkout master
git rebase upstream/master

然後,創建功能分支

git checkout -b feature/add-function

4.提交 commit
在 feature/add-function 分支上開發代碼,開發完代碼後,提交 commit

git fetch upstream # commit 前需要再次同步 feature 跟 upstream/master
git rebase upstream/master
git add <file>
git status
git commit

分支開發完成後,可能會有一堆 commit,但是合併到主幹時,我們往往希望只有一個(或最多兩三個)commit,這可以使功能修改都放在一個或幾個 commit 中,便於後面的閲讀和維護。這個時候,我們可以用 git rebase 來合併和修改我們的 commit,操作如下 :

git reset HEAD~5
git add .
git commit -am "Here's the bug fix that closes #28"
git push --force

squash 和 fixup 命令,還可以當作命令行參數使用,自動合併 commit

git commit --fixup
git rebase -i --autosquash

5.push 功能分支到個人遠程倉庫
在完成了開發,並 commit 後,需要將功能分支 push 到個人遠程代碼倉庫,代碼如下:

git push -f origin feature/add-function

6.在個人遠程倉庫頁面創建 pull request
提交到遠程倉庫以後,我們就可以創建 pull request,然後請求 reviewers 進行代碼 review,確認後合併到 master。

這裏要注意,創建 pull request 時,base 通常選擇目標遠程倉庫的 master 分支。我們已經講完了 Forking 工作流的具體步驟,你覺得它有什麼優缺點呢?

結合操作特點,我們來看看它的優點:Forking 工作流中,項目遠程倉庫和開發者遠程倉庫完全獨立,開發者通過提交 Pull Request 的方式給遠程倉庫貢獻代碼,項目維護者選擇性地接受任何開發者的提交,通過這種方式,可以避免授予開發者項目遠程倉庫的權限,從而提高項目遠程倉庫的安全性,這也使得任意開發者都可以參與項目的開發

但 Forking 工作流也有侷限性,就是對於職能分工明確且不對外開源的項目優勢不大。Forking 工作流比較適用於以下三種場景:(1)開源項目中;(2)開發者有衍生出自己的衍生版的需求;(3)開發者不固定,可能是任意一個能訪問到項目的開發者

1.5、總結

基於 Git 向你介紹了 4 種開發模式,現在跟我回顧一下吧

  • 集中式工作流:開發者直接在本地 master 分支開發代碼,開發完成後 push 到遠端倉庫 master 分支
  • 功能分支工作流:開發者基於 master 分支創建一個新分支,在新分支進行開發,開發完成後合併到遠端倉庫 master 分支
  • Git Flow 工作流:Git Flow 工作流為不同的分支分配一個明確的角色,並定義分支之間什麼時候、如何進行交互,比較適合大型項目的開發
  • Forking 工作流:開發者先 fork 項目到個人倉庫,在個人倉庫完成開發後,提交 pull request 到目標遠程倉庫,遠程倉庫 review 後,合併 pull request 到 master 分支

集中式工作流是最早的 Git 工作流,功能分支工作流以集中式工作流為基礎,Git Flow 工作流又是以功能分支工作流為基礎,Forking 工作流在 Git Flow 工作流基礎上,解耦了個人遠端倉庫和項目遠端倉庫

每種開發模式各有優缺點,適用於不同的場景,我總結在下表中:

工作流 優點 缺點 使用場景
集中工作流 上手最簡單 代碼管理較混亂,容易出問題 團隊人數少,開發不頻繁,不需要同時維護多個版本的小項目
功能分支工作流 上手比較簡單,支持並行開發,支持Code Review 無法給分支分配明確的目的,不利於團隊配合 開發團隊相對文婷、規模較小的項目
GitFlow工作流 每個分支分工明確,這可以最大程度減少它們之間的相互影響,可以並行開發,支持Code Review 一定的上手難度 比較適合開發團隊相對固定,規模較大的項目
Forking工作流 完全解耦個人源端倉庫和項目遠端倉庫,最大程度上保證遠端倉庫的安全 對陣只能分工明確且不對外開源項目優勢不大 比較適合於開源項目中,或者開發者有衍生出自己的衍生版本的需求,或者開發者不固定,可能是任意一個能訪問到項目的開發者

總的來説,在選擇工作流時,我的推薦如下:

  • 非開源項目採用 Git Flow 工作流
  • 開源項目採用 Forking 工作流

2、commit message規範

注意 : 分支和提交message建議使用英文

分支命名 類型 提交message
feature/xxx 新增功能 feat:xxx
bugfix/xxx(bugId) Bug 修復 fix:xxx
refactor/xxx 其他代碼類的變更,這些變更不屬於feat、fix、perf和style,例如簡化代碼、重命名變量、刪除冗餘代碼等 refactor:xxx
performance/xxx 提供代碼性能的變更 perf:xxx
style/xxx  代碼格式類的變更,比如用gofmt格式化代碼、刪除空行等 style:xxx
test/xxx  新增測試用例或更新現有測試用例 test:xxx
docs/xxx  文檔類的更新,包括修改用户文檔或者開發文檔等 docs:xxx
ci/xxx  持續集成和部署相關的改動,比如修改Jenkins、GitLab CI等CI配置文件或者更新systemmd unit文件 ci:xxx
chore/xxx  其他類型,比如構建流程、依賴管理或者輔助工具的變更等 chore:xxx

3、多個commit合併到一個commit

qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
8760df9 (HEAD -> master, origin/master, origin/HEAD) feat : README.md update
47601ec feat : README.md update
f4ffa32 Initial commit

比如説要把8760df9、47601ec兩個commit合併到一個commit,此時需要找到f4ffa32

git rebase -i f4ffa32

會出現如下的交互頁面 :
image.png

然後上最上面的commit不動,下面的前綴修改為s即可,如下圖 :
image.png

wq退出後會進入下一個交互編輯框,如下圖 :
image.png

此時修改你的commit內容,wq退出即可,比如修改如下 :
image.png

最後提交 :

git push --force

查看結果 :
image.png

4、修改Commit Message

4.1、修改最近一次提交的 Commit Message

直接使用即可,比較簡單,不做示例

git commit --amend

4.2、修改某一次提交的 Commit Message

比如 :

qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
3a8bcc0 (HEAD -> master, origin/master, origin/HEAD) feat : README.md update2
b463731 feat : README.md update-1
f4ffa32 Initial commit

需求,是想讓 b463731 這個 commit 修改為 feat : README.md update-1

使用命令 git rebase -i <父 commit ID> :

qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git rebase -i f4ffa32

將要修改的commit id的前綴修改為 r,如下圖 :
image.png

wq保存退出,修改內容如下 :
image.png

wq保存退出,提交

qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git push --force

如下所示 :

qiaozhanwei@qiaozhanweideMacBook-Pro git-test % git log --oneline
212afef (HEAD -> master, origin/master, origin/HEAD) feat : README.md update2
165ce07 feat : README.md update1
f4ffa32 Initial commit

如感興趣,點贊加關注,謝謝!!!

user avatar
0 位用戶收藏了這個故事!

發佈 評論

Some HTML is okay.