The Immutable Object Model

Life as a Git repositoryintermediate · standard · comprehensive | model: claude-opus-4-8 | 2026-06-17
Quick ref
git cat-file -p <oid> print any object
git rev-parse HEAD resolve ref to OID
git for-each-ref list all refs
git reflog recover lost commits
git switch -c new create + move branch
git restore --staged . unstage everything
git push -u origin HEAD push + set upstream

The Immutable Object Model

Git is a content-addressed key/value store: every object's name is the hash of its contents, so identical content is stored once and any change creates a new object.

ObjectContainsPoints to
blobRaw file bytes (no name)nothing
treeMode + name + OID rowsblobs and subtrees
commitTree OID, parents, author/committer, messageone tree + parent commits
tagTarget OID, type, tagger, messageany object (usually a commit)
git cat-file -t <oid>
Show an object's type.
git cat-file -s <oid>
Show its size in bytes.
git cat-file -p HEAD^{tree}
Pretty-print the root tree of HEAD.
git cat-file --batch-all-objects --batch-check
Enumerate every object with type and size.
Note OIDs are SHA-1 by default; new repos can use SHA-256 via git init --object-format=sha256. The model is identical, only the hash differs.

Plumbing: Building Objects by Hand

Porcelain commands are choreographies over these primitives; learning them demystifies the entire system.

git hash-object -w file
Write a blob from a file, print its OID.
echo hi | git hash-object -w --stdin
Hash arbitrary content into a blob.
git update-index --add file
Stage a path into the index.
git write-tree
Snapshot the index into a tree object.
git commit-tree <tree> -p <parent> -m msg
Build a commit pointing at a tree.
git update-ref refs/heads/main <oid>
Move a branch to a commit.
# A commit, entirely by hand:
blob=$(echo "hello" | git hash-object -w --stdin)
git update-index --add --cacheinfo 100644 $blob hello.txt
tree=$(git write-tree)
commit=$(git commit-tree $tree -m "manual commit")
git update-ref refs/heads/main $commit
Tip git commit = write-tree + commit-tree + update-ref, plus hooks and the reflog entry.

Refs, HEAD & Reflogs

Refs are mutable pointers (just files holding an OID) layered over the immutable graph; moving them is how history "advances."

git symbolic-ref HEAD
Show which branch HEAD points at; detached when it holds an OID.
git for-each-ref --sort=-committerdate refs/heads
List branches by recency with format control.
git rev-parse --abbrev-ref HEAD
Print the current branch name.
git reflog show main
Where a ref has been; the safety net for "lost" work.
git pack-refs --all
Collapse loose refs into .git/packed-refs.
git update-ref -d refs/heads/old
Delete a ref directly.
Note Reflogs are local, per-ref, and time-limited (default 90/30 days); they're invisible to collaborators and the only record of where refs used to point.

The Three Trees: Working Tree, Index, HEAD

What they are

HEAD is the last committed snapshot, the index is the proposed next snapshot (staging area), and the working tree is your editable sandbox on disk.

How commands move them

add copies working tree → index; commit seals index → HEAD; reset and restore push data back the other way.

git reset --soft <c>
Move HEAD only; index and working tree untouched.
git reset --mixed <c>
Default: move HEAD and reset index, keep working tree.
git reset --hard <c>
Move all three; discards working-tree changes.
git restore --staged --worktree file
Restore a path in both index and working tree.
git switch -c topic
Create and check out a branch (ref-focused checkout).
git status -sb
Compact view of the three trees' divergence.
Warning reset --hard and clean -fd destroy uncommitted work that has no object and no reflog — there is nothing to recover.

Revision Selection, Pathsp