C.W.K.
Stream
Lesson 01 of 04 · published

Objects: Blob, Tree, Commit, Tag

~22 min · objects, internals

Level 0Untracked Rookie
0 XP0/47 lessons0/14 achievements
0/100 XP to next level100 XP to go0% complete

Four object types and a content-addressable store

Everything Git knows is stored as one of four object types in .git/objects/, addressed by SHA-1 hash of the content. Once you can identify a blob, a tree, a commit, and a tag in the repo, every higher-level Git command stops feeling magical — it is all manipulation of these four shapes.

Blob stores the raw bytes of a single file. No filename, no permissions — just contents. Two files with identical contents (a duplicate config, the same fixture in two test suites) hash to the same blob and are stored once. Tree stores a directory listing: name, mode (file permissions and type), and the hash of either a blob (for a file) or another tree (for a sub-directory). Trees give names and structure to blobs.

Commit ties it together: a small object holding the hash of the root tree (the project's snapshot), the hash(es) of parent commit(s), the author and committer with timestamps, and the message. Walking commit parents takes you backwards through history; reading a commit's tree takes you sideways into "what did everything look like at this point." Tag is the rarer fourth type: a named pointer with metadata, used for annotated tags. Lightweight tags are just refs, not tag objects.

The store itself is content-addressable. echo "hello" | git hash-object --stdin computes the hash; git cat-file -p <hash> reads any object back. The first two characters of the hash become the directory name under .git/objects/, and the remaining 38 are the filename — that is why early Git inspections show files like .git/objects/3a/4f5d6e.... As repos grow, Git compresses many objects into pack files for efficiency (next lesson). The mental model survives compression: hash → object → content.

Code

Tour your repo's object types·bash
# Pick the latest commit:
HASH=$(git rev-parse HEAD)

# Read the commit object:
git cat-file -p $HASH
# Output shows tree, parent(s), author, committer, message.

# Read the root tree the commit points at:
TREE=$(git cat-file -p $HASH | head -1 | awk '{print $2}')
git cat-file -p $TREE
# Each line: <mode> <type> <hash>\t<name>

# Pick any blob hash from the tree and read it:
git cat-file -p <blob-hash>

# What type is a hash?
git cat-file -t $HASH        # commit
git cat-file -t $TREE        # tree
Compute and store objects manually·bash
# Compute the hash without storing:
echo "hello world" | git hash-object --stdin

# Compute AND store as a blob in .git/objects/:
echo "hello world" | git hash-object --stdin -w

# Read it back by hash:
git cat-file -p <hash>

# List all unique blob hashes in HEAD's tree:
git ls-tree -r HEAD | awk '{print $3, $4}'

External links

Exercise

In any small repo, run git rev-parse HEAD to get the commit hash, then walk the chain manually: git cat-file -p on the commit, then on its tree, then on one blob from the tree. Now run git ls-tree -r HEAD | wc -l to count the blobs in the current snapshot. Note in writing two facts you confirmed about Git's storage that you might have misunderstood from training-day mental models.

Progress

Progress is local-only — sign in to sync across devices.
Spotted a bug or have feedback on this page?Report an Issue

Comments 0

🔔 Reply notifications (sign in)
Sign inPlease sign in to comment.

No comments yet — be the first.