外观
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 files | Git 从未入库 | 低,不要 clean |
| reflog 里有刚才的 SHA | 本地历史 | 很高,reset / switch -c 回去 |
rebase/merge in progress | 进行中的操作 | 先考虑 --abort |
detached HEAD | HEAD 没挂在分支上 | 先建分支保住 |
还原之后验收:
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/公司备份。以后先 add 或 stash -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 a1b2c3dbranch -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 救不回。立刻:
- 编辑器 Local History / VS Code Timeline
- 回收站
- 同步盘的版本历史
预防:git clean -n 先看;值钱的未跟踪文件先 git add 或 stash -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以后更新 main 用 git pull --ff-only。
浅克隆导致 log / rebase 怪异
bash
git fetch --unshallow仓库提示 corrupted / 对象缺失
bash
git fsck先从 origin 再 fetch,或从同事那里拷贝 .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。