返回博客

Git revert vs reset:哪个才能保住你的提交历史?

2026年9月15日

TL;DR:git revert 新增一个提交来撤销某个旧提交——历史保留,在别人已经拉取的分支上安全。git reset 把分支指针向后移——历史被改写,只对还没推送的提交安全。共享分支用 revert,本地清理用 reset。reset --hard 还会删除未提交的工作区改动;真正吃掉工作的就是它。

git revert 和 git reset 有什么区别?

两者都能让项目看起来像某个过去的提交从未发生过。分歧在于怎么做到:

  • git revert <sha> 计算一个提交的逆补丁并提交它。历史多出一个提交,写着”撤销那一个”。坏提交留在日志里,后面跟着它的抵消。其余一切的 SHA 原封不动。
  • git reset <sha> 把当前分支的指针移到 <sha>。之后的提交被摘下来——在垃圾回收之前仍存在于对象库里,但不再能从任何分支或 git log 到达。

一个什么都不改写,另一个假装什么都没发生。仅凭这一条性质,就能决定每种情况该用哪个:

# 演示:一个可以动手跑的临时仓库
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:历史保留两个提交,外加一个撤销 "two" 的提交
git revert --no-edit HEAD
git log --oneline        # 3 个提交:revert、two、one

# Reset:分支指针后移,"two" 从日志里消失
git reset --hard HEAD~1
git log --oneline        # 1 个提交:one

两条都跑一遍,看看 git log——这种不对称就是全部答案。

git reset —soft、—mixed 和 —hard 到底做了什么?

三种模式控制的是指针移动之后,你的改动活在哪里

模式分支指针暂存区工作区你的改动
--soft后移保留在暂存区不动完整保留
--mixed(默认)后移取消暂存不动保留,未暂存
--hard后移重置重置被删除

对照这张表的具体读法:

  • git reset --soft HEAD~1——“我提交早了。“一切回到已暂存状态,随时可以重新提交,或许还捎上更多工作。
  • git reset HEAD~1(mixed)——“我暂存错了文件。“改动还在工作区,暂存区是空的。如果还想顺手清掉杂散文件,搭配 git remove untracked files
  • git reset --hard HEAD~1——“这个提交和我未提交的改动都是垃圾。“Git 不会问第二遍;除非你先 stash 或 commit,工作区那部分没有撤销键。

revert 没有模式,因为它从不销毁任何东西——它只会追加一个补偿提交。

什么时候该用 git revert 而不是 git reset?

只问一个问题:这个提交有没有被别人拉取过?有,reset 就出局。

在一个别人已经基于其开发的分支上 reset,等于改写共享历史。他们下次 pull 会撞上分叉的分支,而”修复”通常是一次 force-push,把你一个人的失误变成所有人的问题。revert 追加的是普通提交,一次平平无奇的 git pull 就能让所有人干净地合并——这就是为什么在 main 和 GitHub pull request 上 revert 是默认答案:GitHub 提供 “Revert” 按钮,恰恰因为改写已合并 PR 的历史不是一个选项。

reset 属于共享之前的窗口:三十秒前的提交、暂存错的文件、本地实验分支。如果工作的唯一副本就在你机器上,你可以随意重排历史——这就是推送前窗口的全部意义。

有个中间情形:你推送到了自己的 feature 分支,没有别人在它之上开发。reset 后 force-push 在那里社交上可以接受,但 revert 仍然省协调。force-push 留给你独自拥有的分支。

在共享分支上,git revert 是不是比 git reset 更安全?

是的,而且是结构上的安全——不只是约定:

  • revert 产生普通提交;CI 会在它上面跑,diff 可评审,git log 会向未来的你解释这次改动为什么消失。
  • reset 无声地丢弃中间状态。事后评审的人看不到撤销了什么、何时撤销,因为日志里是一条从未包含它的直线。

两个实际的锋利边缘:

  • 撤销合并提交需要 git revert -m 1 <sha>(保留第一父线那一侧)。不加 -m,git 会拒绝,并让你自己去读报错。
  • revert 不是时光机:它撤销的是一个提交的 diff。如果后面的提交碰过同样的行,你可能要解决冲突——那是 git 在告诉你这次撤销和别的改动纠缠在一起,这是有用的信息,不是故障。

专门针对”撤销最后一个提交”——保留或丢弃其改动——各选项在 git undo last commit: keep the changes 里有完整铺开。

git 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 revert vs reset vs restore” 放在一起搜,理由充分——它们本是同一个心智模型,被拆进三条命令。旧的 git checkout 写法处处可用;新写法只是防止你在只想取消暂存一个文件时,把整条分支送进碎纸机。

如果你的目标是把一个提交复制过来,而不是抹掉一个坏提交,那是 cherry-pick 的活——见 git cherry-pick:多提交、跨分支、解冲突

快速决策回顾

  1. 提交已公开(已推送、别人已拉取)→ git revert <sha>
  2. 提交只在本地,想重做 → git reset --soft--mixed,然后重新提交。
  3. 只在本地,想连同未提交的乱摊子一起清掉 → git reset --hard,先诚实地看一眼工作区里还有什么。
  4. 只有一个文件被搞坏 → git restore <file>,分支别动。

这份清单里唯一真正危险的是 --hard——其余都能用 git reflog 找回来。git 里永久性的损失范围很窄,而且大多需要你显式敲出来;值得养成的习惯是在 --hard 前停半秒,而不是躲开 git 的锋利工具。

— mrsaynothing

Get the next one by email

One email per post. No spam, no algorithms.

self-hosted · no third parties · one-click unsubscribe

what is this?

systemd 服务起不来?排查与修复

喜欢这些文章?我的本职工作就是这样的工程。 雇用我