Tags: web-dev concept

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:

ObjectHolds
BlobFile contents. No name, no path
TreeA directory — names, permissions, and pointers to blobs and other trees
CommitA tree, a parent (or several), author, message
TagA 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/main

A 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 it

Why this makes commands obvious

Once the graph is the model, the commands stop being arbitrary:

  • merge — create a commit with two parents, joining two lines
  • rebase — replay commits onto a different base, creating new commits with new hashes — Merge vs Rebase
  • reset — move a branch pointer, optionally changing the index and working tree
  • revert — a new commit undoing an old one, leaving history intact
  • cherry-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.