Why do I have a detached HEAD after checking out the most recent commit?
Asked Answered
C

4

7

Recently, while working in a git repository, I wanted to view the code at an old commit (68cce45), so I did

git checkout 68cce45

After viewing the changes, I wanted to return to the current version of the repository and keep working. Since 2bcfd11 was the most recent commit, I did

git checkout 2bcfd11

I then made some changes and did

git add *

and then

git status

which gave me the warning: HEAD detached at 2bcfd11.

I'm confused. I can understand why I would be in a "detached HEAD state" if the last commit I checked-out was several versions ago. But since the last commit I checked-out was the most current version of the repository, then why would I be in a detached HEAD state? Isn't HEAD now pointing to the "top" of the repository?

Conlin answered 18/8, 2019 at 2:3 Comment(1)
Don't forget the new git restore command with Git 2.23. See my answer below: no detached HEAD!Ranita
B
9

why would I be in a detached HEAD state?

Because you've checked out a commit instead of a branch. Checkout any commit — and you're in a detached HEAD state.

Isn't HEAD now pointing to the "top" of the repository?

git doesn't really knows if it's the top. You have to explain that to git by checking out a branch:

git checkout master

Now git knows it's the head of a known branch. The end of detached HEAD problem.

Buller answered 18/8, 2019 at 2:10 Comment(3)
It's worth noting that a detached HEAD state isn't necessarily a problem. Checking out an entire branch isn't helpful if you're trying to experiment with a specific commit, which is when you'd typically checkout one. A developer may still wonder about the detached HEAD message, though.About
@BrandonEssler You cannot check out an entire branch anyway. Branch is a pointer to a commit, so git checkout master doesn't checkout an entire branch master, it checks out the commit pointed to by master.Buller
You are, of course, correct. But that's just semantics. I worded my comment poorly, but the point still stands. If you're checking out a specific commit, you want that one; not the commit a branch is pointing to.About
W
2

To expand on phd's answer a bit: in Git, HEAD, spelled in all uppercase like this,1 is a very special name. HEAD can either be attached (to a branch name), or detached. In both cases, Git will be able to tell you which commit you're using:

git rev-parse HEAD

will print some hash ID. But only when HEAD is attached to a branch name can Git tell you which branch name you're using:

git rev-parse --symbolic-full-name HEAD
git symbolic-ref HEAD

Both will give you the name of the current branch (prefixed with refs/heads/) if you're on a branch. If you're in detached HEAD mode, the former will just print HEAD and the latter will produce an error:

$ git checkout --detach master
HEAD is now at 7c20df84bd Git 2.23-rc1
Your branch is up to date with 'origin/master'.
$ git rev-parse --symbolic-full-name HEAD
HEAD
$ git symbolic-ref HEAD
fatal: ref HEAD is not a symbolic ref

Many forms of git checkout will detach HEAD. A few forms will attach it. Using git checkout branch-name attaches it, while—as shown above—you can add --detach to make sure it becomes or stays detached.

Using a raw hash ID such as 7c20df84bd always results in a detached HEAD, even if there are one or more branch names that identify this particular commit.

Note that you can have as many branch names as you like that all identify the same commit:

$ for i in m1 m2 m3; do git branch $i master; done
$ git checkout m1
Switched to branch 'm1'
$ git rev-parse HEAD
7c20df84bd21ec0215358381844274fa10515017
$ git checkout m2
Switched to branch 'm2'
$ git rev-parse HEAD
7c20df84bd21ec0215358381844274fa10515017

If I explicitly check out 7c20df84bd21ec0215358381844274fa10515017, which of the four names—m1, m2, m3, or master—would you like Git to use? But it uses none of them: if you want it to use a name, you must supply a name yourself:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

after which we can delete the extra names so that commit 7c20df84bd21ec0215358381844274fa10515017 is only on, and at the tip of, master, rather than being on and at the tip of four branches all at the same time.

$ for i in m1 m2 m3; do git branch -d $i; done
Deleted branch m1 (was 7c20df84bd).
Deleted branch m2 (was 7c20df84bd).
Deleted branch m3 (was 7c20df84bd).

Remember, HEAD has two functions. It finds the current branch (name), or fails to do so if HEAD is detached; and it finds the current commit.2 The answer you get from Git depends on the question you ask: did you want to know the branch name, or did you want to know the current commit hash ID?


1You can, on some systems, sometimes spell it out in lowercase, head, and get the same effect. However, this starts to fail mysteriously in added work-trees. It's best to stick with the all-caps HEAD, or if that's too annoying to type, the single character @ has the same special meaning.

2This too can fail, but only in a special state. You are in this state in a new, totally-empty repository, in which your current branch name is master, but branch master itself does not yet exist. This is because a branch name must contain the hash ID of some existing, valid commit object. In a new, totally-empty repository, there are no commits at all. So no branch names are allowed to exist. Nonetheless, HEAD is attached to the name master.

When you're in this state—some parts of Git call this an orphan branch, as in git checkout --orphan, and others call it an unborn branch, as in what git status will say—the next commit you make causes the branch name to come into existence. The name is already somewhere—specifically, stored in HEAD—but the commit creates the name as a valid branch name, after first creating a valid commit whose hash ID the name can hold.

Worden answered 18/8, 2019 at 5:16 Comment(1)
Thanks for the explanation. The part about multiple branches being able to identify the same commit helps clarify things. In that case, with git checkout <hash-number> it wouldn't be clear which branch is being checked out.Conlin
F
0

HEAD is whatever commit you currently have checked out. There may or may not be a branch (like master maybe) that points to HEAD or not. When you did git checkout 2bcfd11, you updated your HEAD, but remained detached - that is, you didn't indicate to git that you want to have some symbolic name associated with that. If you have a branch that points to 2bcfd11, you can git checkout that branch and be fine. If you don't, git branch will let you create a branch at 2bcfd11 with whatever name you want.

Fermi answered 18/8, 2019 at 2:10 Comment(0)
R
0

With Git 2.23 (released yesterday, August 2019), do a git restore

git restore -s <SHA1> -- .

You won't have a detached HEAD then (you remain on your current branch, master for instance, but with a different content).

Once you are done, you can, well, restore the proper working tree with:

git restore -s master -- .
Ranita answered 18/8, 2019 at 6:41 Comment(6)
Thanks for the suggestion. So just to make sure that I understand correctly, is this how I would use git restore in my case (referencing the SHA1 values I originally posted in my question)? To view the old commit, I would do git restore -s 68cce45 -- . instead of git checkout 68cce45? And then to get back to the most recent commit I would do git restore -s master -- . instead of git checkout 2bcfd11?Conlin
@wxyz Yes. That way, you never switch branch, you are still on master branch after restoring master content.Ranita
Ah, ok. Also, what does the -- . do at the end of the command?Conlin
@wxyz it indicates the path of the content you want to restore: current path. Execute that at the root of the local repository, and you would restore the commit for the all working tree.Ranita
So could you leave the -- . off completely? Would that just restore the commit for the entire working tree?Conlin
@wxyz You have to put a path (unless you restore with the patch option: git-scm.com/docs/git-restore#Documentation/git-restore.txt--p). To restore the entire working tree (from any subfolder) you can use -- :/ (see examples: git-scm.com/docs/git-restore#_examples)Ranita

© 2022 - 2024 — McMap. All rights reserved.