The Three-Tree Model

Updated

October 30, 2026

The Three-Tree Model

After this page you can name Git’s three snapshots of the same project and say which command copies between them.

Three snapshots, one project

Everyday Git is three versions of the same tree:

Name On disk Role
Working directory your files What you edit
Index (staging area) .git/index What the next commit will contain
HEAD commit an object in .git/objects Last committed snapshot on this branch
working directory  --git add-->  index  --git commit-->  HEAD
                 <--git restore--      <--git restore --staged / git reset--

 ~/desk/desk-api/              .git/index                 .git/objects
 README.md, src/…              next commit                last commit (HEAD)
  • git add copies working-tree bytes into the index (and writes blob objects).
  • git commit writes a tree from the index, then a commit pointing at that tree and at the current HEAD, then slides the branch name forward.
  • git restore <file> copies from the index (or a commit) onto the working tree.
  • git restore --staged <file> copies from HEAD into the index, leaving your working-tree edit in place.

There is no fourth “GitHub tree.” A remote is another repository’s names plus objects you have fetched.

Why the index exists

The index lets the next commit be a chosen subset of what is on disk. You can edit five files and commit two.

echo "ok" >> README.md
echo "wip" >> notes.txt
git add README.md
git status
# README.md is staged; notes.txt is not

git commit -a copies already tracked files from the working tree into the index and commits. New files still need git add. Prefer explicit git add until that distinction is boring.

status is the three-tree report

git status

Git compares:

  • working tree vs index → “Changes not staged for commit”
  • index vs HEAD → “Changes to be committed”
  • untracked files → paths the index does not know

When all three match, status is clean. That is the happy state after a commit.

reset in one sentence

git reset moves HEAD and optionally the index and working tree. Soft / mixed / hard are which of the three trees follow. Chapter 7 (reset) and chapter 17 (reflog) use this on purpose. For now, prefer git restore to throw away an unstaged edit, and git restore --staged to unstage.

Check it

git status
git diff              # working tree vs index
git diff --staged     # index vs HEAD

If git diff is empty and git diff --staged is not, the next commit already contains the change. If both are empty and status still lists a file, it is untracked — git add it.