Git, CI/CD & GitOpsII · Git ArchitectureArchitecture
The three-trees model — HEAD, index, and working tree as a navigation system
What you'll learn
- Name the three trees Git uses and the role of HEAD as the named commit pointer
- Explain the difference between git diff with no args, --cached, and HEAD
- Choose between git reset, git restore, and git checkout for a given state transition
- Trace how a failed merge surfaces as a divergence between the index and HEAD
- Recognise why HEAD is a ref, not a commit, and how detached HEAD changes the model
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
The three-trees model is the navigation system for Git. Where lesson II-01 introduced the three areas as storage, this lesson treats them as trees of file content — three views of the same repository, each at a different point in the commit lifecycle — and shows how every diff and reset command is a movement between two of them. The mental shift is from “what file am I editing?” to “which two of the three trees am I comparing?”
The three trees
flowchart TB
HEAD["HEAD tree\n(the last commit)"]
IDX["Index tree\n(the next commit)"]
WT["Working tree\n(the filesystem)"]
HEAD -- "git diff HEAD" --> WT
HEAD -- "git diff --cached" --> IDX
IDX -- "git diff" --> WT
- HEAD tree. The file contents of the commit that
HEADcurrently points at.HEADis a ref (a file under.git/refs/or an entry in.git/packed-refs) that names a commit. The tree is the set of files that commit records. - Index tree. The candidate snapshot. It is the file contents
that will become the next commit if you run
git commitnow. - Working tree. The actual files on disk. This is the only tree a human or an editor touches.
The crucial observation: these three trees can diverge. They
usually diverge during a working session — the user edits the
working tree, runs git add, which updates the index, and only at
git commit does the HEAD tree catch up. A merge in progress
diverges all three: HEAD is the merge base, the index has two
parents of unresolved conflict, and the working tree holds the
conflict markers.
The three diff forms
git diff with no arguments compares the working tree to the
index. The other two common forms compare the other two pairs:
git diff # working tree vs index (uncommitted edits)
git diff --cached # index vs HEAD (what will be committed)
git diff HEAD # working tree vs HEAD (everything unstaged or staged)
flowchart LR
A["git diff\nworking tree vs index"] --> B["edits not yet staged"]
C["git diff --cached\nindex vs HEAD"] --> D["staged changes"]
E["git diff HEAD\nworking tree vs HEAD"] --> F["all uncommitted changes"]
A simple rule for choosing the right form:
- “What did I change since my last
git add?” →git diff - “What will my next commit contain?” →
git diff --cached - “What is the total delta in my working tree vs the last
commit?” →
git diff HEAD
Reset, restore, and checkout — moves between the trees
The three commands restore / move state between the three trees:
git checkout $REF # HEAD -> $REF, then working tree -> HEAD
git restore $FILE_PATH # working tree -> index (default) or index -> HEAD
git reset $REF # HEAD -> $REF (--soft), index -> HEAD (--mixed), or working tree -> HEAD (--hard)
The --soft, --mixed, and --hard flags of git reset are the
classic three-trees movement diagram:
flowchart LR
HEAD["HEAD ref"] --soft--> IDX["Index unchanged"]
HEAD --mixed--> IDX2["Index -> HEAD"]
HEAD --hard--> WT["Working tree -> HEAD"]
git reset --soft <ref>: move HEAD only. The index and working tree are untouched. The changes that were committed are now staged for re-commit.git reset --mixed <ref>(the default): move HEAD and reset the index to match. The working tree is untouched. The changes that were committed are now unstaged edits.git reset --hard <ref>: move HEAD, reset the index, and reset the working tree. The changes that were committed are gone from all three trees (recoverable only viagit reflogfor a short window).
Detached HEAD
When a ref like main is checked out, HEAD is a symbolic ref
pointing at refs/heads/main. When a commit hash is checked out
instead, HEAD is detached — it points directly at a commit
object, not at a branch ref.
git checkout 8a3f9d2 # detached HEAD
git checkout main # symbolic HEAD -> refs/heads/main
A detached HEAD is normal for bisects, for inspecting old commits,
and for signing tags from a known commit. It is dangerous only
when commits are made in the detached state and then git checkout
moves to a different branch — the new commits become unreachable
and are eligible for garbage collection within ~30 days. The
recovery is git reflog to find the orphaned commits, then
git checkout -b <branch> <oid> to give them a branch.
Failed merges and the three trees
A merge in progress is the most informative state of the
three-trees model. After git merge <branch> reports conflicts:
flowchart LR
HEAD["HEAD tree\n(merge base)"] --> IDX["Index tree\n(2 parent entries, stage 1 / 2 / 3)"]
IDX --> WT["Working tree\n(conflict markers)"]
The index now has up to three entries per conflicted path: stage 1
holds the merge base, stage 2 holds the HEAD version, stage 3 holds
the other branch’s version. The working tree contains the conflict
markers (<<<<<<<, =======, >>>>>>>). Resolving the merge
means editing the working tree to the resolved content, then
git adding the path, which collapses the three index entries
back to a single stage 0 entry. The commit that ends the merge
has both branch tips as its parents.
git ls-files --stage --unmerged
# 100644 <base-oid> 1 terraform/main.tf
# 100644 <ours-oid> 2 terraform/main.tf
# 100644 <theirs-oid> 3 terraform/main.tf
Production discipline
- Inspect the diff of the area pair you care about. Before every commit, before every push, before every CI run, name the two trees you are comparing and run the matching diff form.
- Treat
--hardas a last resort. A--hardreset is a local-only operation that overwrites the working tree. In a shared repository, the production-safe alternative isgit revert, which produces a new commit and preserves the audit trail. - Pin branches out of detached HEAD. If you need to make
commits from a detached state (for example, to apply a fix from
an old tag), create a branch first:
git checkout -b fix/from-v3.2.7 <oid>. Never commit to a detached HEAD without a branch name to receive the commit.
Cross-course references
- Kubernetes for Production Sysadmins - Part XXIV (GitOps) uses the three-trees model to reason about the convergence loop: the GitOps controller observes the cluster (working tree), the declared state (HEAD), and the buffer of pending changes (index).
- Terraform for Production Sysadmins - Part IX (State) uses the three-trees model to describe a Terraform plan: the state file is HEAD, the configuration is the working tree, and the plan file is the index.
- Ansible for Production Sysadmins - Part IX (Inventory) describes the analogous three-trees state for an Ansible run: the inventory (HEAD), the live hosts (working tree), and the pending changes (index).
Quiz
Knowledge check · 4 questions
Q1. An engineer has staged a change with `git add` and now runs `git diff` with no arguments. The output is empty. Which of the following is true?
Q2. `git reset --hard HEAD~1` is not necessarily the safest way to undo the most recent commit on a branch that has already been pushed to the remote.
Q3. Name the three trees and the diff form that compares each pair of them.
Q4. Diagnose the state of an in-progress merge and the right resolution path.
An engineer runs `git merge feature/add-cache` to bring a new feature branch into `main`. The merge reports conflicts in `terraform/main.tf` and `ansible/hosts.yml`. The engineer runs `git diff` and sees the conflict markers, runs `git diff --cached` and sees nothing, and runs `git status` which lists both files as 'Unmerged'. The engineer wants to know exactly what state the three trees are in and what commands will resolve the merge.
Passing score: 75%. Answers are checked in this browser.