Table of Contents
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 asHEAD^
, similarlyHEAD~~
is always the same asHEAD^^
, 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 likeHEAD@{1}
andmaster@{1}
, is just a synonym/alias/shortcut for the special Git referenceHEAD
- 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:
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:
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.:
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 ID | Using Tilde (~ ) |
Using at-sign (@{} ) |
Using Caret (^ ) |
Using Tilde (~ ) and Caret (^ ) Both |
Comments |
---|---|---|---|---|---|
40a90b4 (Merged prod) |
HEAD~0 |
HEAD@{0} |
Prod branch merged into main | ||
93adddf (prod-commit-2) |
HEAD@{4} |
HEAD^2 |
HEAD^2~0 |
||
6792e83 (prod-commit-1) |
HEAD@{5} |
HEAD^2~1 |
|||
cfbf79c (dev-commit-1) |
HEAD@{7} |
HEAD^2~2 |
|||
da6553f (dev-commit-2) |
HEAD@{6} |
||||
6850575 (main-commit-4) |
HEAD~1 |
HEAD@{1} |
HEAD^1 |
HEAD^1~0 |
|
28a60d8 (Merged dev) |
HEAD~2 |
HEAD@{2} |
HEAD^1~1 |
dev branch merged into main | |
f5aaccb (main-commit-3) |
HEAD~3 |
HEAD@{3} |
HEAD^1~2 |
||
e37fd19 (main-commit-2) |
HEAD~4 |
HEAD@{9} |
HEAD^1~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@{0} |
HEAD^1~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@{16} |
HEAD^1~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:
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,
- The commit
cfbf79c
,da6553f
are done on dev branch - The prod branch was started from dev branch. On prod branch we have
6792e83
and93adddf
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:
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:
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:
List all commits using HEAD with tilde(~) sign
Here is a representation of all the commits in my repository using HEAD with tilde(~
) sign:
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:
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:
List of all commits using at-sign (@)
Here is a representation of all the commits using at-sign(@{}
):
$ git reflog
Sample Output:
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