深入理解 git reset 的 soft、mixed、hard、keep 四种模式,掌握合并撤销的正确姿势,不再害怕误操作。

reset — Git 给你的后悔药

谁都会犯错:提交了不该提交的文件、合并错了分支、写完代码发现方向不对……

git reset 就是 Git 的”后悔药”,它能把你的仓库状态回退到任意历史提交。但不同模式的 reset 影响范围差别很大,搞错了可能丢代码!


一、先理解三个区域

在学 reset 之前,必须搞清 Git 的三个工作区域:

1
2
3
4
5
6
7
┌──────────┐    ┌──────────┐    ┌──────────┐
│ 仓库 │ │ 暂存区 │ │ 工作区 │
│ (Repo) │ → │ (Stage) │ → │ (Work) │
│ │ │ │ │ │
│ 提交历史 │ │ add 的文件 │ │ 你正在 │
│ │ │ │ │ 编辑的文件 │
└──────────┘ └──────────┘ └──────────┘
区域说明对应操作
仓库(Repository)已提交的历史记录git commit
暂存区(Staging)等待提交的修改git add
工作区(Working)你正在编辑的文件直接用编辑器修改
记住

git reset 本质上是移动 HEAD 指针(当前所在的提交),不同模式决定了指针移动后,暂存区和工作区怎么处理。


二、四种模式核心对比

模式命令HEAD暂存区工作区使用场景
softgit reset --soft回退保留保留只想修改提交信息
mixedgit reset --mixed回退清除保留撤销 add(默认模式)
hardgit reset --hard回退清除清除完全放弃,重头再来
keepgit reset --keep回退尝试保留尝试保留安全回退,有冲突则中止

--hard 是唯一会丢失工作区修改的模式,执行前请三思!


三、软重置 — soft

1
git reset --soft HEAD~1

效果:只把 HEAD 指针回退一个提交,暂存区和工作区完全不动

典型场景:修改上一次提交的信息

1
2
3
4
5
6
7
8
# 提交后发现信息写错了
git commit -m "feat: 添加用户登陆功能""登陆"写错了

# 软重置
git reset --soft HEAD~1

# 重新提交
git commit -m "feat: 添加用户登录功能" ← 改正了

更简单的方式

如果只是想改提交信息,不改代码,用 --amend 更方便:

1
git commit --amend -m "feat: 添加用户登录功能"

软重置的状态变化

1
2
3
4
5
6
7
8
9
10
11
# 重置前
仓库: A → B → C (HEAD)
暂存区: [C 的修改]
工作区: [C 的代码]

# 软重置到 B
git reset --soft HEAD~1

仓库: A → B (HEAD)
暂存区: [C 的修改] ← 还在!
工作区: [C 的代码] ← 还在!

你的代码还在暂存区,可以重新 commit。


四、混合重置 — mixed(默认)

1
2
3
git reset --mixed HEAD~1
# 或者简写为
git reset HEAD~1

效果HEAD 回退,暂存区清除,工作区保留

典型场景:撤销 git add

1
2
3
4
5
6
7
8
9
10
# 不小心 add 了不该提交的文件
git add .

# 发现多了个 .env 文件也被 add 了
# 用 mixed 重置(或直接 git reset)
git reset

# 这时 .env 从暂存区移出,但文件还在工作区
# 然后重新 add 需要的文件
git add src/

mixed 的状态变化

1
2
3
4
5
6
7
8
9
10
11
# 重置前
仓库: A → B → C (HEAD)
暂存区: [C 的修改]
工作区: [C 的代码]

# 混合重置到 B
git reset HEAD~1

仓库: A → B (HEAD)
暂存区: [] ← 清空了!
工作区: [C 的代码] ← 还在!

你的代码还在,只是从暂存区移出来了,需要重新 git add

最常用

mixed 是最常用的模式(也是默认模式),适用于大部分”想撤销但保留代码”的场景。


五、硬重置 — hard

1
git reset --hard HEAD~1

效果HEAD 回退,暂存区和工作区全部清除,回到目标提交的状态。

典型场景:彻底放弃当前修改

1
2
3
4
5
# 代码改得一塌糊涂,想全部丢弃
git reset --hard HEAD~1

# 或者回退到某个特定提交
git reset --hard abc1234

hard 的状态变化

1
2
3
4
5
6
7
8
9
10
11
# 重置前
仓库: A → B → C (HEAD)
暂存区: [C 的修改]
工作区: [C 的代码]

# 硬重置到 B
git reset --hard HEAD~1

仓库: A → B (HEAD)
暂存区: [] ← 清空了!
工作区: [B 的代码] ← C 的代码消失了!

hard 是危险操作

  • 工作区未提交的修改会被直接覆盖
  • 被 reset 掉的提交如果没 push,很难找回
  • 执行前务必确认你真的要丢弃这些代码

安全措施:执行前可以先 git stash 备份:

1
2
3
git stash           # 先备份
git reset --hard HEAD~1
git stash pop # 需要时恢复

