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。