HEAD的重置即检出
HEAD可以理解为“头指针”,是当前工作区的“基础版本”,当执行提交时,HEAD指向的提交作为新提交的父提交。看看当前HEAD的指向。
1 | $ cat .git/HEAD |
可以看出HEAD指向了分支master,此时执行git branch
会看到当前处于master分支。
1 | $ git branch -v |
现在使用git checkout
命令检出该ID的父提交,看看会怎么样。
1 | $ git checkout 23995a2 |
翻译一下上面的输出结果:
1 | $ git checkout 23995a2 |
什么叫做“分离头指针”状态?查看一下此时的HEAD的内容就明白了。
1 | $ cat .git/HEAD |
原来“分离头指针”状态指的就是HEAD头指针指向了一个具体的提交ID,而不是一个引用(分支)。
查看最新提交的reflog也可以看到当针对提交执行git checkout
命令时,HEAD头指针就被更改了:由指向master分支变成了指向一个提交ID。
1 | $ git reflog -l |
注意上面的reflog是HEAD头指针的变迁记录,而非master分支。
查看一下HEAD和master对应的提交ID,会发现现在它们指向的不一样。
1 | $ git rev-parse HEAD master |
前一个是HEAD头指针的指向,后一个是master分支的指向。而且还可以看到执行git checkout
命令与执行git reset
命令不同,分支(master)的指向并没有改变,仍旧指向原有的提交ID。
现在版本库的HEAD是指向23995a提交的,再做一次提交,HEAD会如何变化?
- 先做一次修改:创建一个新文件detached-commit.txt,添加到暂存区中。
1 | $ touch detached-commit.txt |
- 看一下状态,会发现其中有“当前不处于任何分支”的字样,显然这是因为HEAD处于“分离头指针”模式。
1 | $ git status |
- 执行提交。在提交输出中也会出现
[detached HEAD...]
的标识,这也是对用户的警示。
1 | $ git commit -m "commit in detached HEAD mode" |
- 此时头指针指向了新的提交。
1 | $ cat .git/HEAD |
- 再查看一下日志会发现新的提交是建立在之前的提交基础上的。
1 | $ git log --graph --oneline |
记下新的提交ID(1fe77f7b9993cd79b050826df1c73715cc1d1e45),然后以master分支名作为参数执行git checkout
命令,会切换到master分支上。
- 切换到master分支上,再没有之前大段的文字警告。
1 | $ git checkout master |
- 因为HEAD头指针重新指向了分支,而不是处于“分离头指针模式”。
1 | $ cat .git/HEAD |
- 切换之后,之前本地建立的
detached-commit.txt
文件不见了。
1 | $ ls |
- 切换之后,刚才的提交日志也不见了。
1 | $ git log --graph --oneline |
刚才的提交还存在于版本库的对象库中吗?看看刚才记下的提交ID。
1 | $ git show 1fe77 |
可以看出这个提交现在仍在版本库中。由于这个提交没有被任何分支跟踪到,因此不能保证这个提交会永久存在。实际上当reflog中含有该提交的日志过期后,这个提交随时都会从版本库中彻底清除。
挽救分离头指针
在“分离头指针”模式下进行的测试提交除了使用ID(1fe77)访问之外,不能通过master分支或其他引用访问到。如果这个提交是master分支所需的,那么该如何处理?
如果使用git reset
,的确可以将master分支重置到该测试提交的1fe77
,但这就会丢掉master分支原先的提交23995a2
。使用合并操作git merge
可以实现两者的兼顾。
下面的操作会将提交1fe77
合并到master分支中来,具体操作如下:
- 确认当前处于master分支。
1 | $ git branch -v |
- 执行合并操作,将
1fe77
提交合并到当前分支。
1 | $ git merge 1fe77 |
- 工作区中多了一个
detached-commit.txt
文件。
深入了解git checkout
命令
git checkout
是Git常用的命令之一,同时也是很危险的命令,因为这条命令会重写工作区。检出命令的用法如下:
1 | 用法一: git checkout [-q] [<commit>] [--] <paths>... |
用法一和用法二的区别在于,用法一在命令中包含路径
第一种用法的
因此重置一般用于重置暂存区(除非使用–hard参数,否则不重置工作区),而检出命令主要是覆盖工作区(如果
用法一(包含了路径
用法二(不使用路径
用法二的最主要作用就是切换到分支。如果省略
用法三主要是创建和切换到新分支(refs/heads
命名空间下引用。