Blobs, trees and commits

Open the box: see that a repository is four kinds of object in a content-addressed store.

04of 16
≈ 13Min read
5Parts

Four object types, and that is all

Every one is stored under the SHA-1 of its content. That single idea gives you deduplication (the same file in fifty commits is stored once), integrity (a changed byte is a changed id) and cheap branching (a branch is a forty-character file).

ObjectHoldsPoints at
blobthe bytes of one file — no name, no pathnothing
treea directory listing: names, modes, idsblobs and trees
commita message, an author, a timeone tree, and its parents
tagan annotated tagusually a commit

The id is computed, not assigned

That is the whole algorithm for a blob. A tree is a sorted list of mode name\0 plus the raw twenty bytes of each id, hashed the same way; a commit is the text you saw in the last lesson, hashed the same way.

you can do it by hand
$ echo "hello" | git hash-object --stdin
ce013625030ba8dba906f756967f9e9ca394464a

# what was hashed:   "blob 6\0hello\n"
#                     type, a space, the length, a NUL byte, then the content
The terminal beside this lesson does exactly that

Run git hash-object on a file here and you will get the same forty characters your own machine gives for the same content. The lab computes real SHA-1 over the real object format — that claim is tested against the git binary in the repository's own test suite.

Follow the chain yourself

Three commands and you are looking at your own bytes. Nothing in git is hidden; .git is a directory of files you can read.

commit → tree → blob
$ git cat-file -p HEAD
tree 9a2f...            ← the snapshot
parent 23fd...
author …

$ git cat-file -p 9a2f...
100644 blob ce01...    notes.md      ← the file, by id

$ git cat-file -p ce01...
hello

Why the same content always gets the same id

Copy a file and git stores nothing new. Revert a file to what it was three commits ago and the blob is already there. Rename a file and only the tree changes — which is why git has no "rename" operation and detects renames by comparing content afterwards.

two files, one blob
echo "same" > a.txt
echo "same" > b.txt
git add .

# the tree has two entries — pointing at ONE blob

Where it all lives

Deleting .git deletes the history and leaves your files. Copying .git copies the history. A branch really is a file with an id in it — cat .git/refs/heads/main prints it.

inside .git
.git/
  objects/          loose objects, then packed into .pack files
  refs/heads/main   a file containing forty characters
  refs/tags/v1.0
  HEAD              "ref: refs/heads/main"
  index             the staging area, in a binary format
  config            this repository's settings
SHA-1, and why it is still here

Git uses SHA-1 with a hardened check against the known collision attack, and the object format includes a length prefix, which makes a malicious collision much harder. The migration to SHA-256 exists and is slow because every id in every system would change. For your purposes: ids are unique.

Practice