从指针行为理解Git中的reset操作
节点状态
先确立一个状态概念,也就是工作区、暂存区、工作树上的节点N看作一组状态、

当commit之后,状态得以固定:
-
工作区内容固定为为commit时的内容,如第二次提交时,工作区内容固定为
bash第二次提交 -
暂存区被"清空"(简单理解为被提交到节点上去了,自然就不需要暂存了。)
-
HEAD指向被创建的节点
至此,HEAD以及HEAD之前的节点的状态都可以被确定,之后对工作区和暂存区的更改,我们可以将其看作是对一个未被创建的虚拟节点------待存节点状态的操作

假设第N个(N<=3)固定状态对应的内容:
- 工作区:第N次提交
- 暂存区:空
- 节点:第N次提交
此时,我们对代存节点的状态(工作区和暂存区)进行修改(在1.txt中输入文字):
- 修改工作区内容:
第4次提交 - 提交到暂存区:
git add .,此时暂存区内容也为第4次提交 - 再次修改工作区内容:
第5次提交
在git插件中我们可以看到如下情况:

于是现在待存节点的状态可以看作:

将这个状态当作我们实验的初始状态,然后我们就可以来看看使用三种不同的reset方法时到底发生了什么
--hard
bash
git reset --hard C2
//c725740f0a4... 第二次提交id,简化为C2
此时可以发现:
- 工作区:第二次提交 (无显示状态)
- 暂存区:没有内容
- HEAD指向C2
而这个恰好是C2的状态!
先不着急得出结论,不妨把剩下的运行结果全部看完
--soft
回到我们的初始状态,然后
bash
git reset --soft C2
神奇的事情发生了!

工作区与暂存区的内容不仅没有更改,甚至连状态都没有改变,与初始状态一模一样!
- 工作区:第五次提交 (M,已修改)
- 暂存区:第四次提交 (M,已修改)
- HEAD指向C2
这样看来,虽然节点对应的状态是唯一 的,但是工作区、暂存区、HEAD的变化并不是统一的
我们可以为其分别设置三个虚拟指针,来探究到底发生了什么
- 工作区:
HEAD_Work - 暂存区:
HEAD_Index - 节点:自然是
HEAD
看到这里或许大家内心都已经有了猜想,我们不妨再来看看最后一个操作验证一下
--mixed
可以确定的是,reset后HEAD是必然改变的(有点像废话。。),有区别的是:
- 工作区:
HEAD_Work - 暂存区:
HEAD_Index
这两个指针是如何变化的?
从上述内容的表现来看,--hard和--soft分别代表两个极端,一个是全都跟着HEAD变,一个是都不变,那么很容易猜想到--mixed就是改变其中一个,那么这个猜想是否正确?改变的又是哪一个呢?为什么
猜想验证
回到初始状态
bash
git reset --mixed C2
可以看到:

- 工作区:第五次提交 (M,已修改)
- 暂存区:空
- HEAD指向C2
猜想得到验证!
指针变化
- 工作区:
HEAD_Work不变 - 暂存区:
HEAD_Index跟着HEAD变 - 节点:指向C2
为什么是改变暂存区的指针而不是工作区的指针?(猜想)
想象这样一个场景:
你辛辛苦苦写好了一段代码放在工作区,突然发现当前的版本不兼容,而你知道你的代码经过微调后,C2是兼容的,于是你想要回退到C2,但是代码已经写好了,如果用--hard,工作区指针也指向C2,你写的代码将被清除,白忙活一场;如果用--soft,暂存区的指针不改变,虽然不影响后续,但其实没啥必要,恰好,--mixed就实现了这个功能:回退版本,不改变你的代码,清空暂存区,perfect!
这也是为什么--mixed成为了git reset的默认方式吧
当然如果不嫌麻烦,假设我们将--mixed设置为工作区的指针跟着HEAD跑,暂存区的不变,那么我们必然要多走一步,也就是将暂存区的代码git restore --stage 1.txt回退到工作区,再进行操作,想想都觉得麻烦~
总结
| 模式 | git reset --soft | git reset --mixed (默认) | git reset --hard |
|---|---|---|---|
| HEAD 指针 | 移动 | 移动 | 移动 |
| 暂存区 (Index) | 不动 | 重置到 HEAD | 重置到 HEAD |
| 工作区 (Working Dir) | 不动 | 不动 | 重置到 HEAD |
以上只是我这个刚开始学习计算机知识小白的一些拙见,作为学习记录,或许会有人在刚学git的时候有些类似的疑惑,欢迎大家批评!