git
git status
git diff
version control
troubleshooting

'git status' shows changed files, but 'git diff' doesn't

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

When git status shows files as modified but git diff shows nothing, the changes are already staged (in the index). git diff compares the working tree against the index, while git status compares both the index against HEAD and the working tree against the index. Use git diff --staged (or --cached) to see staged changes. The other common cause is line-ending or file permission changes that git diff considers insignificant by default.

The Discrepancy Explained

bash
1# git status shows modified files
2$ git status
3Changes not staged for commit:
4  modified:   app.js
5
6# But git diff shows nothing
7$ git diff
8(empty output)

This happens because the file changed in a way git diff does not display by default (line endings, permissions), or more commonly, the changes are staged.

Cause 1: Changes Are Already Staged

bash
1# You staged the file earlier
2$ git add app.js
3
4# git status shows it as staged
5$ git status
6Changes to be committed:
7  modified:   app.js
8
9# git diff compares working tree vs index. Since both match, nothing shows
10$ git diff
11(empty)
12
13# Use --staged to see what's staged (index vs HEAD)
14$ git diff --staged
15diff --git a/app.js b/app.js
16...shows the actual changes...

git diff = working tree vs index (staging area) git diff --staged = index vs last commit (HEAD)

Understanding the Three Trees

 
1HEAD (last commit)     Index (staging area)     Working Tree (files on disk)
2        ↑                       ↑                         ↑
3   git diff --staged       git diff              (what you see in editor)
4   (staged changes)     (unstaged changes)
bash
1# See ALL changes (both staged and unstaged)
2$ git diff HEAD
3
4# See only staged changes
5$ git diff --staged
6
7# See only unstaged changes
8$ git diff

Cause 2: Line Ending Changes (CRLF vs LF)

bash
1$ git status
2Changes not staged for commit:
3  modified:   README.md
4
5$ git diff
6(empty)
7
8# Check with --word-diff or whitespace options
9$ git diff --ws-error-highlight=all
10(still empty, line endings are not shown by default)
11
12# Check the actual difference
13$ git diff --no-index --binary README.md README.md
14# Or check .gitattributes
15$ cat .gitattributes
16* text=auto

Git's core.autocrlf or .gitattributes with text=auto converts line endings on checkout. git status detects the difference, but git diff considers it a no-op after normalization.

Fix:

bash
1# Normalize line endings in the repo
2$ git add --renormalize .
3$ git commit -m "Normalize line endings"
4
5# Or configure for your platform
6$ git config core.autocrlf input   # Linux/macOS
7$ git config core.autocrlf true    # Windows

Cause 3: File Permission Changes

bash
1$ git status
2Changes not staged for commit:
3  modified:   script.sh
4
5$ git diff
6old mode 100644
7new mode 100755
8# Only the permission changed, no content change

On Unix/macOS, chmod +x script.sh changes the file mode. git status reports this as modified, but git diff may show only the mode change (no content diff).

Fix: Either commit the permission change or ignore it:

bash
1# Ignore file mode changes globally
2$ git config core.fileMode false
3
4# Or per-repo
5$ git config --local core.fileMode false

Cause 4: Submodule Changes

bash
1$ git status
2Changes not staged for commit:
3  modified:   lib/vendor (new commits)
4
5$ git diff
6(empty or just shows submodule summary)

When a submodule has new commits, git status reports it as modified, but git diff does not show file-level diffs for submodules by default.

bash
# See submodule changes
$ git diff --submodule
$ git submodule status

Cause 5: Stat-Only Changes (Timestamps)

Git uses file timestamps and sizes to quickly detect changes. Sometimes a file's timestamp changes (from a build tool or touch) without content changing:

bash
1$ git status
2Changes not staged for commit:
3  modified:   config.json
4
5$ git diff
6(empty)
7
8# Refresh the index to fix the timestamps
9$ git update-index --refresh
10
11# Or
12$ git status   # Running status usually fixes this automatically

Diagnostic Commands

bash
1# Show exact status of each file
2$ git status --porcelain
3 M app.js        # Space + M = modified in working tree only
4M  app.js        # M + space = modified in index (staged)
5MM app.js        # Modified in both (staged + further working tree changes)
6
7# Show what git thinks changed
8$ git diff --stat
9# Shows files and line counts, even if full diff is empty
10
11# Show raw diff including whitespace
12$ git diff --ignore-cr-at-eol
13
14# Check git's internal status
15$ git ls-files -m     # List modified files
16$ git ls-files -s     # List staged files with mode

Quick Reference

Scenariogit status showsgit diff showsSolution
Staged changes"Changes to be committed"NothingUse git diff --staged
Line ending changes"Changes not staged"Nothinggit add --renormalize .
Permission changes"Changes not staged"Mode change onlygit config core.fileMode false
Submodule updates"modified (new commits)"Nothinggit diff --submodule
Stale timestamps"Changes not staged"Nothinggit update-index --refresh

Common Pitfalls

  • Forgetting --staged: The most common cause of confusion. git diff only shows unstaged changes. Use git diff --staged for staged changes and git diff HEAD for everything.
  • Mixed staged and unstaged: After git add followed by more edits, the file appears in both "staged" and "unstaged" sections. git diff shows the unstaged part, git diff --staged shows the staged part.
  • .gitattributes autocrlf surprises: Cloning a repo on Windows with text=auto converts all line endings, making every file appear modified. Run git add --renormalize . to fix.
  • core.fileMode on shared filesystems: NFS, Docker mounts, and WSL can change file permissions unexpectedly. Set core.fileMode false in these environments.
  • Assuming git diff shows all changes: By default, it only compares the working tree to the index. There are four possible comparisons (working vs index, index vs HEAD, working vs HEAD, commit vs commit). Use the right flags for each.

Summary

  • git diff shows unstaged changes (working tree vs index)
  • git diff --staged shows staged changes (index vs HEAD)
  • git diff HEAD shows all changes (working tree vs HEAD)
  • Line ending and permission changes cause status to report modifications that diff considers insignificant
  • Use git status --porcelain to see the exact state of each file
  • git update-index --refresh clears false positives from stale timestamps

Course illustration
Course illustration

All Rights Reserved.