4、git reset 详解(上):三种模式原理与实战
git reset 这个命令,我敢说十个人里有八个都用不对。不是因为它难,而是因为它的三种模式——--soft、--mixed、--hard——太容易混淆了。
我自己刚入行那会儿,就吃过 --hard 的亏。当时在项目里一顿操作,代码全丢了,差点没被组长骂死。后来我才明白,git reset 本质上就干一件事:移动 HEAD 指针。三种模式的区别,只在于它顺带帮你处理了「暂存区」和「工作目录」里的内容。
说白了,你选哪种模式,就是告诉 Git:「指针挪过去之后,我工作区和暂存区里的东西,你要不要帮我清一清?」
4.1 --soft 模式:只动指针,不动代码
--soft 是最温柔的一种模式。它只做一件事:把 HEAD 指针挪到你指定的 commit 上。至于你的工作目录和暂存区,它碰都不碰。
什么意思呢?就是你回退之后,所有「新提交」里的改动,全部保留在暂存区里。你随时可以重新提交。
我个人习惯在什么场景下用 --soft?
- 提交写错了,想重新整理 commit 消息——比如你刚提交了一个 commit,发现消息写错了,或者想拆分成多个 commit。
- 合并了几个小 commit——你想把最近几个零散的提交合并成一个完整的提交。
举个例子:
# 当前在最新 commit,想回退到上一个版本
git reset --soft HEAD~1
# 此时 HEAD 指向了上一个 commit
# 但刚才那个 commit 里的所有改动,都还在暂存区里
# 你可以直接 git commit -m "新的提交消息"
嗯,这里要注意:--soft 不会动你的工作目录。你写了一半的代码,还在。你改了一半的文件,也还在。它只是把「提交」这个动作撤销了,但内容给你留着。
git commit --amend 更方便。但如果你想拆 commit 或者合并 commit,--soft 才是正解。
4.2 --mixed 模式:默认行为,回到未暂存状态
--mixed 是 git reset 的默认模式。也就是说,你直接敲 git reset 不带参数,用的就是 --mixed。
它比 --soft 多做了一步:把暂存区也清空了。但工作目录里的文件改动,它不动。
你想想看,这有什么用?
- 你 add 错了文件——比如你本来只想提交 A 文件,结果不小心把 B 文件也 add 进去了。用
--mixed回退,暂存区清空,但工作目录里的改动还在。 - 你想重新组织提交——把暂存区清空,然后重新
git add你真正想提交的文件。
我在项目中遇到过这种情况:有一次我改了一堆文件,然后一股脑全 git add 了。提交之后才发现,有些文件根本不应该进这个 commit。怎么办?
# 回退到上一个 commit,但保留工作目录的改动
git reset --mixed HEAD~1
# 此时暂存区空了,但工作目录里所有改动都在
# 你可以重新 git add 你真正想提交的文件
git add src/important.js
git commit -m "只提交重要文件"
说白了,--mixed 就是给你一次「重新组织暂存区」的机会。它比 --soft 更彻底,但又比 --hard 温柔。
--mixed 和 --soft 差不多,结果有一次在团队项目里用了 --mixed,把同事已经 add 好的暂存区给清空了。虽然文件没丢,但同事一脸懵逼。所以,在共享分支上慎用 git reset,尤其是 --mixed 和 --hard。
4.3 --hard 模式:最危险,也最干脆
--hard 是三种模式里最暴力的。它不光移动 HEAD 指针,清空暂存区,还会直接修改你的工作目录。
什么意思?就是你回退之后,工作目录里的文件会变成目标 commit 时的样子。所有未提交的改动,全部丢失。嗯,你没看错,全部丢失。
我刚开始用 Git 时,就犯过这个错。当时在本地改了一堆代码,还没提交,想试试 git reset --hard HEAD~1 是什么效果。结果你猜怎么着?代码全没了。那叫一个欲哭无泪。
所以,--hard 只适合以下场景:
- 你确定要彻底放弃当前的所有改动——比如你改了一堆乱七八糟的东西,想回到一个干净的状态。
- 你只是想看看历史版本的样子——看完之后再用
git reset --hard ORIG_HEAD回来。 - 你在清理本地分支——比如你想把本地分支重置到和远程一致。
# 彻底回退到上一个 commit,放弃所有未提交的改动
git reset --hard HEAD~1
# 注意:这个操作不可逆!
# 除非你记得之前的 commit hash,否则找不回来
git reset --hard 回退后再 push。这会导致远程仓库的历史被重写,其他协作者会崩溃的。正确的做法是用 git revert。
4.4 三种模式对比:一张表说清楚
为了方便你记忆,我整理了一张对比表。你可以把它收藏起来,用的时候翻出来看看。
| 模式 | HEAD 指针 | 暂存区 | 工作目录 | 典型场景 |
|---|---|---|---|---|
--soft |
移动 | 不变 | 不变 | 修改 commit 消息、合并 commit |
--mixed |
移动 | 清空 | 不变 | 重新组织暂存区、撤销 add |
--hard |
移动 | 清空 | 覆盖 | 彻底放弃改动、重置到干净状态 |
你发现没有?这三种模式其实是一个「递进」的关系:
--soft最轻,只动指针。--mixed中等,动指针 + 清暂存区。--hard最重,动指针 + 清暂存区 + 改工作目录。
我个人建议:除非你 100% 确定要放弃所有改动,否则永远不要用 --hard。先用 --mixed 试试,看看效果,不满意再换 --hard 也不迟。
4.5 核心逻辑流程图
下面这张图,我用 SVG 画了一下三种模式的核心逻辑。你可以把它当成一个「决策树」来用:
这张图其实已经把三种模式的核心区别讲清楚了。你从顶部开始,先决定用哪种模式,然后看它会影响哪些区域。记住:越往右,破坏性越大。
git status 看一眼。如果显示「working tree clean」,那用 --hard 就相对安全。否则,三思而后行。
好了,这一章我们聊了 git reset 的三种模式。下一章我会继续讲 git reset 的高级用法,包括如何用 --keep 模式、如何配合 ORIG_HEAD 做安全回退,以及一些实战中的骚操作。到时候见。