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 --hard | git restore file |
| 取消暂存一个文件 | git reset HEAD file | git 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:多提交、跨分支、解冲突。
快速决策回顾
- 提交已公开(已推送、别人已拉取)→
git revert <sha>。 - 提交只在本地,想重做 →
git reset --soft或--mixed,然后重新提交。 - 只在本地,想连同未提交的乱摊子一起清掉 →
git reset --hard,先诚实地看一眼工作区里还有什么。 - 只有一个文件被搞坏 →
git restore <file>,分支别动。
这份清单里唯一真正危险的是 --hard——其余都能用 git reflog 找回来。git 里永久性的损失范围很窄,而且大多需要你显式敲出来;值得养成的习惯是在 --hard 前停半秒,而不是躲开 git 的锋利工具。
— mrsaynothing
Get the next one by email
One email per post. No spam, no algorithms.
what is this?喜欢这些文章?我的本职工作就是这样的工程。 雇用我