Git中的HEAD是什么?

1317

您可以看到Git文档中说:

分支必须在HEAD中完全合并。

但是Git中的HEAD究竟是什么?


11
请参考https://dev59.com/h3NA5IYBdhLWcg3wbNQ4#967611。 - VonC
有趣的是,“heads”和(命名的)“branches”之间几乎存在一对一的对应关系(请参见git ls-remote),但“HEAD”的定义似乎很难确定。git branch的文档简单地说明HEAD实际上是“当前分支的末端”。所有的混淆实际上都必须关于分支的定义 - 这是令人惊讶的微妙:我们到底是什么意思“branch”? - Brent Bradburn
_HEAD指的是你在工作树中所做更改所基于的提交。来源:gitrevisions manual - David Balažic
27个回答

960

你可以把HEAD想象成“当前分支”。当你使用git checkout切换分支时,HEAD修订版会更改为指向新分支的最新版本。

你可以通过以下方式查看HEAD指向:

cat .git/HEAD
在我的情况下,输出结果为:
$ cat .git/HEAD
ref: refs/heads/master

HEAD可以指向一个没有分支名称关联的特定版本。这种情况称为游离的HEAD


69
Git HEAD的值取决于你所在的分支,对吗?进一步说,对于每个开发者来说也是如此?我猜我想知道的是,Git HEAD是一个库全局的东西,还是每个开发者都有自己的Git HEAD? - bobobobo
124
@bobobobo:没错,HEAD 就像一个指针,指向当前的分支。当你切换到另一个分支时,HEAD 会改变指向新的分支。当前的 HEAD 是每个仓库本地的,因此对于每个开发者来说都是独立的。 - Greg Hewgill
82
@动静能量:HEAD可以指向任何提交记录,它不必是任何分支中的最后一个提交记录。当HEAD指向的提交记录不是某个分支的最新提交记录时,就被称为“游离HEAD”。 - Greg Hewgill
188
HEAD并不是指“当前分支”,而是指初始化工作树当前状态的提交所对应的名称。更实际地说,可以将其看作是指向已检出提交的符号引用。 - Ben Collins
28
被投反对票。我同意@BenCollins的观点。依我之见,第一句话相当令人困惑,可以改为:“你可以把HEAD看作是当前分支上的当前提交”。 - dalvarezmartinez1
显示剩余30条评论

234

引用其他人的话:

一个“head”只是指向提交对象的引用。每个“head”都有一个名称(分支名称或标签名称等)。默认情况下,每个代码库中都有一个名为master的“head”。一个代码库可以包含任意数量的“head”。在任何时候,一个“head”被选定为“当前head”。这个“head”被别名为HEAD,总是大写的。

请注意区别:“head”(小写)是指代码库中的任一命名head,“HEAD”(大写)专门指当前活动的head。这个区别在Git文档中经常使用。

另一个快速了解Git内部工作原理(因此更好地理解heads / HEAD)的良好来源可以在这里找到。 参考(ref:)或head或branch可以被视为粘贴在提交历史记录中的便笺。通常它们指向一系列提交的顶部,但是可以通过git checkoutgit reset等移动它们。


1
每个头都有一个名称。其中一个头被选为“当前头”。这个头被别名为HEAD。因此,我得出结论,"HEAD"并不指代“分离的HEAD”状态。 - gxyd
2
如果HEAD并非指向“head”,而是指向您已指定的提交ID(例如,使用git checkout HEAD~2),那么它就是一个分离的HEAD。 它指向的不是已知头部的提交ID。请参阅http://eagain.net/articles/git-for-computer-scientists/上的文章以获取更详尽的说明。 (注意:该翻译仅供参考,对于特定上下文可能不适用或存在更佳翻译。) - Silfheed
3
一般来说,我认为这个回答在概念上比被采纳的回答更合理(尽管使用小写的“head”来指代一个分支会让很多人感到困惑)。然而,“git revert”并不是将一个分支移动到不在末端的好例子,因为“git revert”只会创建一些新的提交,仍然将当前分支留在(新的)末端。 - LarsH
5
“which is not the commit id of a known head”可以翻译为“这不是已知分支的提交ID”。实际上,一个分离的HEAD可能会指向已知分支(或多个分支)的提交ID。它之所以被称为分离的,是因为HEAD直接指向该提交ID,而不是指向分支。这将影响未来的commitreset等行为。 - LarsH
3
@LarsH: 关于分离 HEAD 指向一个提交 ID 而非引用的观点很好。你可以通过查看.git/HEAD文件来验证这一点。如果它是分离的,它将包含一个提交的哈希值,即使它是已知 HEAD 的相同提交也是如此。如果它是附加的,它将包含指向 HEAD 的路径(即 'ref:refs/heads/bob')。至于还原命令,在8年后我从未发现那个错别字。Git reset 可以让你将特定的 HEAD 指向特定的提交。 - Silfheed
这个答案在指出_HEAD_和_head_之间的区别方面很有用,但除此之外我觉得有点混淆。HEAD只是一个引用,它将成为你要创建的下一个提交的父提交。 - legends2k

