1、Git開發模式
在使用 Git 開發時,有 4 種常用的工作流,也叫開發模式,按演進順序分為集中式工作流、功能分支工作流、Git Flow 工作流和 Forking 工作流。接下來,我會按演進順序分別介紹這 4 種工作流
1.1、集中式工作流
我們先來看看集中式工作流,它是最簡單的一種開發方式。集中式工作流的工作模式如下圖所示 :
A、B、C 為 3 位開發者,每位開發者都在本地有一份遠程倉庫的拷貝:本地倉庫。A、B、C 在本地的 master 分支開發完代碼之後,將修改後的代碼 commit 到遠程倉庫,如果有衝突就先解決本地的衝突再提交。在進行了一段時間的開發之後,遠程倉庫 master 分支的日誌可能如下圖所示:
和其他工作流相比,集中式工作流程的代碼管理較混亂,容易出問題,因此適合用在團隊人數少、開發不頻繁、不需要同時維護多個版本的小項目中。當我們想要並行開發多個功能時,這種工作流就不適用了,這時候怎麼辦呢?我們接下來看功能分支工作流
1.2、功能分支工作流
功能分支工作流基於集中式工作流演進而來。在開發新功能時,基於 master 分支新建一個功能分支,在功能分支上進行開發,而不是直接在本地的 master 分支開發,開發完成之後合併到 master 分支,如下圖所示:
相較於集中式工作流,這種工作流讓不同功能在不同的分支進行開發,只在最後一步合併到 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,如下圖所示 :
點擊 Compare & pull request 後會進入 PR 頁面,在該頁面中可以根據需要填寫評論,最後點擊 Create pull request 提交 PR
5.代碼管理員收到 PR 後,可以 CR 代碼,CR 通過後,再點擊 Merge pull request 將 PR 合併到 master,如下圖所示 :
圖中的“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,如下圖所示 :
創建完 pull request 之後,我們就可以指定 Reviewers 進行 code review,如下圖所示 :
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 工作流的流程如下圖所示
假設開發者 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
會出現如下的交互頁面 :
然後上最上面的commit不動,下面的前綴修改為s即可,如下圖 :
wq退出後會進入下一個交互編輯框,如下圖 :
此時修改你的commit內容,wq退出即可,比如修改如下 :
最後提交 :
git push --force
查看結果 :
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,如下圖 :
wq保存退出,修改內容如下 :
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
如感興趣,點贊加關注,謝謝!!!