返回博客

Git 撤销上一次提交:保留改动,安全收场

2026年9月3日

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 --softcherry-pickrevert 之间,没有哪个提交是搬不动或抹不掉的——诀窍是在伸手敲参数之前,先想清楚要”搬家”还是要”撤销”。

该用哪种撤销方式?一份快速决策清单

  1. 未推送,想改完重新提交git reset --soft HEAD~1
  2. 未推送,想选择性重新暂存git reset HEAD~1(mixed)
  3. 改动彻底不要了git reset --hard HEAD~1(后悔了 reflog 也记得)
  4. 已推送到共享分支git revert HEAD
  5. 提交本该在另一条分支上 → 用 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.

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

what is this?

journalctl 速查表:查看、过滤与实时跟踪 Linux 日志

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