[CLI] git 整理 commit 的技巧,讓你輕鬆拆分或合併節點
宛如一位 commit 風格師
最近工作上學到一些整理 git commit 的技巧,大部分圍繞在 git rebase 的功能使用。透過這些方法整理出易懂的程式開發過程,也方便 code review 的人或是未來的自己了解這一段時間的所作所為。
有鑑於每個人或團隊適合消化的 commit 數量不同,必須跟著需求調整。最常見的整理不外呼是:將一個過大的 commit 切細、將過多細小的 commit 合併為一個。
本篇主要以 CLI 操作為準,全部的行為都會以命令列為主。在這裡以一個簡化的專案作為範例,操作流程可以套用至各個專案,應該是大同小異。
警告:修改歷史可能會造成 conflict,請小心為上。
本範例 repository 線圖如下:
切分 commit
TL;DR
1. git rebase <hash>~ -i
2. 將需要拆分的 commit 設為 edit
3. 因 rebase 在過程途中,git 會停在該節點等待你編輯
4. git reset HEAD~
5. 對 Code 做任何調整,add 變更後 commit。
這一階段沒有限制 commit 的數量
6. 最後 git rebase --continue
取得 commit 的 hash 字串
本例以範例程式碼的 a4c1c99
commit 為例,將它拆分細部程式碼。
你可以透過 git log 指令取得每個 commit 的 hash 字串,如果你想要像範例圖片的 git log 表現方式,可以使用下列的指令:
$ git log --oneline --graph
複製a4c1c99
commit 的 Hash 字串,如果你使用 git log,則複製標題 commit 旁邊的 Hash (會比較長)。
接下來鍵入下列指令,進行 rebase,別忘了 Hash 後面加上「~」。
$ git rebase <hash>~ -i
git rebase 修改歷史
使用這個指令,會進入 rebase interactive mode 。進入該模式後,將需要拆分的 commit 設為 edit
git 預設會將 vim 編輯器開啟,請將你想要拆分的 commit 設為 edit,並存檔離開。 (Tip: 你可以用 Esc 離開 Insert Mode 後,連打兩次大寫 Z,即是存檔離開)
因在 rebase 的過程之中,git 會停在該節點等你編輯
在這個畫面,請鍵入以下指令,讓我們回到尚未 commit 以前的狀態。
$git reset HEAD~
你可以用下列指令
$ git status
檢查目前程式碼狀態,是否回到了 commit 以前。
你可以開始對 code 做任何調整,add 變更後 commit,這一階段沒有限制 commit 的數量,可以要切多細有多細。就當做一般在編輯程式碼一樣操作即可。
這邊需要注意有上面提到的:
修改歷史可能會造成 conflict,請小心為上。
因為有可能會影響到之後節點的程式碼結構。所以,這裡我會推薦使用:
$ git add -i
這會進入 add 的 interactive mode,請使用 patch 功能切細程式碼,而不做額外的編輯。
範例程式碼做了三次 commit,從一個大 commit 變成了 3 個小 commit。
一切處理完成後,下指令:
$ git rebase --continue
一切順利完成, commit 切分的工作也就告一段落。
如果很不幸地遇到了 code conflict 的情形,代表歷史因為你的介入,而影響到了未來時間線。同時時空管理局員工也是你本人,遭受蝴蝶效應後果並且需要收拾殘局。🤖
範例程式碼最後的結果如下圖:
合併 commit
TL;DR
1. git rebase <hash>~ -i
2. 將需要合併的 commit 設為 s(squash)
(squash 會把下面的 commit 合併進上面的,故第一個保留 pick)
3. 編輯合併後的訊息
取得 commit 的 hash 字串
一樣藉由 git log 取得,本範例使用的顯示模式語法是:
$ git log --oneline --graph
請先複製你想合併的最後一個 commit 的 hash 字串。
本範例以 7b0ebf5
commit 作為合併的最後一個 commit,並將它和上方兩個 commit — 23b557a
與 e423840
合成為一個 commit
複製 7b0ebf5
的 hash 字串,鍵入以下指令啟動 rebase 功能:
$ git rebase <hash>~ -i
同樣別忘了 hash 字串後需要加上「~」。
合併多個 commit
git 開啟文字編輯器 vim,畫面上呈現選定的 commit 至最新的 commit 排排站。在這裡,我們打算合併 7b0ebf5
以及它下方兩個 commit,故將下方兩個設定為「s」。
大家可以想像被下 s 的 commit 會被擠進上面的 commit,而最上面的 commit 不用設定成 s,因為它沒有其他人可以往上擠。
設定好了之後,存檔離開。
git 會再次開啟文字編輯器,並且請你編輯 squash 後的 commit message。這裡其實沒有硬性規定須照著它給的格式,我們如果另有需求,也能將上面 message 的部份改成任何訊息。
存檔離開後可以透過 git log 檢查是否已經合成一個 commit 了。
squash vs fixup
除了 squash,還有一個 fixup 也是用來合併 commit。兩者的差別在於 squash 會使用被合併的 commit message 而 fixup 則否。
autosquash
TL;DR
1. git commit --fixup <HASH>
2. git rebase <FIXUP_HASH>~ -i --autosquash
3. 進入已自動設定好的 rebase interactive 界面,儲存離開
4. 編輯合併後訊息
我們可以在 commit 變更時指定要合併進入的 commit,並使用 rebase 自動觸發合併。
請從 git log 取得欲合併進入的 commit,步驟就不再贅述。
在 commit 之前,請確認變更已經 add 進 stage,呈現尚未 commit 的狀態,使用以下指令:
$ git commit --fixup <HASH>
鍵入完指令後,不用輸入任何 commit message (還記得 fixup 不會使用 commit message 嗎?)
藉由 git log 查看線圖,會發現最上面多了一個
fixup! <commit message from fixed up commit> 的 commit。
接下來步驟就如本文介紹的合併 commit 時相同,請找到被 fixup 的 commit hash 字串,並且透過以下指令進入 rebase (即是在 rebase -i
指令後加上 —-autosquash
),別忘了在 <FIXUP_HASH> 後加上「~」。
$ git rebase <FIXUP_HASH>~ -i --autosquash
git 會開啟文字編輯器,我們會發現有一個 commit 已被自動加上了 fixup。
儲存後離開,如果一切順利,git 還會再次請你確認 commit message。
就如同本文多次強調的,這類修改歷史的行為,都很有可能產生 code conflict,這點需要特別注意。
rebase 完成後,我們可以透過 git log 檢查,是不是 fixup 已經被合併,從線圖之中消失了。
本文整理了我的多篇工作日誌,集結成這一篇分享出來。
如果覺得有學到一些奇怪的新知識,別忘了拍手;並且分享給你所有想成為commit 風格師的朋友悶~