Git Basics: Compare Two Versions of a Patch Series
After revising a branch, a normal tree diff shows the final file changes but loses the patch-by-patch review context. Build two tiny local series, retaining both with tags, and compare the revisions before sharing either.
Prepare a scratch directory
Tested on Linux with Git 2.52.0 and GNU Bash 5.2.37. You need Bash, Git, a text editor, and standard mktemp, rm, rmdir, cat, head, wc, and sed utilities. Run from an ordinary shell without repository-selection variables such as GIT_DIR, GIT_WORK_TREE, or GIT_INDEX_FILE set. The script isolates global and system Git configuration and uses fictional identities only in its temporary repository.
Keep the same terminal open. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as demonstrate-range-diff.sh:
#!/usr/bin/env bash
set -euo pipefail
export LC_ALL=C
fixture_dir="$(mktemp -d)"
trap 'rm -rf -- "$fixture_dir"' EXIT
export GIT_CONFIG_NOSYSTEM=1
export GIT_CONFIG_GLOBAL=/dev/null
export GIT_AUTHOR_NAME='Example Author'
export GIT_AUTHOR_EMAIL='author@example.invalid'
export GIT_COMMITTER_NAME="$GIT_AUTHOR_NAME"
export GIT_COMMITTER_EMAIL="$GIT_AUTHOR_EMAIL"
export GIT_AUTHOR_DATE='2026-10-29T00:00:00+00:00'
export GIT_COMMITTER_DATE="$GIT_AUTHOR_DATE"
git init -q --object-format=sha1 -b main "$fixture_dir/repo"
cd -- "$fixture_dir/repo"
git config commit.gpgsign false
git config core.hooksPath /dev/null
printf 'timeout=10\n' > app.conf
git add app.conf
git commit -qm 'Start configuration'
git tag base
printf 'timeout=20\nretries=3\nbackoff=linear\nlogging=on\nmode=safe\n' > app.conf
git commit -qam 'Raise timeout'
printf 'Enable debug logging for diagnosis.\n' > notes.txt
git add notes.txt
git commit -qm 'Add diagnostic notes'
git tag version-one
git switch -q -c revised base
printf 'timeout=30\nretries=3\nbackoff=linear\nlogging=on\nmode=safe\n' > app.conf
git commit -qam 'Raise timeout'
printf 'Enable debug logging for diagnosis.\n' > notes.txt
git add notes.txt
git commit -qm 'Add diagnostic notes'
git tag version-two
git range-diff --no-color --creation-factor=90 base..version-one base..version-two
test "$(git show version-one:app.conf | head -1)" = 'timeout=20'
test "$(git show version-two:app.conf | head -1)" = 'timeout=30'
git diff --exit-code version-one version-two -- notes.txtRun it:
bash demonstrate-range-diff.shObserved output:
1: c773890 ! 1: 3577096 Raise timeout
@@ Commit message
## app.conf ##
@@
-timeout=10
-+timeout=20
++timeout=30
+retries=3
+backoff=linear
+logging=on
2: d693143 = 2: 1947b67 Add diagnostic notesRead the result
The first row pairs a changed patch with !; the inner diff shows the timeout changing from 20 to 30 between versions. The second row uses = because its patch is unchanged, even though its parent and commit ID differ. The fixture uses --creation-factor=90 to encourage pairing similar patches: without that adjustment, this tiny first patch was classified as a deletion and addition on the tested Git version.
Apply it carefully
The two-dot ranges exclude base and include commits reachable from each version tag. Pairing is heuristic, so inspect the underlying patches when a match looks surprising. Increasing the creation factor makes Git less willing to treat a pair as unrelated. This human review output is not a stable parsing format or an applicable patch. Commit IDs may differ with another Git version or environment.
The official Git reference documents these options. The script creates and removes only its own temporary fixture; it does not contact a remote or change your existing repository.
Clean up
Remove the saved script, return to the starting directory, and remove the empty practice directory:
rm -- demonstrate-range-diff.sh
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect its remaining files rather than deleting unknown content.
Acknowledgements
This post was created with assistance from AI.