您现在的位置是:首页 >技术杂谈 >Git HEAD及detached head网站首页技术杂谈

Git HEAD及detached head

Penguinbupt 2023-06-23 12:00:03
简介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

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。