git HEAD~ vs HEAD^ vs HEAD@{} Explained with Examples

 

Understanding HEAD~ vs HEAD^ vs HEAD@{} - Brief comparison

  • The tilde (~), caret(^) and at-sign(@) are reference suffixes used in GIT
  • The tilde(~) sign refers to the first parent in the commit history. HEAD~ is always the same as HEAD^, similarly HEAD~~ is always the same as HEAD^^, and so on.
  • The caret(^) sign refer to the parent of that particular commit. So, if you place a ^ (caret) at the end of a commit reference, Git resolves it to mean the parent of that commit.
  • The at-sign (@), without a leading branch/reference name and ordinal {n} suffix like HEAD@{1} and master@{1}, is just a synonym/alias/shortcut for the special Git reference HEAD
  • A suffix ^ to a revision parameter means the first parent of that commit object. ^<n> means the <n>th parent (i.e. <rev>^ is equivalent to <rev>^1). As a special rule, <rev>^0 means the commit itself and is used when <rev> is the object name of a tag object that refers to a commit object.
  • A suffix ^ followed by an at-sign (@) is the same as listing all parents of <rev> (meaning, include anything reachable from its parents, but not the commit itself).

You may not be able to understand the comparison completely, so let's take multiple examples to understand and compare HEAD^ vs HEAD~ and HEAD@{}.

 

Lab Environment

To demonstrate this example, I have created a new repository on gitlab and have done some dummy commits. First we will clone the repository:

Advertisement
deepak@ubuntu:~$ git clone git@gitlab.com:golinuxcloud/git_examples.git
Cloning into 'git_examples'...
remote: Enumerating objects: 3, done.
remote: Counting objects: 100% (3/3), done.
remote: Compressing objects: 100% (2/2), done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.

As you can check below, I have three branches in this repo i.e. main, dev and prod. I have simultaneously done commits in all three branches.

echo 1 >> file
git add file
git commit -m "main-commit-1" -a
echo 1 >> file
git commit -m "main-commit-2" -a
git checkout -b dev
echo 1 >> dev
git add dev
git commit -m "dev-commit-1" -a
git checkout -b prod
echo 1 >> prod
git add prod
git commit -m "prod-commit-1" -a
git switch main
echo 1 >> file
git commit -m "main-commit-3" -a
git switch dev
echo 1 >> dev
git commit -m "dev-commit-2" -a
git switch prod
echo 1 >> prod
git commit -m "prod-commit-2" -a
git switch main
git merge -m "Merged dev" dev
echo 1 >> file
git commit -m "main-commit-4" -a
git merge -m "Merged prod" prod

 

Display commit graph in git

Here is a graphical representation of the commit history on all the branches from our repository.

deepak@ubuntu:~/git_examples$ git log --graph --decorate --oneline

Sample Output:
git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

 

How to get all the parent commit ID from commit history in Git

Since we will be dealing with parents when using tilde (~) or caret (^) sign, so we should also be familiar with the ways to check parent of any commit.

Based on our commit history, to list the available parents we can use:

deepak@ubuntu:~/git_examples$ git log --no-walk HEAD^@
commit 6850575b9e6e19d7144b4dec70b29efbcf7c4777
Author: Deepak Prasad <admin@golinuxcloud.com>
Date:   Tue Sep 21 08:33:04 2021 +0530

    main-commit-4

commit 93adddfc946b331daf983be6d14eab7f421053d6 (prod)
Author: Deepak Prasad <admin@golinuxcloud.com>
Date:   Tue Sep 21 08:33:04 2021 +0530

    prod-commit-2

Here, we used ^@ to get all parents and the --no-walk option to show only the parents and not their ancestors. So in our commit history we have two parent commit IDs i.e.:

Advertisement
commit 6850575b9e6e19d7144b4dec70b29efbcf7c4777
commit 93adddfc946b331daf983be6d14eab7f421053d6 (prod)

 

How to get the parent details of any commit ID in Git

We can use following command to get the parent ID of individual commits in Git:

To get the parent for 6850575 and f5aaccb commit ID respectively:

deepak@ubuntu:~/git_examples$ git log --pretty=%P -n 1 6850575
28a60d8fffbe3a613ba8b3da501f37d215c177c2

deepak@ubuntu:~/git_examples$ git log --pretty=%P -n 1 f5aaccb
e37fd194328c1c9b0ac062017ad0e68700a09552

Now that we are familiar with the commands to get the parent of any commit, lets start using HEAD^, HEAD~ in our examples to explain the difference.

 

Compare HEAD^ vs HEAD~ vs HEAD@{n}

