If git was to rename detached HEAD
I would have it named as a HEAD that isn’t identified by a branch and will soon be forgotten.
We as people can easily remember branch names. We do git checkout new-button-feature
/ git checkout main
. main
and new-button-feature
are easy to remember. And we can just do git branch
and get a list of all branches. But to do the same with just commits you'd have to do git reflog
which is very tedious. Because you have thousands of commits but only very few branches.
A detached commit’s identifier is just its SHA. So suppose you checked out a commit (not a branch) i.e. you did git checkout d747dd10e450871928a56c9cb7c6577cf61fdf31
you'll get:
Note: checking
out 'd747dd10e450871928a56c9cb7c6577cf61fdf31'.
You are in 'detached HEAD' state.
...
Then if you made some changes and made a commit, you're still NOT on a branch.
Do you think you'd remember the commit SHA? You won't!
git doesn't want this to happen. Hence it's informing your HEAD is not associated to a branch so you're more inclined to checkout a new branch. As a result below that message it also says:
If you want to create a new branch to retain commits you create, you
may do so (now or later) by using -b with the checkout command again.
Example:
git checkout -b
To go a bit deeper a branch is built in a way that it's smart. It will update its HEAD as you make commits. Tags on the other hand are not meant to be like that. If you checkout a tag, then you're again on a detached HEAD. The main reason is that if you make a new commit from that tag then given that that commit is not referenced by anything (not any branch or tag) then still its considered a detached HEAD.
Attached HEADs can only happen when you're on a branch.
For more see here
HEAD is a pointer, and it points — directly or indirectly — to a
particular commit:
Attached HEAD means that it is attached to some branch (i.e. it
points to a branch).
Detached HEAD means that it is not attached to any branch, i.e. it
points directly to some commit.
To look at from another angle, if you're on a branch and do cat .git/HEAD
you'd get:
ref: refs/heads/Your-current-branch-name
Then if you do cat refs/heads/Your-current-branch-name
then you'd also see the SHA of the commit that your branch is pointing/referencing to.
However if you were on a detached HEAD you and cat .git/HEAD
you'd just get the SHA of the commit and nothing more:
639ce5dd952a645b7c3fcbe89e88e3dd081a9912
By nothing more I mean the head isn't pointing to any branch. It's just directly pointing to a commit.
As a result of all this, anytime you checkout a commit (without using the branch name to checkout), even if that commit was the latest commit of your main branch, you're still in a detached HEAD because your HEAD is not pointing to any of your local branches. Hence even checking out a tag will put you in a detached HEAD. To add onto that, even checking out a remote branch that you have fetched into your computer would result in a detached head ie git checkout origin main
would also end up as a detached head...
Summary
All of the following will cause detached head:
- checkout any commit
- checkout any tag
- checkout any remote branch
You're only on an attached head, if you've checked out a local branch
Special thanks to Josh Caswell & Saagar Jha in helping me figure this out.
branch-name@{n}
, the nth previous position ofbranch-name
. But no matter what, at some point there must've been agit checkout <rev>
. If that doesn't ring a bell, then probably you did what Will mentioned - tried to dogit checkout <file>
and managed to specify a revision by accident. – Tangiblegit checkout remotes/origin/my-branch
instead ofgit checkout my-branch
orgit checkout origin/my-branch
. – Battatgit checkout my-branch
andgit checkout origin/my-branch
is explained here: https://mcmap.net/q/11941/-can-39-t-do-a-checkout-with-multiple-remotes. As for the first case,remotes/origin/my-branch
, I'd find it reasonable for git to behave similarly for all its commands.git show
handles such branch names fine. I'd go as far as call it a bug, or at least an inconsistency in git-cli. – Battatgit switch -c <branch> --track <remote>/<branch>
– Claqueur