Git Tools - Revision Selection
# Git Tools - Revision Selection
Git can specify single commits, groups of commits, or ranges of commits in various ways. Knowing all of them is not strictly necessary, but it is useful to be aware of them.
Revision refers to: a commit
# Single Revisions
You can refer to a single commit by its full 40-character SHA-1 hash, but there are more human-friendly ways to do the same thing. This section covers multiple methods for referencing a single commit.
# Short SHA-1
Git is smart enough that you only need to provide the first few characters of a SHA-1 to get the corresponding commit, as long as the partial SHA-1 is at least 4 characters long and unambiguous -- meaning no other object in the database starts with the same SHA-1 prefix.
For example, to examine a specific commit where you know a certain feature was added, first run git log to locate the commit:
$ git log
commit 734713bc047d87bf7eac9674765ae793478c50d3
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
fixed refs handling, added gc auto, updated tests
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
added some blame and merge stuff
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# Viewing a Commit by SHA-1
In this example, suppose the commit you want starts with 1c002dd..... You can inspect that commit using several variations of git show (assuming the short version is unambiguous):
$ git show 1c002dd4b536e7479fe34593e72e6c6c1819e53b
$ git show 1c002dd4b536e7479f
$ git show 1c002d
2
3
Git can generate short, unique abbreviations for SHA-1 values. If you add the --abbrev-commit parameter to git log, the output will show short but unique values; it defaults to seven characters but may use more to avoid SHA-1 ambiguity:
$ git log --abbrev-commit --pretty=oneline
ca82a6d changed the version number
085bb3b removed unnecessary test code
a11bef0 first commit
2
3
4
Typically, 8 to 10 characters are more than enough to ensure uniqueness within a project. For example, as of February 2019, the Linux kernel -- a fairly large Git project -- has over 875,000 commits and seven million objects, yet only the first 12 characters are needed to guarantee uniqueness.
| Note | A brief note about SHA-1. Many people worry that two different objects in their repository could end up with the same SHA-1 hash. What then? If you do commit an object to the repository that hashes to the same SHA-1 value as a previous different object, Git will see that object's hash already exists and assume it was written, using it directly. If you later try to check out that object, you'll get the data from the first object. But this scenario is incredibly unlikely. The SHA-1 digest is 20 bytes (160 bits). You would need 2^80 randomly hashed objects for a 50% probability of a single collision (calculated with p = (n(n-1)/2) * (1/2^160)). 2^80 is 1.2 x 10^24 -- 1,200 times the number of grains of sand on Earth. To illustrate how a SHA-1 collision might occur: if all 6.5 billion humans on Earth were programming, each producing the equivalent of the entire Linux kernel history (6.5 million Git objects) every second and pushing it to one enormous Git repository, it would take about two years before there would be enough objects for a 50% chance of a single SHA-1 collision -- which is still less likely than every member of your programming team being attacked and killed by wolves in unrelated incidents on the same night. |
|---|---|
# Branch References
One straightforward way to reference a specific commit is if it is the tip commit of a branch -- you can use the branch name in any Git command that requires a commit reference.
# Viewing the Last Commit
For example, if you want to view the last commit on a branch and the topic1 branch points to commit ca82a6d..., the following commands are equivalent:
$ git show ca82a6dff817ec66f44342007202690a93763949
$ git show topic1 # topic1 is the branch name
2
If you want to know which specific SHA-1 a branch points to, or see the full SHA-1 for any of the shortened examples, you can use a Git plumbing tool called rev-parse. You can find more about plumbing tools in Git Internals (opens new window). Basically, rev-parse is designed for lower-level operations rather than day-to-day work. But it can sometimes be useful when you want to see exactly what state Git is in. You can run rev-parse on your branch:
$ git rev-parse topic1
ca82a6dff817ec66f44342007202690a93763949
2
# RefLog
# HEAD Pointer History
While you work, Git silently keeps a reference log (reflog) in the background -- a log of where your HEAD and branch references have pointed over the last few months.
You can view the reflog with git reflog:
$ git reflog
734713b HEAD@{0}: commit: fixed refs handling, added gc auto, updated
d921970 HEAD@{1}: merge phedders/rdocs: Merge made by the 'recursive' strategy.
1c002dd HEAD@{2}: commit: added some blame and merge stuff
1c36188 HEAD@{3}: rebase -i (squash): updating HEAD
95df984 HEAD@{4}: commit: # This is a combination of two commits.
1c36188 HEAD@{5}: rebase -i (squash): updating HEAD
7e05da5 HEAD@{6}: rebase -i (pick): updating HEAD
2
3
4
5
6
7
8
Every time your HEAD changes position, Git stores that information in the reflog. You can also use reflog data to get previous commits. If you want to see what HEAD pointed to five steps ago, use the @{n} syntax to reference reflog entries:
$ git show HEAD@{5}
You can also use this syntax to see where a branch pointed at a certain time. For example, to see where your master branch pointed yesterday:
$ git show master@{yesterday}
This shows which commit the master branch tip pointed to yesterday. This only works for data still in your reflog, so you cannot use it to look up commits from several months ago.
You can run git log -g to view reflog information in a format similar to git log output:
$ git log -g master
commit 734713bc047d87bf7eac9674765ae793478c50d3
Reflog: master@{0} (Scott Chacon <schacon@gmail.com>)
Reflog message: commit: fixed refs handling, added gc auto, updated
Author: Scott Chacon <schacon@gmail.com>
Date: Fri Jan 2 18:32:33 2009 -0800
fixed refs handling, added gc auto, updated tests
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Reflog: master@{1} (Scott Chacon <schacon@gmail.com>)
Reflog message: merge phedders/rdocs: Merge made by recursive.
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
It is important to note that reflog information is strictly local -- it is a log of what you have done in your own repository. The reflogs in someone else's copy of the repository will not be the same as yours, and when you freshly clone a repository, the reflog will be empty because you haven't done anything in the repository yet. git show HEAD@{2.months.ago} will only work if you cloned the project at least two months ago -- if you just cloned it, there will be no results.
| Tip | Think of the reflog as Git's version of shell history. If you have a UNIX or Linux background, think of the reflog as Git's version of shell history -- with the emphasis that it is only relevant to you and your session, not to anyone else. |
|---|---|
# Ancestry References
Ancestry references are another way to specify a commit. If you place a ^ (caret) at the end of a reference, Git resolves it to mean the parent of that commit. Suppose your commit history is:
$ git log --pretty=format:'%h %s' --graph
* 734713b fixed refs handling, added gc auto, updated tests
* d921970 Merge commit 'phedders/rdocs'
|\
| * 35cfb2b Some rdoc changes
* | 1c002dd added some blame and merge stuff
|/
* 1c36188 ignore *.gem
* 9b29157 add open3_detach to gemspec file list
2
3
4
5
6
7
8
9
You can use HEAD^ to see the previous commit -- the "parent of HEAD":
$ git show HEAD^
commit d921970aadf03b3cf0e71becdaab3147ba71cdef
Merge: 1c002dd... 35cfb2b...
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 15:08:43 2008 -0800
Merge commit 'phedders/rdocs'
2
3
4
5
6
7
| Note | Escaping the caret on Windows. In Windows cmd.exe, ^ is a special character and must be treated differently. You can either double it or put the commit reference in quotes: $ git show HEAD^ # Does not work on Windows $ git show HEAD^^ # Works $ git show "HEAD^" # Works |
|---|---|
You can also add a number after ^ to specify which parent -- for example, d921970^2 means "the second parent of d921970." This syntax is only useful for merge commits, which have multiple parents. The first parent is the branch you were on when you merged (usually master), and the second parent is the branch you merged (e.g., topic):
$ git show d921970^
commit 1c002dd4b536e7479fe34593e72e6c6c1819e53b
Author: Scott Chacon <schacon@gmail.com>
Date: Thu Dec 11 14:58:32 2008 -0800
added some blame and merge stuff
$ git show d921970^2
commit 35cfb2b795a55793d7cc56a6cc2060b4bb732548
Author: Paul Hedderly <paul+git@mjr.org>
Date: Wed Dec 10 22:22:03 2008 +0000
Some rdoc changes
2
3
4
5
6
7
8
9
10
11
12
13
The other way to specify an ancestor commit is the ~ (tilde). This also refers to the first parent, so HEAD~ and HEAD^ are equivalent. The difference becomes apparent when you add a number. HEAD~2 means "the first parent of the first parent" -- the grandparent. Git follows the chain of first parents the specified number of times. For example, in the history shown earlier, HEAD~3 would be:
$ git show HEAD~3
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date: Fri Nov 7 13:47:59 2008 -0500
ignore *.gem
2
3
4
5
6
This can also be written as HEAD~~~, which is the first parent of the first parent of the first parent:
$ git show HEAD~~~
commit 1c3618887afb5fbcbea25b7c013f4e2114448b8d
Author: Tom Preston-Werner <tom@mojombo.com>
Date: Fri Nov 7 13:47:59 2008 -0500
ignore *.gem
2
3
4
5
6
You can also combine these syntaxes -- HEAD~3^2 refers to the second parent of the reference three generations back (assuming it is a merge commit).
# Commit Ranges
Now that you know how to specify individual commits, let's see how to specify ranges of commits. This is especially useful when managing branches -- you can use commit ranges to answer questions like "What commits are on this branch that haven't been merged into the main branch?"
# Double Dot
The most common range specification syntax is the double dot. This lets Git select commits that are in one branch but not in another. For example, given the following commit history Example history for range selection (opens new window):

