List and Compare Commits with git rev-list

Reach for git rev-list when you need commit IDs a script can consume, not a pretty log a human reads. It lists commit IDs, compares the history of two refs, restricts the walk to a path, and feeds the result into other commands. The examples were checked with Git 2.43.0. Allow about ten minutes.

1. Choose a starting ref

git rev-list walks parent links from one or more commits. With no extra ordering option it prints commits in reverse chronological order, newest first. Start with the current commit:

$ git rev-list --max-count=5 --oneline HEAD
47060a6 main change
704573f update app
8b96128 initial

Your abbreviated IDs and subjects will differ. HEAD means the commit currently checked out; swap in a branch, tag or full object ID for a different starting point. --max-count=5 only limits the result, it does not alter history.

Checkpoint: if Git says HEAD is ambiguous or cannot be resolved, check that you are in the intended repository with git rev-parse --show-toplevel, then look at available refs with git branch --all and git tag.

2. Read a range as set subtraction

The most useful mental model here is set subtraction. A ref on its own includes its reachable ancestors. A caret excludes a ref and everything reachable from it. The two-dot form is shorthand for that subtraction:

$ git rev-list --oneline main..topic
40ab292 topic change

This asks for commits reachable from topic but not from main, the usual answer to "what would this branch add compared with main?" Direction matters: topic..main asks what main has that topic lacks.

Spell out the long form when teaching or debugging a complicated command:

$ git rev-list --oneline topic ^main
40ab292 topic change

There is also a three-dot symmetric difference, listing commits reachable from either side but not both, useful for seeing both branches' unique work:

$ git rev-list --left-right --oneline main...topic
< 47060a6 main change
> 40ab292 topic change

The markers identify the side, not a severity or merge status. Add --left-only or --right-only if you only want one side. These range expressions select commits; they do not compare file contents or produce a patch.

3. Limit the walk to a file or directory

Put -- before paths so Git can tell a path apart from a ref or option. This lists commits in HEAD's history that touched app.txt:

$ git rev-list --oneline HEAD -- app.txt
704573f update app
8b96128 initial

The separator is a good habit even when the path looks unambiguous. For a directory, give the directory path instead:

$ git rev-list --oneline HEAD -- Documentation/

Path limiting has a subtle default: history simplification can drop commits that do not look necessary to explain the final state of the selected path. If you are auditing full ancestry rather than looking for a readable story, try --full-history and compare the result. Merge-heavy histories may need further decisions about simplification, so do not treat a short path-limited list as proof that no related merge exists.

Checkpoint: confirm the path is spelled relative to the repository root. git rev-parse --show-prefix tells you the prefix when you are in a subdirectory, and a path matching no tracked file produces no commits without raising a syntax error.

4. Produce output a script can consume

For a human, --oneline is compact. For a script, use a format that states exactly which fields it emits. --format adds a formatted line for each commit:

$ git rev-list --format='%H%x09%an%x09%s' --max-count=2 HEAD
commit 47060a667a0a7b31569f020d84e6fb844727e8d3
47060a667a0a7b31569f020d84e6fb844727e8d3	Guide	main change
commit 704573f4239de6d9c4e16a044ba40c2393bb4380
704573f4239de6d9c4e16a044ba40c2393bb4380	Guide	update app

The extra commit ... lines are part of git rev-list --format's output. If a consumer needs exactly one line per commit, use --pretty=format:... instead and choose placeholders deliberately:

$ git rev-list --pretty=format:'%H%x09%an%x09%s' --max-count=2 HEAD
47060a667a0a7b31569f020d84e6fb844727e8d3	Guide	main change
704573f4239de6d9c4e16a044ba40c2393bb4380	Guide	update app

Treat subjects as data: they can contain spaces and punctuation. For dependable machine processing, pick a delimiter your input policy excludes, or hand the IDs to another Git command rather than splitting arbitrary commit messages on spaces.

5. Separate selection from ordering

Options that select commits are applied before ordering and formatting. Combine a date limit with a count when you need a small recent sample:

$ git rev-list --since='30 days ago' --max-count=20 --oneline HEAD

--since and its synonym --after select commits newer than the date given. The date text is interpreted by Git, so use an explicit date such as 2026-01-01 when reproducibility matters. Add --reverse after selection when you need oldest first, for example to replay a list in ancestry order:

$ git rev-list --reverse --oneline main..topic

This is still read-only. Do not confuse --reverse with reversing parent links or changing commits; it only changes the order printed.

6. Use exit status for a quiet connectivity check

When you only need to know whether Git can walk the requested range, add --quiet. It suppresses normal output and is faster than redirecting output, because Git skips formatting the commits:

if git rev-list --quiet --max-count=1 HEAD; then
    printf '%s\n' 'at least one reachable commit'
else
    status=$?
    printf 'rev-list failed with status %s\n' "$status" &> /dev/stderr
    exit "$status"
fi

Do not treat a successful quiet check as proof that a branch is current, clean or safely merged. It only reports the result of this traversal request; run git status separately when working-tree state matters.

Common traps

Done means