Git explained: Commit ranges
Git's log
and diff
commands are useful for inspecting your repository changes. Both commands accept ranges of commits in different formats, which can be confusing. In this post, I'll shed some light on the differences between a b
, a..b
and a...b
commit ranges. Check out the repository that I'll be using as an example.
This is part 2 of my Git explained series.
- Part 1: Rewriting history
- Part 2: Commit ranges
git log
The git log command lists all commits that are reachable from a certain commit.
git log feature
You can also specify multiple commits separated by a space, which will list all commits that are reachable from any of them. In that case, the branch order makes no difference:
git log main feature
git log feature main
You might want to exclude certain commits from git log
. The following commands are equivalent and will list all commits that are reachable from feature
but not from main
:
git log main..feature
git log ^main feature
git log feature --not main
Another special notation is the triple dot, which excludes the common ancestor of two commits. The following example lists all commits that are reachable from either feature
or main
, but not both of them. The branch order doesn't matter:
git log main...feature
git log feature...main
git diff
The git diff command displays the differences (changes) between commits.
Separating two commits by either a space or a double dot will show the full difference between those commits:
git diff main feature
git diff main..feature
-D
-E
-F
+G
+H
Another way to think about this: what changes have to be applied to move from main
to feature
? In the previous example, we need to first "revert" commits F
, E
, D
and then apply commits G
, H
.
The branch order is important:
git diff feature main
git diff feature..main
+D
+E
+F
-G
-H
The triple dot notation is useful for displaying only the changes from a certain branch:
git diff main...feature
+G
+H
This example displays only the differences in feature
compared to main
.
git merge-base
The git merge-base command displays the first common ancestor of multiple commits:
git merge-base main feature
git merge-base feature main
Examples
Here are some common use cases for log
and diff
commands.
Pull request preview
GitHub's pull request view uses git log
and git diff
under the hood. For example, the "Commits" tab displays all commits from feature
that will be merged into main
:
If you want to preview the commits before creating your pull request, here's the command:
git log main..feature --reverse
* 11dcc47 - G
* b8434e1 - H
The "Files changed" tab displays the actual changes to be applied:
Here's how to reproduce this locally:
git diff main...feature
+ G
+ H
Check whether a branch contains a commit
Let's assume we want to know whether our feature
branch contains (= can reach) commit d0bb602
. In this case, we can use the following command:
git branch --contains d0bb602 | grep feature
A more powerful way is to use git log
, as it works with more than branches:
git log feature --format=format:%H | grep d0bb602
Another solution is to use commit ranges, as explained in this post:
git log d0bb602 ^feature
If feature
can reach d0bb602
, there'll be no output, as we specifically exclude feature
from the log. Otherwise, the command will output all commits that d0bb602
can reach but feature
can't.
Amend diff
If you're used to rewriting history, amend
is one of your best friends. In the following example, commit 5c7a82
is missing some changes. To keep the Git history clean, we've amended those changes (2b049ea
):
* 2b049ea - (HEAD -> feature) Fix defect 47
| * 5c7a782 - (origin/feature) Fix defect 47
|/
* fb2546f - Add checkout page
We might want to verify what we've amended before pushing our changes:
git diff origin/feature feature
- Hello
+ Hello world
The change looks good, so we'd like to push it. The original commit 5c7a782
is public, but it's on our own branch, so we decide to "force push" with git push --force-with-lease
.
But what if someone else works on the same branch? After fetching feature
, they'll see the inverted graph:
* 2b049ea - (origin/feature) Fix defect 47
| * 5c7a782 - (HEAD -> feature) Fix defect 47
|/
* fb2546f - Add checkout page
Now they'll be interested in the changes on origin/feature
compared to their local commit (the one before our amend
). In other words, what change needs to be applied to get from feature
to origin/feature
:
git diff feature origin/feature
- Hello
+ Hello world
Notice how the diff is the same as before, but the command looks different this time because of the commit order.
Cheat sheets
If you're using this post as a reference, here's all the information in one place: