skip to content

In git diff, what does A...B compare that A..B does not?

level: middleimportance: should knowfreq 55%

answer

  1. Count the dots, change the question
  2. One form consults the graph
  3. Common ancestor does the work
  4. State difference versus branch contribution
  5. git log reverses the convention

basics

~20 s

git diff A..B compares the two commits' trees directly, identical to git diff A B. git diff A...B compares the merge base of A and B against B, so it shows only what B added, ignoring changes made on A since they diverged.

solid answer

~40 s

`git diff A..B` is just a synonym for `git diff A B`: it compares the tree of A with the tree of B, so anything that changed on A since the branches split appears in the output as a **reverse** change, which is usually not what you want when reviewing a feature branch. `git diff A...B` (three dots) first finds the **merge base** of A and B — their common ancestor — and diffs from there to B. That is the change the feature branch actually introduces, the same view a code review of the branch shows. The trap is that three dots means something different in `git log`, where `A...B` is the symmetric difference of the two commit sets. Same punctuation, different operation, because one command compares trees and the other selects commits.

go deeper

for a junior

Recall that git diff main...feature is the right way to see what a feature branch adds, and that the plain two-dot form can show other people's work as if you deleted it.

for a middle

Explain the three-dot form as diffing from the merge base to the right-hand commit, and show you can rewrite it manually with git merge-base.

for a senior

Explain why the notation is inverted between git diff and git log — sets of commits versus pairs of trees — and know the degenerate cases: ancestor relationships and unrelated histories.

for a principal

Frame it as a reviewability question: teams that habitually read state diffs instead of contribution diffs mis-attribute changes and waste review cycles; standardize the form in tooling and docs.

## The two forms `git diff` accepts two revisions and produces the difference between their trees. Two spellings exist for supplying them: - **`git diff A B`** and **`git diff A..B`** are exactly equivalent. Git compares the tree recorded at commit A with the tree recorded at commit B, line for line, with no regard for how the two are related in the history graph. - **`git diff A...B`** is defined as `git diff $(git merge-base A B) B`. Git first computes the best common ancestor of the two commits and then diffs from that ancestor to B. ## Why the difference matters Suppose `main` and `feature` diverged, and since then both moved: `feature` added a new endpoint, and `main` received an unrelated bugfix from someone else. `git diff main..feature` compares the two current trees. It shows your endpoint as an addition **and** shows the other person's bugfix as a *removal*, because that fix exists on `main` but not on `feature`. Reading that output, it looks as though your branch deleted someone else's work. It did not — the two trees simply differ in both directions. `git diff main...feature` diffs from the point where the branches split up to `feature`. The bugfix is not in the merge base and not in `feature`, so it never appears. What remains is precisely the change your branch introduces: the review-shaped view. ## The mental model Two dots asks a **state** question: how do these two snapshots differ right now? Three dots asks a **contribution** question: what did this branch add relative to where it left the other one? A consequence worth stating plainly: `git diff A..B` is symmetric in the sense that swapping the arguments only reverses the patch, while `git diff A...B` is genuinely asymmetric — `git diff main...feature` and `git diff feature...main` compute different things, because the merge base is the same but the right-hand tree changes. ## The collision with git log This is the single most-asked follow-up, and it catches experienced people. In revision-walking commands such as `git log`: - `A..B` means commits reachable from B but not from A — "what is on B that is not on A". - `A...B` means the **symmetric difference**: commits reachable from either but not from both, i.e. everything unique to each side. So in `git log`, three dots widens the result to include both sides. In `git diff`, three dots *narrows* the result to one side's contribution. The notations look parallel and behave nearly oppositely. The reason is structural: `git log` selects a **set of commits**, and set operations are the natural meaning; `git diff` compares **two trees**, and there is no such thing as diffing a set, so Git repurposed the syntax to mean "use the merge base". A useful pairing to remember: `git log main..feature` (the commits your branch adds) lines up conceptually with `git diff main...feature` (the changes your branch adds). The dot counts differ between the two commands even though the question is the same. ## Degenerate cases - If A is an ancestor of B — a fast-forward relationship — the merge base *is* A, so `git diff A..B` and `git diff A...B` produce identical output. This is why the distinction often stays invisible until a branch falls behind. - If the two histories are unrelated with no common ancestor, `git diff A...B` fails because there is no merge base to diff from. - With only one argument, `git diff A` compares A to your **working tree**, which is a third behaviour again. ## Practical use When you want to review what a branch contributes before merging, reach for the three-dot form or, equivalently, `git diff $(git merge-base main feature) feature`. When you want to know how two deployed states differ — for example, what is actually different between the tree at tag `v2.0` and the tree at tag `v2.1` — use the two-dot form, because there you genuinely care about the end states rather than about anyone's contribution. Combining either form with `--stat` first is a good habit on large branches: get the file-level summary, then drill into individual paths with `git diff main...feature -- src/`.

  • Why does the three-dot notation mean something different in git log than in git diff?
    git log selects a set of commits, so A...B naturally means the symmetric difference — everything unique to either side. git diff compares two trees and cannot diff a set, so Git reused the notation to mean "diff from the merge base to the right-hand commit". Same punctuation, different underlying operation.
  • When do git diff main..feature and git diff main...feature produce identical output?
    When main is an ancestor of feature, because then the merge base is main itself. That is why the distinction is invisible on a freshly branched or freshly rebased branch and only bites once main has moved ahead independently.
  • How would you write the three-dot diff without using the three-dot syntax?
    git diff $(git merge-base main feature) feature. It computes the same common ancestor explicitly and diffs from there to the branch tip, which is a useful way to prove to yourself what the notation is doing.
  • What happens if the two revisions share no common ancestor?
    The three-dot form fails because there is no merge base to diff from, while the two-dot form still works — it just compares the two trees directly. Unrelated histories arise when repositories are joined or a branch was created with an orphan checkout.

saying these in an interview costs you the question

  • Says three dots means both directions in diff
  • Assumes A..B and A...B always agree
  • Reads the other branch's work as deletions
  • Applies git log's range meaning to git diff
  • Thinks the merge base is the branch's first commit

context