想把最近一次 Git 提交撤掉、但本地代码改动还想留着,通常用 git reset --soft HEAD~1 或 git reset HEAD~1:前者把改动留在暂存区,后者把改动退回工作区。动手前先确认两件事——这条提交有没有推送到远端,以及工作区里有没有还没提交的改动。这两点决定了你该用哪个参数,也决定了出错后还能不能救回来。
撤销本地提交前先确认两件事:提交是否已推送、工作区改动是否要保留。多数情况用 git reset--softHEAD~1 保留改动并留在暂存区,git reset HEAD~1 把改动退回工作区;git reset--hard会连未提交的工作区改动一起丢弃,只在确认改动已备份或不再需要时使用。已经推送过的分支不建议直接 reset 后强推,需要先和团队约定确认,必要时改用 git revert 生成一条反向提交。
Git撤销提交到本地代码的命令
这一步对应的是“把分支指针往回挪一条提交、工作区不动”这个动作,命令就是 git reset。判断是不是它,看两处:git log --oneline -5 的输出里,第一行是准备撤掉的那条提交,第二行是你要退回到的父提交,带 HEAD -> 分支名 标记的那一行就是当前指针位置;执行完 reset 后再跑一次 git log --oneline -3,那条提交应该从列表里消失,指针落到它的父提交上。
三个参数对应三种结果,靠 git status 的分区标题就能核对:
git reset --soft HEAD~1 # 改动留在暂存区,git status 显示 Changes to be committed
git reset HEAD~1 # 等价于 --mixed,改动退回工作区,显示 Changes not staged for commit
git reset --hard HEAD~1 # 暂存区和工作区一起对齐到目标提交,未提交改动丢失
还可以用 git reflog -5 复核动作有没有发生:最新一条记录会写成 reset: moving to HEAD~1 这样的形式,上一条则是 commit: 原始提交信息。reflog 是本地操作日志,不受提交是否推送影响,这也是后面能反悔的依据。
Git撤销提交到本地代码的文件
这一步对应的不是命令,而是“这次提交到底动了哪几个文件、撤销之后它们各自处在哪个状态”。核对位置是 git show --stat HEAD,输出的文件列表就是这条提交涉及的范围;但要注意时机——执行 reset 之后 HEAD 已经变了,要看原来的提交得写成 git show --stat HEAD@{1} 或直接用 reflog 里的 SHA。
撤销提交后逐个确认文件状态,看这三个视图:git diff --cached --stat 比较暂存区和上次提交的差异,git diff --stat 比较工作区和暂存区的差异,git status --porcelain 给出机读版的两列标记。如果只想把某个文件从暂存区退回工作区、其余保持不动,用 git restore --staged <文件路径>,再跑一次 git status 就能看到该文件从 Changes to be committed 挪到 Changes not staged for commit。文件名里有空格或中文时注意加引号,重命名文件在 stat 输出里会显示成 旧名 => 新名 的形式。

撤销提交到本地代码:先把能核对的事实摆清楚
先把对象列清楚:哪个本地分支、HEAD 当前指向哪条提交、这条提交包含哪些文件、暂存区和工作区各自是什么状态。这几项都能直接读出来,不需要推测。
git branch -vv:看当前分支跟踪的远端分支,以及ahead N的数字。数字为 0 或没有ahead标记,说明本地没有未推送的提交。git log origin/<分支名>..HEAD --oneline:有输出说明这些提交还没推上去;报错找不到远端分支,说明本地还没有对应的远端引用。git status:输出分三块,Changes to be committed、Changes not staged for commit、Untracked files。哪块为空就说明那一层没有内容。git stash list:确认之前有没有暂存过改动,避免把 stash 里的东西当成丢了。
这些事实摆完再决定用哪个参数。已经推送过的提交如果直接 reset,本地和远端就会分叉;工作区还有未提交改动时用 --hard,丢的东西不在 reflog 覆盖范围内。这两条都是前提条件,不是结论。
撤销提交到本地代码最容易搞错的一点
最常见的误解是把 git reset --hard 当成“撤销提交但代码还在”。实际上 --hard 是把 HEAD、暂存区、工作区三者一起对齐到目标提交,未提交过的改动不会进任何 Git 对象,reflog 里也没有它的副本,恢复不了。--soft 和默认的 --mixed 只动指针和暂存区,工作区文件内容不变。
判断分界很清楚:看 reset 之前工作区有没有内容。区分动作是先跑 git status --porcelain,有输出就说明工作区存在未提交改动,此时用 --hard 就会丢东西;输出为空,--hard 才只是移动指针和清空暂存区。更稳妥的做法是在任何 reset 之前先执行 git stash push -u -m before-reset,再用 git stash list 确认出现了 stash@{0},后面需要时用 git stash pop 取回。
撤销提交到本地代码:一个照着就能做完的做法
下面这条路径默认“撤销最近一次提交、改动全部保留、且这条提交还没推送”。按顺序执行,每一步都写了该看到什么。

# 1. 记录现场
git status
git log --oneline -5
git branch -vv
应看到 git log 第一行是要撤掉的提交,前面带 HEAD -> 指向当前分支;git branch -vv 里没有 ahead 数字。
# 2. 先备份工作区未提交的改动
git stash push -u -m before-reset
git stash list
应看到列表里出现 stash@{0}: On <分支名>: before-reset。工作区本来就干净时,这一步会提示没有本地改动可暂存,直接跳过即可。
# 3. 撤销提交,改动保留
git reset --soft HEAD~1
# 4. 核对结果
git log --oneline -3
git status
git diff --cached --stat
应看到原来的提交从 git log 里消失、HEAD 落到父提交;git status 里出现 Changes to be committed,文件列表和 git show --stat HEAD@{1} 对得上。
# 5. 想把这些改动退回工作区(不再暂存)
git restore --staged .
git status
# 6. 取回第 2 步备份的改动
git stash pop
# 7. 只想改提交信息或往最后一条提交里补文件
git add <文件路径>
git commit --amend --no-edit
第 5 步执行后,git status 里同一批文件应从 Changes to be committed 移到 Changes not staged for commit,文件内容不变。第 7 步的 --no-edit 表示提交信息保持不变,要改信息就换成 -m "新的提交信息"。
# 8. 撤销做错了,回滚到操作前
git reflog -10
git reset --hard HEAD@{1}
从 git reflog 里找 commit: 开头的那条记录,确认 SHA 后再 reset。--hard 只适合在这里使用,因为此时改动已经在上一步保留好了。如果这条提交已经推送过,不要走第 3 步,改用 git revert HEAD 生成一条反向提交,推上去之后远端历史仍然是连续的。