本节摘要:撤销手段按"动不动引用、动不动 index、动不动工作区"分类就再也不会混:reset 挪分支指针有三档力度,restore/checkout 覆盖工作区或 index,revert 用新提交反向抵消旧提交。最后是一套 reflog 自救实录——对象模型侦探的看家本领。
-m 父序号git reset <目标> 的核心动作只有一个:把当前分支引用挪到目标提交。三档力度决定顺带回填多远:
git reset --soft HEAD~1 # 只挪指针 index 工作区都不动 git reset --mixed HEAD~1 # 挪指针 并把index回填成目标提交 默认档 git reset --hard HEAD~1 # 指针 index 工作区全部对齐目标 快刀斩乱麻
用三区模型演绎一遍"提交后想重来":soft 档下,刚才提交的内容仍完整躺在 index 里(状态显示为"已暂存"),改改说明就能重新 commit;mixed 档下内容退回"未暂存",重新分袋;hard 档连工作区文件都回到提交前,未提交的改动原地蒸发。三档的共同安全边界:被 reset 跳过的提交本身仍是对象库里的完整证物,reflog 记录着指针的移动轨迹,随时能回去。
reset 的定位是"重写本地未共享的历史"。一旦提交已被推送到共享分支,挪指针就意味着下次必须强推——2.3 的黄金法则再次生效。共享历史的撤销,用 revert。
git revert <哈希> 做的是"反做":以该提交的补丁取反,生成一个新提交。历史只增不减,不需要强推,这就是它在公共分支上不可替代的原因。
git revert e5f6a7b # [main 9d0e1f2] Revert "dev edit"
新提交的 diff 恰好是旧提交的镜像,两者叠加等于什么都没发生,但卷宗里永远留着"做过又撤销"的记录——审计视角这是优点,洁癖视角这是噪音,取舍属于团队文化。
revert merge commit 的特殊参数:merge commit 有两个 parent,Git 必须知道"撤销后回到哪一边",-m 1 表示以第一父(主分支侧)为基准:
git revert -m 1 <merge提交哈希>
不给 -m 直接拒绝执行。更凶险的是后续:revert 掉一个 merge 之后,被合并的那批提交若将来要重新合入,不能直接 merge——那批提交的哈希在主线上"已被记录为撤销",Git 会认为它们已存在。正确做法是先 revert 那次 revert,或对内容重新提交。这个连环坑坑过无数团队,值得写进任何一本 Git 教材。
细粒度回退用 restore(3.1 与 1.3 已铺垫):git restore --staged 文件 退袋、git restore 文件 用 index 覆盖工作区、git restore --source=HEAD~2 文件 用任意历史快照覆盖工作区。它们不碰提交链、不碰引用,是最安全的撤销层。
四类撤销的边界一表定乾坤:
| 命令 | 引用 | index | 工作区 | 新提交 | 适用 |
|---|---|---|---|---|---|
| restore 文件 | 否 | 否 | 覆盖 | 否 | 丢弃工作区改动 |
| restore --staged | 否 | 回填 | 否 | 否 | 退袋 |
| reset --soft | 挪 | 否 | 否 | 否 | 本地重来 分袋不变 |
| reset --hard | 挪 | 回填 | 覆盖 | 否 | 本地全部不要 |
| revert | 否 | 否 | 否 | 是 | 公共历史撤销 |
完整的实战剧本。背景:功能分支上干了半天,误以为代码已提交,一把 git reset --hard origin/main 想回到主干状态:
git reflog # a1b2c3d HEAD@{0}: reset: moving to origin/main # e5f6a7b HEAD@{1}: commit: wip 登录修复 <- 灾难发生前的顶点 # 3f4g5h6 HEAD@{2}: commit: wip 第一步 # ...
reflog 按 HEAD 移动逐条记录,@{n} 是倒数第 n 次的位置。找回:git reset --hard HEAD@{1},工作区瞬间恢复到 reset 前的提交。若丢的是"从未提交过"的工作区改动——对象库里没有它的 blob,reflog 也无能为力,这正是 1.3 强调的"危险边界:未提交内容不受保护"。若丢的是某次被 rebase 洗掉的旧提交,同样手法:reflog 里找到变基前哈希,git branch rescue <哈希> 给它发一本"户口"。
reflog 默认保留 90 天(不可达引用 30 天),配合对象库两周以上的滞留期,构成了 Git 的"证据有效期"。定期把重要工作 push 到远端,才是终极备份——这也正是下一章的主题。
⚠️ 常见坑:在公共分支上
reset --hard origin/main而本地领先若干提交,push 时被拒,情急之下 force push 把同事的工作一并抹掉。链条断在两个环节:推送前不看git log main..dev自查,强推不用--force-with-lease护栏。两个习惯都该写进团队手册。
💡 关键直觉:判断撤销手段,先问两个问题——"这段历史有没有别人引用?"有则 revert,无则 reset;"要撤的是提交还是工作区改动?"提交用 reset/revert,工作区用 restore。两问过后,四条命令各就各位。
-m,revert 后重合入有连环坑。@{n} 定位、reset 找回;未提交内容是盲区。第 3 章结案。你已经具备单机作战的全部侦探技能——第 4 章把对象库接入网络,进入团队协作的领域。