Git: HEAD, Detached HEAD & Reset HEAD

What is Git HEAD?

“HEAD” is an alias for your current working commit.

It’s the latest commit of your currently checked out branch.

If you checkout a another branch then HEAD will be the latest commit in that particular branch.

Check Git HEAD status

The following shows that the working directory is the tip of the master branch.


> cat .git/HEAD
ref: refs/heads/master

Get commit related to the HEAD


> git rev-parse --short HEAD
32547fa

Get details of the HEAD

It shows the commit ID, commit message, and a textual diff representation of the changes in that commit.


> git show HEAD 

commit 32547fa0c16d99f316965693d647d8775f7c59be (HEAD -> master)
Author: Mukesh Chapagain <example@gmail.com>
Date:   Sat Jan 14 23:09:09 2023 -0500

    Add hello file

diff --git a/hello.md b/hello.md
new file mode 100644
index 0000000..e69de29

The above output is similar to getting details of a particular commit id.


> git show <commit-id>

HEAD vs head

  • HEAD (all capital letters) is a single commit which is the latest commit in the currently working branch. There can be only one HEAD.
  • head (all small letters) is a latest commit of each branch. A repository can have multiple branches. “head” is the the latest commit of each branch. Hence, there can be multiple heads in a single repository.

HEAD^ vs HEAD~ vs HEAD@{}

There are different characters used as suffix to the HEAD (caret (^), tilde (~), at the rate (@)).

HEAD~

HEAD~ = first parent of the commit history
HEAD~~ = two commits older than HEAD
HEAD~2 = two commits older than HEAD
HEAD~~ = HEAD~2
HEAD~ = HEAD~1
HEAD~0 = HEAD

HEAD^ is equal to HEAD~
HEAD^^ is equal to HEAD~~
and so on

HEAD^

HEAD^ = parent of that particular commit
HEAD^1 = HEAD^
HEAD^0 = HEAD

We can also use the caret (^) sign in the commit as well.


> git show 32547fa^

HEAD@{n}

HEAD@{2} = used to view the value of the HEAD in two prior changes that are stored in git reflog
HEAD@{5} = fift prior changes in the HEAD


> git reflog 

5890040 (HEAD -> master) HEAD@{0}: checkout: moving from f0af0dee44a8edff423838425d00a0e88de480c8 to master
f0af0de HEAD@{1}: checkout: moving from master to f0af0de
5890040 (HEAD -> master) HEAD@{2}: commit: Update hello file
32547fa HEAD@{3}: commit: Add hello file
87ea803 (origin/master, origin/HEAD) HEAD@{4}: reset: moving to 87ea803e91c96ac8e7ab0540451cc8cc694eec86
defaa7b HEAD@{5}: commit: Update README
bb9e515 HEAD@{6}: commit: Add test file
87ea803 (origin/master, origin/HEAD) HEAD@{7}: reset: moving to 87ea803e91c96ac8e7ab0540451cc8cc694eec86
c28f3c6 HEAD@{8}: commit: Update README
f1197cd HEAD@{9}: commit: Add test file
87ea803 (origin/master, origin/HEAD) HEAD@{10}: clone: from https://github.com/chapagain/awesome-php.git

> git show HEAD@{2}

commit 589004045c34d8a7de879cb40b8a380ca0cd6c62 (HEAD -> master)
Author: Mukesh Chapagain <example@gmail.com>
Date:   Sun Jan 15 19:26:08 2023 -0500

    Update hello file

diff --git a/hello.md b/hello.md
index e69de29..e965047 100644
--- a/hello.md
+++ b/hello.md
@@ -0,0 +1 @@
+Hello

Git Detached HEAD

HEAD refers to the top/latest commit of your current branch.

If you checkout a particular previous commit then you are in a “detached” HEAD state.


> git log --oneline -n 5 

5890040 (HEAD -> master) Update hello file
32547fa Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes

❯ git checkout 87ea803
Note: switching to '87ea803'.

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

