Skip to content

12 · 急救手册:误操作还原

症状找,不要按命令名试。先做「诊断四件套」,再跳到对应小节。

诊断四件套(先做这个)

停下。不要 clone,不要再 reset --hard,不要 push --force,不要 gc --prune=now

bash
git status
git reflog -20
git stash list
git branch -vv

判断改动在哪一层:

你在 status / reflog 里看到改动大概在救回来的希望
Changes to be committed暂存区高,不要 restore 工作区
Changes not staged工作区且曾被跟踪中,先别 restore / hard
Untracked filesGit 从未入库低,不要 clean
reflog 里有刚才的 SHA本地历史很高,reset / switch -c 回去
rebase/merge in progress进行中的操作先考虑 --abort
detached HEADHEAD 没挂在分支上先建分支保住

还原之后验收:

bash
git status
git log --oneline -5

永远先认识的三个锚点

bash
git reset --hard ORIG_HEAD     # 上一次 merge / rebase / reset 之前的 HEAD
git reset --hard HEAD@{1}      # reflog 里的上一位置(先 git reflog 看清编号)
git switch -c rescue/<name> <sha>   # 不覆盖当前分支,另开一条把 sha 保住

reflog 默认大约保留 90 天(不可达对象更短,约 30 天)。窗口期内不要 gc --prune=now


提交说明写错或漏文件(未 push)

bash
git add 漏掉的文件          # 若只是改说明则跳过
git commit --amend --no-edit
# 或
git commit --amend -m "正确的说明"

已经 amend 得更糟了:

bash
git reflog
git reset --soft HEAD@{1}     # 回到 amend 前,改动仍在暂存区

若该提交已经 push:不要 amend。再做一次新 commit 补文件;说明错误就留着,或与团队约定后才 force-with-lease(仅限独享分支)。

多提交了几个 commit(未 push)

想拆掉提交、保留代码:

bash
git reset --soft HEAD~3       # 最近 3 颗变成暂存区,再重新 commit

想连暂存也撤掉、代码留在工作区:

bash
git reset HEAD~3

连代码也不要了(先确认):

bash
git stash push -u -m "以防万一"
git reset --hard HEAD~3

已经 push:用 git revert 逐个或一段撤销,不要 reset 共享分支。

reset --hard 之后代码没了

只要那些内容曾经 commit 过(或至少 add 过,见下一节):

bash
git reflog -20
# 找到 hard 之前的那一行,例如 HEAD@{1} 或某个 sha
git reset --hard HEAD@{1}
# 或
git reset --hard ORIG_HEAD

想先保住现场再研究:

bash
git switch -c rescue/before-hard HEAD@{1}

丢掉了未提交的改动

分三种:

