Git Model
Date: 2026-08-17
Git stores snapshots, not diffs, and every command is a manipulation of a directed graph of those snapshots. Understanding the four object types once makes every confusing command obvious — and almost all git confusion comes from having learned the commands without the model.
Git is a content-addressed object store with a graph of commits on top. Four object types, and everything else is built from them:
| Object | Holds |
|---|---|
| Blob | File contents. No name, no path |
| Tree | A directory — names, permissions, and pointers to blobs and other trees |
| Commit | A tree, a parent (or several), author, message |
| Tag | A named pointer to an object, with a message |
Every object is addressed by the SHA-1 hash of its contents, which is why identical content is stored once however many times it appears — Hashing.
Snapshots, not diffs
The single most useful correction to most people’s mental model.
COMMIT A COMMIT B
tree ──┐ tree ──┐
├ a.js v1 ├ a.js v1 ← SAME blob,
└ b.js v1 └ b.js v2 reused
A commit points at a complete tree of the whole project. Git shows you diffs by computing them between two snapshots; it doesn’t store them. That’s why checking out any commit is fast regardless of how far back it is.
The commit graph
Commits point at their parents, forming a directed acyclic graph — a DAG, meaning arrows only go one way and there are no loops.
A ── B ── C main
\
D ── E feature
Branches are not containers. A branch is a pointer to one commit, stored as a file containing a hash. Creating one writes 41 bytes.
cat .git/refs/heads/main
# 3f2a9c1e...That’s why branching in git is instant and why the mental model of “copying the code into a branch” leads people astray.
The three areas
WORKING TREE your files, as they are
│ git add
STAGING (index) what will go in the next commit
│ git commit
REPOSITORY the object store, permanent
The staging area is git’s most-questioned feature and its most useful one — it lets you commit a subset of your changes, which is what makes atomic commits possible when you’ve done two things at once — Commit Hygiene.
HEAD, and detached HEAD
HEAD is a pointer to the branch you’re on — usually a reference to a reference.
cat .git/HEAD
# ref: refs/heads/mainA detached HEAD is HEAD pointing directly at a commit rather than at a branch. Nothing is broken; you simply have no branch, so a new commit would be unreachable once you leave.
git checkout abc123 # detached
git switch -c fix # attach a branch to itWhy this makes commands obvious
Once the graph is the model, the commands stop being arbitrary:
merge— create a commit with two parents, joining two linesrebase— replay commits onto a different base, creating new commits with new hashes — Merge vs Rebasereset— move a branch pointer, optionally changing the index and working treerevert— a new commit undoing an old one, leaving history intactcherry-pick— replay one commit somewhere else
reset versus revert is the pair that confuses most, and the model settles it: reset moves a pointer backwards, revert adds a commit forwards. Only one is safe on a shared branch — Rewriting History.
Nothing is lost until it’s collected
Commits removed from a branch still exist as objects. The reflog records every position HEAD has held, which is the recovery route from almost any mistake:
git reflog
git reset --hard HEAD@{3}Unreachable objects are eventually removed by garbage collection, typically after weeks — so “I lost my work” is nearly always recoverable if acted on promptly.