Git 完全指南 / 08 - 撤销操作:reset、revert、checkout、restore
第八章:撤销操作
犯错是开发的常态,Git 提供了丰富的撤销工具,让你从容应对各种情况。
8.1 撤销操作全景图
┌──────────┬──────────────┬──────────────┬──────────────┐
│ 操作目标 │ 工作区 │ 暂存区 │ 仓库 │
├──────────┼──────────────┼──────────────┼──────────────┤
│ 丢弃修改 │ git restore │ git restore │ git reset │
│ │ │ --staged │ --soft HEAD~ │
│ 还原提交 │ git revert │ │ git revert │
│ 恢复文件 │ git checkout │ git reset │ git checkout │
│ │ -- <file> │ HEAD <file> │ <commit> │
└──────────┴──────────────┴──────────────┴──────────────┘
8.2 git reset — 重置提交
git reset 移动 HEAD 指针,可选择性地修改暂存区和工作区。
8.2.1 三种模式
| 模式 | HEAD | 暂存区 | 工作区 | 用途 |
|---|---|---|---|---|
--soft | ✅ 移动 | ❌ 不变 | ❌ 不变 | 撤销提交,保留所有修改在暂存区 |
--mixed (默认) | ✅ 移动 | ✅ 重置 | ❌ 不变 | 撤销提交和暂存,保留工作区修改 |
--hard | ✅ 移动 | ✅ 重置 | ✅ 重置 | 完全重置,丢弃所有修改 |
8.2.2 使用示例
# 撤销最后一次提交,保留修改在暂存区
$ git reset --soft HEAD~1
# 撤销最后一次提交,取消暂存(默认 --mixed)
$ git reset HEAD~1
# 完全撤销最后一次提交(危险:丢失修改)
$ git reset --hard HEAD~1
# 撤销最后 3 次提交
$ git reset --soft HEAD~3
# 重置到特定提交
$ git reset --soft abc1234
# 取消暂存文件(保留工作区修改)
$ git reset HEAD file.txt
# 或 Git 2.23+
$ git restore --staged file.txt
# 丢弃工作区修改(恢复到暂存区版本)
$ git checkout -- file.txt
# 或 Git 2.23+
$ git restore file.txt
8.2.3 reset 流程图
初始状态:
HEAD → C3 → [暂存区] → [工作区]
git reset --soft HEAD~1:
HEAD → C2 → [暂存区=C3] → [工作区=C3]
提交被撤销,但修改仍在暂存区,可以直接重新提交
git reset --mixed HEAD~1 (默认):
HEAD → C2 → [暂存区=C2] → [工作区=C3]
提交和暂存被撤销,修改仍在工作区
git reset --hard HEAD~1:
HEAD → C2 → [暂存区=C2] → [工作区=C2]
一切回到 C2 的状态,C3 的修改完全丢失
8.3 git revert — 撤销提交(安全方式)
git revert 创建一个新提交来反转指定提交的变更,不修改历史。
# 撤销指定提交
$ git revert abc1234
# 撤销多个提交
$ git revert abc1234 def5678
# 撤销提交范围
$ git revert abc1234..def5678
# 撤销合并提交(指定保留哪个父提交)
$ git revert -m 1 <merge-commit>
# 不自动提交(先审查)
$ git revert --no-commit abc1234
# 撤销 merge commit 的示例
$ git revert -m 1 HEAD # 撤销最后一次合并
revert vs reset 对比
| 特性 | git revert | git reset |
|---|---|---|
| 修改历史 | ❌ 创建新提交 | ✅ 重写历史 |
| 安全性 | 🟢 高 | 🔴 低(公共分支危险) |
| 适用场景 | 公共分支 | 个人分支 |
| 提交记录 | 保留撤销记录 | 丢失被撤销的提交 |
| 可追溯性 | 高 | 低 |
⚠️ 规则:已经推送到远程的提交使用
revert,未推送的可以使用reset。
8.4 git checkout / git restore — 恢复文件
Git 2.23 引入了 git restore 和 git switch 来替代 git checkout 的部分功能。
8.4.1 丢弃工作区修改
# 传统方式
$ git checkout -- file.txt
# 新方式(推荐)
$ git restore file.txt
# 恢复多个文件
$ git restore file1.txt file2.txt
# 恢复整个目录
$ git restore src/
# 恢复所有文件
$ git restore .
8.4.2 取消暂存
# 传统方式
$ git reset HEAD file.txt
# 新方式(推荐)
$ git restore --staged file.txt
# 取消暂存多个文件
$ git restore --staged file1.txt file2.txt
# 取消暂存所有文件
$ git restore --staged .
8.4.3 从特定提交恢复文件
# 从上一个提交恢复文件
$ git restore --source=HEAD~1 file.txt
# 从特定提交恢复
$ git restore --source=abc1234 file.txt
# 从其他分支恢复
$ git restore --source=feature file.txt
# 传统方式
$ git checkout abc1234 -- file.txt
$ git checkout feature -- file.txt
8.5 git clean — 清理未追踪文件
# 预览要删除的文件(干运行)
$ git clean -n
$ git clean --dry-run
# 删除未追踪文件
$ git clean -f
# 删除未追踪文件和目录
$ git clean -fd
# 包含被忽略的文件
$ git clean -fdx
# 交互式清理
$ git clean -i
| 参数 | 说明 |
|---|---|
-n | 预览模式,不实际删除 |
-f | 强制删除 |
-d | 包含目录 |
-x | 包含被 .gitignore 忽略的文件 |
-X | 只删除被忽略的文件 |
-i | 交互式选择 |
8.6 修改最近一次提交
# 修改提交信息
$ git commit --amend -m "New commit message"
# 追加文件到上次提交(不修改信息)
$ git add forgotten-file.txt
$ git commit --amend --no-edit
# 修改作者信息
$ git commit --amend --author="New Name <[email protected]>"
8.7 恢复丢失的提交
使用 reflog 恢复
# 1. 查看 reflog 找到丢失的提交
$ git reflog
abc1234 HEAD@{0}: reset: moving to HEAD~3
def5678 HEAD@{1}: commit: This commit was lost!
# 2. 恢复
$ git cherry-pick def5678
# 或
$ git reset --hard def5678
使用 fsck 恢复
# 查找悬空对象
$ git fsck --unreachable --no-reflogs
# 查看悬空提交
$ git show <dangling-commit-hash>
# 恢复
$ git branch recovered <dangling-commit-hash>
8.8 撤销操作速查表
| 场景 | 命令 |
|---|---|
| 丢弃工作区修改 | git restore <file> |
| 取消暂存 | git restore --staged <file> |
| 修改上次提交 | git commit --amend |
| 撤销本地提交(保留修改) | git reset --soft HEAD~1 |
| 撤销本地提交(丢弃修改) | git reset --hard HEAD~1 |
| 撤销已推送的提交 | git revert <commit> |
| 清理未追踪文件 | git clean -fd |
| 恢复误删文件 | git restore --source=HEAD <file> |
| 恢复误删分支 | git branch <name> <reflog-hash> |
| 恢复误 reset 的提交 | git reflog + git reset --hard HEAD@{n} |
业务场景
| 场景 | 推荐方案 |
|---|---|
| 改了不该改的文件 | git restore <file> |
| 提交了不该提交的文件 | git restore --staged <file> + 更新 .gitignore |
| 提交信息写错了 | git commit --amend |
| 提交了错误代码(未推送) | git reset --soft HEAD~1 |
| 需要撤销线上某个提交 | git revert <commit> |
| 本地实验失败想回退 | git reset --hard HEAD |
| 误删分支/提交 | git reflog 恢复 |
扩展阅读
- git-scm.com: Undoing Things
- git-scm.com: git-reset
- git-scm.com: git-revert
- git-scm.com: git-restore
- Oh Shit, Git!?! — 常见 Git 事故恢复