The Object Model — immutable, content-addressed

Life as a Git repositoryintermediate · standard · comprehensive | model: claude-opus-4-8 | 2026-06-21
Quick ref
git cat-file -p <oid> pretty-print any object
git rev-parse HEAD resolve a revision to its OID
git reflog recover "lost" commits
git switch -c name create + move to a branch
git restore --staged . unstage everything
git fsck --lost-found find dangling objects
git push origin HEAD:main explicit refspec push

The Object Model — immutable, content-addressed

Every value in a repo is one of four object types, named by the hash of its contents. Identical content yields one object; rewriting content yields a new object and never mutates the old.

blob
Raw file bytes — no name, no mode, no history.
tree
A directory: list of (mode, name, oid) pointing at blobs/trees.
commit
A snapshot: one root tree, zero+ parents, author/committer, message.
tag
Annotated tag object: target oid, tagger, message, optional signature.
ConceptMeaning
OIDSHA-1 (or SHA-256) of type + size + \0 + content
Loose objectOne zlib-deflated file under .git/objects/ab/cdef…
PackfileMany objects in one .pack, delta-compressed, with a .idx
Commit DAGDirected acyclic graph formed by parent links
ReachabilityAn object lives if a ref can reach it through the DAG
Note Commits store full snapshots, not diffs. The "diff" you see is computed on demand by comparing two trees; delta compression in packfiles is a storage detail, unrelated to commit semantics.

Plumbing — building objects by hand

Porcelain commands are scripts over these primitives. Knowing them demystifies everything else.

git hash-object -w file
Write a blob from a file, print its OID.
git cat-file -t/-s/-p <oid>
Show an object's type / size / contents.
git update-index --add file
Stage a path into the index by hand.
git write-tree
Snapshot the current index into a tree object.
git commit-tree <tree> -p <parent> -m msg
Forge a commit pointing at a tree; prints new OID.
git update-ref refs/heads/x <oid>
Point a ref at a commit — the move that "publishes" work.
blob=$(git hash-object -w hello.txt)
git update-index --add --cacheinfo 100644,$blob,hello.txt
tree=$(git write-tree)
commit=$(git commit-tree $tree -m "by hand")
git update-ref refs/heads/main $commit
Tip The whole of git commit is just these five steps. Creating objects and moving refs are separate acts — that separation is the key to recovery.

The Three Trees — HEAD, index, working tree

What each is

HEAD is the last committed tree (usually via a branch). The index is the staged next commit. The working tree is your editable files.

Modern verbs

switch changes branches; restore moves file content between the three trees. They split the old overloaded checkout.

CommandHEADIndexWorktree
git reset --soft <c>moves
git reset --mixed <c>movesreset
git reset --hard <c>movesresetreset
git restore --staged freset
git restore freset
git checkout <c> -- fsetset
git commit --amend
Replace the tip commit with a new object — old one becomes unreferenced.
git stash push -u
Shelve worktree + index (incl. untracked) as hidden commits.
git clean -fdx
Delete untracked files/dirs, including ignored — irreversible.
git switch -c feat origin/main
New branch tracking a remote start point.
Warning reset --hard and clean -fdx overwrite uncommitted work that was never turned into objects. Only committed/stashed state is recoverable via reflog.

Refs, Reflogs & Reachability

Refs are mutable pointers into the immutable graph: a file under .git/refs/ or a line in packed-refs holding one OID. HEAD is usually a symbolic ref pointing at a branch.

git for-each-ref --sort=-committerdate
Script-friendly listing of all refs and their targets.
git symbolic-ref HEAD
Show what branch HEAD points to (detached if not a ref).
git reflog show main
Local history of where a ref has pointed — your undo