The object model: blobs, trees, commits

Git · Expert · 15 min

Knowing the object model explains why branches are cheap, why history rewriting changes IDs, and how recovery works.

At its core Git is a content-addressed key-value store. Every object is stored under the hash of its content (SHA-1 by default, with collision detection; repositories can opt into SHA-256 with git init --object-format=sha256). Same content, same ID, stored once.

There are four object types:

  • blob: the raw contents of a file. No name, no permissions; two identical files anywhere in history are one blob.
  • tree: a directory listing; each entry has a mode (100644 file, 100755 executable, 040000 subtree, 120000 symlink, 160000 submodule), a name and an object ID.
  • commit: points to one root tree (the snapshot), zero or more parent commits, author and committer with timestamps, and the message.
  • tag (annotated): points to an object, with tagger, message and optional signature.

An object's ID is the hash of a header like blob 12\0 followed by its content. Because a commit contains its tree ID and parent IDs, its hash transitively covers the entire snapshot and all of history: a Merkle structure. Changing one byte anywhere changes every descendant commit's ID. That is why rebases produce new commits and why a commit hash identifies the full history behind it.

You can explore with plumbing commands:

  • git cat-file -t <id> gives the type; -p pretty-prints the content.
  • git ls-tree HEAD lists the root tree.
  • git hash-object -w file stores a blob and prints its ID.
  • git rev-parse HEAD^{tree} resolves to a commit's tree.

Branches and tags (refs) are just names pointing into this graph, so everything else in Git is built on these few objects.

Example

git cat-file -p HEAD
# tree 8f3e1a...
# parent 3f9a2c1...
# author Ada <ada@example.com> 1760000000 +0200
# committer Ada <ada@example.com> 1760000000 +0200
#
# Add search index
git cat-file -p 'HEAD^{tree}'
# 100644 blob 5d1f0c...    README.md
# 040000 tree 2b7e94...    src
echo 'hello' | git hash-object --stdin   # ce013625030ba8dba906f756967f9e9ca394464a
printf 'blob 6\0hello\n' | sha1sum        # same ID: header + content
git cat-file -t ce01362                   # blob

Remember

  • Four object types: blob (content), tree (directory), commit, annotated tag
  • Object ID = hash of type header + content: identical content is stored once
  • Commits hash their tree and parents, so one ID pins all history
  • cat-file -p, ls-tree and hash-object let you inspect it directly

Watch out for

  • Thinking Git stores diffs between versions — objects are full snapshots; deltas only appear later as a packfile compression detail.

Check yourself

  1. Two different files in different directories have exactly the same content. How many blobs does Git store?
  2. Why does editing an old commit's message change the IDs of all later commits?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: git-scm.com/book/en/v2/Git-Internals-Git-Objects