# Make git diff see a new file without staging its content
git add -N <new-file>
# Undo git add -N only (file must have no staged content)
git reset HEAD <new-file>git diffonly compares the index against the working tree.- An untracked file has no index entry, so
git diffcannot see it. git add -Ncreates a zero-length index entry (“intent to add”) that letsgit diffpick up the file without staging any content.
🎯 Goal
Why git diff ignores untracked files
git diff (with no flags) compares two things:
index (staging area) ←→ working tree
Every tracked file has an index entry — a record in Git’s internal database that stores the file’s path. git diff iterates over these entries and compares each one to the corresponding file.
An untracked file has no index entry at all. From Git’s perspective it simply does not exist yet, so git diff has nothing to compare it against and silently skips it.
Why you want to care
A common workflow is to pipe git diff into an LLM to code-review:
git --no-pager diff | claude -p "code-review for this diff"If your changeset includes new files that are still untracked, git diff silently omits them — the LLM sees an incomplete picture and the review will miss those additions entirely. Running git add -N on each new file first ensures every change, including brand-new files, appears in the diff you hand off.
git add -N new-feature.py
git --no-pager diff | claude -p "code-review for this diff"--no-pager?
Without it, Git may invoke a pager (e.g. less) when stdout is not a terminal. A pager takes over the terminal rather than writing plain text to stdout, which breaks the pipe — Claude receives nothing. --no-pager forces Git to write directly to stdout regardless of terminal detection.
git add -N: intent-to-add
git add -N (long form: git add --intent-to-add) registers a file in the index with an empty blob as its content. It does not stage the actual file contents.
git add -N new-feature.pyAfter this command:
git statusshowsnew-feature.pyas a new file with unstaged changesgit diffnow shows the full diff of the file (index = empty, working tree = file content)git diff --cachedshows nothing (no content has been staged)git commitwill refuse to commit it — the staged content is empty
$ git add -N new-feature.py
$ git status --short
A new-feature.py # wait — why 'A'?git status shows A but nothing is actually staged
- The
Ain the left column means “added to index”, but the index entry is empty. git diff --cachedwill show an empty diff. Onlygit diff(unstaged) reveals the full file content.
Typical workflow with git add -N
# 1. You create a new file
touch new-feature.py
# 2. Register intent-to-add so git diff can see it
git add -N new-feature.py
# 3. Edit the file, review the diff as you work
vim new-feature.py
git diff new-feature.py # full diff visible
# 4. When satisfied, stage for real
git add new-feature.py
# 5. Commit
git commit -m "Add new-feature.py"Resetting git add -N with git reset
If you added intent-to-add by mistake and want to go back to treating the file as untracked, use git reset:
git reset HEAD <file>git reset HEAD <file> works correctly here because an intent-to-add entry has an empty blob — there is nothing staged to lose. If you later ran git add <file> and staged real content, this command would discard those staged changes. Use git restore --staged <file> to unstage a fully-staged file instead.
git reset HEAD <file> is a --mixed-mode reset scoped to one file. It removes the file’s index entry entirely, returning the file to untracked status. The file’s content on disk is never touched.
What --mixed means here
git reset has three modes that control how much state is rolled back:
| Mode | HEAD moves | Index cleared | Working tree cleared |
|---|---|---|---|
--soft |
✔️ | ✅ kept | ✅ kept |
--mixed |
✔️ | ❌ cleared | ✅ kept |
--hard |
✔️ | ❌ cleared | ❌ cleared |
When you run git reset HEAD <file> (without a mode flag), Git uses --mixed by default and applies it only to that single file:
- The index entry for
<file>is removed (or reset toHEAD’s version) - The working tree file is untouched
- HEAD itself does not move (no commit is changed)
$ git add -N new-feature.py
$ git status --short
A new-feature.py
$ git reset HEAD new-feature.py
$ git status --short
?? new-feature.py # back to untrackedgit reset HEAD <file> vs git reset HEAD
git reset HEAD <file>— scoped to one file; only removes that file’s index entrygit reset HEAD— resets the entire index to matchHEAD; all staged changes are unstaged
Bulk-undo all intent-to-add entries at once
When you have multiple intent-to-add files and want to undo all of them without touching any fully-staged files, use --diff-filter=A:
git diff --cached --name-only --diff-filter=A \
| xargs git reset HEAD --Breaking it down:
| Part | What it does |
|---|---|
git diff --cached --name-only |
Lists file names that differ between HEAD and the index (i.e. staged changes) |
--diff-filter=A |
Keeps only Added entries — files with no prior version in HEAD, which is exactly the state git add -N creates |
xargs git reset HEAD -- |
Runs git reset HEAD -- <file> for each name; the -- separator prevents file names from being misread as flags |
Because --diff-filter=A matches only new index entries, fully-staged modifications (M) and deletions (D) are left completely untouched.
Summary
| Situation | Command |
|---|---|
See why a new file is invisible to git diff |
git status --short |
Make a new file visible to git diff |
git add -N <file> |
| Stage the full content after reviewing | git add <file> |
Undo git add -N (no staged content) |
git reset HEAD <file> |
| Unstage a fully staged file | git restore --staged <file> |
Glossary
- def: Index (staging area)
description: |
Git's intermediate layer between the working tree and a commit.
Every file Git tracks has an entry here. `git add` writes content
into the index; `git commit` turns the index into a new commit object.
- def: Intent-to-add (git add -N)
description: |
A special index entry with an empty blob, created by
`git add --intent-to-add`. It tells Git "this file will eventually
be added" so that `git diff` can compare the (empty) index entry
against the working tree file, producing a visible diff without
staging any content.
- def: git diff
description: |
Compares the index against the working tree.
Shows changes you have made but not yet staged.
Untracked files (no index entry) are invisible to this command.
- def: git diff --cached
description: |
Compares HEAD against the index.
Shows changes that are staged and will be included in the next commit.
Also written as `git diff --staged` (identical behaviour).
An intent-to-add entry (`git add -N`) produces an empty diff here
because no content has been staged yet.
- def: git reset --mixed (default)
description: |
Moves HEAD to a target commit and clears the index to match,
but leaves the working tree files untouched. When scoped to a
single file (`git reset HEAD <file>`), it only removes that file's
index entry — HEAD does not move.