The special characters i.e. tilde(~), caret(^) and at-sign(@) are to be used with a particular commit ID to get respective details. We are using HEAD so you have to understand that position of HEAD can be different for individual branch.

Accordingly the commit history varies for each branch on the repository, so you should cautious about the branch which you want to use to get the required details.

Commit IDUsing Tilde (~)Using at-sign (@{})Using Caret (^)Using Tilde (~) and Caret (^) BothComments
40a90b4 (Merged prod)HEAD~0HEAD@{0}Prod branch merged into main
93adddf (prod-commit-2)HEAD@{4}HEAD^2HEAD^2~0
6792e83 (prod-commit-1)HEAD@{5}
HEAD@{10}
HEAD^2~1
cfbf79c (dev-commit-1)HEAD@{7}
HEAD@{11}
HEAD@{12}
HEAD^2~2
da6553f (dev-commit-2)HEAD@{6}
6850575 (main-commit-4)HEAD~1
HEAD~
HEAD@{1}HEAD^1HEAD^1~0
28a60d8 (Merged dev)HEAD~2
HEAD~~
HEAD@{2}HEAD^1~1dev branch merged into main
f5aaccb (main-commit-3)HEAD~3
HEAD~~~
HEAD@{3}
HEAD@{8}
HEAD^1~2
e37fd19 (main-commit-2)HEAD~4
HEAD~~~~
HEAD@{9}
HEAD@{13}
HEAD@{14}
HEAD^1~3
HEAD^2~3
Since this commit was done before creating any branch hence it is part of both the available parents
9059692 (main-commit-1)HEAD~5
HEAD~~~~~
HEAD@{0}
HEAD@{15}
HEAD^1~4
HEAD^2~4
Since this commit was done before creating any branch hence it is part of both the available parents
a6b2526 (Initial commit)HEAD~6
HEAD~~~~~~
HEAD@{16}HEAD^1~5
HEAD^2~5
Since this commit was done before creating any branch hence it is part of both the available parents

 

In this section we will check the commit history from our main branch:

git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

The following two commit IDs are where we have merged two branches into the main branch.

28a60d8 Merged dev
40a90b4 (HEAD -> main) Merged prod

Here,

Advertisement
  • The commit cfbf79c, da6553f are done on dev branch
  • The prod branch was started from dev branch. On prod branch we have 6792e83 and 93adddf commit.

Now we can go ahead an query individual commit to get their parent details:

git show --oneline HEAD

This will give you the last commit in the history which will be the parent for HEAD:
git HEAD~ vs HEAD^ vs HEAD@{} Explained with Examples

We can now add caret (^) sign to HEAD to get the parent of last commit i.e. from HEAD

$ git show --oneline HEAD^

OR

$ git show --oneline 40a90b4^

Sample Output:
git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

We can add ^^ or ^2 with HEAD or the last commit ID to get parent of our last parent i.e. 6850575 (so that should be technically grand parent I assume)

$ git show --oneline HEAD^2

OR

$ git show --oneline 40a90b4^2

Sample Output:
git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

 

List all commits using HEAD with tilde(~) sign

Here is a representation of all the commits in my repository using HEAD with tilde(~) sign:

git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

List of all commits using both caret(^) when combined with tilde(~) sign

Here is a representation of all the commits using both tilde(~) and caret(^) sign for HEAD^1:

$ git log --oneline --graph HEAD^1

Sample Output:

git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

 

Here is a representation of all the commits using both tilde(~) and caret(^) sign for HEAD^2:

$ git log --oneline --graph HEAD^2

Sample Output:

git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

 

List of all commits using at-sign (@)

Here is a representation of all the commits using at-sign(@{}):

$ git reflog

Sample Output:

git HEAD~ vs HEAD^ vs HEAD@{} Explained [Easy Examples]

 

Summary

In this tutorial we learned the difference between the usage of tilde(~), caret(^) and at-sign(@{}) used in git. These suffix can be preferred in use cases where we don't have to deal with multiple parents in the commit history. You can always first locate the respective parent commit id and then use both tilde and caret sign together to refer individual commits part of the respective parent. Ideally if HEAD was a merge, then first parent is the branch into which we merged and second parent is the branch we merged.

 

References

Git Tools - Revision Selection
HEAD~ vs HEAD^ vs HEAD@{} also known as tilde vs caret vs at sign

 

Didn't find what you were looking for? Perform a quick search across GoLinuxCloud

If my articles on GoLinuxCloud has helped you, kindly consider buying me a coffee as a token of appreciation.

Buy GoLinuxCloud a Coffee

For any other feedbacks or questions you can either use the comments section or contact me form.

Thank You for your support!!

Leave a Comment