一般來說,我們在創建 PR 時會有一個通用的工作流程:
- 創建一個分支,比如 feature
- 提交 PR
- 讓別人 review
- 根據 review 意見修改
- 合併 PR 到主分支
但這個 feature 分支可能會有很多次提交,如何合併到主分支就成為了一個問題,GitHub 會給你三個選項:
創建一個合併提交#
創建一個連接兩個分支歷史記錄的 "合併提交"
我們可以在本地模擬圖中這個過程,首先在主分支創建三個提交,使用 git log --oneline --graph
可以看到提交記錄如下:
* b8472e4 (HEAD -> main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
然後在新的分支 git checkout -b feature
創建兩個提交:
* 9770b82 (HEAD -> feature) add e.txt
* 4e65cc4 add d.txt
* b8472e4 (main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
切回主分支,使用命令合併:
git checkout main
git merge --no-ff feature
再次查看記錄:
* 7da24fd (HEAD -> main) Merge branch 'feature' into main
|\
| * 9770b82 (feature) add e.txt
| * 4e65cc4 add d.txt
|/
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
如上,GitHub 在幕後使用 git merge --no-ff
的默認選項會創建一個連接兩個分支歷史記錄的合併提交。
這種方法的優點就是保留了所有的歷史記錄以及各個時間節點,缺點就是多分支或者長期分支會一團亂麻。
壓縮並合併#
將分支中的所有提交壓縮替換為單個提交後合併
依舊來嘗試本地模擬,首先 git reset --hard b8472e4
回到合併前:
* b8472e4 (HEAD -> main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
使用 git merge --squash feature
壓縮合併,然後編寫適當的提交消息。
最後主分支的提交記錄如下:
* 52c548d (HEAD -> main) add d.txt and e.txt
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
可以看出這種方法的優點就是主分支歷史清晰且線性,一個功能用一個提交解決,但是缺點就是丟失了合併以及各個獨立提交的上下文信息,甚至沒法知道這個分支到底合併沒有。
另一種 Squash 的方法#
使用交互式 rebase 命令實現更細緻的操作:
git checkout feature
git rebase -i main
選擇 squash 將後面的提交融合到前一個提交:
pick 4e65cc4 add d.txt
squash 9770b82 add e.txt
編寫適當的提交消息,此時 feature 分支提交記錄變為:
* f37e60f (HEAD -> feature) add d.txt and e.txt
* b8472e4 (main) add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
最後回到主分支合併即可(當然你也可以用 cherry-pick 合併單獨或組合的提交):
git checkout main
git merge feature
Bonus#
這種搞壞提交記錄的操作怎麼恢復?
git reflog
# 你會看到你做的所有 git 操作,找到搞壞的前一步 reset 即可
git reset HEAD@{index}
Rebase and merge#
所有提交都會被添加合併到主分支的 "頂部"
沒圖,但是可以從描述中了解到主分支最後的結果應該是:
恢復分支狀態,然後執行以下操作:
git checkout feature
git rebase main
git checkout main
git merge feature
完成後主分支提交記錄如下:
* 9770b82 (HEAD -> main, feature) add e.txt
* 4e65cc4 add d.txt
* b8472e4 add c.txt
* 2db7159 add b.txt
* b5b52d8 add a.txt
優點是主分支歷史清晰線性,沒有多餘的合併提交,其他分支的提交歷史也會直接保存在主分支記錄裡。
缺點與 squash 合併類似,並且更多小提交可能會影響對大局的注意力。
建議#
所有的選擇都是 trade-off 的結果,應該權衡這幾個關鍵的因素:
- 團隊偏好
- 提交歷史記錄:完整還是清晰?
- 更小的提交還是整個功能的完整提交
- 有無長期運行的分支
- ...
我只有一個建議:多提交,多寫 PR,不要堆積代碼,更小的代碼片段意味著更容易的 code review,更簡單的單元測試以及更符合單一職責原則的代碼。