| English | 中文 | Interactive demo | Runnable lab |
When an AI agent says that a change is complete, you still need to ask one question: is the change only in the working tree, staged in the index, or already recorded in a commit?
These three locations can hold three versions of the same file at the same time. This model explains exactly what git add, git commit, git diff, and git restore affect.
HEAD snapshot Index snapshot Working Tree
current commit next commit draft files on disk
version=1 version=2 version=3
HEAD points to the currently checked-out commit, which records a project snapshot.git commit takes its input from the index. Unstaged working-tree content does not enter that commit.
In the user-facing logical model, each commit records a snapshot of the project. git diff computes the difference between two states and shows what changed between them.
Git may use delta compression inside packfiles to save storage. That implementation detail does not change the snapshot model represented by a commit.
The three common comparisons are:
HEAD -------- git diff --cached --------> Index
Index ----------- git diff -------------> Working Tree
HEAD ------------ git diff HEAD --------> Working Tree
| Command | Compared states | Main question |
|---|---|---|
git diff |
Index and working tree | What has not been staged yet? |
git diff --cached |
HEAD and index | What will the next commit record? |
git diff HEAD |
HEAD and working tree | What is the total tracked change from the current commit? |
Open the Snapshots and State interactive. Step through edit, stage, edit again, and commit to see how app.txt changes across all three locations.
To view it through a local static server, run this command from the repository root:
python3 -m http.server 8000
Then visit:
http://localhost:8000/interactive/git-mental-model/snapshots-and-state.html
Press Ctrl-C in the server terminal when you are finished.
This experiment creates a new temporary repository and does not modify the current project.
lab_dir=$(mktemp -d "${TMPDIR:-/tmp}/my-git-snapshots.XXXXXX")
git -c init.defaultBranch=main init -q "$lab_dir"
cd "$lab_dir"
git config user.name "My Git Lab"
git config user.email "lab@example.com"
printf 'version=1\n' > app.txt
git add app.txt
git commit -q -m "record version 1"
git status --short
Expected output: no output, because HEAD, the index, and the working tree match.
printf 'version=2\n' > app.txt
git status --short
Expected output:
M app.txt
The first column represents the index and the second represents the working tree. The blank first column and M in the second mean that only the working tree has changed.
HEAD=version=1 Index=version=1 Working Tree=version=2
git add app.txt
git status --short
Expected output:
M app.txt
The M moves to the first column. The index differs from HEAD, while the working tree matches the index.
HEAD=version=1 Index=version=2 Working Tree=version=2
printf 'version=3\n' > app.txt
git status --short
Expected output:
MM app.txt
The file now has three simultaneous versions. Read each location directly:
git show HEAD:app.txt
git show :app.txt
cat app.txt
The expected outputs, in order, are:
version=1
version=2
version=3
The :app.txt syntax reads app.txt from the index.
git commit -m "record version 2"
git status --short
The object ID in the commit output varies by environment. Its normalized form is:
[main <object-id>] record version 2
1 file changed, 1 insertion(+), 1 deletion(-)
The following git status --short still shows:
M app.txt
Final state:
HEAD=version=2 Index=version=2 Working Tree=version=3
The commit recorded version=2 from the index. Version=3 remains as an uncommitted working-tree edit.
After staging, this command may produce no output:
git diff
That result only means that the working tree matches the index. It does not prove that the repository has no changes. Continue with:
git diff --cached
git status --short
When an agent or a person reports that there is no diff, the report should identify which comparison was run.
If tracked app.txt was deleted only from the working tree, inspect the state first:
git status --short
git diff -- app.txt
After confirming that the deletion can be discarded, restore the file from HEAD:
git restore --source=HEAD --worktree -- app.txt
git status --short
git restore overwrites the working-tree state for the selected path. Confirm that the current edit is disposable before running it.
Git cannot recover an untracked file that was never added, committed, or saved by another tool. Understanding that boundary matters more than memorizing a recovery command.
A commit records the index. Unstaged working-tree content remains on disk.
git diff means there are no changes”By default, git diff compares the index with the working tree. Use git diff --cached to inspect staged changes.
Git presents commits as snapshots. A diff is a comparison result, while packfiles may use delta compression internally.
Recovery requires content recorded in Git objects, the index, a stash, or another backup. Git has no recovery source for untracked content that was never saved.
After completing a task, an agent should distinguish these forms of evidence:
| Fact to confirm | Inspection |
|---|---|
| Unstaged working-tree changes | git diff |
| Snapshot proposed by the index | git diff --cached |
| All tracked changes from HEAD | git diff HEAD |
| Untracked, staged, or mixed states | git status --short |
| Content of the latest commit | git show --stat --oneline HEAD |
Practical rules:
git diff as a substitute for git diff --cached and git status.git add -A.git status --short show MM app.txt?MM app.txt state?