Learning Objectives
By the end of this module, you will be able to:
- Read and interpret unified diff format — headers, hunks, context lines, and markers
- Choose the right
git diffvariant for any comparison — working directory, staging area, commits, branches - Use two-dot (
..) and three-dot (...) range syntax correctly and understand when each applies - Leverage
--stat,--name-only,--name-status, and--word-difffor different levels of detail - Configure external diff tools (VS Code, Sublime Merge, vimdiff, meld) for visual comparison
- 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 version7d8e9f0— blob SHA of the new version100644— 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
| Part | Meaning |
|---|---|
-12,7 | In the old file, this hunk starts at line 12 and shows 7 lines |
+12,9 | In 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 --cachedThis 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 HEADThis combines both views. Useful when you want to see everything that's different from the last commit, regardless of staging state.
Quick Reference
| Command | What It Compares | When to Use |
|---|---|---|
git diff | Working dir ↔ Index | "What did I change but not stage yet?" |
git diff --staged | Index ↔ HEAD | "What will my next commit contain?" |
git diff HEAD | Working 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 featureThe 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..featureTwo 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) featureThree 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
| Syntax | Shows | Use Case |
|---|---|---|
git diff A B or A..B | All differences between A and B | "How do these two states differ?" |
git diff A...B | Changes 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:
| Syntax | git diff | git log |
|---|---|---|
A..B | Compare snapshots A and B | Commits reachable from B but not A |
A...B | Changes since merge base to B | Commits 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..featuresrc/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..featureM src/auth.js
M src/config.js
A tests/auth.test.js
Status codes:
| Code | Meaning |
|---|---|
A | Added |
M | Modified |
D | Deleted |
R | Renamed (shown as R100 old-name new-name) |
C | Copied |
T | Type 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 = exclude5. --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-diffThis 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=colorRemoved 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-eolChecking for Whitespace Problems
# Highlight trailing whitespace and other problems
git diff --checkThis 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 -U99999More 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 # = FCombining ~ and ^
# Go back 2, then take the second parent
HEAD~2^2
# Go to merge commit's second parent, then back 1
HEAD^2~1Using 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 HEADgit 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~2Useful 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 falseConfiguring Other Popular Tools
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 vimdiffMeld:
git config --global diff.tool meld
# Meld auto-detects, no cmd config neededKDiff3:
git config --global diff.tool kdiff3
# KDiff3 auto-detects, no cmd config neededUsing 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..featureThis 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..featureThis 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-123Diff 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...featureCommand Reference
| Command | Description |
|---|---|
git diff | Unstaged changes (working dir vs. index) |
git diff --staged | Staged changes (index vs. HEAD) |
git diff HEAD | All changes (working dir vs. HEAD) |
git diff A..B | Difference between commits A and B |
git diff A...B | Changes on B since merge base with A |
git diff --stat | Summary with insertions/deletions per file |
git diff --name-only | List only changed file names |
git diff --name-status | File names with change type (A/M/D/R) |
git diff --shortstat | One-line summary of total changes |
git diff --word-diff | Word-level diff (inline additions/removals) |
git diff -w | Ignore all whitespace changes |
git diff -U<n> | Show n lines of context (default 3) |
git diff --check | Warn about whitespace problems |
git diff --diff-filter=A | Show only added files |
git difftool | Open changes in configured external tool |
git difftool --dir-diff | Open all changes in one directory comparison |
git rev-parse HEAD~3 | Resolve 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.0Part 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.jsExercise: 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 HEADExercise: Read the diff output and answer:
- How many files changed? (Use
git diff --stat v1.0 HEAD) - What does the
@@hunk header say about line numbers? - 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 HEADPart 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 --statCheckpoint: 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.mdCheckpoint: 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^2Exercise: 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^2Checkpoint: 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..HEADCheckpoint: You should see A src/utils.js, D src/formatter.js, and M src/calculator.js.
Challenge
- Configure VS Code (or another editor) as your diff tool using
git config - Create a branch with changes to 3+ files
- Use
git difftool --dir-diff main...your-branchto review all changes at once - 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 - Use
git diff --word-diff-regex=.to compare two commits at the character level
Common Pitfalls
| Pitfall | What Happens | Prevention |
|---|---|---|
Confusing git diff with git diff --staged | You think nothing changed, but your changes are staged | Always check both; use git diff HEAD for everything |
.. vs ... confusion in git diff vs git log | They have different semantics in each command | Remember: diff ... = merge-base diff; log .. = reachability |
| Diffing wrong direction | Changes appear as additions instead of deletions | First argument = old, second = new; swap them if inverted |
| Ignoring whitespace-only diffs | A diff looks empty but git status shows changes | Use git diff --check or look for trailing whitespace |
Using git diff after staging everything | Output is empty (all changes are in the index) | Use git diff --staged to see staged changes |
Forgetting --no-prompt with difftool | Prompted for every file in a multi-file diff | Set git config --global difftool.prompt false |
| Merge commit diffs showing nothing | Default diff of a merge commit can be empty | Use git diff HEAD^1 HEAD^2 to compare the parents |
Pro Tips
-
git diff --stagedbefore 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. -
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. -
--statfirst, then dive in: When looking at a large changeset, start withgit diff --statto see which files changed and how much. Then drill into individual files. -
-U0for focused patches: When you only care about the exact lines that changed (no context), use-U0. This is useful for scripting and automated analysis. -
Character-level diffs: For comparing hashes, tokens, or encoded strings, use
git diff --word-diff-regex=.to see exact character differences. -
--dir-difffor large changesets: When reviewing a branch with many file changes,git difftool --dir-diffopens them all in one session instead of prompting file by file. -
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
- 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).
- What's the difference between
git diffandgit 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.
- 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.
- You run
git diffand see no output, butgit statusshows 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.
- 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=..
- What does
HEAD~3refer to? How aboutHEAD^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.
- What's the difference between
git diff --statandgit 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.
- 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- 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.
- 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.