A common source of confusion when using Git is not knowing what it all means when you see an output like this:
You are in 'detached HEAD' state. You can look around, make
experimental changes and commit them, and you can discard any
commits you make in thisstate without impacting any branches
by switching back to a branch.
To really understand what is happening, we need to look inside Git.
Git commits are immutable—meaning you can create new ones, but what’s already inside will never be changed. This is how our repository keeps data safe for us. The only way of making changes to the repository is by creating new commits.
Git commits gives us a stable, complete history of the changes to the repository. On top of this stable base, we have a dynamic part: branches. What can be surprising at first is that branches are just labels. They are pointing to one commit. They literally have no other data than the name and its position.
Anything besides that—for example,
feature/<info>
, or <branch-owner>/<description>
—is just a convention the teams are using. From Git’s point of view, your main
or master
branch is no different than lorem-ipsum
.HEAD is a pointer to the current commit—the place where you are in the repository right now. It is used as:
git diff
Your HEAD is a central piece of the state of your repository.
The typical workflow with Git is as follows:
If you limit yourself to checking branches only, you will never leave this state. When you run git status
, it tells you what branch you are on:
$ git status
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
So far, HEAD (current commit) and branch were in a normal, aligned state. Things get complicated when you check out something that is not a branch—for example, when you switch to a commit by its ID:
$ git checkout abc01e7
where abc01e7
is an ID of any commit in the repository—it can be inside of any of the branches, on top of one of them, or in another place of the repository history.
Because you are not on a branch anymore, the git status
changes to:
$ git status
HEAD detached at abc01e7
nothing to commit, working tree clean
To call your attention to this situation, Git colors HEAD detached
to red.
Because you can, and it can be sometimes useful. A common scenario is to see how the application worked a few commits earlier—for example, for troubleshooting. We noticed something wrong, such as a feature not working as expected, and we check out the last commit that we expect to work OK. In this way, we can pinpoint when exactly the issue was introduced.
Because you are not on a branch, Git doesn’t have a branch to update when you create a new commit. So all the commits you a create there are stored but are left dangling: there is no way to easily reach them, and, as such, they are assumed to be unimportant. At some point, the garbage collector will remove them permanently from the repository.
You left the branch and moved your HEAD elsewhere. It could happen:
git checkout <commit ID>
—as abovegit checkout HEAD^
—the last commitgit checkout <tag>
—tags are similar to branches, but they are meant to be immutable and are not updated as branchesgit rebase
stopped halfway—due to conflict or to let you make changes to the commits,git bisect
Luckily, you get a bit of instruction directly in the CLI when you check out non-branch reference:
$ git checkout abc01e7
Note: switching to 'abc01e7'.
You are in 'detached HEAD' state. You can look around, make
experimental changes and commit them, and you can discard any
commits you make in this state without impacting any branches
by switching back to a branch.
If you want to create a new branch to retain commits you
create, you may do so (now or later) by using -c with the
switch command. Example:
git switch -c <new-branch-name>
Or undo this operation with:
git switch -
Turn off this advice by setting config variable
advice.detachedHead to false
You can create a new branch, exactly in the point where you are—including all the commits that you maybe created from there. You can create the branch with the following syntax:
git switch -c <new-branch-name>
or the command form Git versions older then 2.23:
git checkout -b <new-branch-name>
Those commands create a new branch, and set it as your current branch.
Alternatively, you can just create a new branch on you current commit and stay in the detached HEAD state:
git branch <new-branch-name>
Alternatively, you can pick an existing branch and either merge your dangling commits or cherry-pick them. If you are not sure what those terms mean, then stick to creating a new branch, because it will allow you to do the same things, but in a less confusing setup.
Besides the git status
, how can you see this state?
If you use git log --oneline --graph --decorate --all
(something I recommend to define as a git tree
alias) it will show your HEAD differently. When it’s on branch, the output looks like this:
$ git tree
* abc01e7 (HEAD -> main, origin/main) Add lorem ipsum to readme
* edd3504 Add readme
The HEAD
points to the branch you are on with an arrow. When you are in “detached HEAD” state, main
and HEAD
are displayed separately, as an unrelated reference:
$ git tree
* abc01e7 (HEAD, origin/main, main) Add lorem ipsum to readme
* edd3504 Add readme
The difference is subtle but obvious when you know why you should pay attention to it.
At main
branch:
“Detached head” state:
Can you see the difference in the middle part of the header? One is showing "Current branch"; while the other “Detached HEAD”.
At main
branch:
“Detached head” state:
It shows as the current branch HEAD.
Git has many confusing parts when you start using it, but it gets way simpler once you understand better the way it stores its data. If you are interested in learning more about Git, sign up here to get updates about my Git-focused content.
Originally published here.