Git HEAD 指的是分支与提交

max*_*max 3 git git-branch

根据Pro Git Book

\n\n
\n

在 Git 中,HEAD 是指向您当前所在的本地分支的指针。

\n
\n\n

这与计算机科学家的 Git一致:

\n\n
\n

HEAD 引用的特殊之处在于它实际上指向另一个引用。它是指向当前活动分支的指针。

\n
\n\n

事实证明

\n\n
\n

HEAD 不是最新版本,而是当前版本。通常,它是当前分支的最新版本,但不一定是。

\n
\n\n

例如

\n\n
\n

如果您签出较旧的内容(例如像 git checkout v1.1 这样的标签),那么您的 HEAD 会更改为该标签的提交。它可能不是最新的提交。

\n
\n\n

所以 HEAD 可以指向一个分支或者。当 HEAD 引用分支 X 时,与 HEAD 引用分支 X 的实际头提交相比,git 命令的行为是否有不同?(在类似 C 的表示法中,我正在讨论当 **HEAD 引用某个提交时与 *HEAD 引用相同提交时的情况。)

\n

tor*_*rek 5

HEAD只是一个引用,很像masteror (如果存在)branch,但有两个额外的特殊属性:

\n
    \n
  1. HEAD通常是一个符号引用(通常,没有其他引用是符号引用,尽管您可以使用您喜欢的任何符号引用git symbolic-ref)。符号引用只是一个包含另一个名称的名称,而不是哈希 ID。当读取或写入符号引用时,Git 通常会说“哦,好吧,这个是符号引用,所以我现在就去读取或写入另一个”。

    \n

    显然,这可能会导致无限循环:如果引用a说“看b”又说b“看a”,那么您可以永远来回追逐。但只要你不这样做,或者做HEAD唯一符号引用,你就会没事,因为你无法HEAD回到HEAD. 另外,符号引用的工作效果也不是很好:如果你让分支glorp指向master然后要求删除glorp,Git 就会删除master!我们很快就会看到这实际上是一件好事。

    \n
  2. \n
  3. 文字字符串HEAD内置于许多 Git 命令中,文件本身非常重要\xe2\x80\x94 在很多地方使用\xe2\x80\x94,它实际上是一个目录本身是否是 Git 存储库的测试。这意味着如果某些事情(例如特别不合时宜的崩溃)擦除了您的HEAD文件,Git 将不再相信您的.git目录是存储库!(通常没什么大不了的:只需将文件放回去,一切都会恢复正常。)

    \n
  4. \n
\n

每当您进行新的提交时,Git 使用的底层流程是:1

\n
    \n
  1. 从 读取提交 ID HEAD。这是当前提交:如果您处于“分离 HEAD”模式,并且原始提交 ID 位于 中HEAD,那么这就是 Git 获取的内容。如果您位于一个分支上,那么它HEAD包含分支的名称,Git 会遵循分支名称的间接寻址并读取该名称,从而给出该分支的最顶端提交。无论哪种方式,这都是当前提交。

    \n
  2. \n
  3. 写出进行提交 ( git write-tree) 所需的所有树,并写入新提交本身 ( git commit-tree),并将其父 ID 设置为步骤 1 中获得的 ID(如果这是合并提交,则加上所需的任何其他父代),其树设置为在步骤 2 中获得的 ID,并将其提交消息设置为任何适当的内容。

    \n
  4. \n
  5. 将从 获得的新提交的 IDgit commit-tree写入HEAD。如果HEAD是符号\xe2\x80\x94,即,您位于分支\xe2\x80\x94,则该分支将替换为分支名称。现在分支名称指向该分支的新的最尖端提交!

    \n

    但请注意,在步骤 3 中,如果您处于“分离 HEAD”模式,Git仍会将新 ID 写入HEAD. 结果是HEAD指向新分支的尖端。换句话说,“detached HEAD”模式仅意味着包含匿名HEAD分支尖端的ID 。添加新提交的工作方式与往常完全相同,更新当前分支。只是当前分支只有名称。(这一个名称,它不是分支的名称。具体来说,所有分支名称都以 开头。因为不是,所以它不是分支名称,它只是一个引用。如果名称以其远程跟踪分支名称开头,如果它以标签开头,但根本不以任何内容开头,那么它只是一个引用。)HEADrefs/heads/HEADrefs/remotes/refs/tags/HEAD

    \n
  6. \n
\n

您的反对意见也可以用另一种方式重新表述:

\n

但这意味着许多分支都可以指向一个提交 ID!

\n

确切地。这是完全正常的,每次创建新分支时都会发生:2

\n
...--o--o--o     <-- HEAD, master\n         \\\n          o      <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n

如果HEAD是“分离”并且我们进行新的提交:

\n
             o   <-- HEAD\n            /\n...--o--o--o     <-- master\n         \\\n          o      <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n

如果HEAD不是分离\xe2\x80\x94,如果相反,它指向master\xe2\x80\x94 git checkout -b newbr,并且我们在进行新提交之前执行此操作,那么我们就从这里开始(这次我将绘制HEAD -> newbr以表明这HEAD是象征性并指向newbr):

\n
...--o--o--o     <-- HEAD -> newbr, master\n         \\\n          o      <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n

提交后我们有:

\n
             o   <-- HEAD -> newbr\n            /\n...--o--o--o     <-- master\n         \\\n          o      <-- branch\n
Run Code Online (Sandbox Code Playgroud)\n

请注意,在“之前”图片中,我们为当前提交提供了三个HEAD名称: 、newbrmasterall 都指向它(尽管HEAD必须先经过newbr)。

\n
\n

1也就是说,这是一个正常的过程git commit。如果您使用git commit --amend,这个过程会稍微修改一下:HEADGit 不是从 读取 ID,而是查找当前提交的父级,并在步骤 3 中使用这些 ID。这意味着新提交一旦完成,与当前提交具有​​相同的父级。通过将新提交的 ID 写入HEAD分支,这似乎更改了提交。但实际上并没有:它只是将“旧的当前”提交推到一边。

\n

如果您完成一个具有两个或多个分支名称指向同一提交的示例,您将确切地了解如何以及为何在已推送到另一个存储库的已发布git commit --amend提交\xe2\x80\x94a 提交上使用,并且其他人现在的名称为 \xe2\x80\x94 可能会出现问题。(练习/提示:更新时,第 3 步中更改了多少分支名称引用?)HEAD

\n

2除非您使用git checkout --orphan. 它所做的就是将HEAD其置于与新的空存储库中相同的特殊状态:现在包含实际上尚不存在的HEAD分支的名称。也就是说,它是对不存在分支的符号引用。上面的三步提交序列知道如何处理从 读取 ID 失败的情况:它进行一个没有父级的新提交,然后将新 ID 写入,这会产生实际创建分支的副作用。HEADHEAD

\n

这解决了新的空存储库的引导问题:分支名称只能指向实际提交;但是master,在一个新的空存储库中,根本无法指向任何提交,因为根本没有任何提交。因此,在您进行第一次提交之前,新存储库实际上并不具有分支master,即使HEAD已设置为使您位于分支master

\n