Git path · The data model · Topic 1 of 13

Blobs, trees and commits: Git's object model

Not started

Under every Git repository is a small database of objects, stored in .git/objects and each named by a hash of its contents. There are only three kinds you meet every day: a blob holds a file's bytes, a tree lists the names in one directory and points at blobs and other trees, and a commit points at one tree (the whole project at that moment) plus its parent commits, author and message. Branches, merges and history are all built from these.

Why it matters: Every other Git command reads or writes these three kinds of object. Once you can picture them, branches, merges and undo stop being magic.

  1. 1Named by their content

    Every object is stored under the hash of its type, size and bytes. Same content, same name, stored once.

  2. 2Three kinds do the work

    A blob is a file's bytes, a tree is a directory listing, a commit is a tree plus parents, author and message.

  3. 3A commit is a snapshot

    A commit points at a whole tree, not at a diff. Diffs are computed when you ask for them.

  4. 4Objects never change

    Changing anything, even one byte of a message, makes a new object with a new hash.

1

How it works

The explanation, with examples you can run.

Look inside a commit

Make a repository with one file and ask Git what it stored. The commands below were run with Git 2.50; your commit hash will differ because it includes your name and the time, but the blob hash will match exactly.

git init -q -b main demo && cd demoecho "hello" > greeting.txtgit hash-object greeting.txtgit add greeting.txtgit commit -q -m "Add greeting"git cat-file -p HEAD
ce013625030ba8dba906f756967f9e9ca394464atree 57e9529754dc514a3ec10db2ff882018fbe1fcbfauthor Ada <ada@example.com> 1767607200 +0000committer Ada <ada@example.com> 1767607200 +0000 Add greeting

git cat-file -p prints any object. The commit is just text: a tree hash, author and committer lines with Unix timestamps, a blank line and the message. This first commit has no parent line. Follow the tree:

git cat-file -p 'HEAD^{tree}'git cat-file -p ce01362
100644 blob ce013625030ba8dba906f756967f9e9ca394464a	greeting.txthello

The tree holds the name and the file mode (100644 is a normal file, 100755 executable, 040000 a subdirectory). The blob holds only the bytes.

What the hash is a hash of

A blob's name is the SHA-1 of a short header (blob, a space, the size in bytes, a NUL byte) followed by the content. You can reproduce it without Git (shasum on macOS, sha1sum on Linux):

printf 'blob 6\0hello\n' | shasum
ce013625030ba8dba906f756967f9e9ca394464a  -

This is content addressing: the name is derived from the data, so the same content always gets the same name and any corruption is detectable. Copy the file into a new folder and commit; Git writes a new tree for the folder and a new root tree and commit, but reuses the blob.

mkdir docs && cp greeting.txt docs/copy.txtgit add docs && git commit -q -m "Copy greeting into docs"git cat-file -p 'HEAD:docs'git count-objects
100644 blob ce013625030ba8dba906f756967f9e9ca394464a	copy.txt6 objects, 24 kilobytes

Six objects: one blob, three trees (two root trees and docs) and two commits. The second commit's text has a parent line naming the first, and that chain of parents is the history.

Why this matters

  • Snapshots, not diffs. Each commit names a complete tree. Unchanged files and folders are shared by hash, so snapshots are cheap. git diff and git log -p compute changes on demand.
  • Immutable. An object never changes. Amending, rebasing and cherry-picking all make new commits; the old ones stay in the database until garbage collection removes unreachable ones.
  • Integrity. A commit hash covers its tree, which covers every blob, and its parent, which covers all earlier history. If any byte changes, every hash above it changes.
  • Packs. Loose objects are zlib-compressed files under .git/objects/xx/. git gc and pushes and fetches bundle them into packfiles that store similar objects as deltas. That is a storage detail; the model is still snapshots.

New repositories still use SHA-1 by default (with collision detection built in). git init --object-format=sha256 creates a SHA-256 repository, but hosting support for it is still limited, so most teams stay on the default for now.

2

Check yourself

3 questions. Get them all right to finish this topic.

  1. 1.Two files in different folders have exactly the same contents. How many blob objects does Git store for them?
  2. 2.Where does Git keep a file's name?
  3. 3.You change only the message of the latest commit with git commit --amend. What happens to its hash?

0 of 3 right so far. Get all three to finish this topic.