核心觀念:git 幾乎不刪除東西
git reset --hard、git branch -D、甚至 git push --force——這些操作感覺很危險,但 git 在本機保留了幾乎所有的 commit 至少 30 天(gc.reflogExpire 設定)。
關鍵是找到 commit 的 hash,然後把它恢復出來。
reflog:所有 HEAD 移動的歷史
git reflog 顯示 HEAD 在什麼時間指向哪個 commit:
$ git reflog
abc1234 HEAD@{0}: reset: moving to HEAD~3 ← 最近的操作
def5678 HEAD@{1}: commit: fix authentication
ghi9012 HEAD@{2}: commit: add user model
jkl3456 HEAD@{3}: checkout: moving from main to feature/authHEAD@{1} 就是你 reset --hard 之前的 commit。
情境 1:git reset --hard 後悔了
# 你做了
git reset --hard HEAD~3 # 刪掉了最近 3 個 commit
# 找回來
git reflog # 找到你 reset 之前的 commit hash(e.g., def5678)
git reset --hard def5678 # 或 HEAD@{1}情境 2:Branch 被 -D 刪掉了
# 你做了
git branch -D feature/payment # 刪掉了整個 branch
# 找回來
git reflog | grep feature/payment # 找到 branch 最後指向的 commit
# 或者
git reflog # 找到那個 branch 最後一個 commit 的 hash
git checkout -b feature/payment <hash> # 用那個 hash 重建 branch情境 3:Detached HEAD 狀態
# 發生原因:直接 checkout 一個 commit hash
git checkout abc1234
# 現在 HEAD 不指向任何 branch,你的 commit 沒有 branch 記錄
# 如果你在 detached HEAD 做了 commit,先找到那個 commit
git reflog # 找到那些 commit 的 hash
# 方案 1:建立新 branch 把那些 commit 接住
git checkout -b save-my-work
# 方案 2:把那些 commit cherry-pick 到目標 branch
git checkout main
git cherry-pick abc1234..def5678情境 4:Force push 搞壞了遠端 branch
# 你做了
git push --force origin main # 把遠端 main 的 history 搞壞了
# 如果有另一個人的本機還有舊的 commit:
# 讓他執行:
git reflog # 找到遠端被覆蓋前的 commit
git push --force origin <hash>:main # 把遠端推回去如果沒有其他人有舊版本:
# 在你本機的 reflog 找你 force push 之前的 commit
git reflog # e.g., abc1234
git push --force origin abc1234:main情境 5:誤 commit 了不該有的檔案(比如 .env)
# 這個 commit 還沒 push
git reset HEAD~1 # 保留改動但 unstage
# 或
git reset --soft HEAD~1 # 保留 staged 狀態
# 把不該有的檔案加入 .gitignore
echo ".env" >> .gitignore
git add .gitignore
git commit -m "remove .env from tracking"如果已經 push 了且 .env 裡有 secret:先撤銷 secret(因為任何人都可能已經看到),再清理 history。用 git filter-repo(官方推薦)或 BFG Repo Cleaner 從整個 history 移除檔案。
情境 6:stash 用完沒了
# git stash drop 之後後悔了
git stash list # stash 還在 reflog 裡
# 找到 stash 的 commit hash
git fsck --no-reflogs | grep "dangling commit"
# 或
git reflog stash
git stash apply <hash>預防大於救援
push --force用push --force-with-lease替代——這個指令只在遠端 branch 和你預期一樣時才允許 force push,防止覆蓋別人的 commit- 保護 main branch:在 GitHub / GitLab 設定 protected branch,不允許直接 force push
- 重要的操作前先
git reflog記住當前 commit hash