TL;DR:想撤销上一次提交但保留改动,跑 git reset --soft HEAD~1(改动留在暂存区)或 git reset HEAD~1(改动留在工作区)。提交已经推送过了,就改用 git revert HEAD——它生成一个抵消旧提交的新提交,不改写历史。这三条命令几乎覆盖所有”提交早了”的瞬间。本文剩下的部分逐个场景给出可复制的命令,讲清 --soft、--mixed、--hard 的区别,并演示出了岔子时怎么找回。以下每条命令在 Linux、macOS、Windows 的任何新版 Git 上都能直接用。
怎么撤销上一次提交但保留改动?
最常见的情形:提交完了,才发现一个错别字、一个漏掉的文件,或者意识到这些改动本该属于另一个提交。代码还没推送。撤销提交,把一切放回原位:
# 提交从历史里消失——改动回到暂存区
git reset --soft HEAD~1
# 改动回到工作区(未暂存)
git reset HEAD~1 HEAD~1 的意思是”HEAD 当前指向之前的第一个提交”。无论用哪种,磁盘上的文件都原封不动——移动的只是分支指针。用 git status 验证:带 --soft 时改动是已暂存的,改完直接重新提交即可;不带参数则是未暂存的,你可以先随意编辑。
如果你只是想往最后一次提交里补文件,更省事的习惯是压根不撤销它:
git add forgotten-file.txt
git commit --amend --no-edit --amend 原地替换上一次提交(这里不改提交信息)。注意:amend 一个已推送的提交等于改写历史——后文细说。
—soft、—mixed 和 —hard 有什么区别?
这一段值得背下来,因为参数决定了改动落在哪——以及它们会不会丢:
| 参数 | 提交被撤销? | 磁盘上的改动 | 改动已暂存? | 典型用途 |
|---|---|---|---|---|
--soft | 是 | 保留 | 是 | 小修之后重新提交 |
--mixed(默认) | 是 | 保留 | 否 | 重新分组改动,选择性暂存 |
--hard | 是 | 被删除 | — | 彻底丢掉这些工作 |
git revert | 否(生成新提交) | 保留 | — | 撤销一个已推送的提交 |
--hard 是唯一危险的那个:它把提交和改动一起丢掉。任何 reset --hard 之前,先 stash 或开个分支保底:
git branch backup-before-reset # 廉价保险
git reset --hard HEAD~1 # 提交加改动一起消失 如果你已经跑过危险版本,也并非无力回天——git reflog 记得 HEAD 去过哪里:
git reflog # 找到你弄丢的那个提交哈希
git reset --hard HEAD@{1} # 或者:git reset --hard <hash> reflog 默认把悬空提交保留约 90 天,所以只要在垃圾回收之前动手,“手滑 hard reset 了”几乎总能救回来。
提交已经推送了怎么办?
如果提交在共享分支上(除你自己的功能分支以外的任何分支),不要改写历史。用 git revert:它计算出与旧提交相反的改动,并作为一个新提交交上来:
git revert HEAD
git push 其他人 pull 之后只是多拿到一个新提交,它抵消了旧提交的改动。没有 force push,没有同事遭殃。要撤销一串提交,可以对区间 revert:git revert --no-commit HEAD~3..HEAD && git commit。
另一条路——git reset --hard HEAD~1 && git push --force-with-lease——只在没有任何人基于其开发的分支上可以接受,而且 --force-with-lease(永远别用裸 --force)是唯一安全的形式:如果期间有人推送过,它会拒绝覆盖。强推共享分支,正是团队弄丢提交、CI 日志神秘地对不上任何人的检出的经典原因。
想撤销提交、又想留着以后用怎么办?
有时提交本身是好活,只是地址错了——分支不对,或者提交早了。与其撤销,不如搬家:
git branch stash-commit # 把提交先停到一条新分支上
git reset --hard HEAD~1 # 然后清理当前分支 或者只把这一个提交取到另一条分支,不动当前的:
git cherry-pick <hash> # 在目标分支上执行 在 reset --soft、cherry-pick 和 revert 之间,没有哪个提交是搬不动或抹不掉的——诀窍是在伸手敲参数之前,先想清楚要”搬家”还是要”撤销”。
该用哪种撤销方式?一份快速决策清单
- 未推送,想改完重新提交 →
git reset --soft HEAD~1 - 未推送,想选择性重新暂存 →
git reset HEAD~1(mixed) - 改动彻底不要了 →
git reset --hard HEAD~1(后悔了 reflog 也记得) - 已推送到共享分支 →
git revert HEAD - 提交本该在另一条分支上 → 用
cherry-pick,别撤销
最后一条运维建议:如果坏提交真的上了服务器——部署钩子搞砸了、force push 之后 CI runner 行为异常——下一个该看的地方是机器日志,不是 Git。任何 systemd 机器上,journalctl -u <service> -n 100 会精确告诉你什么在什么时候跑了;我们的 journalctl 速查表收录了可直接复制的过滤模式。如果你的工作流还会用本地 LLM 工具审查 diff,我们在 Ollama vs LM Studio里对比过两大主流选项。
— mrsaynothing
Get the next one by email
One email per post. No spam, no algorithms.
what is this?journalctl 速查表:查看、过滤与实时跟踪 Linux 日志
喜欢这些文章?我的本职工作就是这样的工程。 雇用我