■ 空间 · 散文 lane

■ 先给答案

有没有别人 pull 过这个提交? 有 → git revert <sha>。没有 → git reset,任意模式。

--soft 让改动保持 staged,--mixed(默认)unstaged,--hard 把它们删掉——唯一的碎纸机.

只有一个文件被搞坏? git restore <file> ——别去动分支。

一次性仓库演示

在一个临时仓库里花两分钟,差别就不再是一段定义——而是你亲眼见过的 log:

搭一个两个提交的仓库 — 2 commits · 错误已提交,树干净

git init /tmp/revert-vs-reset && cd /tmp/revert-vs-reset
echo one > file.txt && git add . && git commit -m "one"
echo two > file.txt && git add . && git commit -m "two"

revert — 历史变长

git revert --no-edit HEAD
git log --oneline
# 1a2b3c4 (HEAD -> main) Revert "two"   ← the cancellation
# 5d6e7f8 two                          ← still in the log
# 9f8e7d6 one

reset — 历史变短

git reset --hard HEAD~1
git log --oneline
# 9f8e7d6 one                          ← "two" is unreachable

verify: revert 之后 git log --oneline → 3 commits · reset 之后 → 1 commit — 不对称本身就是全部答案

reset 的三种模式

这些模式控制 你的改动在指针 移动之后活在哪里——而这张表里只有一行删除工作:

模式分支指针暂存区工作树你的改动
--soft后退保留并保持 staged不受碰完整保留
--mixed(默认)后退unstaged不受碰保留,unstaged
--hard后退重置重置删除

verify: git reset --soft HEAD~1 → git status 显示改动「to be committed」 · --mixed → 「changes not staged」

revert 没有各种模式,因为它从不销毁任何东西——只是补出一个对冲提交。

那一个问题

有没有别人 pull 过这个提交? 有的话,reset 出局。reset 一条别人在其上盖了楼的分支,等于重写共享历史:他们下一次 pull 会迎面撞上分叉的分支,「修复」通常是 force-push,把你的失误变成所有人的麻烦。revert 追加一个普通提交,所以一条普通的 git pull 能为所有人干净地合并——这就是 revert 是 main 和 GitHub pull request 上默认答案的原因,那里专门有「Revert」按钮,正是因为重写一个已合并 PR 的历史不是选项。

reset 留给共享 之前 的窗口:三十秒前的提交、暂存失误、本地实验分支。如果这份工作的唯一副本就在你机器上,你可以随意重排历史——这就是 push 前窗口的全部意义。中间情形:你 push 到了自己的 feature 分支、没人从它往上盖楼——reset 之后跟一个 force-push 在那里社会性可接受,但 revert 仍省协调。把 force-push 只留给独自拥有的分支。

revert 还更安全,是 结构上的,不只因惯例:revert 产出的是一个普通提交——CI 在它上面跑,diff 可供审阅,而 git log 向未来之你解释那个改动为何消失。reset 无声地丢弃中间状态;后来审阅的人看不见什么被撤销、何时被撤销。

revert 的两处锋利边缘

restore 放在哪儿

git restore(还有 git switch,它的搭档)在 git 2.23 登场,接管 reset 因名字过载而做不好的那些活:

任务老办法清楚的办法
丢弃一个文件在工作树里的改动git checkout -- file / git reset --hardgit restore file
把文件移出暂存区git reset HEAD filegit restore --staged file
把分支移到另一个提交git reset <sha>git reset <sha>(没有替代者——这个留着)

现代分工:restore 修文件,reset 动分支,revert 撤公开提交。 它们是同一个心理模型切成三条命令。老的 git checkout 形式处处仍能用;新形式只是阻止你把整条分支送进碎纸机,而你本意只是把一个文件移出暂存区。

回顾

  1. 提交已公开(已 push、别人 pull 过) → git revert <sha>.
  2. 提交只在本地,想重来一遍 → git reset --soft 或 --mixed 然后重新提交。
  3. 只在本地、想连同未提交的烂摊子一起清掉 → git reset --hard,之前先诚实地看一眼工作树里还装着什么。
  4. 只有一个文件被搞坏 → git restore <file>,别去动分支。

verify: git reflog → HEAD@{n} 列出 reset 前的位置 — git reset --hard HEAD@{1} 走回去

那份名单里唯一真正危险的条目是 --hard ——其余的都能用 git reflog 救回来——它把脱队的提交记约 90 天。git 里的永久损失范围很窄,而且大多要求你亲手显式敲出来;值得养成的习惯是在 --hard 前顿半秒,而不是把 git 的锋利工具一概敬而远之。如果整份撤销菜单让你觉得是一场仪式,这批评是公道的——jujutsu,这个 git 兼容的 VCS,重做的正是这一块。

■ 诚实的盖印 测试于:git 2.4x · Linux · macOS · Windows。 首发于 2026-09-15,记录已在 2026-10-10 刷新。上面的演示是一个可实际运行的一次性仓库——展示的 log 形状就是 git 真实打印的。各模式的行为、merge-revert 语义和 reflog 窗口都是 git 的文档行为,从使用中敲出来。

撤销二重奏到此为止 · 未取任何远程内容 · 全程本地