修改 Commit
Commit 後才發現訊息打錯了?忘記加入某個檔案?Git 提供了幾種方式讓你修改 commit。
修改最後一次 Commit
修改 Commit 訊息
git commit --amend -m "新的 commit 訊息"
或者開啟編輯器修改:
git commit --amend
加入遺漏的檔案
# 把遺漏的檔案加到暫存區
git add forgotten-file.js
# 合併到上一個 commit(不改訊息)
git commit --amend --no-edit
同時修改訊息和檔案
git add new-file.js
git commit --amend -m "Updated commit message"
--amend 的原理
--amend 並不是真的「修改」commit,而是用一個新的 commit 取代舊的:
原本:A --- B --- C
amend 後:A --- B --- C'(新的 commit,不同的 hash)
舊的 commit (C) 還存在,只是沒有分支指向它,之後會被 Git 垃圾回收。
修改更早的 Commit
使用 Interactive Rebase
如果要修改的不是最後一個 commit,需要用 interactive rebase:
# 修改最近 3 個 commit 中的某一個
git rebase -i HEAD~3
會開啟編輯器:
pick a1b2c3d First commit message
pick b2c3d4e Second commit message
pick c3d4e5f Third commit message
修改 Commit 訊息
把要修改的 commit 前面的 pick 改成 reword(或 r):
pick a1b2c3d First commit message
reword b2c3d4e Second commit message # 修改這個
pick c3d4e5f Third commit message
儲存後,Git 會讓你編輯那個 commit 的訊息。
修改 Commit 內容
把 pick 改成 edit(或 e):
pick a1b2c3d First commit message
edit b2c3d4e Second commit message # 要修改這個
pick c3d4e5f Third commit message
儲存後:
# Git 會停在那個 commit
# 做你要的修改
git add changed-file.js
# 修改 commit
git commit --amend
# 繼續 rebase
git rebase --continue
合併多個 Commit
使用 Squash
git rebase -i HEAD~3
把要合併的 commit 改成 squash(或 s):
pick a1b2c3d First commit
squash b2c3d4e Second commit # 合併到前一個
squash c3d4e5f Third commit # 合併到前一個
Git 會讓你編輯合併後的 commit 訊息。
使用 Fixup
fixup 和 squash 類似,但會直接捨棄被合併 commit 的訊息:
pick a1b2c3d Add feature
fixup b2c3d4e Fix typo # 訊息會被捨棄
fixup c3d4e5f Another fix # 訊息會被捨棄
快速建立 Fixup Commit
# 建立一個 fixup commit,之後會自動合併到指定的 commit
git commit --fixup=a1b2c3d
# 執行 rebase 自動處理
git rebase -i --autosquash HEAD~5
刪除 Commit
在 interactive rebase 中,把 pick 改成 drop(或 d),或直接刪除那一行:
pick a1b2c3d Keep this
drop b2c3d4e Delete this # 這個 commit 會被刪除
pick c3d4e5f Keep this too
重新排序 Commit
在 interactive rebase 中調換行的順序:
pick c3d4e5f Third commit # 移到最前面
pick a1b2c3d First commit
pick b2c3d4e Second commit
實際範例
情境一:修正 Commit 訊息的錯字
git commit --amend -m "Fix: correct the typo"
情境二:忘記加入檔案
git add forgotten.js
git commit --amend --no-edit
情境三:把瑣碎的 Commit 合併
# 假設最後 5 個 commit 都是修修補補
git rebase -i HEAD~5
# 在編輯器中
pick a1b2c3d Add feature
squash b2c3d4e Fix bug
squash c3d4e5f Fix another bug
squash d4e5f6g Fix typo
squash e5f6g7h Final fix
情境四:修改較早的 Commit
git rebase -i HEAD~5
# 標記要修改的 commit
edit a1b2c3d The commit to edit
# Git 會停在那裡
# 做修改...
git add .
git commit --amend
git rebase --continue
注意事項
不要修改已推送的 Commit
修改 commit 會改變 commit hash。如果已經推送:
- 其他人可能已經基於舊 commit 開發
- 你需要強制推送
git push --force - 可能造成團隊協作問題
只在本地還沒推送的 commit 上使用這些技巧。
強制推送的安全做法
如果真的需要修改已推送的 commit:
# 比 --force 安全,會檢查遠端是否有你沒看過的 commit
git push --force-with-lease
取消 Rebase
如果 rebase 過程中出問題:
git rebase --abort
復原被修改的 Commit
如果 amend 或 rebase 後後悔了,可以用 reflog 找回:
git reflog
# 找到原本的 commit
git reset --hard HEAD@{1}
小結
| 需求 | 指令 |
|---|---|
| 修改最後 commit 的訊息 | git commit --amend -m "new message" |
| 加入遺漏的檔案 | git add file && git commit --amend --no-edit |
| 修改更早的 commit | git rebase -i HEAD~n + edit |
| 合併多個 commit | git rebase -i HEAD~n + squash |
| 刪除某個 commit | git rebase -i HEAD~n + drop |