Handbooks  /  Git Internals
Engineering~13 min readIntermediate
Deep Dive

Git internals: learn the model, and the commands become obvious.

Most people use Git the way medieval sailors used stars — memorized incantations, deep superstition, occasional disaster. But under .git sits one of the most elegant data structures in software: a content-addressed, immutable graph with a few movable pointers on top. Learn that model once and every scary command — reset, rebase, detached HEAD — turns out to be a one-line pointer operation.

01

Everything is content-addressed

Git's foundation is one decision: every piece of data is named by the hash of its content. Store a file, and its bytes are hashed; that hash IS the file's identity, forever. Two consequences do enormous work. Deduplication is automatic — a thousand commits containing the same unchanged file store its blob exactly once. And nothing can be silently corrupted or edited — change a single byte anywhere and every hash above it changes, which is why Git can verify an entire repository's integrity by checking one hash.

→ The mental model

Git is not a version-control system that happens to use hashes. It is a content-addressed database (objects in, hashes out) with a thin version-control UI on top. Every mystery below dissolves against this fact.

02

Four object types, one graph

The whole database schema
BlobFile contents. Just bytes — no filename, no permissions, no history. The name lives one level up.
TreeA directory: a list of (name, mode, hash) entries pointing at blobs and other trees. One tree hash captures an entire directory snapshot.
CommitA tree hash (the snapshot) + parent commit hash(es) + author + timestamp + message. History is commits pointing at parents — a DAG.
TagA named, optionally signed pointer at a commit. The only object type most people can ignore.

Note what a commit is not: a diff. Every commit is a full snapshot — a pointer to a complete tree. Diffs are computed on demand by comparing two trees. This is why checkout of any ancient commit is instant (follow one pointer), and why Git needs packfiles (section 07) to keep full-snapshot storage cheap.

03

Branches are 41-byte files

Run cat .git/refs/heads/main. You'll see one commit hash and a newline — 41 bytes. That file is the branch. There is nothing else. "Creating a branch" writes this file. "Committing" appends an object and rewrites this file with the new hash. "Deleting a branch" deletes the file — the commits remain, merely unlabeled.

HEAD is one more level of indirection: a file usually containing ref: refs/heads/main — a pointer to a pointer, marking "where you are." The infamous detached HEAD is simply HEAD containing a raw commit hash instead of a ref name. Nothing is broken; you're just standing on a commit rather than riding a branch — and any commits you make there are reachable only via the reflog until you name them.

Q: Why is branching in Git "free" when it was expensive in older systems?
A: Because a branch never copies anything. It's a 41-byte name for a node in a graph that already exists.
04

The three trees: what add, commit and reset really move

Git juggles three snapshots at all times: HEAD (last commit), the index (staging area — the next commit, prebuilt), and the working directory (your actual files). Every day-to-day command is a synchronization between them:

CommandWhat it syncs
git addworking directory → index (stage this version)
git commitindex → new commit; branch pointer advances
git reset --softmoves branch pointer only — index & files untouched
git reset --mixed…also index ← HEAD (unstages)
git reset --hard…also working directory ← HEAD (destroys local edits)
git checkout / switchall three ← the target commit (with safety checks)

This table is the entire mystery of reset: the three flags are just how far the sync propagates. And add -p stops feeling exotic — you're editing the index, a real, inspectable snapshot (git diff --staged shows it) that exists so you can compose a commit deliberately instead of committing your whole messy desk.

05

Merge and rebase: two ways to join histories

Merge is honest arithmetic on the DAG: find the common ancestor (merge base), three-way compare both sides against it, combine, and record a commit with two parents. History stays true — the fork happened, the join happened, the graph says so.

Rebase is rewriting: take your commits since the fork, and replay them one at a time on top of the new base. Each replayed commit has a different parent and timestamp — so it hashes to a new commit. The old ones aren't modified (nothing ever is); they're abandoned, and your branch file now points at the copies.

→ Why "never rebase shared branches" is a law

Rebase replaces commits with lookalikes under new hashes. Anyone who built work on the originals now holds orphaned parents — and Git will try to "helpfully" merge the duplicates back. Rewrite what only you have seen; merge what others have.

Same logic explains --force-with-lease: a rewritten branch no longer fast-forwards, so pushing needs force — and the lease makes force safe by refusing if the remote moved since you last looked (i.e., a teammate pushed).

06

The reflog: why nothing is ever lost

Here's the fact that turns Git panic into Git calm: objects are never edited and almost never deleted. Reset "back" in history? The commits ahead still exist — unlabeled. Botch a rebase? The originals still exist. Git additionally keeps a private journal — the reflog — of every position HEAD has occupied, for ~90 days.

The universal recovery recipe
git reflog
find the last
good position
git reset --hard <hash>
or branch/cherry-pick it

Garbage collection eventually prunes objects nothing references — but "eventually" is weeks away by default. The honest statement of Git safety: anything you ever committed is recoverable for months; only never-committed working-directory changes can truly vanish (reset --hard and restore are the two genuinely dangerous commands).

07

Remotes and packfiles: the wire and the disk

A remote is just someone else's object database plus their refs. fetch downloads their new objects and updates your remote-tracking refs (origin/main — your read-only bookmark of their state); pull is fetch + merge (or rebase) into your branch; push uploads your objects and asks the remote to move its ref — refused unless it's a fast-forward, hence the force flags. All the sync commands are pointer diplomacy between two object stores.

And the storage trick that makes full-snapshot commits affordable: loose objects are periodically compressed into packfiles, where similar objects are stored as deltas against each other. Snapshots in the model, diffs on the disk — each layer choosing the representation that serves it best. It's the same play databases run with WALs and B-trees, and it's why a 15-year repo with 100K commits fits in a coat pocket.

08

The model in one breath

ConceptWhat it actually is
Object storeimmutable, content-addressed blobs/trees/commits
Historycommits pointing at parents — a DAG of snapshots
Brancha 41-byte file naming one commit
HEADa pointer to a branch (or, detached, to a commit)
add / commit / resetsyncs between working dir, index, HEAD
Mergea two-parent commit joining histories truthfully
Rebasereplayed copies under new hashes; old ones abandoned
Reflog90 days of everywhere HEAD has been — the undo of last resort
Frequently asked

Quick answers

What are Git's object types?

Blob (file bytes), tree (directory listing), commit (tree + parents + metadata), tag. All immutable, all named by content hash.

What is a branch?

A 41-byte file containing one commit hash. HEAD points at the branch; committing rewrites the file.

Soft vs mixed vs hard reset?

How far the sync goes: pointer only → +index → +working directory. Only --hard touches your files.

Is a lost commit recoverable?

Almost always: git reflog shows every HEAD position for ~90 days — reset or branch to the good hash.

Git Internals · Engineering · Vibe Engines · 2026
Finished this one? 0 / 49 Handbooks done

Explore the topic

See this alongside everything else on the same subject — handbooks, system designs, challenges and tools, in one place.