Learning Objectives

By the end of this module, you will be able to:

  1. Read and interpret unified diff format — headers, hunks, context lines, and markers
  2. Choose the right git diff variant for any comparison — working directory, staging area, commits, branches
  3. Use two-dot (..) and three-dot (...) range syntax correctly and understand when each applies
  4. Leverage --stat, --name-only, --name-status, and --word-diff for different levels of detail
  5. Configure external diff tools (VS Code, Sublime Merge, vimdiff, meld) for visual comparison
  6. Traverse the commit graph with ~ (tilde) and ^ (caret) notation

1. Reading Unified Diff Format

Every git diff output follows the unified diff format. Understanding this format is essential — you'll encounter it in terminals, pull requests, code reviews, and patch files.

Anatomy of a Diff

diff --git a/src/auth.js b/src/auth.js
index 3a4b5c6..7d8e9f0 100644
--- a/src/auth.js
+++ b/src/auth.js
@@ -12,7 +12,9 @@ function authenticate(user) {
   const token = generateToken(user.id);
   const expiry = Date.now() + 3600000;
 
-  return { token, expiry };
+  return {
+    token,
+    expiry,
+    refreshToken: generateRefreshToken(user.id)
+  };
 }

Let's break down every line:

File Header

diff --git a/src/auth.js b/src/auth.js
  • a/ prefix = the "old" version (before the change)
  • b/ prefix = the "new" version (after the change)
  • Same filename on both sides means the file was modified (different names would indicate a rename)

Index Line

index 3a4b5c6..7d8e9f0 100644
  • 3a4b5c6 — blob SHA of the old version
  • 7d8e9f0 — blob SHA of the new version
  • 100644 — file mode (regular file, not executable)

File Markers

--- a/src/auth.js
+++ b/src/auth.js
  • --- marks the old file
  • +++ marks the new file
  • For new files: --- /dev/null
  • For deleted files: +++ /dev/null

Hunk Header

