TL;DR — Use
A...Bfor “what actually differs since the common ancestor”,A..Bfor “what’s in B that isn’t in A”. Start with ahead/behind counts and a stat diff, then drill into commit graphs, rename‑aware diffs, and range‑diff for rebases.
Table of Contents
- Quick Answers (copy/paste)
- Double vs Triple Dots (mental model)
- File & Content Diffs That Read Well
- Commit‑Level Analysis (surgical view)
- What Will Actually Merge? (merge‑base)
- Triage & Impact (where the churn lives)
- Review‑Ready Workflows (pre‑PR / dry‑run)
- Windows / PowerShell Equivalents
- Pro Aliases (drop‑in)
- Decision Cheatsheet
Quick Answers (copy/paste)
Replace
A/Bwith your branches (e.g.,origin/mainandfeature/payments).
# How far apart are they?
git rev-list --left-right --count A...B # → <A-only> <B-only>
# High-level file summary
git diff --stat A...B
# Full content diff (triple dot: versus merge-base)
git diff A...B
Graph + commits per side:
git log --oneline --left-right --cherry-pick --graph A...B
Names only / status:
git diff --name-only A...B
git diff --name-status A...B # A/M/D/R status
Detect renames/moves better:
git diff -M -C A...B
Double vs Triple Dots (mental model)
| Pattern | Interprets As | Use For |
|---|---|---|
A..B |
commits in B not in A | “What would I pull from B into A?” |
A...B |
compare each side vs merge‑base(A,B) | “What actually differs since we diverged?” (PR view) |
Find the merge base explicitly:
git merge-base A B
File & Content Diffs That Read Well
# Files changed (names only / with status)
git diff --name-only A...B
git diff --name-status A...B
# Ignore whitespace noise
git diff -w A...B
# Word-level (docs/markdown)
git diff --word-diff A...B
# Function/context aware (C/C++/Java/C#)
git diff -W A...B
# Focus a path/glob
git diff A...B -- src/api/**.cs
Rename/move detection boosters:
git diff -M -C --stat A...B
Commit‑Level Analysis (surgical view)
Linearized graph with sides marked:
git log --oneline --graph --decorate --left-right --cherry-pick A...B
Which commits from B are not in A (cherry candidates):
git cherry -v A B
Compare two series of commits (e.g., before/after rebase):
git range-diff oldA..oldB newA..newB
# common real-world: compare 'topic' before/after rebase onto 'main'
git range-diff origin/main..feature v2/main..feature
What Will Actually Merge? (merge‑base)
Preview conflicts safely:
git switch A
git merge --no-commit --no-ff B # dry run
git merge --abort # back out
Show deltas vs merge‑base:
mb=$(git merge-base A B)
git diff --stat $mb A
git diff --stat $mb B
Triage & Impact (where the churn lives)
Top churn files in this comparison:
git diff --numstat A...B | sort -k1,1nr | head
# columns: added removed path
Authorship focus for a hot file:
git blame B -- path/to/file | cut -d'(' -f2 | cut -d' ' -f1 | sort | uniq -c | sort -nr
Extension distribution (what kind of work changed):
git diff --name-only A...B | awk -F. '{print $NF}' | sort | uniq -c | sort -nr
Review‑Ready Workflows (pre‑PR / dry‑run)
A) What your PR actually changes
git fetch origin
git diff --stat origin/main...HEAD
git log --oneline --left-right --cherry-pick origin/main...HEAD
B) Safety check before merging B into A
git switch A
git merge --no-commit --no-ff B
git merge --abort
C) Cherry-pick only the good stuff
git cherry -v A B | grep '^+' | awk '{print $2}' | xargs -I{} git cherry-pick {}
Tip: On protected branches (Azure DevOps/GitHub), prefer PRs over history edits. Use
range-diffto prove your rebase didn’t change semantics.
Windows / PowerShell Equivalents
PowerShell doesn’t have xargs by default; use loops.
# Ahead/behind
git rev-list --left-right --count A...B
# Commit graph (works the same)
git log --oneline --left-right --cherry-pick --graph A...B
# Cherry-pick (+ commits from B)
$commits = git cherry -v A B | Select-String '^\+' | ForEach-Object { ($_ -split ' ')[1] }
foreach ($c in $commits) { git cherry-pick $c }
Prefer Git Bash or WSL for the awk|xargs one‑liners:
git diff --name-only A...B | awk -F. '{print $NF}' | sort | uniq -c | sort -nr
Pro Aliases (drop‑in)
Put these in ~/.gitconfig:
[alias]
aheadbehind = rev-list --left-right --count
lg = log --oneline --graph --decorate
lga = log --oneline --graph --decorate --all
cmp = !sh -c 'git lg --left-right --cherry-pick "$1...$2"' -
whatmerges = !sh -c 'mb=$(git merge-base "$1" "$2"); echo MERGE-BASE:$mb; git diff --stat $mb "$1"; echo ----; git diff --stat $mb "$2"' -
rdiff = range-diff
Usage:
git aheadbehind A...B
git cmp A B
git whatmerges A B
Decision Cheatsheet
-
High‑level difference fast →
git diff --stat A...B+git aheadbehind A...B -
Exact commits differing →
git log --left-right --cherry-pick A...B -
Preview merge conflicts →
git merge --no-commit --no-ff Bon branch A, then--abort -
Compare two rebases/series →
git range-diff old..new base..topic -
Renames/moves matter → add
-M -Ctogit diff -
Windows shell → prefer Git Bash/WSL for
awk|xargs, or PowerShell loops
✍️ Written by: Cristian Sifuentes — Full-stack developer & AI/JS enthusiast, passionate about React, TypeScript, and scalable architectures.

Top comments (2)
This is a solid breakdown, Cristian 👏
The quick-copy “decision cheatsheet” alone is gold.
Most devs know the double vs triple-dot difference in theory, but struggle to apply it when reviewing PRs — your examples make the mental model stick instantly.
Bookmarking this as a go-to reference for code review workflows!
That double vs. triple dot explanation is golden! 🌟 Using git cmp (your alias) and range-diff for pre-PR checks is absolutely the senior dev move. Excellent playbook!