周五下午四点,我正准备切到主分支合并一个功能,同事在旁边说“那个改动先不要了,帮我把本地回退一下”。我顺手敲了 git reset --hard HEAD~1。回车之后,他脸色变了:“我下午写的代码呢?”那一瞬间我冷汗就下来了。
后来靠 git reflog 把丢失的提交找了回来。那次之后,我把 Git 误操作的恢复方法整理成一份急救笔记,也在团队内部做过分享。这篇就是那份笔记的完整版:当你已经把代码弄丢、分支删错、提交推错、rebase 到一半想跑路时,具体应该敲哪些命令,以及为什么这些命令能救命。
别管你是刚接触 Git,还是已经写了好几年,只要你还在用 Git 管理代码,就早晚会遇到一次“误操作”。Git 和 Word 最大的不同是:它没有撤销按钮,但它把几乎每一次操作痕迹都留在了本地仓库里。你需要的只是知道去哪里翻案发现场。
1. 急救第一课:先把手从键盘上拿开
1.1 第一反应别是“反着再执行一次”
误操作之后的黄金三分钟,最重要的事情不是补救,而是别再制造新的破坏。
我见过太多次这种场面:有人 git reset --hard 之后发现代码没了,本能反应是再执行一个反向的 git reset,或者赶紧 git checkout .,结果因为对当前 HEAD 指向哪里并没有把握,反而把原本还能救的提交覆盖了。还有的人会顺手关掉终端、重新打开 IDE,结果在反复启动中触发了自动清理,好端端放在 reflog 里的记录被 GC 机制处理掉。
正确做法是:
- 停手,不关闭终端窗口,不执行任何写入类 Git 命令;
- 把屏幕上最后一条带 commit hash 的输出复制下来,哪怕你不确定它有没有用;
- 执行
git status查看当前状态,只读命令不会破坏现场; - 心态稳下来,对照下面几章找对应的救援手段。
这里要说一个核心概念:Git 本质上是一个内容寻址的对象数据库。分支、HEAD 这些名字,只是一串指向 commit 对象的“引用”。危险操作之所以危险,是因为它让这些引用指向了别的位置,但只要对象还没有被垃圾回收,数据就还在仓库里。所以大多数“代码丢了”的惊魂时刻,实际是“引用丢了”,不是“数据没了”。
1.2 先看清 Git 留了哪几张牌
在开始救援前,你需要知道本地仓库里有什么“牌”可以打。下面这张表是我判断误操作场景时必看的清单:
| 工具/记录 | 作用 | 常见适用场景 |
|---|---|---|
git reflog |
记录 HEAD 和分支引用的历史移动轨迹 | reset、rebase、checkout 切走后的恢复 |
ORIG_HEAD |
某些大操作(merge、reset 等)之前位置的参考 | 刚执行完误操作,想快速回到从前 |
git fsck --lost-found |
扫描当前不可达但尚未被清理的对象 | 分支被删、stash 被丢、提交无引用 |
| IDE 本地历史 | 编辑器自带的时间线备份 | 从未 git add 过的新文件被 clean 删除 |
| 远端仓库 | 只要 push 过,远端就有副本 | 本地分支损坏、历史误改、想从远端拉回 |
我会在后面的每一章里具体展开其中的用法。
有一个观点值得反复强调:Git 的 GC 不是实时运行的。普通提交对象不会因为你删了一个分支就立刻消失,它们会以“悬空对象”的身份继续存在于 .git 目录里一段时间。默认情况下,reflog 中可达的提交保留 90 天,不可达的引用也有 30 天的缓冲期。这其实就是 Git 留给你的后悔药时间窗。真要过了这个时间窗还没发现,那才叫难办——不过那也是相对难办,下面第三章里我专门讲了怎么用 git fsck 去“考古”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 误执行 git reset --hard 后,靠 reflog 把工作区找回来
2.1 事故现场:一条命令冲掉了三天功能
假设你现在在 feature/pay 分支上,已经写了三个提交,还没推送到远端:
bash复制$ git log --oneline
d6e4a1f (HEAD -> feature/pay) feat: 支付结果页调优
c32a8b9 fix: 修复回调签名校验
4ff10aa feat: 接入支付网关
然后你想撤销某个操作,输入了:
bash复制git reset --hard origin/main
回车之后你发现,本地 feature/pay 上的 d6e4a1f、c32a8b9、4ff10aa 三个提交全不见了,工作区变成了远端主分支的最新代码。
很多人这时候会去翻 git log,然后发现 d6e4a1f 根本没有出现在日志里,以为代码彻底没了。不对——git log 只看得到当前引用链上可达的提交,而你现在 HEAD 指向了 origin/main,那三个 commit 自然不在列表里。但它们并没有被删除,只是变成了“不可达对象”。
2.2 读懂 reflog,别只会刷屏
现在执行:
bash复制git reflog
你会看到类似下面的输出:
text复制a71b6c2 HEAD@{0}: reset: moving to origin/main
d6e4a1f HEAD@{1}: commit: feat: 支付结果页调优
c32a8b9 HEAD@{2}: commit: fix: 修复回调签名校验
4ff10aa HEAD@{3}: commit: feat: 接入支付网关
reflog 本质上是 HEAD 引用每次变更的“日志”。它记录了你在本地执行过的每一个会导致引用移动的动作:commit、checkout、reset、merge、rebase、cherry-pick……在这个例子里,最上面一条是你刚刚那个 reset,它的上一条 HEAD@{1} 就是 reset 之前的位置,也就是你真正想回去的地方。
恢复动作也简单:
bash复制git re