144

这些回答中可能存在一个微妙但重要的误解,我想加上我的回答来澄清。

HEAD是什么?

HEAD就是你自己

HEAD是一个符号引用,指向你在提交历史中的位置。它跟随你所做的任何事情,就像一道影子。如果你进行了提交,HEAD会移动。如果你检出了某个东西,HEAD也会移动。无论你做什么,只要你在提交历史中移动到了新的位置,HEAD也随着你移动。针对一个常见的误解:你无法从HEAD中分离出自己。这就不是分离HEAD状态的含义。如果你发现自己思考:“哦不,我处于分离状态!我失去了我的HEAD!”请记住,那是你的HEAD。HEAD就是你自己。你没有从HEAD中分离出来,而是你和你的HEAD从其他地方分离出来了。

HEAD可以附着到什么上面?

HEAD可以指向提交,但通常并不这样。让我再说一遍。通常HEAD不会指向一个提交。它指向一个分支引用。它被附着在该分支上,当你做某些事情(例如commitreset)时,附着的分支将随HEAD一起移动。你可以通过查看底层来看它指向了什么。

cat .git/HEAD

通常你会得到这样的结果:

ref: refs/heads/master

有时你会得到像这样的东西:

a3c485d9688e3c6bc14b06ca1529f0e78edd3f86

HEAD直接指向一个提交时就会发生这种情况。这被称为分离的HEAD,因为HEAD指向的是一个分支引用之外的东西。如果在这个状态下进行提交,master将不再与HEAD关联,也就无法跟随你移动了。无论提交位于何处都不重要。你可能和你的主分支在同一个提交上,但是如果HEAD指向的是提交而不是分支,它是分离的,并且新提交将不与分支引用相关联。

您可以通过尝试以下练习来以图形方式查看此内容。从Git存储库运行此命令。您将获得略有不同的输出,但关键部分都在那里。当需要直接检出提交时,请使用第一个输出中获取的任何缩写哈希值(这里是a3c485d)。

git checkout master
git log --pretty=format:"%h:  %d" -1
# a3c485d:   (HEAD -> master)

git checkout a3c485d -q # (-q is for dramatic effect)
git log --pretty=format:"%h:  %d" -1   
# a3c485d:   (HEAD, master)

好的,这里的输出有一点小区别。直接检查提交记录(而不是分支)会给我们一个逗号而不是箭头。你认为,我们处于脱离HEAD状态吗?HEAD仍然指向与分支名称相关联的特定修订版。我们仍然在主分支上,对吧?

现在尝试:

git status
# HEAD detached at a3c485d

不是。我们处于“分离的HEAD”状态。

你可以通过git log -1查看(HEAD->分支)(HEAD,分支)的相同表示。

总结

HEAD代表你自己。它指向你检出的任何位置,通常不是提交,而是一个分支。如果HEAD指向提交(或标签),即使它是一个分支也指向的相同的提交(或标记),你(和HEAD)也已经脱离了该分支。由于你没有与你附加的分支,所以该分支不会随着你进行新提交而更新,但是HEAD会更新。


6
我喜欢这个回答,因为虽然文档描述了真相,但软件才定义了真相。.git/HEAD 是软件认为的 HEAD。 - Don Branson
18
仅凭其概念定义,这应该是被接受的答案。 - ata
10
我认为这是最佳答案。其他答案暗示HEAD是分支的末端,但实际上它像是磁带头。一个非常简单的方法来证明这一点是使用命令 git checkout HEAD~1 回到前一次提交,然后再使用 git checkout HEAD~1 回到再前一次提交。如果HEAD总是指向分支上最近的提交,在第二个命令中将与第一个命令相同 - 但实际上并不是。第二个命令会将你的HEAD向后移动一个更早的提交,也就是说,现在距离该分支的末端有两个提交。这很微妙但也很重要。 - Dave Kerr
6
我喜欢你说“头脑就是你”的表达方式。 - Ivan dal Bosco
4
这个答案应该被接受,解释得很清楚! - Ahmad

81

HEAD是一个特殊指针,它指向您当前所在的本地分支。

来自Pro Git书籍,第3.1 Git分支 - 分支概述章节,在创建新分支一节中:

如果创建一个新分支会发生什么?这样做会为您创建一个新的指针来移动。假设您创建了一个名为testing的新分支。您可以使用git branch命令执行此操作:

$ git branch testing 

