撤销与回退
Git 提供了多层级的撤销能力——从撤销未暂存的修改到回退整个提交链。
撤销层级概览
场景 命令
──────────────────────────────────────────────────
工作区文件修改(未 add) git restore <file>
暂存区文件(已 add 未 commit) git restore --staged <file>
修改最近一次提交 git commit --amend
回退提交(保留修改) git reset --soft
回退提交(保留修改在工作区) git reset --mixed(默认)
回退提交(彻底丢弃) git reset --hard
公开分支撤销 git revert
找回"丢失"的提交 git reflogreset(重置)
git reset 移动 HEAD 和当前分支指针,有三种模式:
git reset [--soft | --mixed | --hard] <ref>三种模式
原始状态:A ← B ← C ← D (HEAD)
git reset --soft B:
工作区:不修改(D 的修改留在暂存区)
暂存区:包含 D 的修改
HEAD: 移到 B
git reset --mixed B: (默认)
工作区:不修改(D 的修改留在工作区,未暂存)
暂存区:清空
HEAD: 移到 B
git reset --hard B:
工作区:清空(D 的修改彻底丢失⚠️)
暂存区:清空
HEAD: 移到 B| 模式 | HEAD | 暂存区 | 工作目录 | 安全程度 |
|---|---|---|---|---|
--soft |
移动 | 保留 | 保留 | ✅ 安全 |
--mixed |
移动 | 重置 | 保留 | ✅ 较安全 |
--hard |
移动 | 重置 | 重置 | 🚨 危险 |
常用 reset 场景
# 撤销最近一次提交,修改回到暂存区(可以重新 commit)
git reset --soft HEAD~1
# 撤销最近一次提交,修改回到工作区(需要重新 add)
git reset HEAD~1 # 等价于 --mixed
# 撤销最近一次提交及其所有修改(危险⚠️)
git reset --hard HEAD~1
# 取消暂存(unstage)
git reset HEAD <file>
# 等价于新语法:
git restore --staged <file>
# 撤销到某个远程状态
git reset --hard origin/mainrevert(反转提交)
git revert 创建一个新的提交来撤销指定提交的修改,不破坏历史。
git revert <hash> # 撤销单个提交
git revert <hash1>..<hash2> # 撤销范围(需要逐个处理)
git revert -m 1 <merge_hash> # 撤销合并提交(1 = 保留第一个 parent)reset vs revert
reset:
A ← B ← C ← D → A ← B
(C、D 消失,就像从未存在过)
revert:
A ← B ← C ← D ← D' → A ← B ← C ← D ← D'
(D' 是反转 D 的新提交,但历史保留)| 场景 | 用 reset | 用 revert |
|---|---|---|
| 本地的、未推送的提交 | ✅ | 也可以 |
| 已推送到共享仓库的提交 | ❌ 不要用 | ✅ |
| 想彻底删除某提交 | ✅ | ❌ |
| 想保留撤销记录 | ❌ | ✅ |
💡 简单规则:已经 push 过的 →
revert;还没 push 的 →reset。
commit –amend(修改提交)
# 修改最近一次提交的信息
git commit --amend -m "new message"
# 补充遗漏的文件(不改信息)
git add forgotten_file
git commit --amend --no-edit
# 修改作者
git commit --amend --author="Name <email>"🚨 陷阱:
amend会改写 commit hash。如果已经 push 过,amend 后需要--force-with-lease才能推送——和 rebase 一样只能在个人分支上使用。
reflog(引用日志)
🔬 核心原理:reflog 记录 HEAD 和分支指针的每一次变更。即使你用 reset --hard 或 rebase “丢失"了提交,reflog 仍然保留它们的 hash。
# 查看 HEAD 移动记录
git reflog
# 输出示例:
# a1b2c3d HEAD@{0}: commit: Add login
# d4e5f6g HEAD@{1}: reset: moving to HEAD~1
# g7h8i9j HEAD@{2}: commit: Bad commit (这个被 reset 删掉了)
# j0k1l2m HEAD@{3}: commit: Initial commit
# 找回"丢失"的提交
git checkout g7h8i9j # 检出到那个提交
# 或
git cherry-pick g7h8i9j # 摘取那个提交
# 或
git branch recovered g7h8i9j # 基于该提交创建新分支
# 查看特定分支的引用日志
git reflog show main
# 恢复到 reflog 中的某个状态
git reset --hard HEAD@{2}💡 reflog 是 Git 的"后悔药”。基本上只要提交过,就能通过 reflog 找回。reflog 默认保留 90 天(不可达的对象 30 天后会被 gc 清理)。
reflog 恢复实战
# 场景:git reset --hard 误操作,删掉了重要提交
# 1. 查看 reflog
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~3
# d4e5f6g HEAD@{1}: commit: Important work ← 这个丢了
# 2. 恢复
git reset --hard HEAD@{1}
# 或
git cherry-pick d4e5f6gcheckout / restore(文件级撤销)
# 丢弃工作区对某文件的修改(用暂存区覆盖)
git restore <file>
git checkout -- <file> # 旧语法
# 丢弃所有工作区修改
git restore .
# 将文件恢复到某次提交时的状态
git restore --source=<hash> <file>
# 从暂存区移除(取消 add)
git restore --staged <file>
# 从暂存区移除所有
git restore --staged .撤销场景速查
| 场景 | 命令 |
|---|---|
| 修改工作区文件,想还原 | git restore <file> |
| 已经 add 了,想取消暂存 | git restore --staged <file> |
| 已 commit,想修改信息 | git commit --amend |
| 已 commit,想补充文件 | git commit --amend --no-edit |
| 已 commit(未 push),想撤销 | git reset HEAD~1 |
| 已 commit + push,想撤销 | git revert <hash> |
| 已 commit + push,想彻底抹掉敏感信息 | git reset --hard + git push --force-with-lease(仅个人分支) |
| reset 误操作,想找回 | git reflog → git reset --hard HEAD@{n} |
| 删除的 stash 想找回 | git fsck --lost-found |