git status and git diff: Understanding Changes and Comparisons
Understanding git status output, git diff options, comparing branches, and reading diffs effectively for version control and code review.
Introduction
git status and git diff are your eyes into Git’s internal state. While other commands modify the repository, these two commands observe and report. They tell you exactly what has changed, where those changes live, and how they differ from the committed versions. Without mastering these commands, you are committing blindly.
git status gives you a high-level overview — which files are modified, staged, or untracked. git diff gives you the line-by-line details — exactly what was added, removed, or changed. Together, they form the foundation of every Git workflow. Every commit should be preceded by these commands, and every code review starts with understanding a diff.
This guide covers every important form of git status and git diff, from basic usage to advanced comparison patterns. Understanding these tools transforms Git from a mysterious black box into a transparent, predictable system. For the conceptual foundation, see The Three States.
When to Use / When Not to Use
Use git status when:
- Before every commit to verify what will be included
- After pulling or merging to understand the resulting state
- When confused about which branch you are on or what has changed
- After resolving merge conflicts to confirm all conflicts are resolved
Use git diff when:
- Reviewing changes before staging or committing
- Understanding what a colleague changed in a pull request
- Comparing branches to see what will be merged
- Investigating when a bug was introduced
- Preparing release notes by reviewing changes since the last tag
Less critical when:
- Using a GUI Git client that shows changes visually
- Working on throwaway experimental code
- You trust your IDE’s Git integration completely
Core Concepts
git status reads the .git/index file (staging area) and compares it against the HEAD commit and the working directory. It reports three categories of files: staged changes (ready to commit), unstaged changes (modified but not staged), and untracked files (new files Git does not know about).
git diff compares two snapshots and outputs the differences in unified diff format. By default, it compares the working directory against the staging area. With flags, it can compare any two commits, branches, or tree objects.
graph LR
A[HEAD Commit] -->|git diff HEAD| B[Working Directory]
A -->|git diff --staged| C[Staging Area]
C -->|git diff| B
The Three Comparison Planes
graph TD
A[HEAD<br/>Last commit] -->|git diff HEAD<br/>or git diff HEAD --| B[Working Directory<br/>Current files]
A -->|git diff --staged<br/>or git diff --cached| C[Staging Area<br/>Prepared for commit]
C -->|git diff<br/>default| B
Architecture or Flow Diagram
git status Output Structure
graph TD
A[git status] --> B[Branch Info<br/>On branch main]
A --> C[Staged Changes<br/>Changes to be committed]
A --> D[Unstaged Changes<br/>Changes not staged for commit]
A --> E[Untracked Files<br/>Untracked files]
C --> C1[new file]
C --> C2[modified]
C --> C3[deleted]
C --> C4[renamed]
D --> D1[modified]
D --> D2[deleted]
Diff Comparison Matrix
graph LR
A[Command] --> B[Compares]
B --> C[Purpose]
A --> D[git diff]
D --> E[Working vs Staging]
E --> F[Review unstaged changes]
A --> G[git diff --staged]
G --> H[Staging vs HEAD]
H --> I[Review what will commit]
A --> J[git diff HEAD]
J --> K[Working vs HEAD]
K --> L[See all changes]
A --> M[git diff branch1..branch2]
M --> N[Branch comparison]
N --> O[Review before merge]
Step-by-Step Guide / Deep Dive
git status Basics
# Standard status output
git status
Output:
On branch main
Your branch is ahead of 'origin/main' by 2 commits.
(use "git push" to publish your local commits)
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
modified: src/app.py
new file: src/auth.py
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.txt
Short Status Format
git status -s
Output:
M src/app.py
A src/auth.py
M README.md
?? notes.txt
Status codes:
| Code | Meaning |
|---|---|
?? | Untracked file |
A | New file staged |
M | Modified file staged |
D | Deleted file (staged) |
M | Modified file (unstaged) |
MM | Modified in both staging and working directory |
R | Renamed file |
C | Copied file |
!! | Ignored file (with -uall) |
Ignoring Untracked Files in Status
# Do not show untracked files
git status -uno
# Show only untracked files in current directory (not subdirectories)
git status -unormal
# Show all untracked files including those in subdirectories
git status -uall
git diff Basics
# See unstaged changes (working directory vs staging area)
git diff
# See staged changes (staging area vs HEAD)
git diff --staged
# or
git diff --cached
# See ALL changes (working directory vs HEAD)
git diff HEAD
Understanding Diff Output
diff --git a/src/app.py b/src/app.py
index abc1234..def5678 100644
--- a/src/app.py
+++ b/src/app.py
@@ -10,7 +10,9 @@ def process_data(data):
result = validate(data)
- return transform(result)
+ cleaned = clean(result)
+ log.info(f"Processed {len(cleaned)} items")
+ return transform(cleaned)
def transform(data):
Reading the diff:
---is the old version,+++is the new version- Lines starting with
-were removed - Lines starting with
+were added - Lines without prefix are context (unchanged)
@@ -10,7 +10,9 @@means: in the old file, start at line 10, show 7 lines; in the new file, start at line 10, show 9 lines
Comparing Branches
# See what changed in feature compared to main
git diff main..feature
# See what will be merged INTO main FROM feature
git diff feature..main
# See only file names that differ
git diff --name-only main..feature
# See a summary of changes (renames, mode changes)
git diff --stat main..feature
# See diff with word-level highlighting
git diff --word-diff main..feature
Comparing with Specific Commits
# Compare working directory with a specific commit
git diff abc1234
# Compare two commits
git diff abc1234 def5678
# Compare current branch with a tag
git diff v1.0..HEAD
# See what changed between the last two commits
git diff HEAD~1..HEAD
# Compare a file across two commits
git diff abc1234:def5678 -- src/app.py
Filtering Diffs
# Show only changed file names
git diff --name-only
# Show only added/deleted file names
git diff --name-status
# Show diff statistics (files changed, insertions, deletions)
git diff --stat
# Show only changes in specific files
git diff -- src/app.py src/utils.py
# Show only changes in a directory
git diff -- src/
# Ignore whitespace changes
git diff -w
git diff --ignore-space-change
# Ignore all whitespace including blank lines
git diff --ignore-all-space
# Show only the names of changed files with status
git diff --name-status
Color and Format Options
# Enable colored diff output (usually on by default)
git diff --color
# Show diff with function context
git diff --function-context
# Generate a patch file
git diff > changes.patch
# Apply a patch file
git apply changes.patch
# Show diff in raw format (for scripting)
git diff --raw
Production Failure Scenarios + Mitigations
| Scenario | Impact | Mitigation |
| ----------------------------------------------- | ------------------------------------------------ | ---------------------------------------------------------------- | ---------------------------------------------- |
| Not reviewing git diff --staged before commit | Accidental commit of debug code or wrong changes | Make git diff --staged a mandatory step before every commit |
| Misreading diff direction in branch comparison | Merging unexpected changes | Remember: git diff A..B shows what B has that A does not |
| Ignoring whitespace-only diffs | Missing real changes among noise | Use git diff -w to filter whitespace noise when reviewing |
| Not checking git status after merge | Unresolved conflicts or unintended staged files | Always run git status after merge/rebase to verify clean state |
| Diff output truncated in terminal | Missing critical changes at end of large diffs | Use git diff | lessorgit diff > diff.txt for large changes |
| Comparing wrong branches | Reviewing irrelevant changes | Verify branch names with git branch before comparing |
Trade-offs
| Approach | Advantages | Disadvantages | When to Use |
|---|---|---|---|
git diff (default) | Shows unstaged changes, most common use case | Does not show staged changes | Reviewing work in progress |
git diff --staged | Shows exactly what will be committed | Does not show unstaged changes | Final review before commit |
git diff HEAD | Shows all changes at once | Can be overwhelming with many changes | Quick overview of everything |
git diff --stat | Quick summary of scope | No detail about actual changes | Assessing impact, release notes |
git diff --name-only | Fast, scriptable | No change details | CI/CD pipelines, automation |
git diff -w | Ignores whitespace noise | May hide meaningful whitespace changes | Reviewing refactored code |
Implementation Snippets
The Pre-Commit Review Ritual
# Step 1: Overview
git status -s
# Step 2: Review unstaged changes
git diff
# Step 3: Stage intended changes
git add src/feature.py
# Step 4: Review what will be committed
git diff --staged
# Step 5: See everything together
git diff HEAD
# Step 6: Commit
git commit -m "feat: add feature"
Branch Comparison Before Merge
# What will main gain if we merge feature?
git diff main..feature --stat
# Detailed view of changes
git diff main..feature
# Only the files that changed
git diff --name-only main..feature
# Files added in feature
git diff --diff-filter=A --name-only main..feature
# Files deleted in feature
git diff --diff-filter=D --name-only main..feature
Generating a Patch
# Create a patch from uncommitted changes
git diff > my-changes.patch
# Create a patch from staged changes
git diff --staged > staged-changes.patch
# Create a patch between two commits
git diff abc1234 def5678 > commit-diff.patch
# Apply the patch
git apply my-changes.patch
# Apply with verification
git apply --check my-changes.patch
git apply --stat my-changes.patch
Diff with External Tools
# Use vimdiff
git diff --ext-diff=vimdiff
# Use meld (visual diff tool)
git difftool -t meld
# Use beyond compare
git difftool -t bc3
# Configure default difftool
git config --global diff.tool meld
git config --global difftool.prompt false
git difftool
Observability Checklist
- Logs: Use
git status -sfor quick daily checks of repository state - Metrics: Track
git diff --statoutput to measure change velocity per commit - Traces: Use
git diff --name-statusto trace which files were added, modified, or deleted - Alerts: Pre-commit hooks should reject commits exceeding file count or line change thresholds
- Audit: Use
git diff tag1..tag2 --statto audit all changes between releases - Health: Periodically run
git statusto ensure no long-running uncommitted work accumulates - Validation: Always run
git diff --stagedbefore committing to verify exact contents
Security/Compliance Notes
- Diff output may contain secrets: Be careful when sharing diff output or posting it in public channels. It may include API keys, passwords, or internal URLs
- Patches are executable:
git applyapplies patches without verification. Always review patches withgit apply --checkbefore applying - Audit trails:
git diffbetween releases provides a complete audit trail of what changed. This is essential for compliance in regulated industries - Signed tags for releases: Combine
git diffwith signed tags to create verifiable release change records - Data in diffs: Deleted files still appear in diffs. If you delete a file containing secrets, the secret is still visible in the diff of the commit that deleted it
Common Pitfalls / Anti-Patterns
- Confusing
git diffandgit diff --staged: The defaultgit diffshows unstaged changes, not what will be committed. This is the most common source of confusion - Misreading branch comparison direction:
git diff A..Bshows what B has that A does not. Reversing the order reverses the diff - Ignoring the
--statoutput: The diff stat (files changed, insertions, deletions) gives you immediate context about the scope of changes. Skipping it means reviewing blindly - Not using
git diff HEAD: When you have both staged and unstaged changes, neithergit diffnorgit diff --stagedshows the complete picture.git diff HEADdoes - Reading diffs without context: A diff shows what changed but not why. Always read the commit message alongside the diff to understand the intent
- Assuming diff order matches file order: Git sorts diff output by file path, not by the order you staged files. Do not rely on diff order for understanding commit structure
Quick Recap Checklist
-
git statusshows branch, staged, unstaged, and untracked files -
git status -sshows compact single-line status -
git diffcompares working directory vs staging area (unstaged changes) -
git diff --stagedcompares staging area vs HEAD (what will be committed) -
git diff HEADcompares working directory vs HEAD (all changes) -
git diff branch1..branch2compares two branches -
git diff --statshows summary of changes -
git diff --name-onlyshows only changed file names -
git diff -wignores whitespace changes - Lines with
+are additions, lines with-are deletions - Always review
git diff --stagedbefore committing - Use
git difftoolfor visual diff with external tools
Interview Q&A
git diff compares the working directory against the staging area, showing changes you have made but not yet staged. git diff --staged (or --cached) compares the staging area against HEAD, showing what will be included in the next commit. git diff HEAD compares the working directory directly against HEAD, showing all changes regardless of staging state. When you have both staged and unstaged changes, only git diff HEAD shows the complete picture.
A unified diff has several parts. The diff --git line identifies the files being compared. The index line shows blob hashes. The --- line is the old file, +++ is the new file. The @@ hunk header shows line numbers: @@ -old_start,old_count +new_start,new_count @@. Lines starting with - were removed, lines with + were added, and lines without a prefix are context (unchanged lines shown for reference). The function name in the hunk header shows which function the change belongs to.
It shows the changes that feature has that main does not — in other words, what would be added to main if you merged feature into it. The syntax A..B means "show me what B has that A does not." A common mistake is reversing the order. If you want to see what main has that feature does not, use git diff feature..main. For a symmetric difference (changes unique to either branch), use git diff main...feature (three dots).
Use git diff --name-only to see just the file paths that have changed. Use git diff --name-status to see file paths with their change type (A for added, M for modified, D for deleted, R for renamed). Use git diff --stat to see a summary with file names and the number of insertions and deletions per file. These options are useful for quick overviews, scripts, and CI/CD pipelines where you need to know what changed without the full diff content.
How git diff Computes Deltas
git diff does not compare files directly. It compares tree objects — Git’s internal representation of directory snapshots. The process:
- Resolve both commit references to their tree objects
- Walk both trees recursively, matching file paths
- For each matching file, compare blob hashes — if identical, skip
- For differing files, compute a line-by-line diff using the Myers diff algorithm
- Output results in unified diff format
graph LR
A[Tree Object A<br/>Commit snapshot] --> B[Walk trees]
C[Tree Object B<br/>Commit snapshot] --> B
B --> D{Same blob hash?}
D -->|Yes| E[Skip — no changes]
D -->|No| F[Myers diff algorithm]
F --> G[Unified diff output]
When blob hashes match, Git skips content comparison entirely — this is why git diff is fast even on large repositories with mostly unchanged files.
Production Failure: Misleading Diff Output from Whitespace
A code review approves a PR based on git diff output. The diff shows only whitespace changes — tabs converted to spaces, trailing spaces removed, line ending normalization (CRLF to LF). The reviewer assumes the change is harmless formatting.
What was missed: buried among 200 whitespace changes, a single line changed if (user.isAdmin) to if (user.isAdmin || user.isModerator) — a privilege escalation that was not caught because the reviewer skimmed the noisy diff.
Mitigation:
# Ignore whitespace when reviewing formatting-heavy changes
git diff -w # Ignore all whitespace
git diff --ignore-space-at-eol # Ignore trailing whitespace only
# Word-level diff to see actual content changes clearly
git diff --word-diff=color
# See only non-whitespace changes
git diff --ignore-all-space --stat
Always review diffs with whitespace filtering as a second pass, especially when the diff is large.
Implementation: Useful Diff Configurations
Add these to your ~/.gitconfig for a better diff experience:
# Always use color in diff output
git config --global color.diff auto
# Show word-level changes inline (not just whole lines)
git config --global diff.colorMoved zebra
# Detect renames and copies
git config --global diff.renames copies
# Show function context in diffs
git config --global diff.funcContext 5
# Use a better diff algorithm
git config --global diff.algorithm histogram
# Show unified context lines (default is 3)
git config --global diff.context 5
Useful diff aliases:
git config --global alias.d 'diff --color-words'
git config --global alias.ds 'diff --stat'
git config --global alias.dn 'diff --name-status'
git config --global alias.dc 'diff --cached'
git config --global alias.dw 'diff -w'
Quick Recap: Pre-Commit Diff Review Checklist
- Run
git status -sfor a quick overview of changed files - Run
git diffto review unstaged changes - Run
git diff --stagedto review exactly what will be committed - Run
git diff --stat --stagedto verify the scope of changes - Check for accidental debug code, console.log, or print statements
- Verify no secrets, API keys, or credentials are in the diff
- Use
git diff -was a second pass if whitespace changes are noisy - Confirm the diff matches your commit message description
- Check that no unintended files are included (build artifacts, IDE configs)
- For large diffs, review with
git diff --staged | lessto avoid terminal truncation
Resources
- Git Status Documentation — Official reference
- Git Diff Documentation — Complete diff options
- Pro Git — Viewing the Commit History — History navigation
- Understanding Git Diff — Advanced diff techniques
- The Three States — Foundational concepts
Category
Related Posts
Git Blame and Annotate: Line-by-Line Code Attribution
Master git blame for line-by-line code attribution, understanding code history, finding when code changed, and using blame effectively for code comprehension.
Pull Requests and Code Review: Git Collaboration Best Practices
Master pull request workflows and code review — writing effective PR descriptions, review best practices, collaboration patterns, and team workflows.
Centralized vs Distributed VCS: Architecture, Trade-offs, and When to Use Each
Compare centralized (SVN, CVS) vs distributed (Git, Mercurial) version control systems — their architectures, trade-offs, and when to use each approach.