Free sample lesson · Git Internals and Automation Mastery

Object Model

What actually happens on your disk when you create a single commit?

Lesson 1 of the Wondering course Git Internals and Automation Mastery

What a Commit Really Writes

You run git commit -m "fix". Git does not save a diff. It writes objects into .git/objects.

A single commit usually creates three kinds of object: blobs for file contents, trees for directory listings, and one commit object tying it together.

Each object is content-addressed by a SHA-1 hash of its contents.

The object store
  • Git objects: Everything Git stores lives in .git/objects.
  • Blob: Raw file contents.
  • Tree: A directory listing.
  • Commit: Ties a tree to history.
  • Tag: An annotated pointer object.

Once you see these four object types, detached HEAD and strange merge results stop feeling like magic.

Blobs Store Content Only

A blob is the raw bytes of a file. Nothing else.

No filename. No path. No permissions. No timestamp.

Inside a blob
  • Stored
  • Not stored

That is why two identical files in different folders produce exactly one blob. Git deduplicates for free.

It also explains a common surprise: renaming a file creates no new blob. Only the tree that names it changes.

Trees Give Content Names

A tree is a directory listing. Each entry holds a mode, a type, a hash, and a name.

Fields in a tree entry

Entries point either to a blob (a file) or to another tree (a subdirectory).

So the tree is where filenames actually live. Blobs supply the content, trees supply the structure.

One commit references exactly one top-level tree, which recursively describes your whole working directory.

The Commit Object

A commit object is small and mostly text. It records one tree hash, zero or more parent hashes, an author, a committer, and a message.

The tree hash is the snapshot. The parents are the history.

A first commit has no parent. A merge commit has two or more.

What a commit records
  • Trees per commit: The tree hash is the snapshot.
  • Parents of a first commit: A first commit has no parent.
  • Parents of a merge commit: 2 or more
  • Also stored
  • Author
  • Committer
  • Message

Nothing in the commit stores a branch name.

Nothing in the commit stores a branch name. Branches live elsewhere.

Hashing Makes It Immutable

Every object is named by the SHA-1 hash of its own contents.

Change one byte in a file and you get a different blob hash. That changes the tree, which changes the commit, which changes every descendant commit.

How one byte change spreads
  • Edit byte: One byte in a file changes.
  • New blob: The content hashes to a different blob.
  • New tree: The tree naming that blob changes too.
  • New commit: The commit hash changes, and so does every descendant.

This is why git rebase rewrites hashes instead of editing commits in place.

Objects are never modified. They are only created and eventually garbage collected.

Refs Are Just Pointers

A branch is not an object. It is a file containing one 40-character hash.

refs/heads/main holds the hash of the tip commit. Moving a branch means writing a new hash into that file.

HEAD normally holds a symbolic reference such as ref: refs/heads/main.

Two states of HEAD
  • Attached HEAD
  • Detached HEAD

Detached HEAD is simply HEAD holding a raw commit hash instead of a branch reference.

The Graph Under Merges

Because commits point to parents, the history is a directed graph, not a line.

A merge commit has two parents. Git walks backwards from both to find their common ancestor.

Anatomy of a merge
  • Ours: One of the two parents of the merge commit.
  • Theirs: The other parent of the merge commit.
  • Common ancestor: Found by walking backwards from both parents.

That ancestor is the base for the three-way merge: base, ours, theirs.

When a conflict looks impossible to explain, inspect the actual parent links. The graph usually reveals the surprise.

Inspecting Objects By Hand

Three plumbing commands unlock everything above.

git cat-file -t <hash> prints an object type. git cat-file -p <hash> pretty-prints its contents.

git rev-parse HEAD resolves any reference to a raw hash. git hash-object computes a hash without storing.

Walking the object store
  • Resolve ref: git rev-parse HEAD gives a raw hash.
  • Check type: git cat-file -t prints the object type.
  • Read commit: Pretty-print it to find the tree hash.
  • Read tree: List entries with their hashes and names.
  • Read blob: Print the raw file contents.
  • tree hash
  • entry hash
  • subdirectory

Chain them in scripts: resolve a ref, read the commit, read its tree, then read the blobs.

Why Nothing Is Ever Lost

Objects are immutable and reachable through refs. A rebase or reset does not delete the old commits; it only moves a pointer.

Those commits stay in the object store until garbage collection removes unreachable ones.

If nothing is deleted, what is lost?

git reflog records where HEAD has been, so you can recover the old hash and check it out.

Putting the Model to Work

Blobs hold content. Trees name it. Commits snapshot a tree and link to parents. Refs point at commits.

The chain of references
  • Refs: Files pointing at commits.
  • Commits: Snapshot one tree and link to parents.
  • Trees: Name the content.
  • Blobs: Hold the raw content.

Each level points down to the one below

With that chain in mind, a detached HEAD is a pointer question, not a crisis.

A confusing merge becomes a graph you can walk with git cat-file.

And your automation scripts can read the store directly instead of parsing porcelain output.

Check yourself

Does a Git blob store a file’s name and path along with its contents?

  • The file’s raw bytes
  • The file’s name and path

Answer: The file’s raw bytes

A blob contains only the file's raw bytes. The filename is stored in a tree entry, not in the blob.

In Git’s object model, a _____ stores file bytes, a _____ stores names and directory structure, a _____ records a snapshot and history, and a _____ points to a commit.

Answer: In Git’s object model, a blob stores file bytes, a tree stores names and directory structure, a commit records a snapshot and history, and a ref points to a commit.

Each blank has a distinct role: blobs contain bytes, trees contain names and structure, commits record snapshots and parents, and refs point to commits.

A file is renamed without changing its contents. What does Git’s object model predict?

  • Only the tree changes; the existing blob can be reused.
  • A new blob is required because the filename changed.
  • Only the commit changes; trees do not store filenames.
  • The old blob is modified to contain the new filename.

Answer: Only the tree changes; the existing blob can be reused.

The blob is content-addressed and contains no filename. If the bytes remain identical, Git reuses the same blob; the tree changes the name associated with it.

Keep going

This is lesson 1 of 9 in Git Internals and Automation Mastery. Next up:

  1. Directory Trees
  2. Branch References
  3. Detached HEAD
  4. Merge Conflicts
  5. Reflog Recovery

Start this course free

Wondering turns any topic into short lessons you can read, watch, or listen to, with practice questions and an AI tutor.