Darek Kay's picture
Darek Kay
Solving web mysteries

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.

git log

The git log command lists all commits that are reachable from a certain commit.

git log feature

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Highlighted commits: H, G, C, B, A.

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

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. All commits are highlighted.

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

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Highlighted commits: H, G.

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 graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Highlighted commits: F, E, D, H, G.

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

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Red commits: F, E, D. Green commits: H, G.

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

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Red commits: H, G. Green commits: F, E, D.

The triple dot notation is useful for displaying only the changes from a certain branch:

git diff main...feature
+G
+H

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Green commits: H, G.

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

Git graph. Branch main points to F→E→D→C→B→A. Branch feature points to H→G→C→B→A. Green commit: C.

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:

GitHub screenshot with two commits G and H.

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:

GitHub screenshot with two changed files.

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:

Git log cheatsheet


Git diff cheatsheet


Related posts

Git explained: Commit ranges