Git 撤销操作速查

Git 的“撤销”分散在好几个命令里,选哪个取决于两件事:改动现在在哪一层(工作区、暂存区、提交),以及有没有推送给别人。下面按场景整理。命令基于 Git 2.23 之后引入的 git restore,旧版本对应的写法会顺带注明。

1. 撤销工作区的修改

文件改乱了,还没 git add,想恢复到上次提交时的样子:

git restore app.py         # 旧写法:git checkout -- app.py
git restore .              # 恢复当前目录下所有已跟踪文件

这一步会直接丢弃未提交的修改,且无法通过 Git 找回。不确定时先 git stash 存一份。

新建的、从未被跟踪的文件不受影响,需要清理时用 git clean -n 预览,确认后再 git clean -f。

2. 取消暂存

不小心把不该提交的文件 add 进去了,想移出暂存区但保留修改:

git restore --staged config.local.toml    # 旧写法:git reset HEAD config.local.toml

3. 修改最近一次提交

提交信息写错了,或者漏提交了一个文件,并且还没推送:

git add forgotten.txt
git commit --amend                 # 打开编辑器修改提交信息
git commit --amend --no-edit       # 只补文件,不改提交信息

--amend 实际上会生成一个新提交来替换原来的提交,所以已经推送过的提交不要这样改。

4. 撤回本地提交

最近几次提交还没推送,想撤回重新组织。git reset 的三种模式区别在于改动退回到哪里:

git reset --soft HEAD~1    # 撤回提交,改动保留在暂存区
git reset HEAD~1           # 默认 --mixed:改动保留在工作区,未暂存
git reset --hard HEAD~1    # 撤回提交并丢弃改动(危险)

HEAD~1 表示上一个提交,HEAD~3 表示往前三个。日常最常用的是 --soft:把几个零碎提交撤回后重新合并成一个。

5. 撤销已推送的提交

提交已经推送到共享分支,别人可能已经基于它继续开发,这时不要改写历史,而是用 git revert 生成一个“反向提交”来抵消它:

git log --oneline -5       # 找到要撤销的提交
git revert 3f2a9c1         # 生成一个内容相反的新提交
git push

如果是只有自己在用的分支,确实需要改写已推送的历史,强制推送时请用 git push --force-with-lease:远程分支在你上次拉取后被别人更新过时,它会拒绝覆盖。

6. 误操作后用 reflog 找回

执行了 reset --hard 或者删错了分支,先别慌。只要内容曾经提交过, reflog 里就有 HEAD 的移动记录:

git reflog                         # 找到误操作之前的位置,例如 HEAD@{2}
git reset --hard HEAD@{2}          # 回到那个状态
git branch rescue HEAD@{2}         # 或者稳妥一点:新建分支指向它

reflog 只存在于本地,并且旧记录会在一段时间后过期(默认约 90 天),所以发现问题要尽早处理。

速查表

场景命令会丢改动吗
放弃工作区修改git restore <file>会
取消暂存git restore --staged <file>不会
改最近一次提交git commit --amend不会
撤回本地提交git reset --soft HEAD~1不会
撤销已推送提交git revert <commit>不会
找回误删的提交git reflog—

记住一个原则:没推送的随便改,推送了的用 revert。