[CLI] git 整理 commit 的技巧,讓你輕鬆拆分或合併節點

宛如一位 commit 風格師

Ray Yuan Liou
8 min readMay 31, 2020
No

最近工作上學到一些整理 git commit 的技巧,大部分圍繞在 git rebase 的功能使用。透過這些方法整理出易懂的程式開發過程,也方便 code review 的人或是未來的自己了解這一段時間的所作所為。

有鑑於每個人或團隊適合消化的 commit 數量不同,必須跟著需求調整。最常見的整理不外呼是:將一個過大的 commit 切細、將過多細小的 commit 合併為一個。
本篇主要以 CLI 操作為準,全部的行為都會以命令列為主。在這裡以一個簡化的專案作為範例,操作流程可以套用至各個專案,應該是大同小異。

警告:修改歷史可能會造成 conflict,請小心為上。

本範例 repository 線圖如下:

這個範例有 5 個 commit

切分 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
你可以想像每個 Hash 都是該 commit 的 id

複製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 會停在該節點等你編輯

git 在等你辣

在這個畫面,請鍵入以下指令,讓我們回到尚未 commit 以前的狀態。

$git reset HEAD~

你可以用下列指令

$ git status

檢查目前程式碼狀態,是否回到了 commit 以前。

你可以開始對 code 做任何調整,add 變更後 commit,這一階段沒有限制 commit 的數量,可以要切多細有多細。就當做一般在編輯程式碼一樣操作即可。
這邊需要注意有上面提到的:

修改歷史可能會造成 conflict,請小心為上。

因為有可能會影響到之後節點的程式碼結構。所以,這裡我會推薦使用:

$ git add -i

這會進入 add 的 interactive mode,請使用 patch 功能切細程式碼,而不做額外的編輯。

範例是 1 個切 3 個,一鴨三吃

範例程式碼做了三次 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 — 23b557ae423840 合成為一個 commit

複製 7b0ebf5 的 hash 字串,鍵入以下指令啟動 rebase 功能:

$ git rebase <hash>~ -i

同樣別忘了 hash 字串後需要加上「~」。

合併多個 commit

上圖的結果,會是 3 -> 2 -> 1 行的 commit 被合為一個 commit

git 開啟文字編輯器 vim,畫面上呈現選定的 commit 至最新的 commit 排排站。在這裡,我們打算合併 7b0ebf5 以及它下方兩個 commit,故將下方兩個設定為「s」。

大家可以想像被下 s 的 commit 會被擠進上面的 commit,而最上面的 commit 不用設定成 s,因為它沒有其他人可以往上擠。

設定好了之後,存檔離開。

編輯這個合成 commit 的 commit message

git 會再次開啟文字編輯器,並且請你編輯 squash 後的 commit message。這裡其實沒有硬性規定須照著它給的格式,我們如果另有需求,也能將上面 message 的部份改成任何訊息。

存檔離開後可以透過 git log 檢查是否已經合成一個 commit 了。

由 7 個 commit 減少為 5 個

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 自動觸發合併。

範例將變更進入 a4c1c99 這個 commit

請從 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 已經被合併,從線圖之中消失了。

fixup commit 已經消失

本文整理了我的多篇工作日誌,集結成這一篇分享出來。
如果覺得有學到一些奇怪的新知識,別忘了拍手;並且分享給你所有想成為commit 風格師的朋友悶~

--

--