Chapter 9-1 — The Object Model☕ ☕ 20 min read

The Object Model — Blobs, Trees, Commits

Andar ka engine samjho — phir koi Git question nahi bachega.

01Git's Object Database: The Foundation

Git is fundamentally a content-addressable filesystem. What does that mean? It means Git is a key-value store where the key is the hash of the content and the value is the content itself.

Everything Git stores is an "object" in the .git/objects/ directory.

There are exactly 4 types of objects in Git: blob, tree, commit, tag.

Objects are identified by their SHA-1 hash — a 40-character hex string like a94a8fe5ccb19ba61c4c0873d391e987982fbbd3.

Objects are IMMUTABLE — once created, they never change. If you modify a file, Git creates a NEW object with a NEW hash.

Objects are compressed with zlib and stored in .git/objects/XX/ where XX is the first two characters of the hash.

# Explore Git's object store
git init object-demo && cd object-demo

echo "hello" > file1.txt
git add file1.txt

# Where did Git store this?
find .git/objects -type f
# .git/objects/ce/013625030ba8dba906f756967f9e9ca394464a

# The hash "ce0136..." is the SHA-1 of "hello"
# First 2 chars (ce) = directory name
# Remaining 38 chars = filename

# Let's look inside this object
git cat-file -p ce0136
# hello

# What type is it?
git cat-file -t ce0136
# blob
Git doesn't store diffs between files. It stores complete objects for EVERY version of a file. This sounds wasteful, but it's incredibly fast because Git never needs to reconstruct a file by replaying diffs. And identical content always produces the same hash, so Git automatically deduplicates — two files with identical content share ONE blob object.
02Blobs: Pure Content, No Names

A blob (Binary Large OBject) stores FILE CONTENT — just the raw data, nothing else.

A blob does NOT store the filename. It does NOT store permissions. It does NOT store the path.

This is critical: the same file content with different names creates only ONE blob.

This is Git's automatic deduplication. If you have 100 copies of the same README content, Git stores 1 blob, not 100.

Blobs are created by git hash-object and git add.

The blob hash depends on: the file content AND a header ("blob <size>\0<content>").

# Create two files with identical content
echo "hello world" > fileA.txt
echo "hello world" > fileB.txt

git add fileA.txt fileB.txt

# Check the object store
git ls-files -s
# 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 fileA.txt
# 100644 3b18e512dba79e4c8300dd08aeb37f8e728b8dad 0 fileB.txt
#                    ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
#                    SAME HASH — only ONE blob stored!

# Verify with cat-file
git cat-file -p 3b18e51
# hello world

git cat-file -t 3b18e51
# blob
💡 Pro Tip: The filename is stored in the TREE object, not the blob. This separation of content and name is what makes Git so efficient at deduplication. Rename a file? Git stores a new tree pointing to the SAME blob. Change one line? Git stores a NEW blob, and the tree points to the new blob for that file and the old blobs for unchanged files.
03Trees: Directory Structure

A tree object stores DIRECTORY STRUCTURE — it maps names to blobs and other trees.

A tree contains entries like: mode, type (blob/tree), hash, name.

Think of a tree as a folder: it lists what files and subfolders are inside.

Trees can point to blobs (files) and other trees (subdirectories) — creating the full directory structure.

When you git add and git commit, Git builds tree objects that represent your entire project directory.

Trees are created automatically during git write-tree (plumbing) and git commit.

# Create a directory structure
mkdir -p src/utils
echo "main code" > src/main.js
echo "helper" > src/utils/helper.js
echo "# Readme" > README.md

git add .
git commit -m "add project structure"

# Inspect the commit's tree
git cat-file -p HEAD^{tree}
# 100644 blob abc123...    README.md
# 040000 tree def456...    src

# Inspect the src subtree
git cat-file -p def456
# 100644 blob 789abc...    main.js
# 040000 tree xyz789...    utils

# Inspect the utils subtree
git cat-file -p xyz789
# 100644 blob aaa111...    helper.js

# You just walked the entire directory tree manually!
04Commits: The Snapshot Wrapper

A commit object WRAPS a tree with metadata: author, committer, date, message, and parent commit(s).

A commit points to ONE tree (the root tree of the project at that moment).

A commit points to one or more parent commits (0 for root, 1 for normal, 2+ for merges).

The commit hash is computed from: tree hash + parent hash(es) + author + committer + message + timestamps.

This means changing ANYTHING about a commit — even the commit message or timestamp — creates a completely different hash.

This is why git commit --amend creates a new commit, not a modified old one.

# Look at a commit object
git cat-file -p HEAD
# tree 9f8321...          ← the root tree this commit captures
# parent 2b4c67...        ← the commit before this one
# author Sai <sai@devinhyd.com> 1700000000 +0530
# committer Sai <sai@devinhyd.com> 1700000000 +0530
#
# add project structure   ← the commit message

# Every piece of this contributes to the SHA-1 hash
# Change the message? New hash.
# Change the author? New hash.
# Change the timestamp? New hash.
# That's why --amend creates a NEW commit!

# Create a root commit (no parent)
git cat-file -p HEAD~3
# tree abc...
# author ...
# (no parent line — this is the first commit)
05The Complete Snapshot Chain

A commit points to a tree. That tree points to blobs and subtrees. Those subtrees point to more blobs and trees. This IS the snapshot.

When Git says it stores snapshots, this is what it means: a commit object that references a complete tree structure.

If a file didn't change between commits, the new tree points to the SAME blob as the old tree. No duplication.

If a file DID change, the new tree points to a NEW blob for that file, but reuses the old blobs for unchanged files.

The commit chain (parent pointers) provides history. The tree structure at each commit provides the snapshot.

Git NEVER modifies objects. It only creates new ones. History is append-only at the object level.

# Visualize the complete chain for the latest commit
git log --oneline
# a1b2c3d add project structure

# Step 1: Commit → Tree
git cat-file -p a1b2c3d
# tree 9f8321...

# Step 2: Tree → Blobs/Subtrees
git cat-file -p 9f8321
# 100644 blob aaa111... README.md
# 040000 tree bbb222... src

# Step 3: Subtree → Blobs
git cat-file -p bbb222
# 100644 blob ccc333... main.js
# 040000 tree ddd444... utils

# Step 4: Blob → Content
git cat-file -p aaa111
# # Readme

# This is the COMPLETE snapshot!
# Commit → Tree → Tree → Blob = your entire project
Git's object model is a directed acyclic graph (DAG). Commits form a chain via parent pointers. Each commit references a tree. Trees form a hierarchy pointing to blobs and subtrees. This structure is simple, elegant, and incredibly powerful. Every Git command — merge, rebase, checkout, diff — manipulates this graph. Understanding the object model is the key to understanding Git itself.

Lo kar liya — Key Points:

  • ✅ Git stores everything as objects in a content-addressable filesystem — the key is the SHA-1 hash of the content
  • ✅ There are 4 types of objects: blob (file content), tree (directory structure), commit (snapshot + metadata), tag (named reference)
  • ✅ Blobs store ONLY the content — no filename, no permissions. The tree object maps names to blobs
  • ✅ Trees can point to blobs (files) and other trees (subdirectories), creating the full project structure
  • ✅ A commit wraps a tree with metadata: author, committer, date, message, and parent commit(s)
  • ✅ Unchanged files between commits share the same blob — automatic deduplication, no wasted space
  • ✅ Objects are immutable — Git never modifies an object, it only creates new ones
Course Search
Search across all chapters & stages
📖

Search the course

Type any topic — branching, stash, rebase, hooks — and jump straight to that chapter.

merge branchesgit stashundo commitrebase