Show diff between two git branches
Compare two branches' code — for code review, "what did this feature change?" inspection, or merge-conflict preview. The two-dot vs three-dot syntax distinction silently changes what the diff INCLUDES.
How to show diff between two git branches in each shell
git diff main...featureTHREE dots: diff from the merge-base of main and feature to feature — i.e., "what feature added since branching from main". This is what GitHub's PR diff view shows. For "raw tip vs raw tip" (includes everything that landed on main AFTER feature branched), use TWO dots: `git diff main..feature`.
git diff main...featuregit diff main...featuregit diff main...featureSame git binary. pwsh does NOT special-case the `...` triple-dot — git parses it. To page reliably: `git diff main...feature | Out-Host -Paging` (pwsh's auto-pager doesn't engage for native commands). For just the file list: `git diff --name-only main...feature`.
git diff main...featureSame git binary. cmd has no auto-pager — pipe through `more`: `git diff main...feature | more`. For a one-line summary: `git diff --shortstat main...feature` (e.g., "12 files changed, 234 insertions(+), 56 deletions(-)").
Equivalents listed for Bash, Zsh, Fish, PowerShell, cmd.exe.
Gotchas & notes
- **Two-dot vs three-dot is the most-misunderstood git syntax.** For `git diff`: `main..feature` (two dots) means "diff the TIP of main against the TIP of feature" — includes anything on main that landed AFTER feature branched. `main...feature` (THREE dots) means "diff the MERGE-BASE(main, feature) against feature" — shows ONLY what feature added since diverging. For code review, three-dot is almost always what you want; that's what GitHub's PR view computes. CAVEAT: for `git log`, two-dot has DIFFERENT semantics — `git log main..feature` means "commits reachable from feature but not from main" (set difference), and `git log main...feature` is symmetric difference. The same syntax means different things in `diff` vs `log` — always think about which command you're running.
- **Quick mnemonic: three dots = "just my changes".** When reviewing a PR, `git diff main...feature` answers "what did THIS branch change?". When checking for divergence, `git diff main..feature` answers "what differs between these two tips, regardless of who changed what". A common bug: running `git diff main..feature` on a long-lived feature branch shows tens of thousands of lines because main moved forward in parallel — the reviewer sees "feature changed everything" when really feature only added a few files. Use three-dot for review; use two-dot for "are these tips synced".
- **Summary flags make diffs scannable.** `--stat` gives a per-file line-count summary (`src/app.ts | 12 +++++-----------`); `--shortstat` collapses to one line (`12 files changed, …`); `--numstat` is tab-separated machine-readable (`+adds -dels filename`); `--name-only` lists file paths only; `--name-status` adds the A/M/D/R status letter. For PR-size CI gates: `git diff --shortstat main...feature | awk '{print $4}'` extracts the insertions count. For "files that changed but I don't care about the content yet": `git diff --name-only main...feature` and pipe through your linter / formatter / test-impact analyzer.
- **Comparing remote vs local: `git fetch` first.** `git diff main..origin/main` after a fetch shows what your local main is missing relative to origin. `git log main..origin/main --oneline` lists those commits (note: log semantics — "in origin/main, not in local main"). For "did my push get accepted?": `git diff origin/feature..feature --stat` after pushing. If the result is empty, push succeeded. CAVEAT: if you never `git fetch`, `origin/main` is stale (points at whatever main was when you LAST fetched), and the diff lies about divergence.
- **Restrict to a path / file family / time window.** `git diff main...feature -- src/` limits to the `src/` subtree (note the `--` separator — required when path could be confused with a ref). `git diff main...feature -- "*.ts" "*.tsx"` filters to TypeScript files only. `git diff main...feature --since="1 week ago"` (only diffs from commits in the time window — useful for "what changed this sprint"). For the inverse "everything except docs": `git diff main...feature -- . ":(exclude)docs/" ":(exclude)*.md"` (pathspec magic — `:(exclude)` is the literal token; quoting needed in bash/zsh to avoid `:` being interpreted).
Related commands
Related tasks
- Show git commit history as a graph— Render the commit DAG as an ASCII graph so branch topology — merges, forks, fast-forwards, and orphan tips — is visible at a glance, instead of the default linear `git log` that hides every parallel branch.
- List modified files in a git repo— Print the paths of files with uncommitted changes (staged or unstaged) — for pre-commit hooks, CI lint-only-changed gates, and "what am I about to commit".
- List staged changes— Print files (or the full diff) of what's currently in the git index — what `git commit` will commit if you run it now.