In Git, what does git rev-list --left-right --count HEAD...@{u} print?
answer
- Two numbers, tab separated
- Three dots mean symmetric difference
- Marker order follows the written endpoints
- Left endpoint's exclusive commits come first
- Same pair git status renders in prose
basics
~20 sIt prints two tab-separated numbers: commits reachable from HEAD but not from the upstream, then commits reachable from the upstream but not from HEAD — the ahead and behind counts, in the order the two endpoints appear.
solid answer
~40 sThe three-dot form `HEAD...@{u}` is a symmetric difference: every commit reachable from exactly one of the two endpoints. `--left-right` marks which side each commit came from, and `--count` collapses the listing into two tab-separated totals — left first, then right. With `HEAD` on the left the output reads as *ahead* then *behind*; swap the endpoints and the numbers swap too, which is the classic misread. `@{u}` (or `@{upstream}`) resolves to the current branch's configured upstream, so this is precisely the computation `git status` reports in prose. Because it reads only local refs, it is a fast, scriptable check — CI and prompt scripts use it — but the numbers are only as fresh as your last fetch.
code
console · 8 lines$ git rev-list --left-right --count HEAD...@{u}
3 2
$ git rev-list --left-right --count @{u}...HEAD
2 3
$ git rev-list --count @{u}..HEAD
3go deeper
Recall that @{u} means the current branch's upstream and that the command prints ahead and behind as plain numbers, which is handy in scripts and shell prompts.
Explain symmetric difference versus a two-dot range, and state the ordering rule: the left number belongs to the revision written on the left.
Show how you would use it in automation — fetch first, then gate on the behind count — and know that @{push} rather than @{u} is the right endpoint in a triangular remote setup.
Frame it as the machine-readable contract behind branch-freshness policy: integers from plumbing rather than parsed porcelain prose, with an explicit fetch step so the check is not silently stale.
## Reading the command piece by piece `git rev-list` is the plumbing walker underneath `git log`: it emits commit IDs from a revision range. Four parts matter here. - **`HEAD...@{u}`** — the three-dot *symmetric difference*: commits reachable from either endpoint but not from both. Two dots (`A..B`) mean "reachable from B but not A", a one-sided range; three dots is the union of both one-sided ranges. Contrast the merge/diff meaning of three dots elsewhere in Git — in `rev-list` and `log` it is symmetric difference. - **`@{u}`** — shorthand for `@{upstream}`, resolving to the current branch's configured upstream (its remote-tracking ref such as `refs/remotes/origin/main`). It fails with "no upstream configured" if the branch has none. - **`--left-right`** — tags each emitted commit with `<` for the left endpoint's side or `>` for the right's. - **`--count`** — instead of listing commits, print the totals. Combined with `--left-right`, that is two numbers separated by a tab. ## What the two numbers mean Output `3\t2` means: three commits are reachable from `HEAD` and not from the upstream, two are reachable from the upstream and not from `HEAD`. With `HEAD` written first that is **ahead 3, behind 2** — exactly the pair `git status` renders as "have diverged, and have 3 and 2 different commits each". Order is positional, not semantic: `git rev-list --left-right --count @{u}...HEAD` prints `2\t3`. Scripts get this backwards constantly, which is why it is worth stating the rule out loud: left number belongs to the left-hand revision. Both numbers are counted relative to the **merge base** — the most recent common ancestor — because commits on the shared trunk are reachable from both endpoints and therefore excluded from the symmetric difference by definition. No separate merge-base call is needed. ## Why use it instead of git status - **Machine-readable.** Prose parsing of `git status` is fragile and localisable; two integers are not. - **Any pair of refs, not just the upstream.** `git rev-list --left-right --count main...release` compares two arbitrary branches; `git rev-list --count origin/main..HEAD` gives just the unpushed count. - **Composable.** Dropping `--count` lists the commits themselves with `<`/`>` markers, and `git log --left-right --graph --oneline HEAD...@{u}` visualises the same split. ## The freshness caveat `@{u}` resolves to a *local* remote-tracking ref. The command performs no network access, so like `git status` it reports the state as of your last `git fetch`. Any script that gates on "is this branch behind?" must fetch first — `git fetch --quiet origin` then the rev-list — or it will happily report zero behind on a branch that is days stale. ## Related shorthands worth knowing - **`@{push}`** resolves to where a push from this branch would land. In the ordinary setup it equals `@{u}`; in a triangular workflow — fetch from one remote, push to another — they differ, and `HEAD...@{push}` is the honest "what would I be pushing" comparison. - **`git for-each-ref --format='%(refname:short) %(upstream:track)' refs/heads`** prints `[ahead 3, behind 2]` (or `[gone]`) for every local branch in one pass, which is usually nicer than looping rev-list per branch. - **`git branch -vv`** is the human-facing equivalent of that for-each-ref line. ## Typical interview framing Interviewers use this to check that "ahead/behind" is understood as graph reachability rather than as a counter Git maintains. A strong answer names symmetric difference, states the left-then-right ordering rule, points out that the merge base falls out of the definition, and closes with the staleness caveat.
- What changes if you write @{u}...HEAD instead?The two numbers swap. `--left-right` labels commits by which endpoint they are reachable from, and `--count` prints the left total first, so the ordering is purely positional. Writing the upstream on the left yields behind-then-ahead. Scripts that hardcode "first number is ahead" break the moment someone reorders the endpoints.
- How does @{push} differ from @{u}, and when does that matter?`@{u}` is the branch's upstream — where a bare pull integrates from. `@{push}` is where a bare push would land. They coincide in the usual single-remote setup, but diverge in triangular workflows configured with `remote.pushDefault`, where you fetch from one remote and push to another. There, `HEAD...@{push}` is the comparison that matches what a push would actually transfer.
- Why do commits on the shared trunk not appear in either count?Because the symmetric difference excludes anything reachable from both endpoints. Every commit at or before the merge base is reachable from both tips, so it drops out automatically — the counts are inherently merge-base relative, with no separate `git merge-base` call needed.
saying these in an interview costs you the question
- Assumes the first number is always the behind count
- Thinks three dots means the same as two dots
- Believes rev-list contacts the remote for fresh counts
- Says ahead is counted from where the branch was created
- Cannot say what @{u} resolves to