哈啰。准备阅读本文的小伙伴。如果你在使用 Git 的过程中遇到“detatched HEAD”字样,发现代码不能正常向远程仓库推送,而且你又不知道该怎么办时,那么你应该仔细阅读本文后面的内容,相信会给你一个满意的解决方案😄 。
在进入正题解释“什么是Detatched HEAD?”之前,让我们先来回顾一下 Git 的基础知识“什么是 Git 的 ref 和 head?”。这将有利于后续我们深刻认识“什么是Detatched HEAD?”。
commit ID:Git 仓库的记录是由一次次的 Commit 构成的,每次 commit 都由一个 commit ID 唯一标识。这种唯一标识是一个长度 40 位的哈希值(例如:f5946c1e5d45d811e40ac7d2bb862e4911fcb1a6)。显然,这种标识是为计算机服务的,不适合供人类阅读和记忆。
ref:Git 的 ref 是适合人类阅读和记忆的,指向某 commit ID 的指针。
注:Git 的 ref 存储在项目本地仓库中的
.git/refs/
目录下,文件内容是 ref 所指向的 commit ID。
head:Git 的 head 也是一种 ref,并且它们就是代表我们本地分支(例如:master、dev等)的 ref。更重要的是,指代分支的 ref 命名为 head 也有它的内涵,即 head 永远指向它所指代分支的最“头部”的提交(即最新的一次提交)。换句话说,例如分支 master
,master
这个名字是不变的,但它指向的 commit ID 会随着你工作过程中的提交而改变,永远指向你最新一次的提交。
注:Git 的 head 存储在
.git/refs/heads
目录下,内容是其所代表分支的最头部(最新的一次提交)提交的 commit ID。
注意前面说的 Git 的 head(注意是小写的 head)指代的是分支,而且指向其所指代分支的头部提交,即:.git/refs/heads/master
即指向 master
分支的头部提交;.git/refs/heads/dev
即指向dev
分支的头部提交。
Git 的 HEAD(注意是大写的HEAD),在某 Git 仓库范围内只有一个,代表你当前所处的工作位置,即你的后续 commit 将从 HEAD 这里继续。
注:Git 的 head 可以有多个,所以它存储在 .git/refs/heads 目录中;Git 的 HEAD 只有一个,所以它存储在 .git/HEAD 文件中。
通常情况下,HEAD 是指向分支的,而分支又指向分支中的头部提交(最新一次提交),所以 HEAD 通常也是指向你当前所处分支的头部提交。
注:.git/HEAD 中并没有直接写某分支的头部提交的 commit ID,而是间接指向 heads 中的分支名。这种存储方式,实际是让Git知道,当前 HEAD 指向的是某一个分支。
前面讲到过,Git HEAD 通常指向的是你当前正在工作的分支,也即指向你当前正在工作分支的头部提交(最新一次的提交),这也意味着,你可继续工作(提交),即你后续的提交会在当前分支上继续下去。
可如果 Git HEAD 不在指向某分支,而是指向某次具体的提交(通常是历史中的某个位置),即处于 Detatched 状态,这意味着你后续的工作(提交)不会发生在当前分支上,即不会对当前分支产生影响。
当你 checkout 的不是一个分支时,即 HEAD 不再指向分支,而是指向历史中的一个位置时,即处于 Detatched HEAD 状态。
所以下面这些命令都会让你进入 Detatched HEAD 状态:
checkout HEAD^^
checkout <commit id>
checkout <tag>
当你处于 Detatched HEAD 状态时,你所进行的所有后续提交不在隶属于某个特定的分支,所以当你切换到其他分支后,之前所做的修改有可能丢失(注:这里用“可能”是因为还可以通过使用 reflog 等命令尝试找回)。
“Detatched HEAD” 这个名字虽然不太友好,但是它有大用途。
比如今天你发现,用户现场运行的程序有 BUG,不巧的是用户现场的程序还是你上个月发布的版本,更不巧的是你还不敢直接给用户现场升级现在的最新版本(因为这一个月已经对程序进行了大改)。所以唯一可靠的方案,就是在上个月发布到用户现场的程序版本基础上进行 BUG 修复,重新测试后,打包给用户升级。👀️
此时你就需要能跳回到代码仓库中历史上的某个状态,所以你执行了:
git checkout v2.13.16
OK,此时整个代码仓库都已经回退到了历史上的 v2.13.16
结点的状态,你也可以进行 BUG 修复和代码测试了。
显然此时你也进入了 Detatched HEAD 状态。😄
可能你也不是有意的,可能点了某个按钮,或者执行了某个命令,不小心进入了 Detatched HEAD 状态。此时可能你也没干什么,只是想从 Detatched HEAD 状态中脱离出来。
很简单,你只需要进行分支切换即可。
git checkout dev
此时 HEAD 将重新指向 dev
分支,ok,你可以继续进行工作了。😕
可能你确实想将代码仓库退回到历史中的某个结点,并做一些验证性测试,但测试过程中对代码的修改不需要保留,可以直接丢弃。
那么做法也很简单,也直接进行分支切换即可。
git checkout v2.13.16
此时 HEAD 将重新指向 dev
分支,ok,你可以继续进行工作了。😕
如果你将代码仓库退回到历史中的某个结点,并做了修改和实验性的验证,而且在测试完成后希望把修改的内容保存下来,那么你需要做的就是,创建新的分支保存这些修改,未来可以考虑把这些修改重新合并回主分支。
git checkout -b my-new-branch
此时你所做的实验性修改,都会被保存在你新创建的分支中。
如果你需要,可以再将这些修改合并回主分支。
git checkout master
git merge my-new-branch
Detatched HEAD 是指当前你的工作环境(HEAD)指向的并不是一个分支,而是指向历史中的某次 commit 或某个 tag。这可能是由于你 checkout 了一个历史中的一个 commit 或一个 tag 造成的。在Detatched HEAD 状态下进行的提交不隶属于任何分支。我们可以通过直接切换分支(注:丢弃我们在 Detatched HEAD 状态下所做的修改),或创建新分支(即:保留我们在 Detatched HEAD 状态下所做的修改),从 Detatched HEAD 状态中脱离。
参考:
https://initialcommit.com/blog/what-is-git-head https://git-school.github.io/visualizing-git/