Git HEAD及detached head
背景:最近在使用git checkout重置HEAD指向,偶尔会出现Detached HEAD提示,于是想探究一下具体的原理及过程,遂写下了这篇文章。一般checkout用于切换分支和检出历史的某个节点,或恢复工作区的文件,这三个功能。
一、定义
HEAD是一个文件,它记录的是你当前所在的分支,各个分支在引用自己的commitID,整体如下所示:
这图中有3个comit节点,HEAD指向master,master指向 节点 c
如何查看当前的HEAD文件的内容?
通过查看 .git目录下的HEAD文件即可。
# 在master分支上
cat .git/HEAD
ref: refs/heads/master
# 切换分支
git checkout dev
# 在dev分支上
cat .git/HEAD
ref: refs/heads/dev
二、提交
当在master上进行git commit 操作,将会新增节点d,此时在master分支上,如下图所示:
注意:
当一个新的提交节点d被创建,它的父节点是节点c,然后master分支将引用这个新的节点d,然而HEAD一直指向的是master分支,所以HEAD也指向这个新的节点d。
也就是说 master和HEAD是联动的,没有分离开来,HEAD一直指向的只是master,而不是具体commitID,这一点非常重要。
当什么时候会遇到 所说的 “detached head”状态呢?
# 检出 b 节点的信息,这里的b代表b节点,具体信息为 某个commitID
git checkout <commitID>
此时发生了 HEAD直接指向了 b 节点,而不是 HEAD指向某个分支。这种情况与HEAD的定义是不一样的,正确的是HEAD只能是指向某个分支而已,而不能指向某个节点,所以导致 detached head状态。
注意: git reset HEAD^ 或 git reset commitID 不会导致 “detached head”状态,
有意思的是 git checkout 改动的只是HEAD指向,不改动master或其他分支的指向,此时将HEAD直接指向一个新的具体的commitID节点。
而 git reset 是改动的只是master的指向,改动后master指向一个新的具体的commitID节点,而HEAD指向master,也就相应的自动进行了移动。
HEAD --->>> master --->>> commitID
这里多说一句,如何master如何指向的最新的commitID呢?
# 查找refs中的文件
$ find .git/refs -type f
.git/refs/heads/master
.git/refs/heads/foo
# 查看文件master的内容
$ cat .git/refs/heads/master
cf8504c75e1c6fbb95035fa6697b733e3fbdd961
输出的是 master最新的节点 c
三、修复detached head
假设当目前的HEAD指向b节点,并继续生成了 e 节点和 f 节点,如下图所示:
我也实地的在电脑上模拟了一下,git log --pretty=oneline信息如下:
先在master上依次提交了3个节点,分别为a、b、c
然后将HEAD 指向 b节点,命令如下:
# 检出 b 节点信息
git checkout 46b53066
提示信息如下图所示(detached head状态):
当HEAD指向b节点后,再次新增两个节点 e 和 f ,在分离状态下是可以进行任何git操作的,如下图所示:
重点来了,目前只有HEAD指向的是 f 节点,其他没有任何分支指向这个 f 节点,当 HEAD切换到其他节点或切换到某个分支后,这些 e 和 f 节点将会被git垃圾回收进程删除。
如果你不想删除这些 e 和 f 节点,可以执行这个即可:
# 将创建foo分支,将HEAD指向 foo 分支, foo分支指向 f 节点
git checkout -b foo
git checkout -b foo 这样将 e 和 f 保存了下来,并且 修复了 detached head 问题。
我们看一下git log信息,如下图所示:
git log --graph --all --oneline // 图形化显示 log,所有分支,一行显示
master分支和foo分支 在 b 节点进行了分叉,也可以求 共同节点,类似于 求两个单链表的第一个公共节点,命令如下:
git merge-base master foo // 求 master分支和foo分支的第一个公共节点
46b530663f45b17b0ea7645ed69ee4e0ad76ec43 得出 b 节点的 hash值
总结:
1. 所谓的分离HEAD状态,其实就是HEAD不指向某个分支,而是某个具体节点。
2. 修复这种状态,在新建的节点f上新建分支foo就能解决这种状态,让HEAD重新指向分支
也可以 根据你的业务需求,例如: 如果e 和 f 节点没有作用和意义,也无需手动删除,git垃圾回收进程会自动删除,你直接使用 git checkout master 将HEAD指向master分支执行后续的操作即可。
参考资料:
1. detached_head