约 7 分钟读完 · 更新于 2026-10-21 · 文中每条都能在下面的配套工具里动手验证
判定标准只有一条:这份内容有没有变成 Git 对象。进过一次 commit 的一切(文件内容、整棵目录、临时分支)都留在对象库里,reflog 记着每个 commit 的位置,reset、rebase、误删分支都只是移动了指针。差别很大:从没 git add 过的本地改动,Git 一个字节都没存,救不回来——只能去翻编辑器的 Local History、IDE 的撤销栈或备份软件。这条边界先认清,后面所有命令都只是选哪条路回到那个对象。
排错时先看 git status:改动停在哪一层(工作区 / 暂存区 / 提交),就用对应的 undo。往下层级的 undo 永远比上一层级安全,代价是更啰嗦。
| 你干了什么 | 用什么救 | 什么时候别用 |
|---|---|---|
| 改了文件,想整个丢掉 | git restore 文件 | 改动里混了要保留的内容(先 git diff 看一眼) |
| add 错了,想退回工作区 | git restore --staged 文件 | 你本来就准备提交它 |
| commit 错了,想重做这次提交 | git reset --soft HEAD~1 | 这次提交已经推到远端共享分支 |
| commit 错了,想把文件退回未暂存 | git reset HEAD~1 | 同一个提交里还有别的正确改动要留 |
| 想彻底扔掉工作区和暂存区 | git reset --hard | 任何未提交且没备份的改动——不可逆 |
| 已 push 的提交要撤销 | git revert <sha> | 你需要的是「改写」而不是「追加一个反向提交」 |
| rebase 之后提交不见了 | git reflog 找 sha,再 git reset --hard <sha> | 你还没确认哪个 sha 才是目标 |
| 删了文件想找回 | git restore 文件,或 git checkout <commit> -- 文件 | 这个文件从没被提交过 |
| 误删了分支 | git branch 分支名 <sha> | reflog 记录已被清理(默认 90 天过期) |
reset 做的是三件事,区别只在做几件:① 把 HEAD(以及当前分支指针)移到另一个提交;② 把暂存区重置成那个提交的状态;③ 把工作区重置成那个提交的状态。--soft 只做 ①,--mixed 做 ① ②(不加参数时的默认值),--hard 做 ① ② ③。你要的几乎总是「只回退一层」:想重做提交用 --soft,想重新挑选文件进暂存区用 --mixed,想连未提交的改动一起扔掉才用 --hard。
git reflog
# 8f2a1c9 HEAD@{0}: commit: 补了分页的边界条件
# 4b70de1 HEAD@{1}: reset: moving to HEAD~2 ← 提交就是被这一步弄丢的
# a91b7d5 HEAD@{2}: commit: 重构了订单校验
git show a91b7d5 --stat # 先确认内容
git switch -c rescue a91b7d5 # 在新分支上打开它,零风险git branch backup-before-reset # 一个分支指针就是一份备份 git stash push -u -m "before reset" # 未提交 + 未跟踪的改动一起收进 stash git stash list # 后悔时:git stash pop
git rm 文件只删工作区和暂存区两处,HEAD 里的那份还在,git restore 文件就能拿回原内容;直接 rm 文件则只删工作区,同样能恢复。真正删掉内容的是 git rm 加 git commit——提交之后文件进入历史,git restore 拿的是当前分支的版本,要旧版本就得从历史里取:git checkout <旧提交> -- 文件(注意这会把文件放回暂存区)。两条保险值得知道:文件从未被 git add 过,Git 里没有副本,只能去翻编辑器的本地历史;文件被提交过又删除,删除这个动作本身也可以通过 reflog 反向恢复。
push 之后 Git 不允许你直接「改」历史,这是特性不是限制。两条路:想抹掉痕迹(改写历史)用 rebase 或 reset 后强推,风险是所有拉过这个分支的人都会不一致,非要推就用 --force-with-lease 而不是 --force,它会在远端被别人动过时拒绝;想留痕(追加提交)用 git revert 生成一个反向提交,历史变长但任何人都安全,PR 里也更适合表达意图。团队共享分支默认选 revert,只有自己独占的特性分支才考虑改写。
| 场景 | 做法 | 代价 |
|---|---|---|
| 刚 push 自己就发现错了 | reset --soft 后重推 | 远端历史不一致;用 --force-with-lease 兜底 |
| 共享分支上撤一个旧提交 | git revert <sha> 后正常 push | 历史多一条;需在 PR 里说明原因 |
| 特性分支还没合并,想整理提交 | git rebase -i 改写后强推 | 改写历史;同事拉过就得重新对齐 |
| 分支已合并到 main 之后 | git revert -m 1 <merge-sha> | 主干的线性历史被反向提交打断 |
reflog 记录的是 HEAD 这个引用移动的历史,条目默认 90 天后过期(可用 git reflog expire --expire=30.d --all 调整),而悬空对象会在 git gc --prune=1d 之后被真正清理。也就是说:一个被 reset 掉的提交,你有三到四个月的窗口去捞,之后就只能靠备份。重要分支定期推到远端、或用 git bundle 打包一份,是比 reflog 可靠得多的保险。
只要进过提交就能,reflog 记着 HEAD 的每一步:git reflog 找到 sha,再 git reset --hard
只差暂存区:--soft 把 HEAD 移回上一提交,暂存区和工作区原样保留(改动还在暂存区,适合重新提交);--mixed(默认)在 --soft 基础上再把暂存区清空,改动退回工作区,适合重新挑选文件。两者都不动工作区文件。相关命令可在 Git 命令速查里按场景查。
能,但先问一句:这个分支有人拉过吗。没人拉过就 reset --soft 后用 --force-with-lease 重推;有人拉过就用 git revert 生成一个反向提交,历史保留痕迹、任何人都安全。团队共享分支默认选后者。
能,只要 reflog 记录还没过期(默认 90 天)。先 git reflog 找到分支最后一个提交的 sha,再 git branch 分支名