Git 全景課 / 第二部分 分支與歷史 · M6
M6 撤銷與救援
「我搞砸了」的時候,先問一個問題:改動走到哪一步了?還沒提交、已經提交、還是已經和別人分享?答案決定你該用哪一條命令,以及會不會丟資料。
學完本單元,你能夠:
- 按改動所處的階段,選出正確的撤銷命令;
- 說出
reset三種模式各自改變了哪幾個區域; - 分清
reset和revert,知道哪一個可以用在已分享的歷史上; - 用
reflog找回「消失」的 commit 和被刪除的分支。
1先判斷:改動走到哪一步? Where is the change?
| 情況 | 想做的事 | 命令 | 安全嗎 |
|---|---|---|---|
| 未暫存 | 放棄工作目錄的修改 | git restore 檔案 | 不可復原 |
| 已暫存 | 退出暫存,保留修改 | git restore --staged 檔案 | 安全 |
| 剛提交、未分享 | 改說明或補一個漏掉的檔案 | git commit --amend | 安全(舊 commit 留在 reflog) |
| 已提交、未分享 | 把分支搬回較早的 commit | git reset(三種模式) | --hard 會丟掉未提交的修改 |
| 已分享(已 push) | 撤銷某個 commit 的效果 | git revert | 安全:只新增 commit |
| 「commit 不見了」 | 找回 | git reflog | 救援工具 |
一條總原則
已提交的東西幾乎不會真的消失:commit 是物件,物件不會被修改,只會變成沒有分支指向(M3、M4)。真正會永久丟失的,是從未提交過的修改。所以有疑慮時,先提交,再處理。
2還沒提交:restore Before committing
這是 M1 學過的兩條命令:
git restore --staged 檔案:把暫存區還原成 HEAD 的版本(退出暫存);工作目錄不變。git restore 檔案:把工作目錄還原成暫存區的版本,也就是放棄修改。這些修改從未進入 Git,所以無法用任何方法找回。
3剛提交:--amend Fixing the last commit
說明打錯字、漏了一個檔案?修好後執行:
git commit --amend -m "正確的說明" # 只改說明
git add 漏掉的檔案 && git commit --amend # 補檔案,沿用原說明
--amend 並沒有「修改」上一個 commit(M3 說過,物件不能被修改)。它是建立一個新 commit,parent 和原來的一樣,再把分支移過去。舊 commit 仍在,只是沒有分支指向它。
已經 push 的 commit 不要 amend
別人手上的歷史仍有舊 commit,你的分支卻換成了新 commit,兩邊從此分岔。推送時會被拒絕,強行推送(
push --force)則會覆蓋別人的工作(M9、M10)。已分享的錯誤,用 revert 修正。
4reset:把分支標籤搬回去 git reset
git reset <commit> 首先做一件事:把目前的分支(連同 HEAD)搬到 <commit>。之後被跳過的 commit 不再出現在 git log。三種模式的分別,只在於它還要不要順便改寫暫存區和工作目錄:
| 模式 | 分支/HEAD | 暫存區 | 工作目錄 | 結果 |
|---|---|---|---|---|
--soft | 搬回去 | 不變 | 不變 | 被跳過的改動變成「已暫存」,可以立即重新提交 |
--mixed(預設) | 搬回去 | 改成目標的內容 | 不變 | 改動變成「未暫存」,可以重新挑選再提交 |
--hard | 搬回去 | 改成目標的內容 | 改成目標的內容 | 改動從檔案裏消失;未提交的修改永久丟失 |
5revert:用新 commit 抵銷 git revert
git revert <commit> 不改動任何舊歷史。它計算出「把那個 commit 的改動倒過來」的修改,然後提交成一個新 commit,說明自動寫成:
Revert "更新摘要"
This reverts commit 9f3c2e1a….
因為只是新增 commit,它可以安全地用在已經分享的分支上:別人 pull 下來,只是多了一個 commit。如果之後的改動和要撤銷的部分重疊或相鄰,revert 也會像合併一樣產生衝突,處理方法相同(解決 → git add → git revert --continue)。
6reflog:Git 的黑盒子 The reflog
HEAD 每移動一次(提交、切換、合併、reset、amend、revert),Git 都在 .git/logs/HEAD 記一筆。git reflog 把它們列出來,最新的在最上面:
1c44d8d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~2
7635382 HEAD@{1}: commit (amend): 改寫摘要
e997d13 HEAD@{2}: revert: Revert "two"
19a7a2b HEAD@{3}: commit: three
HEAD@{1} 的意思是「HEAD 上一步在哪裏」。所以剛剛 reset --hard 闖了禍,git reset --hard HEAD@{1} 就能回到闖禍之前。被刪除的分支、amend 前的舊 commit、detached HEAD 下的提交,都能在這裏找到編號,再用 git branch 名字 編號 救回。
reflog 的限度
① 它只記錄已提交的東西;
restore 或 reset --hard 丟掉的未提交修改,不在任何地方。② 它是本機記錄,不會 push,也不會隨 clone 複製。③ 舊記錄會過期:沒有分支能到達的記錄預設保留 30 天,其他預設 90 天。
7沙盒:撤銷與救援 Sandbox: undo and rescue
8容易混淆的對照 Easily confused
reset 與 revert
git reset HEAD~1 | git revert HEAD | |
|---|---|---|
| 做法 | 把分支搬回去,改寫「目前的歷史」 | 新增一個抵銷改動的 commit |
| 舊 commit | 不再出現在 log(仍在 reflog) | 保留,歷史完整 |
| 已 push 的分支 | 不適用:會與別人的歷史分岔 | 適用:別人只會多收到一個 commit |
| 可以撤銷較早的某一個 commit 嗎 | 不行,只能把之後的全部一起搬掉 | 可以,只抵銷那一個 |
常見誤解
- 「
git revert abc123會把專案回到 abc123 的版本。」不是。它只撤銷 abc123 那一個 commit 的改動,之後的改動都保留。要回到某個版本的檔案內容,用git restore --source=abc123 檔案,或reset。 - 「
reset --hard之後 commit 就沒了。」commit 仍在,reflog 找得回;真正沒了的是當時未提交的修改。 - 「
--amend修改了原來的 commit。」它建立了一個新 commit,編號不同(M3:物件不能被修改)。 - 「
git reset 檔案和git reset --hard差不多。」完全不同。前者等於git restore --staged 檔案(只退出暫存);後者會改寫工作目錄。這也是新命令restore出現的原因:讓意思一看就懂。
9救援速查:「我搞砸了」 Rescue cheat sheet
| 我…… | 這樣救 |
|---|---|
| commit 說明打錯了(還沒 push) | git commit --amend -m "新說明" |
| 漏了一個檔案在上一個 commit(還沒 push) | git add 檔案 → git commit --amend --no-edit |
| 把幾個零碎的 commit 合成一個(還沒 push) | git reset --soft HEAD~3 → git commit |
| 提交到錯的分支(還沒 push) | git branch 正確分支(先保住)→ git reset --hard HEAD~1 → git switch 正確分支 |
reset --hard 搬過頭了 | git reflog → git reset --hard HEAD@{1} |
| 刪錯了分支 | git reflog 找編號 → git branch 名字 編號 |
| 已 push 的 commit 有錯 | git revert 編號 → git push |
| 改壞了檔案,還沒 add | git restore 檔案(先 git diff 確認要丟掉甚麼) |
10小測 Quiz
本單元重點 Summary
- 先判斷改動的階段:未提交 → restore;剛提交 → amend;已提交未分享 → reset;已分享 → revert。
reset一定搬分支;--soft到此為止,--mixed再改暫存區,--hard連工作目錄也改。revert新增抵銷用的 commit,是唯一適合已分享歷史的撤銷方法。- 已提交的東西幾乎不會消失,reflog 記錄了 HEAD 的每一步;未提交的修改則沒有任何後路。
延伸閱讀
- Pro Git 2.4 Undoing Things
- Pro Git 7.7 Reset Demystified
- Pro Git 10.7 Maintenance and Data Recovery
- git reflog 官方文件
- 有問題?記下來,它很可能也是其他同學的問題 → 學生問題庫