Git’s comparison commands look interchangeable at first glance, yet each one surfaces a different slice of history, index, or working tree. Choosing the wrong flag can bury the exact change you need to see.
Below you’ll find a field-tested map of every diff, range-diff, and log combination that matters in daily work. Copy the snippets verbatim; they are ordered from safest to most destructive so you can stop when you see what you need.
File-level diff: spot changes before you stage
git diff myfile.js renders a colorized unified diff of every unstaged hunk in that file. Add --word-diff to highlight typo-level edits instead of whole-line blocks.
If the file is already staged, git diff --cached myfile.js shows what will be committed next. Miss this switch and you will review the wrong delta during pre-commit checks.
Binary and image shortcuts
Plain diff chokes on PNG, PDF, or ZIP. Swap in git diff --textconv to delegate the comparison to the driver declared in .gitattributes; typical setups call exiftool or strings.
Need a quick size delta? git diff --stat prints “+12 −5” for every binary without attempting to render unreadable octet dumps.
Commit-to-commit diff: read pull requests like a reviewer
git diff main...feature shows only what the feature branch introduced since it forked. The three-dot syntax omits the noisy churn that happened on main in the meantime.
Reverse the order—git diff feature...main—and you’ll see what would be removed if the feature were rebased, a quick way to spot accidental deletions.
Combined diffs for merge commits
Merge commits have two parents; git diff -c HEAD~1 reveals conflicts that were auto-resolved by Git’s union merge driver. Any line starting with ++ or -- signals a clean overlap you should still eyeball.
Pass --cc instead of -c to hide paths that merged cleanly, letting you focus on the handful that needed human touch.
Stash comparisons: never lose work in progress
git stash show -p stash@{1} prints the patch stored in that stash entry. Drop the index to inspect the most recent stash; add --name-only for a bird’s-eye list.
Compare a stash against any branch with git diff stash@{0} feature. This exposes whether the stash still applies cleanly or if upstream refactors have moved the touched lines.
Partial stash reviews
Large stashes hide unrelated experiments. Slice them with git stash show -p -- path/to/module to review only the chunk you care about.
If the output is still verbose, pipe it into delta or diff-so-fancy for side-by-side word wrapping that fits narrow terminals.
Range-diff: review force-push rewrites without guesswork
After an interactive rebase, git range-diff main@{1} main pairs old and new commits line-by-line. Deleted commits appear in red on the left; their replacements appear in green on the right.
Look for the = marker—it labels commits whose diffs are bit-identical even though the SHA changed, proving the rebase was conflict-free.
Spotting squashes and fixups
Range-diff flags squashed commits with < and > arrows. A sequence that collapses from five commits into one will show four red < lines followed by a single green >, making the rewrite obvious to any reviewer.
Log with patch: browse history like a changelog
git log -p --since="2 weeks ago" README.md streams every commit message plus its full diff for that file. Pipe to less -S to avoid line wrap while you skim.
Add --reverse to read oldest-first; this chronological order feels natural when you’re reconstructing why a line was introduced.
Graph plus patch
git log --graph -p prefixes each commit with an ASCII branch diagram. Use it when you suspect parallel feature branches touched the same function and you need to see the merge bubble.
Word-level diff: catch single-character typos
git diff --word-diff-regex=. myfile.tex highlights every altered character inside a LaTeX manuscript. Default word boundaries skip punctuation; the dot regex treats each byte as a word.
Combine with --color-words to produce a compact red-green stripe that fits inside GitHub’s comment box when pasting evidence.
Markdown and prose reviews
For documentation, git diff --word-diff-regex="[^\s]+" considers each space-separated token a word. The result reads like track-changes in Google Docs, perfect for copy-editing passes.
Submodule comparisons: see nested repo drift
git diff --submodule=diff expands the submodule’s own diff right inside the superproject output. Without this flag you only see a single line reporting “Subproject commit abc123..def456”.
If the submodule HEAD is detached, git diff --submodule=log prints the shortlog of new commits, sparing you from entering the child directory.
Recursive blame across submodules
Pinpoint which outer commit updated the submodule pointer with git log -p --submodule --grep="Bump lib". The patch section will show both the new SHA and the lib’s commit message, linking the two timelines.
Three-way merge preview: avoid surprise conflicts
git diff HEAD...MERGE_HEAD previews what will land when you finish the ongoing merge. This is safer than guessing based on conflict markers alone.
If the merge is already half-resolved, git diff --cc shows the combined diff of the merge commit in progress, including your hand-edited resolutions.
OURS vs THEIRS shortcuts
git diff --ours compares the working tree against the OURS version; git diff --theirs does the opposite. Use them to verify that your conflict resolution kept the intended half of each hunk.
Binary patch extraction: ship firmware deltas
git diff --binary HEAD~1 HEAD app.bin > firmware.patch encodes the binary delta in ASCII safe for email. Apply it on another clone with git apply firmware.patch without checking in a 50 MB blob twice.
The same flag works for Docker layers; pair it with --src-prefix=a/layer/ --dst-prefix=b/layer/ to keep track of which tarball changed.
Delta size estimation
git diff --numstat HEAD~1 HEAD prints two columns: insertions and deletions. For binaries the columns show “-” but the third column gives the compressed byte delta, letting you reject oversized firmware before review.
Custom drivers: teach Git to diff CAD files
Create .gitattributes line *.sch diff=altium then run git config diff.altium.textconv /usr/local/bin/altium-to-svg. Now git diff renders a visual schematic delta instead of a Base64 blob.
Store the converter script in the same repo under tools/ so every teammate sees the same comparison without manual setup.
XML-aware diffs
Register xmllint --format as a textconv driver for *.xlsx unzip-and-diff pipelines. Sorted attributes and pretty-printed nodes reduce false positives when Excel reorders sheets.
Performance tuning: stay fast on monorepos
git config diff.renames false disables expensive copy detection in 200 k-file repos. You’ll lose rename hints, but the command returns instantly during iterative debugging.
For repeated comparisons, git update-index --refresh pre-stat files so the next git diff skips disk hits.
Partial clone safe diffs
On partial clones, git diff HEAD~1 HEAD --missing=allow-any suppresses the download of missing blobs just to generate the patch. You’ll see “missing” placeholders instead of content, but the review stays local and fast.
Human-friendly wrappers: color, paging, and IDE glue
git config pager.diff 'delta | less -+F -+X' keeps syntax highlighting while letting you scroll backward. Delta also shows line numbers in the hunk header, matching GitHub’s UI.
VS Code users can run code --diff <(git show main:file) <(git show topic:file) to open a native side-by-side diff with minimap scrolling.
Emacs magit precision
Inside magit, hit d r to invoke range-diff on the commits you marked. The transient popup lets you toggle --stat or --diff-filter=M before the command runs, avoiding shell gymnastics.
Security angle: leak less in public patches
p>Public forks sometimes contain private commit messages. Run git diff main...feature | grep -i "password" before attaching the patch to a GitHub issue.
Strip GitForge metadata with git format-patch --no-signed-off to avoid exposing corporate email addresses in the Signed-off-by trailer.
Token redaction driver
Write a textconv filter that scrubs JWT tokens before diffing JSON. The driver can regex-replace eyJ[A-Za-z0-9_-]* with ***, ensuring accidental diffs never post credentials.
Scripting stable comparisons: same output everywhere
Lock locale and algorithms to make CI diffs reproducible. Export GIT_DIFF_OPTS="--no-ext-diff -M05" and LC_ALL=C so rename detection behaves identically on macOS and Linux runners.
Pin Git version in Docker with FROM alpine:3.18 which ships Git 2.40, avoiding surprise flag changes in newer releases.
Checksum the patch itself
Append sha256sum of the generated patch to the build log. If a reviewer’s local git diff produces a different hash, line-ending or driver divergence is the culprit, not code logic.
Edge-case goldmine: compare what isn’t committed
git diff --no-index /etc/nginx/nginx.conf ./deploy/nginx.conf compares an untracked system file against your repo template. This bypasses Git’s index entirely, useful for drift audits on production boxes.
Because the paths lie outside any repository, Git falls back to a pure POSIX diff, so add --src-prefix=server: --dst-prefix=repo: to keep the output readable.
Comparing two tarballs
Extract both archives to temp dirs and run the same --no-index command. Wrap it in a Git alias git config alias.difftar '!f() { tar -xf "$1" -C /tmp/a && tar -xf "$2" -C /tmp/b && git diff --no-index /tmp/a /tmp/b; }; f' for one-shot vendor drop reviews.