六、保留重置 — keep

1
git reset --keep HEAD~1

效果:类似 mixed,但会检查冲突。如果工作区的修改和目标提交有冲突,会直接中止,不会丢代码。

典型场景:安全回退

1
2
# 想回退一个提交,但不想丢失工作区的修改
git reset --keep HEAD~1

keep vs mixed 的区别

场景mixedkeep
工作区有修改,目标提交没冲突成功成功
工作区有修改,目标提交有冲突覆盖你的修改报错中止,保留修改
推荐

如果你不确定用哪个,--keep--mixed 更安全。它会保护你的工作区修改不被意外覆盖。


七、合并后撤销

这是新手最常遇到的问题:合并了错误的分支,怎么撤销?

首先判断:合并后有没有推送到远程?

1
2
3
4
5
6
7
┌─────────────────────┬─────────────────────────────┐
│ 还没 push │ 已经 push │
├─────────────────────┼─────────────────────────────┤
│ 安全,用 reset 撤销 │ 需要 revert(生成新提交) │
│ git reset --hard │ git revert -m 1 <commit> │
│ 本地操作,不影响别人 │ 安全撤销,不会改写历史 │
└─────────────────────┴─────────────────────────────┘

情况 1:还没推送 — 用 reset

1
2
3
4
5
6
7
8
9
10
11
12
13
# 查看当前提交历史,找到合并前的提交
git log --oneline

# 输出示例:
# a1b2c3d (HEAD) Merge branch 'feature/login' ← 合并提交
# e4f5g6h fix: 修复 bug
# i7j8k9l feat: 之前的功能

# 回退到合并前的状态
git reset --hard HEAD~1

# 或者指定合并前的提交
git reset --hard e4f5g6h

情况 2:已经推送 — 用 revert

1
2
3
4
5
# -m 1 表示保留主分支的内容,撤销被合入的分支的改动
git revert -m 1 <merge_commit_hash>

# 然后推送
git push
为什么已推送的不能用

reset? 因为 reset 会改写历史。如果别人已经 pull 了你的合并提交,你 reset 后 force push,别人的分支就会和远程不一致,造成混乱。revert 不会改写历史,而是生成一个新提交来”抵消”合并的改动。


八、IDEA 中的对应操作

IntelliJ IDEA / WebStorm 的 Git 面板中,右键提交记录可以看到以下菜单:

IDEA 菜单项等价 Git 命令说明
撤销提交(Undo Commit)git reset --soft HEAD~1保留修改到暂存区,可以重新提交
删除提交(Delete Commit)git reset --keep HEAD~1删除提交,保留不冲突的文件修改
回退到此提交git reset --hard <commit>危险!完全回退到指定提交
压缩提交git rebase -i(squash)合并多个提交(属于变基操作)
修改提交信息git rebase -i(reword)修改提交描述
从这里执行交互式变基git rebase -i <commit>完整变基操作

小技巧

  • 撤销提交最常用:写完代码提交后发现信息不对,点”撤销提交”就能修改后重新提交
  • 删除提交也很实用:想删掉一个提交但保留代码改动

九、找回丢失的提交:reflog

如果你不小心用 git reset --hard 丢掉了提交,别慌!Git 有一个”后悔药的后悔药”——reflog

什么是 reflog?

reflog 记录了 HEAD 指针的每一次移动,包括那些被 reset 掉的提交:

1
git reflog

输出示例:

1
2
3
a1b2c3d (HEAD -> main) HEAD@{0}: reset: moving to HEAD~1
e4f5g6h HEAD@{1}: commit: feat: 这个提交被 reset 掉了
i7j8k9l HEAD@{2}: commit: feat: 之前的功能

找回被删除的提交

1
2
3
4
5
6
7
8
9
# 1. 在 reflog 中找到被删除提交的 hash
git reflog

# 2. 硬重置到那个提交
git reset --hard e4f5g6h

# 或者创建一个新分支指向它
git branch rescue e4f5g6h
git checkout rescue

reflog 的有效期:默认保留 90 天的记录。超过 90 天的 reflog 条目会被 Git 自动清理。所以发现误操作要尽快恢复。


总结:reset 选择指南

1
2
3
4
5
6
7
8
9
10
11
12
13
14
想修改上一次提交的信息?
└── git reset --soft HEAD~1 或 git commit --amend

想撤销 git add,保留代码?
└── git reset(默认 mixed)

想完全放弃当前修改?
└── git reset --hard HEAD~1 ⚠️ 危险

想安全回退,有冲突就中止?
└── git reset --keep HEAD~1 ✅ 推荐

不小心 hard reset 丢了代码?
└── git reflog → git reset --hard <hash>

速查表

命令HEAD暂存区工作区场景
git reset --soft HEAD~1回退保留保留改提交信息
git reset HEAD~1回退清除保留撤销 add
git reset --hard HEAD~1回退清除清除完全放弃
git reset --keep HEAD~1回退安全保留安全保留安全回退
git reflog找回丢失提交