Figure 137. Example history for range selection.
You want to see which commits in the experiment branch have not yet been merged into master. You can use master..experiment to have Git show these commits -- meaning "commits in experiment but not in master." For simplicity, the commit object letters from the diagram are used instead of actual log output:
$ git log master..experiment
D
C
2
3
Conversely, if you want to see commits in master that are not in experiment, just swap the branch names. experiment..master shows commits in master but not in experiment:
$ git log experiment..master
F
E
2
3
# Viewing What Will Be Pushed to Remote
This helps you keep the experiment branch up to date and see what you are about to merge. Another common use is to see what you are about to push to a remote:
$ git log origin/master..HEAD
This command shows commits in your current branch that are not in the remote origin. If you run git push and your current branch is tracking origin/master, the commits listed by git log origin/master..HEAD are the ones that will be transferred to the server. If you leave one side of the .. empty, Git defaults to HEAD. For example, git log origin/master.. produces the same result as the previous example -- Git uses HEAD for the omitted side.
# Multiple Points
The double dot syntax is useful, but sometimes you may need more than two branches to determine the revisions you need -- such as finding commits that are in some set of branches but not in your current branch. Git lets you prefix any reference with ^ or --not to indicate branches you don't want commits from. The following three commands are equivalent:
$ git log refA..refB
$ git log ^refA refB
$ git log refB --not refA
2
3
This syntax is powerful because you can specify more than two references in a query, which the double dot syntax cannot. For example, if you want to see all commits included in refA or refB but not in refC, you can use either of:
$ git log refA refB ^refC
$ git log refA refB --not refC
2
This forms a very powerful revision query system for inspecting what is in your branches.
# Triple Dot
The last major range syntax is the triple dot, which selects commits that are included in either of two references but not in both. Looking back at the double dot example history, if you want to see commits that are in either master or experiment but not in both, you can run:
$ git log master...experiment
F
E
D
C
2
3
4
5
This gives you the normal log output sorted by date, with just the four commits shown.
A commonly used parameter with this syntax is --left-right, which shows which side of the range each commit belongs to. This makes the output much clearer:
$ git log --left-right master...experiment
< F
< E
> D
> C
2
3
4
5
With these tools, you can conveniently inspect the commits in your Git repository.