1. 只是撤出了暂存区(restore --staged / reset
工作区还在,git add 回去。

2. 工作区被 restore / checkout -- / reset --hard 清掉,但曾经 git add
暂存时 Git 写过 blob:

bash
git fsck --lost-found
# 到 .git/lost-found/other/ 翻 dangling blob
# 或
git rev-list --objects --all | wc -l   # 先别当还原命令

更直接:

bash
git fsck --unreachable --no-reflogs | findstr blob
# Git Bash / macOS / Linux:
# git fsck --unreachable --no-reflogs | grep blob

对可疑 blob:git show <blob_sha> > recovered.txt。这是碰运气,但经常救回「add 了又 hard」的文件。

3. 从未 add 的新文件或未暂存改动
Git 没有副本。去编辑器 Local History、系统回收站、OneDrive/公司备份。以后先 addstash -u 再做破坏性操作。

在 detached HEAD 上提交了

bash
git status                    # 应显示 detached
git switch -c rescue/detached

如果已经切到别的分支,刚才的提交「看不见」了:

bash
git reflog
# 找到 checkout 走之前、你提交的那颗
git switch -c rescue/detached a1b2c3d

不要在游离状态继续 reset --hard

删错了本地分支

bash
git reflog
# 该分支最后停过的 sha
git switch -c feature/login a1b2c3d

branch -d 拒绝删除是保护;若你用了 -D,走上面这条。远程还在的话更简单:

bash
git fetch origin
git switch feature/login

删错了远程分支

若你本地或同事本地还留着:

bash
git push -u origin feature/login

谁都没有了:在托管平台的网页上找该分支最后一次 commit SHA(GitHub 的 Activity、已关闭的 PR),然后:

bash
git fetch origin a1b2c3d
git switch -c feature/login a1b2c3d
git push -u origin feature/login

合并合错了或后悔了

还在冲突中:

bash
git merge --abort

已经生成 merge commit,未 push:

bash
git reset --hard ORIG_HEAD

已经 push:

bash
git revert -m 1 <merge_sha>
git push

-m 1 表示「主线」是第一父提交(通常是你执行 merge 时所在的分支)。revert merge 之后,同一条功能分支默认不能再原样 merge 进来,需要后续专门处理(基于 revert 再 revert,或 rebase 出新 SHA)。此时停手问同事,不要连续 force。

rebase / cherry-pick 打成一锅粥

bash
git rebase --abort
git cherry-pick --abort

已经 rebase 完才后悔:

bash
git reflog
git reset --hard ORIG_HEAD

功能分支若已按新历史 force-with-lease 推过,再 reset 回旧历史等于第二次改写,先和 PR 审查者说一声。

提交打到错误分支

尚未 push

最新一颗其实该在 feature/login

bash
git switch -c feature/login      # 若分支还不存在:从当前(含错提交)拉出来
git switch main
git reset --hard HEAD~1

错分支上已有该功能分支、只是多了一颗:

bash
git switch feature/login
git cherry-pick <那颗sha>
git switch main
git reset --hard HEAD~1

已经 push 到共享分支(尤其是 main)

不要 reset main。

bash
git switch feature/login
git cherry-pick <那颗sha>
git push

git switch main
git revert <那颗sha>
git push

已 push 的错误提交(共享分支)

bash
git revert <sha>
git push

多颗连续:

bash
git revert --no-commit older_sha^..newer_sha
git commit -m "revert: 撤销 xxx 到 yyy"

不要 reset + push --force

force push 覆盖了别人的提交

本地立刻:

bash
git reflog
# 若你 force 之前 fetch 过,origin/main 可能已经变成新尖端
# 同事电脑上的 reflog 往往是最完整的副本

让每位同事执行 git reflog,找到 force 之前的远程尖端 SHA。谁找到谁推回去:

bash
git switch -c rescue/remote a1b2c3d
git push --force-with-lease origin rescue/remote:main
# 更好:推到 rescue 分支,用 PR 合回,避免再次 force main

托管平台有时能在 PR、Actions、commit URL 里打开旧 SHA。没有任何人的 reflog、平台也查不到,才是真丢。

预防: 只用 --force-with-lease,推前 fetch,默认分支开保护。

git clean -fd 删了未跟踪文件

Git 救不回。立刻:

  1. 编辑器 Local History / VS Code Timeline
  2. 回收站
  3. 同步盘的版本历史

预防:git clean -n 先看;值钱的未跟踪文件先 git addstash -u

stash pop 冲突或条目不见了

冲突时 pop 不会删除条目。先:

bash
git stash list
git status

解决冲突即可,不要 reset --hard。若已经 hard 掉工作区,再 stash apply 一次。

drop / clear 之后:

bash
git reflog show stash
git fsck --unreachable | findstr commit
git show <sha>                 # 看是不是 WIP on ...
git stash apply <sha>

切分支时改动「没了」或切不过去

Git 拒绝切换:未提交改动会与目标分支冲突。不要 hard。

bash
git stash push -u -m "切去修 hotfix"
git switch main
# ...
git switch -
git stash pop

已经切过去且工作区干净、改动不见:先 stash list,再 reflog(是否误 reset)。未跟踪文件通常还在原目录,因为它们不属于任何分支。

提交作者或邮箱写错了

只改最近一颗且未 push:

bash
git commit --amend --reset-author --no-edit
# 若当前 config 已是正确邮箱

最近若干颗未 push:

bash
git rebase -i HEAD~n          # 把要改的标为 edit
# 停下来时:
git commit --amend --reset-author --no-edit
git rebase --continue

已 push 到共享历史:通常不改。新仓库、只有你一个人:独享分支可 rebase 后 --force-with-lease,并通知任何已克隆的人。

pull 之后多了一个莫名其妙的 merge commit

未再 push:

bash
git reset --hard ORIG_HEAD
git pull --rebase             # 或 fetch + rebase

以后更新 maingit pull --ff-only

浅克隆导致 log / rebase 怪异

bash
git fetch --unshallow

仓库提示 corrupted / 对象缺失

bash
git fsck

先从 originfetch,或从同事那里拷贝 .git/objects 里缺失的那个哈希前缀目录。仍修不好再考虑重新 clone——先把当前目录改名为 demo-broken 备份,不要直接删。把未推送分支、stash、.git/config 里的 remote 记下来再迁。

什么时候才真正需要重新 clone

  • 对象库损坏且所有远程/同事都补不齐缺失对象。
  • 团队执行了 filter-repo / LFS 迁移,公告要求所有人放弃本地旧历史。
  • .git 被手动删了一半,fsck 修不回来。

即使如此:先改名备份整个目录,把未 push 的 patch 导出来:

bash
git format-patch origin/main --stdout > ~/rescue-patches.txt
git stash show -p > ~/rescue-stash.diff

新 clone 之后再 git am / git apply


回到 目录 · 速查表:99 · 命令速查