skip to content

In git log, what does main..feature select, and how does main...feature differ?

level: middleimportance: must knowfreq 68%

answer

  1. Ranges are set operations, not timelines
  2. Two dots is subtraction
  3. Three dots is symmetric difference
  4. Same punctuation means something else to git diff
  5. --left-right labels which side each commit came from

basics

~20 s

In git log, main..feature lists commits reachable from feature but not from main. main...feature lists the symmetric difference: commits reachable from either side but not both. In git diff, three dots means something else — merge base against the right side.

solid answer

~40 s

`main..feature` is set subtraction over reachability: everything reachable from `feature` minus everything reachable from `main`. It is shorthand for `git log feature ^main`, and it answers "what is on this branch that master doesn't have yet". `main...feature` is the symmetric difference — commits on either side that the other lacks — and `--left-right` marks each with `<` or `>` so you can see which side it came from. Two crucial cautions. First, ranges are about **reachability**, not time, so a fresh commit on `main` never appears in `main..feature`. Second, `git diff` reuses the same punctuation with different meaning: `git diff A..B` is just A versus B, while `git diff A...B` diffs the merge base of A and B against B, which is why it is the usual way to see "what this branch changed".

code

bash · 5 lines
bash
git log --oneline main..feature
git log --oneline --left-right main...feature
git rev-list --count main..feature
git rev-list --left-right --count main...feature
git diff main...feature

go deeper

for a junior

Know that git log main..feature shows the commits your branch adds on top of main, and that the order of the two names matters.

for a middle

Explain ranges as reachability set operations — two dots as subtraction, three dots as symmetric difference — and state that git diff reinterprets three dots as merge-base versus the right side.

for a senior

Show how you use ranges operationally: counting divergence with rev-list --left-right --count, checking whether a fix is contained, and picking the right form when reviewing a long-lived branch.

for a principal

Be ready to define what "contained in the release" means for your organisation in reachability terms, and to standardise the commands people use to answer it.

## Ranges are set operations on reachability A "revision range" in Git is not a slice of time and not a slice of a branch. It is a set of commits defined by reachability over parent edges. The building block is `^X`, meaning "exclude everything reachable from X". Then: - `A..B` is exactly `B ^A` — every commit reachable from B, minus every commit reachable from A. - `A...B` is the **symmetric difference** — reachable from A or B, but not from both. `git log main..feature` therefore prints the commits your branch adds on top of what `main` already contains. If someone pushed new commits to `main` afterwards, those do not show up: they are reachable from `main`, so they are excluded, and they were never reachable from `feature` anyway. ## Reading the two forms The two-dot form is the one you want almost always. Typical uses: `git log --oneline main..feature` before opening a change for review, `git log @{u}..` to see what you have locally that is not pushed yet, and `git rev-list --count main..feature` to count the commits a branch adds. The three-dot form shines when both sides have moved. `git log --left-right --oneline main...feature` marks commits from the left side with `<` and the right side with `>`, giving you a divergence report in one command. `git rev-list --left-right --count main...feature` collapses that to two numbers — how many commits each side has that the other does not. Omitting a side defaults it to HEAD: `..feature` means `HEAD..feature` and `main..` means `main..HEAD`. ## The diff trap The same punctuation means something different to `git diff`, and this catches experienced people out. - `git diff A B` and `git diff A..B` are identical: compare the two snapshots directly. - `git diff A...B` compares the **merge base** of A and B against B. In other words, it shows only what B changed since the branches parted, hiding changes A made in the meantime. So `git log A...B` (symmetric, both sides) and `git diff A...B` (one-sided, from the fork point) do not correspond to each other at all. When someone asks "what does my branch change", `git diff main...feature` is usually the honest answer, because a plain `git diff main feature` also reports, in reverse, every change `main` picked up that your branch lacks. ## Ranges are not linear A range does not have to be an ancestor relationship. If A and B are unrelated, `A..B` is simply everything reachable from B, since nothing is excluded. If B is an ancestor of A, `A..B` is empty — which is the fastest way to test "is this branch fully merged?": if `git log feature..main` is empty, `main` contains nothing new for you; if `git rev-list --count main..feature` is `0`, your branch adds nothing. Also remember that a range can select merge commits whose side branches are excluded, so the output is a set of commits, not a contiguous path. `--first-parent` or `--no-merges` narrow it when you want a simpler list. ## Verifying your intuition When unsure, resolve the set rather than argue about it: `git rev-list --count main..feature` and `git rev-list --left-right --count main...feature` are cheap and unambiguous, and a quick `git log --graph --oneline main...feature` makes the shape obvious.

  • Why does git diff main...feature usually show what a branch changed, while git diff main feature does not?
    The three-dot form in `git diff` compares the merge base of the two revisions against the right-hand side, so it shows only what `feature` added since the branches diverged. The two-revision form compares the tips directly, so anything `main` gained meanwhile appears as a reversed change from your branch.
  • How do you tell in one command how far two branches have diverged?
    `git rev-list --left-right --count main...feature` prints two numbers: commits only in the left revision and commits only in the right one. Adding `--left-right` to `git log` on the same range labels each commit with `<` or `>` if you want the actual list.
  • What does an empty result from git log main..feature tell you?
    That `feature` has nothing reachable that `main` lacks — either the branch has been fully merged or it was never advanced. It says nothing about `main` having moved ahead; check `git log feature..main` for the other direction.

Two dots is "what's in my bag that isn't in yours"; three dots is "what either of us has that the other doesn't".

saying these in an interview costs you the question

  • Thinks two dots means a time window of commits
  • Assumes three dots behaves the same in log and diff
  • Believes A..B includes new commits on A
  • Reads an empty range as an error
  • Uses git diff main feature to review a branch

context