Skip to main content
RunBook Academy

Git, CI/CD & GitOpsVI · Index / Staging AreaIndex

The index explained — what the staging area is and why Git has one

Intermediate⏱ ~18 mingit

What you'll learn

  • Explain what the index is on disk and how it differs from the working tree and HEAD
  • Articulate why Git has a separate staging area rather than committing directly from the working tree
  • Read git status and map every line to a movement between the three trees
  • Recognise the index as the unit of a commit and not the working tree
  • Identify which commands read from or write to the index

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

The index is the most important Git concept that does not exist in other version-control systems. Subversion, Perforce, Mercurial’s older workflows, and most file-saver mental models treat a commit as “save what I have edited”. Git does not. Git treats a commit as “save what is currently in the index”. The working tree is where you edit; the index is what you are about to commit; the repository is what you have already committed. Get the index wrong and every commit is a surprise. Get it right and every commit is a deliberate, reviewable event.

What the index actually is

The index is a single binary file at .git/index. It is not a directory, not a buffer in memory, not a copy of the files. It is a serialised manifest: a flat list of every path the next commit will contain, paired with the object ID of the blob that path holds, plus the mode bits and a cache of stat() information Git uses to decide whether the working tree file has been modified.

git ls-files --stage
# 100644 <blob-oid> 0   terraform/main.tf
# 100644 <blob-oid> 0   ansible/hosts.yml
# 100755 <blob-oid> 0   scripts/deploy.sh

The “0” column is the stage number. Stage 0 is the normal collapsed entry you see most of the time. Stages 1, 2, and 3 appear only during an unresolved merge and hold the merge base, ours, and theirs respectively (covered in lesson II-02).

The crucial property: the index is what gets committed, not the working tree. When you run git commit, Git reads the paths and blob IDs out of .git/index, constructs a tree object from them, and writes a commit object pointing at that tree. The bytes on disk in the working tree are not part of the commit unless they have first been added to the index.

flowchart LR
    WT["Working tree\n(filesystem edits)"] -- "git add" --> IDX["Index\n(.git/index manifest)"]
    IDX -- "git commit" --> REPO["Repository\n(tree + commit object)"]
    IDX -- "git diff --cached" --> HEAD["HEAD\n(last commit)"]

Why Git has a staging area

The two-area model — working tree and repository — is what Subversion and most other systems use. Git adds the index because a commit should be a deliberate event, not a side effect of saving a file. The index makes four things possible that a two-area model cannot:

  • Partial commits. A file can have several logically independent changes inside it. The index lets you stage some changes and not others, so one commit can describe one logical change even when the working tree holds several.
  • Review before commit. The contents of the next commit can be inspected (git diff --cached) before the commit happens. Pre-commit hooks, CI plan computation, and signed-off-by checks all read the index — they would have nothing to read in a two-area model.
  • Cross-file atomicity. A commit is the contents of the index at one moment. Multiple files staged together become one commit; partial stages across files let you describe one logical change that touches several files.
  • External staging tools. Because the index is a file, tools like git add -p, git gui, git citool, and IDE front-ends can edit it without ever touching the working tree bytes.

Reading the three trees through git status

git status is the cheap way to see all three trees at once. It compares the working tree to the index, and the index to HEAD, and prints the differences as four named buckets:

git status
# On branch main
# Changes to be committed:
#   modified:   terraform/main.tf
# Changes not staged for commit:
#   modified:   ansible/hosts.yml
# Untracked files:
#   playbooks/rotate-creds.yml

Each line names a state transition:

  • Changes to be committed — the index differs from HEAD. The bytes are staged but not yet committed.
  • Changes not staged for commit — the working tree differs from the index. The file has been edited since the last git add.
  • Untracked files — the working tree has a path that is not in the index at all.
  • (Implicit) No changes — all three trees are aligned. The working tree matches the index and the index matches HEAD.

A common mistake is to read “Changes not staged for commit” as “this file is not in the commit”. It is the opposite: the file is in the index exactly as HEAD recorded it, but the working tree has uncommitted edits that have not been added. Run git add and the file moves into the “Changes to be committed” bucket.

The index is the unit of a commit

The single sentence to remember from this lesson: a commit is the contents of the index at one moment. Everything else — the working tree, the editor, the diff, the message — is preparation for that snapshot.

sequenceDiagram
    participant Dev as Engineer
    participant WT as Working tree
    participant IDX as Index
    participant REPO as Repository

    Dev->>WT: edit terraform/main.tf
    Dev->>IDX: git add terraform/main.tf
    Note over IDX: index now holds the new blob
    Dev->>IDX: git diff --cached (review)
    Dev->>REPO: git commit
    REPO->>REPO: write tree, write commit, advance ref
    Note over WT,IDX: working tree and index are now identical to HEAD

If you take nothing else from this lesson, take this: when you are about to commit, ask “what is in the index?” not “what have I edited?”. The two are usually the same, but when they diverge — because a previous git add left a stale change staged, because an interactive staging tool was abandoned mid-edit, or because a merge added an entry to the index — the commit will follow the index, not the working tree.

Production discipline

  1. Read the index before you commit. A 30-second git diff --cached catches the wrong file in the right commit. In an infrastructure repository the cost of a wrong commit is a wrong apply.
  2. Trust git status, not your editor. Your editor may show files as “saved” while the working tree still differs from the index. git status is the only authoritative source of what the next commit will contain.
  3. Treat the index as a checkpoint, not a buffer. The index survives process exits, can be inspected, and can be replaced with GIT_INDEX_FILE. A pipeline that has staged a partial apply can be examined, paused, or reset by acting on the index directly.

Cross-course references

  • Linux for Production Sysadmins - Part IX (Filesystem) covers the inode-level representation of a file; the index is the Git analogue of a directory entry, listing paths and the OIDs of their current blobs.
  • Ansible for Production Sysadmins - Part XXXVII (RepoArch) treats the index as the boundary between untrusted working changes and the repository that the playbook will be run from.
  • Terraform for Production Sysadmins - Part IX (State) covers the equivalent of the index for Terraform: the plan file, which is the candidate next state reviewed before apply.

Quiz

Knowledge check · 4 questions

  1. Q1. An engineer has edited a Terraform file and run `git add terraform/main.tf`. They then run `git diff` and see nothing, but `git diff --cached` shows the full change. What does this tell them about the three areas?

  2. Q2. The index is a transient buffer in memory that is discarded when the Git process exits.

  3. Q3. Name the two storage locations a `git commit` reads from, and identify which one is the actual source of the commit.

  4. Q4. Diagnose why a commit contains an unexpected change that the engineer insists they did not make in this session.

    An engineer edits `terraform/main.tf`, runs `git add terraform/main.tf`, then `git commit -m 'fix bucket tags'`. The resulting commit also includes an unrelated change to `terraform/backend.tf` that the engineer did not touch. `git diff HEAD~1 -- terraform/backend.tf` shows the unexpected change in the commit. The engineer claims they only edited and staged `main.tf`.

Passing score: 75%. Answers are checked in this browser.