@@ -12,7 +12,9 @@ function authenticate(user) {

This is the most important line to understand:

@@ -OLD_START,OLD_COUNT +NEW_START,NEW_COUNT @@ CONTEXT
PartMeaning
-12,7In the old file, this hunk starts at line 12 and shows 7 lines
+12,9In the new file, this hunk starts at line 12 and shows 9 lines
function authenticate(user) {Nearest enclosing function/scope (context hint)

The count difference (9 - 7 = 2) tells you this hunk has a net addition of 2 lines.

Content Lines

 ← space = unchanged context line (shown for orientation)
-← minus = line removed from old version
+← plus = line added in new version

Context lines (prefixed with a space) appear before and after changes. By default, Git shows 3 lines of context. The context helps you:

  • Orient yourself in the file
  • Verify that the right section is being changed
  • Apply patches even if surrounding code has shifted

Multiple Hunks

A single file diff can contain multiple hunks — separate @@ sections — when changes are far apart in the file:

@@ -5,7 +5,7 @@ const config = {
   port: 3000,
-  host: "localhost",
+  host: "0.0.0.0",
   debug: false
 };
@@ -45,6 +45,8 @@ function startServer() {
   app.listen(config.port);
+  console.log(`Server running on ${config.host}:${config.port}`);
+  monitor.ping();
 }

2. git diff Variants — Comparing Different Areas

Git has three main areas (working directory, staging area, repository), and git diff lets you compare any two of them.

The Comparison Map

                  git diff             git diff --staged
Working Dir  ──────────────→  Index  ──────────────────→  HEAD (last commit)
                                                            │
                              git diff HEAD                  │
Working Dir  ────────────────────────────────────────────────┘

Working Directory vs. Staging Area (Index)

# Show unstaged changes (what you've modified but haven't git-added)
git diff
 
# Same thing, explicit
git diff --

This is what you see when you've edited files but haven't staged them yet. Once you git add a file, it disappears from git diff output.

Staging Area vs. Last Commit (HEAD)

# Show staged changes (what will go into the next commit)
git diff --staged
 
# Older alias, same thing
git diff --cached

This shows exactly what git commit will record. Use this as a final review before committing.

Working Directory vs. Last Commit (HEAD)

# Show ALL changes — both staged and unstaged
git diff HEAD

This combines both views. Useful when you want to see everything that's different from the last commit, regardless of staging state.

Quick Reference

CommandWhat It ComparesWhen to Use
git diffWorking dir ↔ Index"What did I change but not stage yet?"
git diff --stagedIndex ↔ HEAD"What will my next commit contain?"
git diff HEADWorking dir ↔ HEAD"Everything different from last commit"

Diff for Specific Files

# Any variant can take file paths
git diff -- src/app.js
git diff --staged -- src/app.js src/config.js
git diff HEAD -- "*.test.js"

The -- separator is optional but prevents ambiguity between file paths and branch names.


3. Comparing Commits, Branches, and Ranges

Between Two Commits

# Diff between any two commits (by SHA, tag, or branch)
git diff abc1234 def5678
git diff v1.0 v2.0
git diff main feature

The order matters: the first argument is "old," the second is "new." Reversing them flips the +/- signs.

Two-Dot Syntax (..)

# These are equivalent:
git diff main feature
git diff main..feature

Two dots in git diff means exactly the same as listing two commits. It simply compares the snapshots at those two points. The .. is optional syntactic sugar.

A --- B --- C           (main)
       \
        D --- E         (feature)

git diff main..feature
→ Compares snapshot at C with snapshot at E
→ Shows ALL differences between them, including changes on main that aren't on feature

Three-Dot Syntax (...) — The Merge Preview

# Show only changes on feature since it diverged from main
git diff main...feature
 
# Equivalent to:
git diff $(git merge-base main feature) feature

Three dots finds the merge base (common ancestor) and diffs from there to the second argument. This answers: "What would this branch bring in if merged?"

A --- B --- C           (main)
       \
        D --- E         (feature)

git diff main...feature
→ Finds merge base (B)
→ Compares snapshot at B with snapshot at E
→ Shows ONLY what feature added — excludes changes on main

This is what pull request diffs show. When reviewing a PR, you want to see what the branch changed, not how it differs from the current state of main (which may have moved since the branch was created).

When to Use Each

SyntaxShowsUse Case
git diff A B or A..BAll differences between A and B"How do these two states differ?"
git diff A...BChanges on B since it diverged from A"What does this branch introduce?" (PR review)

Important: .. and ... Mean Different Things in git diff vs. git log

This is a common source of confusion:

Syntaxgit diffgit log
A..BCompare snapshots A and BCommits reachable from B but not A
A...BChanges since merge base to BCommits reachable from A or B, but not both

Don't mix up the semantics between the two commands.


4. Output Format Options

--stat — Summary Statistics

git diff --stat main..feature
 src/auth.js     | 15 ++++++++++-----
 src/config.js   |  3 ++-
 tests/auth.test.js | 42 ++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 50 insertions(+), 6 deletions(-)

The bar graph gives a visual sense of where the most change happened. Useful for getting a high-level overview before diving into details.

# Limit the width of the stat output
git diff --stat=80 main..feature
 
# Stat with a count limit (only top N files)
git diff --stat main..feature | head -20

--name-only — Just File Names

git diff --name-only main..feature
src/auth.js
src/config.js
tests/auth.test.js

Perfect for scripting or getting a quick file list.

--name-status — File Names with Change Type

git diff --name-status main..feature
M	src/auth.js
M	src/config.js
A	tests/auth.test.js

Status codes:

CodeMeaning
AAdded
MModified
DDeleted
RRenamed (shown as R100 old-name new-name)
CCopied
TType changed (e.g., file → symlink)

--shortstat — Just the Numbers

git diff --shortstat main..feature
 3 files changed, 50 insertions(+), 6 deletions(-)

One line. Great for commit hooks or CI scripts that check change size.

--diff-filter — Filter by Change Type

# Show only added files
git diff --diff-filter=A --name-only main..feature
 
# Show only deleted files
git diff --diff-filter=D --name-only main..feature
 
# Show only modified files (exclude additions and deletions)
git diff --diff-filter=M --name-only main..feature
 
# Exclude renames
git diff --diff-filter=d --name-only main..feature   # lowercase = exclude

5. --word-diff — Comparing Prose and Inline Changes

Standard diffs show entire lines as added/removed, even if only one word changed. --word-diff highlights changes at the word level.

Default Word Diff

git diff --word-diff
This is a [-simple-]{+powerful+} example of word-level diffing.
The function returns [-false-]{+true+} when validation passes.
  • [-removed-] — words that were deleted
  • {+added+} — words that were inserted
  • Unchanged words appear normally

Color Mode (Cleaner for Terminals)

git diff --word-diff=color

Removed words appear in red, added words in green — no brackets. Looks cleaner in the terminal but doesn't work in plain text.

Custom Word Boundaries

# Treat each character as a "word" (useful for comparing hashes, IDs)
git diff --word-diff-regex=.
 
# Split on whitespace only (default is more aggressive)
git diff --word-diff-regex='[^ ]+'
 
# Split on programming tokens
git diff --word-diff-regex='[a-zA-Z_][a-zA-Z0-9_]*|[^[:space:]]'

When Word-Diff Shines

  • Documentation and prose — see exactly which words changed in a paragraph
  • Configuration files — spot value changes in long lines
  • Single-line changes — when a line has minor modifications, word-diff shows the exact delta

6. Whitespace and Context Control

Ignoring Whitespace

# Ignore all whitespace changes
git diff -w
git diff --ignore-all-space
 
# Ignore changes in amount of whitespace (but not addition/removal)
git diff -b
git diff --ignore-space-change
 
# Ignore blank lines added or removed
git diff --ignore-blank-lines
 
# Ignore whitespace at end of lines
git diff --ignore-space-at-eol

Checking for Whitespace Problems

# Highlight trailing whitespace and other problems
git diff --check

This shows warnings for:

  • Trailing whitespace
  • Mixed tabs and spaces (if configured)
  • Lines with only whitespace changes

Adjusting Context Lines

# Show 5 lines of context instead of the default 3
git diff -U5
 
# Show 10 lines of context
git diff -U10
 
# Show zero context (only changed lines)
git diff -U0
 
# Show the entire file (maximum context)
git diff -U99999

More context helps when you need to understand the surrounding code. Less context helps when you want to focus on just the changes.


7. Traversing the Commit Graph — ~ and ^

Before we look at diff tools, you need to understand how to reference specific commits relative to a starting point. This is essential for constructing diff ranges.

Tilde ~ — Go Back N Generations

Tilde follows the first parent chain (straight back in history):

A --- B --- C --- D     (main) ← HEAD

HEAD~0  = D  (current commit)
HEAD~1  = C  (one back)
HEAD~2  = B  (two back)
HEAD~3  = A  (three back)

# Shorthand: HEAD~ = HEAD~1

Caret ^ — Choose a Parent

Caret selects which parent to follow (relevant for merge commits):

        E --- F
       /       \
A --- B --- C --- G    (main) ← HEAD  (G is a merge commit)
                  |
                  G has two parents:
                    G^1 = C  (first parent — the branch you were on)
                    G^2 = F  (second parent — the branch being merged in)
# First parent (default)
HEAD^     # = HEAD^1 = C
 
# Second parent (the merged branch)
HEAD^2    # = F

Combining ~ and ^

# Go back 2, then take the second parent
HEAD~2^2
 
# Go to merge commit's second parent, then back 1
HEAD^2~1

Using with git diff

# Compare current commit with its parent
git diff HEAD~1 HEAD
 
# Compare two commits ago with current
git diff HEAD~2 HEAD
 
# Compare the two parents of a merge commit
git diff HEAD^1 HEAD^2
 
# What changed in just the last commit?
git diff HEAD~1..HEAD
# Or simply:
git show HEAD

git rev-parse — Resolve References to SHAs

# See what SHA a reference resolves to
git rev-parse HEAD
git rev-parse HEAD~3
git rev-parse main^2
 
# Short SHA
git rev-parse --short HEAD
git rev-parse --short=7 HEAD~2

Useful for debugging your ~ and ^ navigation or for scripting.


8. External Diff Tools

The terminal diff output is powerful, but graphical diff tools provide side-by-side comparison, syntax highlighting, and better navigation for large changesets.

Configuring VS Code as a Diff Tool

# Set VS Code as the diff tool
git config --global diff.tool vscode
 
# Tell Git how to launch it
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
 
# Disable the "Launch VS Code?" prompt
git config --global difftool.prompt false

Sublime Merge:

git config --global diff.tool smerge
git config --global difftool.smerge.cmd 'smerge "$LOCAL" "$REMOTE"'

vimdiff (built-in, no configuration needed):

git config --global diff.tool vimdiff

Meld:

git config --global diff.tool meld
# Meld auto-detects, no cmd config needed

KDiff3:

git config --global diff.tool kdiff3
# KDiff3 auto-detects, no cmd config needed

Using the Diff Tool

# Launch the configured diff tool
git difftool main..feature
 
# Use a specific tool (overrides config)
git difftool --tool=meld main..feature
 
# Skip the confirmation prompt
git difftool --no-prompt main..feature
 
# Compare staged changes in a tool
git difftool --staged
 
# Open all changed files at once (directory diff)
git difftool --dir-diff main..feature

--dir-diff — The Power Move

Instead of opening files one at a time, --dir-diff creates temporary directories with all changed files and opens a single comparison session:

git difftool --dir-diff main..feature

This lets you browse all changes in one window, click between files, and get a holistic view of the changeset.


9. Diff in Pull Request Reviews

Understanding diff output is critical for code reviews. Here's how diff concepts map to PR workflows.

What GitHub/GitLab PR Diffs Show

PR diffs use the three-dot comparison — they show what the branch changed since it diverged from the base branch, not the raw difference between the two branch tips:

# This is what a PR diff shows:
git diff main...feature
 
# NOT this:
git diff main..feature

This is why PRs don't show changes that teammates merged into main after you branched. The PR only shows YOUR changes.

Reviewing a PR Locally

# Fetch the PR branch
git fetch origin pull/123/head:pr-123
git checkout pr-123
 
# See the summary
git diff --stat main...pr-123
 
# See file-level changes
git diff --name-status main...pr-123
 
# Review the full diff
git diff main...pr-123
 
# Or file by file in a diff tool
git difftool main...pr-123

Diff Tips for Code Reviews

# Focus on a specific file in the PR
git diff main...feature -- src/critical-file.js
 
# Ignore whitespace (focus on logic changes)
git diff -w main...feature
 
# Word-level diff for config or documentation changes
git diff --word-diff main...feature -- "*.md"
 
# How many lines changed per file?
git diff --stat main...feature
 
# Show only the list of changed files
git diff --name-only main...feature

Command Reference

CommandDescription
git diffUnstaged changes (working dir vs. index)
git diff --stagedStaged changes (index vs. HEAD)
git diff HEADAll changes (working dir vs. HEAD)
git diff A..BDifference between commits A and B
git diff A...BChanges on B since merge base with A
git diff --statSummary with insertions/deletions per file
git diff --name-onlyList only changed file names
git diff --name-statusFile names with change type (A/M/D/R)
git diff --shortstatOne-line summary of total changes
git diff --word-diffWord-level diff (inline additions/removals)
git diff -wIgnore all whitespace changes
git diff -U<n>Show n lines of context (default 3)
git diff --checkWarn about whitespace problems
git diff --diff-filter=AShow only added files
git difftoolOpen changes in configured external tool
git difftool --dir-diffOpen all changes in one directory comparison
git rev-parse HEAD~3Resolve a commit reference to its SHA

Hands-On Lab

Setup

mkdir diff-lab && cd diff-lab
git init
 
# Create initial project structure
mkdir -p src tests docs
 
cat > src/calculator.js << 'EOF'
function add(a, b) {
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
function multiply(a, b) {
  return a * b;
}
 
function divide(a, b) {
  return a / b;
}
 
module.exports = { add, subtract, multiply, divide };
EOF
 
cat > src/formatter.js << 'EOF'
function formatCurrency(amount) {
  return "$" + amount.toFixed(2);
}
 
function formatPercent(value) {
  return (value * 100).toFixed(1) + "%";
}
 
module.exports = { formatCurrency, formatPercent };
EOF
 
cat > docs/README.md << 'EOF'
# Calculator App
 
A simple calculator with basic arithmetic operations.
 
## Features
- Addition
- Subtraction
- Multiplication
- Division
EOF
 
cat > tests/calculator.test.js << 'EOF'
const { add, subtract, multiply, divide } = require('../src/calculator');
 
test('add', () => expect(add(2, 3)).toBe(5));
test('subtract', () => expect(subtract(5, 3)).toBe(2));
test('multiply', () => expect(multiply(3, 4)).toBe(12));
test('divide', () => expect(divide(10, 2)).toBe(5));
EOF
 
git add .
git commit -m "Initial project setup"
git tag v1.0

Part 1: Understanding the Three Diff Areas

# Make changes to two files WITHOUT staging
cat > src/calculator.js << 'EOF'
function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Arguments must be numbers');
  }
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
function multiply(a, b) {
  return a * b;
}
 
function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}
 
module.exports = { add, subtract, multiply, divide };
EOF
 
echo "// v1.1 update" >> src/formatter.js

Exercise: Explore the three diff areas.

# 1. See unstaged changes (working dir vs. index)
git diff
# You should see changes in BOTH calculator.js and formatter.js
 
# 2. Stage only calculator.js
git add src/calculator.js
 
# 3. Now check each area
git diff
# Only formatter.js appears (calculator.js is staged, no longer "unstaged")
 
git diff --staged
# Only calculator.js appears (it's staged, waiting to be committed)
 
git diff HEAD
# BOTH files appear (everything different from last commit)

Checkpoint: Verify that git diff shows only formatter.js, git diff --staged shows only calculator.js, and git diff HEAD shows both.

Part 2: Reading and Interpreting Hunks

# Commit the staged changes
git commit -m "feat: add input validation to calculator"
 
# Stage and commit the remaining change
git add src/formatter.js
git commit -m "chore: add version comment"
 
# Now look at the diff between v1.0 and current
git diff v1.0 HEAD

Exercise: Read the diff output and answer:

  1. How many files changed? (Use git diff --stat v1.0 HEAD)
  2. What does the @@ hunk header say about line numbers?
  3. What's the net line change? (Use git diff --shortstat v1.0 HEAD)
# Verify your answers
git diff --stat v1.0 HEAD
git diff --shortstat v1.0 HEAD
git diff --name-status v1.0 HEAD

Part 3: Two-Dot vs. Three-Dot Comparison

# Create a feature branch
git checkout -b feature/advanced-math
 
cat > src/calculator.js << 'EOF'
function add(a, b) {
  if (typeof a !== 'number' || typeof b !== 'number') {
    throw new TypeError('Arguments must be numbers');
  }
  return a + b;
}
 
function subtract(a, b) {
  return a - b;
}
 
function multiply(a, b) {
  return a * b;
}
 
function divide(a, b) {
  if (b === 0) {
    throw new Error('Division by zero');
  }
  return a / b;
}
 
function power(base, exp) {
  return Math.pow(base, exp);
}
 
function sqrt(n) {
  if (n < 0) throw new Error('Cannot sqrt negative number');
  return Math.sqrt(n);
}
 
module.exports = { add, subtract, multiply, divide, power, sqrt };
EOF
 
git add src/calculator.js
git commit -m "feat: add power and sqrt functions"
 
# Now make a change on main too (simulating team activity)
git checkout main
 
cat > docs/README.md << 'EOF'
# Calculator App
 
A simple calculator with basic arithmetic operations.
 
## Features
- Addition
- Subtraction
- Multiplication
- Division
 
## Installation
Run `npm install` to get started.
EOF
 
git add docs/README.md
git commit -m "docs: add installation instructions"

Exercise: Compare two-dot and three-dot diffs.

# Two-dot: raw difference between branch tips
git diff main..feature/advanced-math --stat
 
# Three-dot: only what the feature branch changed (since diverging)
git diff main...feature/advanced-math --stat

Checkpoint: The two-dot diff should show changes in BOTH calculator.js AND README.md (because README changed on main). The three-dot diff should show ONLY calculator.js (the feature branch's changes).

Part 4: Word-Level Diffing

git checkout main
 
# Make prose changes to README
cat > docs/README.md << 'EOF'
# Calculator Application
 
A powerful calculator with comprehensive arithmetic operations and input validation.
 
## Features
- Addition
- Subtraction
- Multiplication
- Division (with zero-check)
 
## Installation
Run `npm install` to get started.
EOF
 
git add docs/README.md
 
# Compare with standard diff vs word diff
echo "=== Standard diff ==="
git diff --staged -- docs/README.md
 
echo ""
echo "=== Word diff ==="
git diff --staged --word-diff -- docs/README.md

Checkpoint: The standard diff shows entire lines replaced. The word diff highlights exactly which words changed: "App" → "Application", "simple" → "powerful", etc.

git commit -m "docs: improve README descriptions"

Part 5: Navigating with ~ and ^

# Merge the feature branch to create a merge commit
git merge --no-ff feature/advanced-math -m "Merge feature/advanced-math"
 
# Explore graph traversal
echo "Current commit (HEAD):"
git log --oneline -1
 
echo ""
echo "One commit back (HEAD~1):"
git log --oneline -1 HEAD~1
 
echo ""
echo "Two commits back (HEAD~2):"
git log --oneline -1 HEAD~2
 
echo ""
echo "Merge commit's first parent (HEAD^1):"
git log --oneline -1 HEAD^1
 
echo ""
echo "Merge commit's second parent (HEAD^2):"
git log --oneline -1 HEAD^2

Exercise: Use these references in diffs.

# What did the merge commit bring in?
# (Compare the two parents of the merge)
git diff HEAD^1 HEAD^2 --stat
 
# What changed in the last 3 commits?
git diff HEAD~3 HEAD --stat
 
# What's different between the merge's parents?
git diff HEAD^1..HEAD^2

Checkpoint: HEAD^1 should be the last commit on main before the merge. HEAD^2 should be the tip of feature/advanced-math.

Part 6: Using Diff Filters and Options

# Create multiple types of changes
git checkout -b feature/cleanup
 
# Add a new file
cat > src/utils.js << 'EOF'
function clamp(value, min, max) {
  return Math.min(Math.max(value, min), max);
}
module.exports = { clamp };
EOF
 
# Delete a file
rm src/formatter.js
 
# Modify a file
echo "// Updated" >> src/calculator.js
 
git add .
git commit -m "refactor: add utils, remove formatter, update calculator"

Exercise: Use diff filters to isolate each change type.

# What files were added?
git diff --diff-filter=A --name-only HEAD~1..HEAD
 
# What files were deleted?
git diff --diff-filter=D --name-only HEAD~1..HEAD
 
# What files were modified?
git diff --diff-filter=M --name-only HEAD~1..HEAD
 
# Full name-status view
git diff --name-status HEAD~1..HEAD

Checkpoint: You should see A src/utils.js, D src/formatter.js, and M src/calculator.js.

Challenge

  1. Configure VS Code (or another editor) as your diff tool using git config
  2. Create a branch with changes to 3+ files
  3. Use git difftool --dir-diff main...your-branch to review all changes at once
  4. Write a one-liner that counts the total lines of code added across all files in a branch: git diff --stat main...your-branch | tail -1
  5. Use git diff --word-diff-regex=. to compare two commits at the character level

Common Pitfalls

PitfallWhat HappensPrevention
Confusing git diff with git diff --stagedYou think nothing changed, but your changes are stagedAlways check both; use git diff HEAD for everything
.. vs ... confusion in git diff vs git logThey have different semantics in each commandRemember: diff ... = merge-base diff; log .. = reachability
Diffing wrong directionChanges appear as additions instead of deletionsFirst argument = old, second = new; swap them if inverted
Ignoring whitespace-only diffsA diff looks empty but git status shows changesUse git diff --check or look for trailing whitespace
Using git diff after staging everythingOutput is empty (all changes are in the index)Use git diff --staged to see staged changes
Forgetting --no-prompt with difftoolPrompted for every file in a multi-file diffSet git config --global difftool.prompt false
Merge commit diffs showing nothingDefault diff of a merge commit can be emptyUse git diff HEAD^1 HEAD^2 to compare the parents

Pro Tips

  1. git diff --staged before every commit: Make this a habit. It's your last chance to review what you're about to commit. Catch accidental debug statements, console.logs, and TODO comments.

  2. Three-dot for reviews, two-dot for comparisons: When reviewing someone's PR locally, always use git diff main...feature. Two-dot will show you changes on main that aren't relevant to the review.

  3. --stat first, then dive in: When looking at a large changeset, start with git diff --stat to see which files changed and how much. Then drill into individual files.

  4. -U0 for focused patches: When you only care about the exact lines that changed (no context), use -U0. This is useful for scripting and automated analysis.

  5. Character-level diffs: For comparing hashes, tokens, or encoded strings, use git diff --word-diff-regex=. to see exact character differences.

  6. --dir-diff for large changesets: When reviewing a branch with many file changes, git difftool --dir-diff opens them all in one session instead of prompting file by file.

  7. Create aliases for common diffs: git config --global alias.review 'diff --stat main...HEAD' gives you a quick summary of your branch's changes.


Quiz / Self-Assessment

  1. What do the @@ markers in a diff hunk header represent?
Answer

The @@ markers define the hunk range: @@ -OLD_START,OLD_COUNT +NEW_START,NEW_COUNT @@. For example, @@ -12,7 +12,9 @@ means: in the old file, this hunk starts at line 12 and spans 7 lines; in the new file, it starts at line 12 and spans 9 lines. The text after the second @@ is an optional context hint (usually the enclosing function name).

  1. What's the difference between git diff and git diff --staged?
Answer

git diff (no flags) shows changes in the working directory that haven't been staged — it compares working directory to the index. git diff --staged (or --cached) shows changes that have been staged (via git add) but not yet committed — it compares the index to HEAD. After staging all changes, git diff will be empty and git diff --staged will show everything.

  1. In git diff main...feature, what does the three-dot syntax do?
Answer

Three-dot finds the merge base (common ancestor) of main and feature, then compares that merge base with feature. This shows only the changes that feature introduced since it diverged from main, excluding any changes that happened on main since the branch point. This is what PR/MR diffs display.

  1. You run git diff and see no output, but git status shows modified files. What happened?
Answer

The modified files have been staged (added to the index with git add). git diff only shows unstaged changes. Use git diff --staged to see the staged changes, or git diff HEAD to see all changes relative to the last commit regardless of staging state.

  1. How do you see a word-level diff instead of line-level?
Answer

Use git diff --word-diff. This shows [-removed-] and {+added+} at the word level instead of showing entire lines as changed. For cleaner terminal output, use --word-diff=color (removed in red, added in green, no brackets). For character-level diffs, use --word-diff-regex=..

  1. What does HEAD~3 refer to? How about HEAD^2?
Answer

HEAD~3 means "go back 3 generations following the first parent chain" — it's the great-grandparent of the current commit. HEAD^2 means "the second parent of the current commit" — this only makes sense for merge commits (the first parent is the branch you were on, the second parent is the branch being merged). ~ navigates depth (generations back), ^ selects which parent at a merge point.

  1. What's the difference between git diff --stat and git diff --shortstat?
Answer

--stat shows a per-file summary with file names, insertion/deletion counts, and a visual bar graph for each file, plus a total at the bottom. --shortstat shows only the single summary line: "N files changed, X insertions(+), Y deletions(-)". Use --stat for a file-level overview and --shortstat when you just want the totals.

  1. How do you configure an external diff tool and use it?
Answer
# Configure
git config --global diff.tool <toolname>
git config --global difftool.<toolname>.cmd '<command> $LOCAL $REMOTE'
git config --global difftool.prompt false
 
# Use
git difftool main..feature           # File by file
git difftool --dir-diff main..feature  # All files at once
git difftool --tool=meld main..feature # Override default tool
  1. You want to see only which files were newly added in a commit, not modified or deleted files. What command do you use?
Answer
git diff --diff-filter=A --name-only HEAD~1..HEAD

--diff-filter=A shows only added files. Combine with --name-only for just file paths, or --name-status to confirm the A status. Use uppercase letters to include a type, lowercase to exclude.

  1. Why do PR diffs on GitHub use three-dot comparison instead of two-dot?
Answer

Three-dot comparison (main...feature) shows only the changes that the feature branch introduced since it diverged from main. Two-dot (main..feature) would show the raw difference between the two branch tips, which includes changes others have merged into main since you branched. PR reviewers want to see what YOUR branch changed, not unrelated changes on main. Three-dot isolates exactly the proposed changes.