Why git diff and git diff --staged Show Different Changes

See exactly why one file can show two different diffs by comparing HEAD, the index, and the working tree in a disposable Git lab.

By Network Nuts · Published 2026-10-21

If git diff and git diff --staged show different changes for the same file, the short answer is that Git is comparing different snapshots. Plain git diff shows working tree versus index, while git diff --staged shows index versus HEAD, as described in the official git diff documentation and reflected by git status. (git-scm.com)

Lab environment

This walkthrough uses the verified lab runtime exactly as captured:

  • Ubuntu 26.04 LTS
  • Git 2.53.0
  • Bash
  • A new disposable local Git repository
  • No remote and no credentials

Prerequisites

You need:

  • Git
  • Bash
  • A new empty working directory outside any existing Git repository

Safety notes:

  • Run this only in a disposable folder.
  • The commands create a local repository and write settings.txt.
  • No remote is configured, so nothing is pushed anywhere.
  • The exact object IDs in diff headers can vary between runs, but the comparison logic stays the same.

If you want more structured practice after this walkthrough, the GIT Version Control Training course covers the staging area and commit workflow in more depth.

The mental model: one path, three snapshots

Git confusion usually starts when you think only in terms of “the file on disk.” For this problem, Git is really tracking three relevant states:

  1. HEAD: the version from the last commit.
  2. The index: the staging area that holds what the next commit will use, as explained in the Git data model documentation. (git-scm.com)
  3. The working tree: the file currently on disk.

That is why a single path can show two different diffs at the same time. Git can see one change already staged in the index and a newer change still unstaged in the working tree. The official git add documentation notes that it adds the file contents at the time you run git add, and later edits are not included until you add the file again. (git-scm.com)

Set up the disposable repository

Create and enter a fresh folder before running these commands.

git init -q
git config user.name 'Example Author'
git config user.email 'author@example.invalid'

What this does:

  • git init -q creates a new repository quietly.
  • The two git config commands set a local name and email so the lab can make one commit without relying on any global Git configuration.

Now create the baseline file, stage it, and commit it:

printf 'workers=1\n' > settings.txt
git add settings.txt
git commit -qm Baseline

At this point:

  • HEAD contains workers=1
  • the index matches HEAD
  • the working tree also matches HEAD

There is no diff yet because all three snapshots agree.

Create a staged change, then change the file again

Now make the first edit and stage it:

printf 'workers=2\n' > settings.txt
git add settings.txt

This is the critical step. After git add, the index now contains workers=2. That staged copy is what the next commit would use, according to the official git diff documentation. (git-scm.com)

Then edit the same file again without staging the new content:

printf 'workers=3\n' > settings.txt

Now the snapshots differ:

  • HEAD = workers=1
  • index = workers=2
  • working tree = workers=3

This is the exact situation that makes git diff and git diff --staged show different results.

Check status before reading diffs

Start with git status --short:

git status --short -- settings.txt

Observed result from the verified lab:

Recorded git-index command output

Rendered output from actual lab commands. Captured 21 October 2026, 14:06 IST. Open full-size image

The important line is:

MM settings.txt

In Git short status, the first position reports index state and the second reports working-tree state. MM means the file is modified in both places, which matches the official git status documentation. (git-scm.com)

That single line is your fastest clue:

  • first M: staged change exists relative to HEAD
  • second M: unstaged change exists relative to the index

So before you even inspect a patch, git status already tells you that two layers of change exist.

Prove the three snapshots directly

The verified lab also checked the committed version, the staged version, and the current working-tree version separately:

git show HEAD:settings.txt
git show :settings.txt
cat settings.txt

Observed values in the verified run:

  • git show HEAD:settings.txt returned workers=1
  • git show :settings.txt returned workers=2
  • cat settings.txt returned workers=3

This is the whole mystery in one view. The path name is the same, but the content differs depending on which snapshot you inspect.

Compare the staged and unstaged diffs

Now run the two diff commands separately.

First, inspect what is already staged:

git diff --staged -- settings.txt

Then inspect what is still unstaged:

git diff -- settings.txt

The lab captured both outputs:

Recorded git-index command output

Rendered output from actual lab commands. Captured 21 October 2026, 14:06 IST. Open full-size image

What git diff --staged is showing

The staged diff was:

-workers=1
+workers=2

That comparison is:

  • before: HEAD
  • after: index

This matches the official git diff documentation, which says --staged is a synonym for --cached and shows changes staged for the next commit relative to HEAD by default. (git-scm.com)

So git diff --staged answers this question:

“What would I commit right now?”

In this lab, the answer is workers=1 changing to workers=2.

What plain git diff is showing

The unstaged diff was:

-workers=2
+workers=3

That comparison is:

  • before: index
  • after: working tree

Again, this follows the official git diff documentation, which describes plain git diff as showing changes in the working tree relative to the index. (git-scm.com)

So plain git diff answers this question:

“What have I changed since the last git add?”

In this lab, the answer is the newer edit from workers=2 to workers=3.

The quick comparison table to memorize

CommandComparisonPractical meaning
git diffworking tree vs indexWhat still needs staging?
git diff --stagedindex vs HEADWhat is ready for the next commit?
git diff HEADworking tree vs HEADWhat changed overall since the last commit?

The first two rows come directly from the official git diff documentation. (git-scm.com)

git diff HEAD -- settings.txt is also useful here. It would show the overall difference between the committed file and the current working-tree file. In this lab, that overall jump is effectively from workers=1 to workers=3. That can be handy for a big-picture check, but it hides the boundary between what is already staged and what is not.

Why Git behaves this way on purpose

This is not a quirk. It is one of Git’s most useful features.

Because the index is separate from the working tree, you can stage a clean change and then continue editing the same file without automatically changing the staged snapshot. The Git data model documentation explains that the index holds the file contents that Git will use to build the next commit tree. (git-scm.com)

That means you can:

  • stage a safe first change,
  • keep experimenting in the file,
  • and decide later whether the newer edits belong in the same commit.

Once you understand that the index is its own snapshot, the two diff outputs stop looking contradictory.

A practical review sequence for daily work

When a file appears in both staged and unstaged form, use this order:

  1. git status --short
  2. git diff -- path
  3. git diff --staged -- path

That sequence works well because git status gives you the shape of the problem first, then each diff answers a different follow-up question. The git status documentation also notes that verbose status can include staged and unstaged textual changes, which reinforces the same two-layer model. (git-scm.com)

A good rule of thumb is:

  • If you care about the next commit, inspect git diff --staged.
  • If you care about what still needs git add, inspect plain git diff.

If reviewing the diffs shows that you staged or edited the wrong layer, a useful next step is What to do after reviewing the wrong staged or unstaged changes.

Cleanup

Delete only the disposable folder you created for this lab.

For example, from its parent directory:

rm -rf your-disposable-folder

That cleanup is enough because the lab stayed local, used no remote, and stored no credentials.

Example source files

The supporting code and configuration on GitHub contain the complete example files discussed in this article.

Sources