先别慌:Git 很少真的丢东西
误执行 git reset --hard、git rebase 出错、误删分支后,很多人第一反应是去翻备份。实际上只要提交对象还在本地仓库的 .git/objects 里没被垃圾回收,就有机会找回。Git 的 reflog 会记录 HEAD 和分支引用的移动历史,这是恢复的第一入口。
注意两点前提:
- 恢复操作要在同一个本地仓库里做,换机器或重新 clone 通常无效。
- 越早操作越好。默认情况下不可达对象会在
gc.reflogExpire(reflog 条目,默认 90 天)和gc.reflogExpireUnreachable(不可达条目,默认 30 天)之后被清理,执行过git gc --prune=now会立即清掉。
第一步:用 reflog 定位丢失的提交
git reflog
输出形如 HEAD@{3}: reset: moving to HEAD~1,每行左侧是提交哈希,右侧是这次引用移动的原因。找到“改乱之前”的那一条,记下它的哈希。
如果只想看某个分支的历史:
git reflog show <branch-name>
只想确认这个提交内容对不对,先用只读方式查看:
git show <hash>
git log --oneline -5 <hash>
确认无误后再决定怎么恢复。
第二步:选择恢复方式
情况 A:整个分支被 reset 或删掉了
如果分支名还在,只是指向了错误位置:
git branch -f <branch-name> <hash>
如果分支已被删除,直接重新创建:
git branch <branch-name> <hash>
然后 git checkout <branch-name> 切过去检查。
情况 B:当前分支需要整体回到旧状态
git reset --hard <hash>
这条命令会丢弃工作区和暂存区的改动,执行前先确认没有未提交的重要修改(可以先 git stash)。
情况 C:只想要其中一两个提交,不想回退整条历史
这正是 cherry-pick 的用途。把目标提交“摘”到当前分支上:
git cherry-pick <hash>
多个提交按顺序写:
git cherry-pick <hash1> <hash2>
一段连续区间用 A..B(不含 A)或 A^..B(含 A)。如果冲突,解决后执行 git cherry-pick --continue;想放弃则 git cherry-pick --abort。
第三步:验证与收尾
恢复之后不要只看 git log 就结束,建议逐项确认:
git log --oneline --graph --all看整体拓扑是否合理。git status确认工作区干净。- 跑一遍测试或构建,确认代码内容真的回来了。
- 需要推送时,如果历史被改写,通常要
git push --force-with-lease(比--force安全,会在远端被别人更新过时拒绝推送)。共享分支上强推前先和协作者沟通。
几个容易踩的坑
- reflog 是本地记录,不会随 push 同步到远端,别人仓库里查不到你的操作轨迹。
git fsck --lost-found可以列出悬空对象(dangling commit/blob),在 reflog 也找不到时作为兜底手段,但输出可读性差,需要逐个git show辨认。- rebase 过程中出错,可以
git rebase --abort回到起点;已经 rebase 完才发现问题,同样用 reflog 找到 rebase 前的HEAD@{n}回退。 - 恢复成功后,如果确定不再需要旧对象,再让 Git 自然回收即可,不必手动
gc。
一句话总结:git reflog 找哈希,git branch -f / git reset --hard 回退,git cherry-pick 挑着捡,最后验证再推送。