修改 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

fixupsquash 類似,但會直接捨棄被合併 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。如果已經推送:

  1. 其他人可能已經基於舊 commit 開發
  2. 你需要強制推送 git push --force
  3. 可能造成團隊協作問題

只在本地還沒推送的 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
修改更早的 commitgit rebase -i HEAD~n + edit
合併多個 commitgit rebase -i HEAD~n + squash
刪除某個 commitgit rebase -i HEAD~n + drop