What does git range-diff show, and when is it the right tool?
answer
- Rebased branches share no commit ids
- Compare series, not end states
- A diff of two patches
- Markers pair, drop, add
- Tag the tip before rebasing
basics
~20 sgit range-diff compares two versions of the same patch series — typically a branch before and after a rebase or rewrite — pairing up commits and showing a diff of their diffs, so you can see what actually changed between iterations.
solid answer
~50 sAfter you rebase or amend a branch, a plain diff is useless for the question "what did I change in version 2 of this series?", because the two versions sit on different bases and share no commits. `git range-diff` solves exactly that: given two commit ranges it pairs commits across them by similarity and then, for each pair, shows a **diff of the two patches**. The output marks each pair: `=` for an unchanged commit, `!` for one whose content or message changed (with the nested diff shown), `<` for a commit only in the old range (dropped), and `>` for one only in the new range (added). Invoke it as `git range-diff <base> <old-tip> <new-tip>`, or `git range-diff <old-base>..<old-tip> <new-base>..<new-tip>` when the bases differ. It is the standard way to answer "did my rebase change anything besides moving the branch?".
go deeper
Not expected at this level. Knowing that Git can compare two versions of a rebased branch at all is already a differentiator worth mentioning.
Be able to say what problem range-diff solves — comparing patch series across a rewrite — and give the basic three-argument invocation.
Demonstrate the workflow: mark the tip before a risky rebase, run range-diff afterwards, and read every ! row as a conflict resolution you must justify.
Position it as a verification control for history rewrites at scale: a rewrite touching many branches needs evidence that only the intended content changed, and range-diff is the evidence-producing step.
## The question no ordinary diff can answer You have a branch of six commits. You rebase it onto a moved `main`, fixing a conflict on the way, and amend one commit's message. Now someone asks a fair question: *besides the rebase, what is different?* No ordinary comparison helps. `git diff old-tip new-tip` compares the two end states, so it drowns everything in the unrelated changes `main` accumulated. `git log` shows two disjoint sets of commit ids, because rebasing rewrote every commit. The commits are conceptually "the same patch", but Git has no identity linking them. `git range-diff` is built for this. It takes two *ranges* of commits, decides which commit in the old range corresponds to which in the new, and then diffs the patches themselves rather than the trees. ## Invocation forms Three spellings exist: - `git range-diff <base> <old-tip> <new-tip>` — both series are taken as starting from the same base. - `git range-diff <old-base>..<old-tip> <new-base>..<new-tip>` — the explicit form, used when the series were rebased onto different bases (the common case after a rebase). - `git range-diff <old-tip>...<new-tip>` — the shorthand that derives the bases from the symmetric difference of the two tips. ## Reading the output The output is a numbered table, one row per paired commit, with a marker in the middle: - **`=`** — the two commits produce an identical patch and have the same message: unchanged by the rewrite. - **`!`** — the commits correspond but differ. Underneath, range-diff prints a nested, dual-coloured diff of the two patches. Lines that belong to the underlying patch keep their normal `+`/`-` meaning, while an outer level of markers shows what changed *between the two versions of that patch*. This double nesting is what makes range-diff output look intimidating at first; the trick is to read the outer level only, asking "what is different between v1 and v2 of this commit". - **`<`** — a commit present only in the old range: you dropped or squashed it. - **`>`** — a commit present only in the new range: you added or split one in. Commit messages are compared too, so a reworded commit shows up as `!` even when its code is identical. ## How commits get paired Pairing is heuristic. range-diff scores candidate pairs by how similar their patches are and picks a matching that minimizes overall cost. `--creation-factor=<percent>` tunes how eager it is to treat a commit as "new" rather than as a heavily modified version of an old one: raise it when a substantially rewritten commit is being reported as a drop plus an add rather than as a `!` pair. ## When it earns its place - **Verifying a rebase was clean.** Rebase a long-lived branch, then run range-diff against the pre-rebase tip (which you can recover from the reflog or from a tag you set beforehand). Every row should be `=`; any `!` is a conflict resolution you made, and you get to check that you resolved it correctly instead of trusting yourself. - **Reviewing a second iteration of a patch series.** When someone updates a series after feedback, range-diff shows precisely what they changed since the last version, which is far smaller than re-reading the whole series. - **Auditing a history rewrite.** After rewriting a range of commits, range-diff between the old and new ranges tells you whether the rewrite changed any content it was not supposed to. A practical habit that makes all of this possible: before a risky rebase, mark the current tip — `git branch backup-before-rebase` or simply note the commit id. Without an old tip there is nothing to compare against, though the reflog usually still holds it. ## Limits range-diff compares patches, so it says nothing about whether the resulting tree is correct — a series can pass range-diff with all `=` rows and still be broken because it now sits on a different base whose code changed underneath it. It is also noisy when the rebase had to adapt many hunks to a moved base: those legitimate adaptations show as `!` rows and you must read them to separate necessary adaptation from accidental damage. And it is a comparison tool only: it never changes any ref or file.
- Why can't git diff answer the same question after a rebase?Because the two versions of the branch sit on different bases. Diffing the tips mixes in every change the base accumulated, and diffing individual commits is impossible since the rebase gave every commit a new id. range-diff pairs commits by patch similarity instead of by identity.
- What do the =, !, < and > markers mean in range-diff output?= means the paired commits are identical in patch and message; ! means they correspond but differ, and the nested diff of the two patches follows; < marks a commit present only in the old range, so it was dropped or squashed; > marks one present only in the new range, so it was added or split out.
- How do you get the old tip to compare against after you have already rebased?From the reflog — the pre-rebase position of the branch is still recorded there, and ORIG_HEAD often points at it as well. Better practice is to create a throwaway branch or note the commit id before starting, so the comparison point is explicit rather than reconstructed.
- What does an all-equals range-diff not prove?That the branch still works. Every patch being unchanged says nothing about the new base: the surrounding code may have changed in ways that make an unchanged patch wrong or non-compiling. range-diff verifies the rewrite, not the result; tests verify the result.
saying these in an interview costs you the question
- Uses git diff between pre- and post-rebase tips
- Thinks range-diff modifies or repairs history
- Expects commit ids to survive a rebase
- Reads only the inner diff level
- Treats all-equal output as proof the branch works