Git Essentials(Second Edition)
上QQ阅读APP看书,第一时间看更新

Detached HEAD

Now it's time to explore another important concept about Git and its references, the detached HEAD state.

For the sake of the explanation, go back to the master branch and see what happens when we check out the previous commit, moving HEAD backward; perform a git checkout HEAD^:

[42] ~/grocery (master)
$ git checkout HEAD^
Note: checking out 'HEAD^'.


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 performing another checkout. 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 <new-branch-name> HEAD is now at e4a5e7b... Add an apple

Wow, a lot of new things to see here. But don't be scared, it's not that complicated: let's take some baby steps into the long message Git showed us.

First, think about it: Git is very kind and often tells us loads of useful information in its output messages. Don't under evaluate this behavior: especially at the beginning, reading Git messages allows you to learn a lot, so read them carefully.

Here, Git says we are in a detached HEAD state. Being in this state basically means that HEAD does not reference a branch, but directly a commit, the e4a5e7b one in this case; do a git log and see:

[43] ~/grocery ((e4a5e7b...))
$ git log --oneline --graph --decorate --all
* a8c6219 (melons) Add a watermelon
* ef6c382 (berries) Add a blackberry
* 0e8b5cf (master) Add an orange
* e4a5e7b (HEAD) Add an apple
* a57d783 Add a banana to the shopping list

First of all, in the shell prompt you can see that between rounds, that now are doubled, there is not a branch name, but the first seven characters of the commit, ((e4a5e7b...)).

Then, HEAD is now stuck to that commit, while branches, especially the master one, remains at their own place. As a result, the HEAD file now contains the hash of that commit, not a ref to a branch as before:

[44] ~/grocery ((e4a5e7b...))
$ cat .git/HEAD
e4a5e7b3c64bee8b60e23760626e2278aa322f05

Going on, Git says that in this state we can look around, make experiments, doing new commits if we like, and then discard them simply by checking out an existing branch, or save them if you like creating a new branch. Can you say why this is true?

Due to reachability of commits, of course. If we do some commits, then move HEAD to an existing branch, those commits become unreachable. They stay in a reachable state until HEAD is on top of the last of them, but when you move HEAD with a git checkout, they are gone. At the same time, if you create a new branch before moving HEAD, there will be a label, a pointer Git can use to reach those commits, so they are safe.

Want to try?

Okay, let's have some fun; modify the shoppingList.txt file, adding a bug:

[45] ~/grocery ((e4a5e7b...))
$ echo "bug" > shoppingList.txt

Then commit this voluntary mistake:

[46] ~/grocery ((e4a5e7b...))
$ git commit -am "Bug eats all the fruits!"
[detached HEAD 07b1858] Bug eats all the fruits!
 1 file changed, 1 insertion(+), 2 deletions(-)

Let's cat the file:

[47] ~/grocery ((07b1858...))
$ cat shoppingList.txt
bug

Ouch, we actually erased all your shopping list files!

What happened in the repository then?

[48] ~/grocery ((07b1858...))
$ git log --oneline --graph --decorate --all

* 07b1858 (HEAD) Bug eats all the fruits! | * a8c6219 (melons) Add a watermelon | * ef6c382 (berries) Add a blackberry | * 0e8b5cf (master) Add an orange |/

* e4a5e7b Add an apple * a57d783 Add a banana to the shopping list

Nice! We have a new commit, the bug one, and we can see as HEAD followed us, so now it points to it. Then, the console drew two different paths, because starting from apple commit, we traced two routes: one that goes to the master branch (then to berries and melons), and one that goes to the bug commit we just made.

Okay, so if we now check out master again, what happens? Give it a try:

[49] ~/grocery ((07b1858...))
$ git checkout master

Warning: you are leaving 1 commit behind, not connected to any of your branches: 07b1858 Bug eats all the fruits! If you want to keep it by creating a new branch, this may be a good time to do so with:
 git branch <new-branch-name> 07b1858

Switched to branch 'master'

Okay, we have already seen this message: Git is aware that we are leaving a commit behind; but in this case, it's not a problem for us, indeed it's actually what we really want.

Let's check the situation:

[50] ~/grocery (master)
$ git log --oneline --graph --decorate --all
* a8c6219 (melons) Add a watermelon
* ef6c382 (berries) Add a blackberry
* 0e8b5cf (HEAD -> master) Add an orange
* e4a5e7b Add an apple
* a57d783 Add a banana to the shopping list

Yay! The bug commit is gone, so nothing is compromised. In the previous message, Git was kind enough to remind us how to recover that commit, just in case; the trick is to directly create a branch that points to that commit, and Git pinned us even the complete command. Let's try it, creating a bug branch:

[51] ~/grocery (master)
$ git branch bug 07b1858

Let's see what happened:

[52] ~/grocery (master)
$ git log --oneline --graph --decorate --all
* 07b1858 (bug) Bug eats all the fruits!
| * a8c6219 (melons) Add a watermelon
| * ef6c382 (berries) Add a blackberry
| * 0e8b5cf (HEAD -> master) Add an orange
|/
* e4a5e7b Add an apple
* a57d783 Add a banana to the shopping list

Wow, that's amazingly simple! The commit is here again, and now we have even a branch to check out if we like.