文件级撤销(最常用的场景)
撤销工作区未提交的修改
文件修改了还没 add,想撤销回原始状态:
$ git checkout -- <file>
⚠️ 重要:
--不可省略,否则 git 会把它当成”切换分支”命令。完整写法git checkout -- <file>是”从 HEAD 检出该文件”。
如果文件已经 add 过,撤销后会回到暂存时的状态,而不是工作区改之前的状态。
取消 add(文件已经在暂存区)
$ git reset HEAD <file>
只取消暂存(文件改动保留在工作区)。常用于 add 多了某个文件想撤下来。
恢复到历史版本
$ git checkout <hash> -- <file>
把指定文件恢复到某个历史提交的状态,工作区其他文件不受影响。
提交级撤销
git reset --soft HEAD^
撤销最后一次 commit,但保留所有改动在暂存区。适合 commit 后想再调整提交内容(改 message、加文件)。
$ git reset --soft HEAD^
# HEAD 回到上一个 commit,但暂存区和工作区不变
git reset --mixed HEAD^(默认)
撤销最后一次 commit,改动回到工作区(未暂存)。
$ git reset HEAD^ # 等价于 --mixed
--mixed是 reset 的默认模式,不显式指定时就是它。
git reset --hard HEAD^
撤销最后一次 commit,暂存区和工作区都同步回退——本次提交的所有改动全部丢弃。
$ git reset --hard HEAD^
⚠️ —hard 是危险的:被丢弃的改动在工作区找不到,但还能从
git reflog找回(见下文)。
回退到 reflog 里的某个状态
$ git reset --hard HEAD@{1}
HEAD@{1} 表示”上一次的 HEAD 位置”,常用于:
reset --hard后反悔,用HEAD@{1}找回- 误删某个分支后用
HEAD@{n}找回
git revert:公共分支的安全回滚
$ git revert HEAD # 撤销最近一次 commit
$ git revert HEAD^ # 撤销上上次
revert 的本质是生成一个新的 commit,把指定提交的改动”反着做一遍”,不修改历史。
黄金法则:
reset改写历史 → 只能在本地未推送的私有分支上用revert不改历史 → 公共分支(如 main)回滚的唯一选择
三件套对比表
git reset
| 模式 | 暂存区 | 工作区 | 适用场景 |
|---|---|---|---|
--soft | 保留改动 | 保留改动 | 想重新 commit |
--mixed(默认) | 撤销 | 保留改动 | 想重新 add |
--hard | 撤销 | 撤销 | 彻底回退(危险) |
git reset vs git checkout vs git revert
| 命令 | 作用域 | 常用情景 |
|---|---|---|
git reset | 提交层 | 在私有分支上舍弃一些没有提交的更改 |
git reset | 文件层 | 将文件从暂存区中移除 |
git checkout | 提交层 | 切换分支或查看旧版本 |
git checkout | 文件层 | 舍弃工作目录中的更改(git checkout -- <file>) |
git revert | 提交层 | 在公共分支上回滚更改 |
git revert | 文件层 | 不支持 |
误删提交找回(reflog + reset)
场景:git reset --hard HEAD~5 后发现回退太多了,想找回之前某个 commit:
$ git reflog
# 输出类似:
# a1b2c3d HEAD@{0}: reset: moving to HEAD~5
# f4e5d6c HEAD@{1}: commit: feat: 完成了重要功能 ← 这就是被回退的提交
# b7c8d9e HEAD@{2}: commit: fix: xxx
$ git reset --hard f4e5d6c # 恢复到那个 commit
reflog 只对本地有效——已经 push 给别人或被其他人 fetch 走的 commit,reflog 帮不了你,需要从其他协作者的本地仓库找回。
下一篇:Git 知识系列(四):分支管理与远程协作,讲本地分支、合并策略、远程追踪、多人协作流程与冲突解决。