Git path · The data model · Topic 3 of 13

The index: what your next commit will be

Not started

Git keeps three versions of every tracked file. HEAD is the last commit. The working tree is the files on disk that you edit. Between them sits the index (also called the staging area, stored in .git/index): a full list of paths, modes and blob hashes that describes the next commit. git add copies a file from the working tree into the index; git commit turns the index into a tree and a commit.

Why it matters: Most confusion about add, diff, reset and restore comes from not seeing that there are three versions of each file, not two.

  1. 1Three places

    HEAD (the last commit), the index (the next commit as it stands) and the working tree (the files you edit).

  2. 2git add copies into the index

    It writes a blob for the file's current bytes and points the index entry at it. Later edits are not included.

  3. 3Two diffs, not one

    git diff compares the working tree with the index; git diff --staged compares the index with HEAD.

  4. 4Commit writes the index

    git commit turns the index into a tree, so a commit contains exactly what was staged.

1

How it works

The explanation, with examples you can run.

Watch a file in all three places

git init -q -b main demo && cd demoecho "hello" > greeting.txtgit add greeting.txt && git commit -q -m "Add greeting"echo "hello, world" > greeting.txtgit add greeting.txtecho "hello, world!" > greeting.txtgit status --short
MM greeting.txt

The two letters are two comparisons. The first column is index against HEAD (staged), the second is working tree against index (not staged). Here both differ, so there are three versions of greeting.txt. The two diffs show each step:

git diff --stagedgit diff
diff --git a/greeting.txt b/greeting.txtindex ce01362..4b5fa63 100644--- a/greeting.txt+++ b/greeting.txt@@ -1 +1 @@-hello+hello, worlddiff --git a/greeting.txt b/greeting.txtindex 4b5fa63..270c611 100644--- a/greeting.txt+++ b/greeting.txt@@ -1 +1 @@-hello, world+hello, world!

The index lines in each diff header show the blob hashes: ce01362 is in HEAD, 4b5fa63 is in the index, 270c611 is the working-tree version, which has no object yet. That middle blob was written by git add before any commit:

git ls-files --stagegit cat-file -t 4b5fa63
100644 4b5fa63702dd96796042e92787f464e28f09f17d 0	greeting.txtblob

git ls-files --stage lists the index: mode, blob, stage number (0 normally; 1 to 3 during a merge conflict) and path. A commit now would record hello, world without the !.

Why have a middle step

The index lets you build a commit deliberately rather than committing whatever happens to be on disk:

  • Split work into clean commits. git add -p shows each change hunk by hunk and stages only the ones you choose, so a bug fix and an unrelated rename go into separate commits.
  • Review before committing. git diff --staged is exactly the change the commit will contain.
  • Merges use it. During a conflicted merge the index holds several versions of a file at once; resolving means writing one version back at stage 0 (see the conflicts topic).
  • It makes status fast. The index caches file sizes and timestamps, so Git can tell which files might have changed without hashing every one of them.

git commit -a stages changes to files Git already tracks and commits in one step; new files still need git add. To take something out of the index without touching your edits, use git restore --staged <path> (covered with the other undo commands).

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.You edit a file, run git add, edit it again and commit. What is in the commit?
  2. 2.git status --short shows MM next to a file. What does it mean?
  3. 3.You run git diff and see nothing, but git commit still records changes. Why?

0 of 3 right so far. Get all three to finish this topic.