How HEAD works in git
I recently conducted a survey at Mastodon asking how confident my readers were in their understanding of HEAD in Git. The results (based on about 1700 votes) surprised me a little:
10% – 100%
36% – quite confident
39% – somewhat confident
15% – I have no idea
I was surprised that people were not sure of their understanding: I thought that HEAD
– This is a fairly simple topic.
Usually, when others find a topic confusing as I do, it is because there is some hidden complexity that I am not considering. And in further discussions it turned out that HEAD
really A little more difficult than I thought!
HEAD actually has several meanings
After talking to a bunch of people about HEAD
I realized that HEAD
actually has several closely related meanings:
File
.git/HEAD
HEAD
Vgit show HEAD
(git calls this a “revision parameter”)All the ways git uses
HEAD
in the output of various commands (<<<<<<<<<<HEAD
,(HEAD -> main)
,detached HEAD state
,On branch main
and so on)
All of these topics are very closely related to each other, but I don't think this connection is obvious to those who are just starting to work with git.
File .git/HEAD
There is a very important file in Git .git/HEAD
. It may contain any of the following:
Name branches (For example,
ref: refs/heads/main
)Commit ID (For example,
96fa6899ea34697257e84865fefc56beb42d6390
)
This file defines what your “current branch” is in Git. For example, when you do git status
and you see the following:
$ git status
On branch main
this means that the file .git/HEAD
contains ref: refs/heads/main
.
If .git/HEAD
contains the commit ID instead of the branch, git calls this the “detached HEAD state”. We'll come back to this later.
(Sometimes people say that HEAD contains the name links Is there a commit ID, but I'm pretty sure this link should be branch. Strictly speaking, Can make sure that .git/HEAD
contained the name of a non-branch link, manually edited .git/HEAD
, but I don't think this can be done using the git command. But I'm curious if it's possible to use the standard git command to turn .git/HEAD into a non-branch link, and if so, why it might be needed!)
HEAD in git show HEAD
In git commands HEAD
very often used as a reference to the commit ID, for example:
Everything shown above (HEAD
, HEAD^^^
, HEAD@{2}
) are called “revision parameters”. They are documented in man gitrevisionsand Git tries to resolve them into a commit ID.
(To be honest, I've never heard the term “revision parameter” before, but that's what will lead you to the documentation of this concept)
HEAD in git show HEAD
has a fairly simple meaning: it resolves current commit, for which checkout! Git resolve HEAD
in one of two ways:
If
.git/HEAD
contains the name of the branch, it will be the last commit on that branch (for example, when reading it from.git/refs/heads/main
)If
.git/HEAD
contains the commit ID, this will be the commit ID
All output formats
We have reviewed the file .git/HEAD
and revision parameter HEAD
used in git show HEAD
. It remains to consider all the ways that git uses HEAD
in the output.
git status: “on branch main” or “HEAD detached”
By doing git status
the first line will look like one of two options:
on branch main
. It means that.git/HEAD
contains a branch.HEAD detached at 90c81c72
. It means that.git/HEAD
contains the commit ID.
Above, I promised that I would explain the meaning of “HEAD detached,” so let’s figure it out.
State detached HEAD
“HEAD is detached” or “detached HEAD state” means that there is no current branch.
Not having a current branch can be a little dangerous because when new commits are made, those commits won't stick to any of the branches – they'll be “orphans”! Orphan commits can cause the following problems:
commits will be harder to find (can't be done
git log somebranch
to find them)orphan commits will be removed by the git garbage collector over time
Personally, I'm very careful to avoid commits in the detached HEAD state, although some people prefer to work this way. Exiting the detached HEAD state is quite simple; you can do one of the following:
Return to thread (
git checkout main
)Create a new branch in this commit (
git checkout -b newbranch
)If you are in the detached HEAD state because a rebase is in progress, complete or abort the rebase (
git rebase --abort
)
Now let's return to other git commands that have in the output HEAD
.
git log: (HEAD -> main)
Having completed git log
and looking at the first line, you can see one of the following three options:
commit 96fa6899ea (HEAD -> main)
commit 96fa6899ea (HEAD, main)
commit 96fa6899ea (HEAD)
Their interpretation is not entirely obvious, so let's look at them:
inside
(...)
git lists all references that reference this commit, e.g.(HEAD -> main, origin/main, origin/HEAD)
means thatHEAD
,main
,origin/main
Andorigin/HEAD
point to this commit (directly or indirectly)HEAD -> main
means the current branch ismain
If this line says
HEAD,
but notHEAD ->
this means that we are in the detached HEAD state (there is no current branch)
If we use these rules to explain the three examples above, the result will be:
commit 96fa6899ea (HEAD -> main)
means:commit 96fa6899ea (HEAD, main)
means:commit 96fa6899ea (HEAD)
means:.git/HEAD
contains96fa6899ea
(detached HEAD).git/refs/heads/main
or contains the ID of another commit, or does not exist
Merge conflicts: <<<<<<< HEAD is confusing
When resolving a merge conflict, you might encounter something like this:
<<<<<<< HEAD
def parse(input):
return input.split("\n")
=======
def parse(text):
return text.split("\n\n")
>>>>>>> somebranch
I believe that HEAD
in this context the situation is very confusing and I usually just ignore it. Reasons for this:
By doing merge
HEAD
in a merge conflict is the same asHEAD
by doinggit merge
. Everything is very simple.By doing rebase
HEAD
in a merger conflict is something completely different: it another commit, on top of which we perform a rebase. That is, it is completely different from thatHEAD
which was during executiongit rebase
. This appears to be because rebase first checkouts another commit and then repeatedly picks up commits on top of it.
In addition, in merge and rebase the meanings of “own” and “foreign” were swapped.
What's the meaning HEAD
changes depending on whether I do a rebase or a merge is too confusing for me so it's much easier for me to ignore completely HEAD
and use another method to figure out which part is where when.
Thoughts on Terminology Consistency
I think HEAD would be more intuitive if git's terminology regarding HEAD was more internally consistent.
For example, git talks about a “detached HEAD” state, but not an “attached HEAD” state – the git documentation never uses the word “attached” to refer to HEAD
. And git talks about being “in” a branch, but it never talks about being “out” of a branch.
It's very difficult to understand what it really is on branch main
opposite HEAD detached
. How is the user supposed to guess that HEAD detached
has something to do with branches in general, or that “on branch main” has something to do with HEAD
?
That's all!
If I remember other uses HEAD
in Git (specifically using HEAD in Git output), I can add them to the post later.
If HEAD is confusing you, then I hope my article helped you a little!