HEAD is now at 87ea803 Update README.md (#904)

Fix Git Detached HEAD

There are two ways to fix the detached HEAD state.

You can either switch to any existing branch

Example:


> git checkout master 

OR, you can create a new branch from the detached HEAD commit location.


> git checkout -b your-new-branch-name

Git Reset HEAD

Resetting to HEAD is useful when you want to reset your uncommited changes in the repository.


> git log --oneline -n 5 

5890040 (HEAD -> master) Update hello file
32547fa Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes

> git status

On branch master
Your branch is ahead of 'origin/master' by 2 commits.
  (use "git push" to publish your local commits)

Git Soft Reset

In the example below, we are using HEAD~1 which SOFT resets the commit history to the previous commit to the HEAD.

  • This command will move the head to the one before the current revision.
  • It only removes the latest commit (as we have used HEAD~1). It will only remove the latest git commit but will keep the latest git add. The files of the latest commits are staged.
  • All your changes to that removed commit will stay.
  • Changes can be seen via git status command.
  • Changes can be added and committed to the repository.

> git reset --soft HEAD~1

> git log --oneline -n 5

32547fa (HEAD -> master) Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes
5a2aaae More clean up

> git status 

On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
    modified:   hello.md

> git commit -m "Update hello file"

Git Mixed Reset

In the example below, we are using HEAD~1 which MIXED resets the commit history to the previous commit to the HEAD.

  • Mixed reset is the default reset option.
  • This will move the HEAD and also update the staging area.
  • So, this will undo git commit and also undo git add.
  • The changes in the files will still be there.

> git reset --mixed HEAD~1
Unstaged changes after reset:
M   hello.md

> git log --oneline -n 5

32547fa (HEAD -> master) Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes
5a2aaae More clean up

> git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
    modified:   hello.md

no changes added to commit (use "git add" and/or "git commit -a")

> git add hello.md 

> git commit -m "Update hello file"

Git Hard Reset

In the example below, we are using HEAD~1 which HARD resets the commit history to the previous commit to the HEAD.

  • Hard reset is the strict reset option.
  • It should be carefully used.
  • This will move the HEAD and also update the staging area.
  • So, this will undo git commit and also undo git add.
  • And, all the changes in the files will be gone.

> git reset --hard HEAD~1
HEAD is now at 32547fa Add hello file

> git log --oneline -n 5

32547fa (HEAD -> master) Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes
5a2aaae More clean up

> git status
On branch master
Your branch is ahead of 'origin/master' by 1 commit.
  (use "git push" to publish your local commits)

nothing added to commit but untracked files present (use "git add" to track)

Undo Git Reset

You can check git reflog and undo any prior reset work (even the hard reset).

Note: Very old commits might not be able to recover due to Git’s garbage-collection to optimize the local repository.


> git reflog -n 15

32547fa (HEAD -> master) HEAD@{0}: reset: moving to HEAD~1
39fdf70 HEAD@{1}: commit: Update hello file
32547fa (HEAD -> master) HEAD@{2}: reset: moving to HEAD~1
bb2594e HEAD@{3}: commit: Update hello file
32547fa (HEAD -> master) HEAD@{4}: reset: moving to HEAD~1
5890040 HEAD@{5}: reset: moving to HEAD
5890040 HEAD@{6}: reset: moving to HEAD
5890040 HEAD@{7}: reset: moving to HEAD
5890040 HEAD@{8}: reset: moving to HEAD
5890040 HEAD@{9}: checkout: moving from 87ea803e91c96ac8e7ab0540451cc8cc694eec86 to master
87ea803 (origin/master, origin/HEAD) HEAD@{10}: checkout: moving from master to 87ea803
5890040 HEAD@{11}: checkout: moving from 87ea803e91c96ac8e7ab0540451cc8cc694eec86 to master
87ea803 (origin/master, origin/HEAD) HEAD@{12}: checkout: moving from master to 87ea803e91c96ac8e7ab0540451cc8cc694eec86
5890040 HEAD@{13}: checkout: moving from f0af0dee44a8edff423838425d00a0e88de480c8 to master
f0af0de HEAD@{14}: checkout: moving from master to f0af0de

> git reset --hard HEAD@{1}
HEAD is now at 39fdf70 Update hello file

> git log --oneline -n 5

39fdf70 (HEAD -> master) Update hello file
32547fa Add hello file
87ea803 (origin/master, origin/HEAD) Update README.md (#904)
00d4c7d Add link
5831a34 Fix dupes

Hope this helps. Thanks.

References: