根据Pro Git Book:
在Git中,HEAD是指向您当前所在的本地分支的指针。
这与Git for Computer Scientists:
一致HEAD ref特别之处在于它实际指向另一个ref。它是指向当前活动分支的指针。
HEAD不是最新版本,它是当前版本。通常,它是当前分支的最新版本,但并非必须如此。
如果您查看较旧的内容(例如git checkout v1.1之类的标记),则HEAD会更改为该标记的提交。它可能不是最新的提交。
因此,HEAD可以将 指向分支或进行提交。当HEAD引用分支X时,与HEAD引用分支X的实际头部提交相比,git命令的行为是否有任何不同? (在C中用符号表示,我在讨论** HEAD引用某个提交时的情况与* HEAD引用同一提交时的情况。)
答案 0 :(得分:2)
好的定义是HEAD总是指向检查的内容。
如果您正在处理分支,则它指向此分支。因此,当您检查另一个分支时,HEAD现在指向这个新分支。
但你也可以直接检查提交,只是为了验证过去的情况,然后HEAD指向这个提交。你处于我们所谓的“独立头”状态。阅读更多相关信息。但是,是的,这个状态不会起作用并创建新的提交。在这种情况下,git命令的行为并没有什么不同,但结果可能是:如果你签出一个分支,你将丢失在这种状态下创建的所有提交的跟踪,并且必须从reflog中恢复它们。
你可以自己看到,因为HEAD是由文件.git\HEAD
实现的。只需打开文件并查看其内容......
答案 1 :(得分:2)
HEAD
只是一个引用,非常类似master
或(如果存在)branch
,但有两个特殊属性:
HEAD
通常是符号引用(通常,没有其他引用是符号的,尽管您可以使用git symbolic-ref
制作任何符号引用)。符号引用只是包含另一个名称的名称,而不是哈希ID。在阅读或编写符号引用时,Git通常会说"哦,好吧,这个是符号,所以我现在只是去读或写另一个。&# 34;
显然这会导致无限循环:如果引用a
说"请查看b
"并且b
说"看a
",你可以永远来回追逐。但是,只要您不这样做,或者只使HEAD
符号参考,您就可以了,因为您无法做出{ {1}}回到HEAD
。此外,符号引用不能很好地工作:如果你将分支HEAD
指向glorp
然后要求删除master
,Git会删除glorp
!我们马上就会看到这实际上是一件好事。
文字字符串master
内置于许多Git命令中,文件本身非常重要 - 在很多地方使用 - 它实际上是对目录是否是一个测试本身就是一个Git存储库。这意味着如果某些事情(例如特别不合时宜的崩溃)消除了您的HEAD
文件,Git将不再相信您的HEAD
目录是一个存储库! (没什么大不了的,通常:只是把文件放回来,一切都很好。)
每当您进行 new 提交时,Git使用的基础流程是: 1
从.git
读取提交ID。这是当前提交:如果你在"分离的HEAD"模式,HEAD
中的原始提交ID,这是Git得到的。如果你在一个分支上,以便HEAD
包含分支的名称,Git会跟随间接到分支名称并读取它,给出该分支的最尖端提交。无论哪种方式,这都是当前的提交。
写出进行提交所需的所有树(git write-tree
),并将新提交本身(git commit-tree
)写入其父ID设置为步骤1中获取的ID(加上需要的任何其他父项(如果这是一个合并提交),它的树设置为步骤2中获得的ID,并将其提交消息设置为适当的。
将从HEAD
获取的新提交的ID写入git commit-tree
。如果HEAD
是象征性的,即你在分支上 - 这就是分支名称。现在,分支名称指向分支的最新提示!
但请注意,在步骤3中,如果您已经分离了HEAD"模式,Git 仍然将新ID写入HEAD
。结果是HEAD
指向新分支的尖端。换句话说,"分离的HEAD" mode只表示HEAD
包含匿名分支的提示ID。添加新提交与始终完全相同,更新当前分支。只是当前分支只有名称HEAD
。 (此是一个名称,它不是分支的名称。具体来说,所有分支名称都以HEAD
开头。自{{1不是,它不是分支名称,它只是一个参考。如果名称以refs/heads/
开头,它就是一个远程 - 跟踪分支名称,如果它以HEAD
标记开头,但refs/remotes/
根本没有任何内容,所以它只是一个参考。)
你的异议也可以用另一种方式改写:
完全。这是完全正常的,每次你创建一个新的分支时都会发生这种情况: 2
refs/tags/
如果HEAD
是"已分离"我们做了一个新的提交:
...--o--o--o <-- HEAD, master
\
o <-- branch
如果HEAD
不已分离 - 相反,它指向 o <-- HEAD
/
...--o--o--o <-- master
\
o <-- branch
- 我们在进行新提交之前执行HEAD
,那么我们从此开始(此时我将绘制master
以表示git checkout -b newbr
是符号并指向HEAD -> newbr
):
HEAD
在提交之后我们有:
newbr
请注意&#34;之前&#34;图片,我们为当前提交提供了三个名称:...--o--o--o <-- HEAD -> newbr, master
\
o <-- branch
, o <-- HEAD -> newbr
/
...--o--o--o <-- master
\
o <-- branch
和HEAD
都指向它(尽管newbr
必须去首先通过master
。
1 也就是说,这是正常HEAD
的过程。如果您使用newbr
,则会稍微修改此过程:Git不会从git commit
读取ID,而是查找当前提交的父级,并在步骤中使用这些ID这意味着新提交一旦生成,就会与当前提交具有相同的父级。通过git commit --amend
将新提交的ID写入分支,此出现以更改提交。但事实并非如此:它只是推动了现在的老电流&#34;放在一边。
如果您使用指向相同提交的两个或多个分支名称的示例,您将确切地看到在上发布的HEAD
的确切方式和原因提交 - 您已推送到另一个存储库的提交,以及其他人现在拥有的名称 - 可能会有问题。 (练习/提示:更新HEAD
时,步骤3中更改了多少个分支名称引用?)
2 除非,即使用git commit --amend
。这样做是将HEAD
置于一个新的空库中的同一特殊状态:git checkout --orphan
现在包含实际上尚未存在的分支的名称。也就是说,它是对不存在的分支的符号引用。上面的三步提交序列知道如何处理从HEAD
读取ID的失败:它使用 no parent进行新提交,然后将新ID写入{{1 ,它具有实际创建分支的副作用。
这解决了新的空存储库的引导问题:分支名称只能指向实际的提交;但是HEAD
,在一个新的空存储库中,根本不能指向任何提交,因为根本就没有任何提交。因此,在您进行第一次提交之前,新的存储库实际上没有HEAD
分支,即使设置了HEAD
以便您 on master
分支。