Skip to content
shellmap

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

Bashunix
git diff main...feature

THREE 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`.

Zshunix
git diff main...feature
Fishunix
git diff main...feature
PowerShellwindows
git diff main...feature

Same 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`.

cmd.exewindows
git diff main...feature

Same 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