这会在当前提交中创建一个新指针。

enter image description here

Git 如何知道您当前位于哪个分支?它保留了一个特殊的指针,称为 HEAD。请注意,这与其他 VCS(例如 Subversion 或 CVS)中您可能习惯的 HEAD 的概念有很大不同。在 Git 中,这是指向您当前所在本地分支的指针。在此情况下,您仍在主分支上。git branch 命令仅创建了一个新分支 - 它并未切换到该分支。

enter image description here


6
好的,可以为分离 HEAD 的情况添加一张图片,但不改变原意。 - Don Hatch
7
好的回答。分支只是标记了的提交,当你创建新的提交时,这个标签就会移动到新的提交上。当你切换到一个没有标签的提交时,它就处于“游离 HEAD”状态。这意味着 HEAD 指向一个没有分支标签的提交。如果你在上面的例子中检出 34ac2,那么现在 HEAD 就会指向该提交,这就叫做游离 HEAD。在这种状态下,你可以进行更改、实验和提交更改,但是一旦你切换到不同的分支,你将失去所有的更改,除非你创建一个新的分支。 - Rukshan
3
@sleepwalkerfx 但是你可以检出一个带有分支标签的提交,仍然处于游离状态,因为你的HEAD不再指向分支标签而是指向分支的提交ID。 - Marc
@Marc 当您检出一个带有分支标签(换句话说是一个分支)的提交时,HEAD 会随着您移动。它不会指向其他地方。因此,在这种情况下,您不会处于分离状态。 - Rukshan
2
@sleepwalkerfx 我认为我们现在谈论的是语义问题。你说得对,分支标签是指向特定提交的文本引用。但它并不是一个提交。所以如果你执行了 git log 命令,并得到类似于 commit ad0265... HEAD -> foo ... 的输出,那么这意味着 foo 分支是指向提交 ID 为 ad0265 的引用。检出文本引用 foo 并不会导致分离头状态。而检出提交 ID ad0265 将导致分离头状态。也许我没有理解你所传达的微妙之处。希望这篇文章能够帮助你找到我迷失的地方。 - Marc
显示剩余5条评论

68
我推荐Github开发者Scott Chacon的定义[视频参考]:

Head 是你当前所在的分支。它是一个符号引用,指向一个分支。你始终都有HEAD,但HEAD会指向其中一个其他指针(即你所在的某个分支)。它是下一次提交的父级,应该是最后一个检出到你的工作目录中的内容……这是你的工作目录的最后已知状态。

整个视频将为您提供Git系统的简要介绍,因此如果有时间,我也建议您观看整个视频。

44
所以真正的定义是“你下一次提交的父级”。 - nicolas
1
请仅返回翻译后的文本:以及“指向下一个将移动的分支的东西” - nicolas
28
这个视频非常棒,但很遗憾,它不适合作为Stack Overflow的答案。如果将来视频被删除了怎么办?那么你的链接将指向空白。更好的答案应该包括Scott在视频中说的文字转录。 - user456814
4
Scott说HEAD是一个指向分支的指针,但HEAD也可以指向旧的提交。这很令人困惑。 - Fixee
很高兴看到有参考资料,但是答案在你的回答中并没有找到。 :P - lindhe
显示剩余2条评论

46

假设这不是一个特殊情况,称为“脱离HEAD”,那么如同《O'Reilly Git书籍》第二版第69页所述,HEAD的意思是:

HEAD总是指当前分支上最新的提交。当你切换分支时,HEAD会更新并指向新分支上的最新提交。

所以

HEAD当前分支的“末端”

请注意,我们可以使用HEAD来引用最近的提交,并使用HEAD~表示尖端之前的提交,HEAD~~HEAD~2表示更早的提交等等。


29

HEAD是指你当前工作副本所指向的提交,即你当前检出的提交。根据Git修订规范官方文档

HEAD指向你在工作树中修改所依据的提交。

然而需要注意的是,在即将发布的Git 1.8.4版本中,@也可以用作HEAD的速记符号,正如Git贡献者Junio C Hamano在他的Git Blame博客中所指出的那样:

你可以使用“@”代替“HEAD”,例如“git log @”。

Stack Overflow用户VonC还在回答另一个问题时发现了一些有趣的信息,解释了为什么选择了@作为速记符号,详情请见此处

值得注意的是,在某些环境下,不必将HEAD大写,特别是在使用不区分大小写的文件系统(如Windows和OS X)的操作系统中。


24

请看创建和操作分支

HEAD 实际上是一个文件,其内容确定了 HEAD 变量所指向的位置:

$ cat .git/HEAD
ref: refs/heads/master
$ cat .git/refs/heads/master
35ede5c916f88d8ba5a9dd6afd69fcaf773f70ed
在这个代码库中,HEAD文件的内容指向一个名为refs/heads/master的第二个文件。文件refs/heads/master包含了主分支上最近提交的哈希值。
结果是HEAD指向主分支提交的哈希值,该哈希值存储在.git/refs/heads/master文件中。


20

Git中的HEAD是什么?(概念上)

HEAD是指向当前检出的分支或提交的指针,它回答了这个问题:我在仓库中的哪个位置?或者说,这是Git知道在哪个提交上映射您的本地工作树以及您当前是否正在工作在一个分支(attached)还是不在(detached)的方式。

Detached HEAD

HEAD可以处于两种状态中的任意一种,附加分离,具体取决于您是否已经检出了分支。默认状态是附加,其中对历史记录的任何操作都会自动记录到当前引用HEAD的分支中。

分离状态下,可以进行实验性更改而不会影响任何现有的分支。请参见下面的信息图,说明在附加状态与分离状态下提交之间的区别。

Illustration of HEAD in attached and detached state.

一个常见的误解是消息您处于“分离HEAD”状态的语气有误,事实上它只是描述了HEAD如何引用当前快照。

可能使HEAD处于分离状态的操作:

  • 检出特定提交,即
    $ git checkout 14ko3
    
  • 明确检出远程分支,即
    $ git checkout origin/master
    
  • 使用分离头指针标记(detached flag)切换分支,即
    $ git switch master --detached
    
  • 查看标签,即
    $ git checkout v1.0.1
    
  • 执行交互式变基或包含冲突更改的常规变基

从分离状态转换为附加状态

要从分离状态转换为附加状态,可以从当前位置创建一个新分支或切换回现有分支。

注意:在分离状态下创建的任何提交将在切换到另一个现有分支之前未在新分支中保存更改的情况下(垃圾收集后)被丢弃。

检查 HEAD 的状态

可以通过不同的方式找出当前 HEAD 的状态,以下是两种选项。

  • 使用 show
  • $ git show HEAD --oneline
    14ko3 (HEAD, master) C1
    
    # If attached, the output would have been
    14ko3 (HEAD -> master) C1
    
  • 使用status
    $ git status
    HEAD detached at 14ko3
    

HEAD到底是什么?(从技术角度)

如果您想要明确地查看HEAD引用了什么,您总是可以检查.git/HEAD文件,这是Git在内部管理HEAD所使用的实际文件。该文件包含分支名称或提交哈希值,具体取决于HEAD是否处于分离状态。

$ cat .git/HEAD
ref: refs/heads/master

# If detached, the output would have been
14ko36e295f1a98ec57397b3acc7bc247da61ff5

来源:以上摘录来自这篇完整文章:Git中的HEAD是什么?


嗨Alexis,你关于Git的博客是我读过的最具启发性的内容之一,非常感谢! - Jeff
1
@Jeff 謝謝您的好評,正是這樣的反饋讓我繼續寫作的動力來源!很高興您覺得有用。 - Alexis Määttä Vinkler

19

阅读了之前的所有答案后,我仍然希望更清晰地理解。这篇官方Git网站的博客http://git-scm.com/blog给了我所需要的:

HEAD:指向最后提交快照的指针,下一个父级

在Git中,HEAD是指向当前分支引用的指针,而当前分支引用本身是指向您上次提交或检出到工作目录中的最后一次提交的指针。这也意味着它将成为下一个提交的父级。通常最简单的想法是,HEAD是您上次提交的快照。


3
“HEAD: 最新提交的快照,下一个父节点” 这句话不准确。 HEAD 不是一个提交,它指向一个提交。 - jub0bs
1
不需要讽刺;在你编辑之前,即使引用是准确的,大而粗的字母也是一种简化和有点误导性的表现。现在,它更好了。 - jub0bs
2
如果你读下一行:Git中的HEAD是指向当前分支引用的指针,而该引用又是指向你最后一次提交或最后一次检出到你的工作目录的提交的指针。-- 请注意这里使用了“指针”一词。 - user3751385
2
虽然“最后提交快照”描述确实给出了 HEAD 通常应该如何使用的概念感觉,但它并不准确。如果我进行一次提交,然后切换到另一个分支,HEAD 就不再指向最后提交的快照了。它指向了我刚刚切换到的分支上的最后提交快照。如果我执行 checkout HEAD^,现在 HEAD 甚至不再指向任何分支上的最后提交快照。 - LarsH
2
“你下一次提交的父提交(如果你现在提交)”更准确,但是除了提交之外,git中还有许多其他操作受到HEAD的影响。实际上,最终HEAD就是HEAD,并且它的本质是通过影响commitmergerebaselog等命令来定义的。但从概念上讲,“(指针指向的)当前位置”可能是一个很好的总结。 - LarsH

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接