很多人在工作中没少被 git reset 坑过。前几天有个同事把开发分支上连着好几个 commit 一次性 --hard 回退掉,然后发现有一个 commit 里是他写了一下午的接口文档,整个人当场傻眼。虽然最后靠 reflog 把问题解决了,但那个下午的惊吓,加上周围一圈人围着命令行敲命令的场景,让我一直想写一篇把 Git Reset 四种模式真正讲透的文章。
Git Reset 表面上看只是"回退版本",但它背后涉及的是 Git 最核心的存储区模型,而且 Soft、Mixed、Hard、Keep 这四种模式对三个存储区的处理方式完全不同,选错了轻则白干半天,重则代码直接人间蒸发。这篇文章我不会只给你背命令,而是把每种模式"为什么是这样"的机制讲明白,再用一个真实可复现的实验仓库,把四种模式跑一遍给你看,最后聊一聊日常开发中最该用哪种、最不该用哪种。
1. 先从一次"事故"讲清楚 Reset 究竟重置了什么
1.1 三个存储区的分工与联动
要理解 git reset 的四种模式,首要任务不是背参数,而是把 Git 的"三棵树"模型装进脑子里。所谓三棵树,其实就是三个存储区:
- HEAD:指向当前分支最近一次提交的指针,它代表的是"我最后一次提交时的项目快照"。
- Index(暂存区):也就是
git add之后内容所在的位置,它是"我准备提交的下一个快照"。 - Working Directory(工作区):你编辑器里正在操作的实际文件,它是"我当前正在修改的快照"。
我见过不少开发者对这三个区的理解停留在"暂存区就是中转站"的层面。实际上,Git 的日常操作本质就是在这三个区之间搬运内容:git add 把工作区内容复制到 Index,git commit 把 Index 内容固化成一个新的 commit 并让 HEAD 指针移动,git checkout 则是把 HEAD 中的内容往工作区方向铺开。
这三个区之间的关系,很像你在写一篇论文。
- 工作区是你的草稿纸,笔在上面随手写写画画;
- 暂存区是你挑选后的素材夹,你挑出一部分段落准备正式入稿;
- HEAD 是你已经排版印刷好的最近一版。
git reset 真正做的事情,就是以某个目标提交为基准,有选择地把这三个区"重置"到那个状态。选哪种模式,本质上就是在决定你愿意动到哪几棵树。
1.2 Reset 的底层动作:移动 HEAD、同步暂存区、同步工作区
很多人以为 git reset 是"删除提交"。这个理解在所有模式下都不够准确,尤其是对 --soft 模式来说,会产生严重的误导。准确地说,git reset 只是把 HEAD 指针移动到你指定的提交上,之前的提交对象本身还安安静静地躺在对象数据库里,只是没有分支引用它了而已。
整个 reset 过程可以拆解成三个独立动作:
- 移动 HEAD 指针到目标提交(四种模式都会做)。
- 决定是否用目标提交的内容覆盖暂存区 Index。
- 决定是否用目标提交的内容覆盖工作区文件。
不同模式的区别,就是这三个动作的组合差异。我画过一张比较直观的表格,先放在这里,后面我会逐个拆解:
| 模式 | 移动 HEAD | 重置 Index(暂存区) | 重置 Working Directory(工作区) | 核心安全级别 |
|---|---|---|---|---|
--soft |
是 | 否 | 否 | 最安全,改动全部保留 |
--mixed(默认) |
是 | 是 | 否 | 安全,已提交内容退回工作区 |
--hard |
是 | 是 | 是 | 危险,未提交的改动可能会被清掉 |
--keep |
是 | 是 | 条件性更新 | 相对安全,有冲突会直接中止 |
看到这个表的时候,你应该已经明白:reset 的四种模式并不是从"完全保留"到"彻底删除"的平滑过渡,而是一组对不同区域的选择性操作。Mixed 和 Keep 乍看只差一栏,但机制上的差异足以决定一个命令是"帮你保留现场"还是"直接把现场推平"。
1.3 一个快速判断模式的口诀
在进入细节前,我先给出一个我平时用来快速记忆的口诀,后面所有内容都围绕它展开:
Soft 只动指针,Mixed 动指针和暂存区,Hard 三区全动,Keep 相当于一个带冲突保护的 Hard。
这句话不严谨,但非常适合新手在命令行前面快速做判断。等你把机制彻底理解了,自然会发现 Keep 和 Hard 的关系远比"带冲突保护的 Hard"要微妙,那些细节我会在第四部分重点讲。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 三种常见模式的机制拆解:Soft、Mixed、Hard
2.1 Soft:只搬 HEAD,变更原封不动送回暂存区
git reset --soft <commit> 是四种模式里动作最小的一种。它只移动 HEAD 指针,暂存区和工作区完全不做任何修改。举个非常日常的场景:你提交了一个 commit,写完 commit message 之后发现里面还包含了一个调试用的临时文件,你想把这个 commit 撤销掉,但保留所有文件变更,再重新精心挑选后提交。
此时 git reset --soft HEAD~1 执行完之后,你看到的状态是:刚才那次提交中涉及的所有文件变更,全都原封不动地出现在暂存区里,处于"已
