git diff is the single tool you reach for whenever you need to ask “what changed?”. The command is the same; the args control the scope.
This guide walks every scope: working tree, staged, between commits, between branches, a single file, plus the most-used flags (--name-only, --stat, exclude paths, ignore whitespace).
Basic usage — three scopes
The three forms you’ll use 95% of the time:
Mental model:
- Working tree → what’s on disk now
- Index / staged → what you’ve
git add-ed but not yet committed - HEAD → last commit
git diff (no args) compares working tree to index. --staged (or --cached — same thing) compares index to HEAD. HEAD skips the index and compares working tree directly to last commit.
Between commits
Between branches
Single file or path
Append a path to scope the diff:
Useful flags
--name-only — list changed files
--stat — summary with line counts
--word-diff — word-level, not line-level
Useful for prose/docs:
Ignore whitespace
Exclude paths
For when package-lock.json and node_modules drown out the real diff:
See the dedicated guide for excluding files from git diff — covers :!, .gitattributes, and custom drivers.
Output to a file (patch format)
Visual / GUI diffs
CLI is fastest, but for big diffs a GUI tool reads easier:
Common scenarios
”What did I change since I started this branch?”
git diff $(git merge-base main HEAD)
Or simpler with the three-dot syntax:
git diff main...HEAD
“What’s in my last commit?”
git diff HEAD~1 HEAD
Or:
git show HEAD
(Same diff, plus the commit message.)
”What’s the smallest possible diff between these two branches?”
Use three-dot + ignore whitespace:
git diff -w main...feature-branch
“Show me only files in src/ that changed”
git diff --name-only -- src/
Quick reference
| Goal | Command |
|---|---|
| Unstaged changes | git diff |
| Staged changes | git diff --staged |
| All uncommitted changes | git diff HEAD |
| Between two commits | git diff abc..def |
| Between branches (full) | git diff main feature |
| Between branches (since fork) | git diff main...feature |
| Single file | git diff path/to/file |
| File list only | git diff --name-only |
| Summary stats | git diff --stat |
| Word-level | git diff --word-diff |
| Ignore whitespace | git diff -w |
| Exclude paths | git diff -- '.' ':!package-lock.json' |
| Save as patch | git diff > changes.patch |
| GUI tool | git difftool |
Conclusion
git diff is one command with many scopes — the args dictate what you’re comparing. Memorize three forms:
git diff— unstagedgit diff --staged— stagedgit diff main...HEAD— since branching
The rest is variations on the same theme. When the output is overwhelming, reach for --stat for a summary or exclude noise with ':!path'.