Git Object Model: Blobs, Trees, Commits, and Tags as SHA-1 Addressed Objects
Every single concept covered across this entire course — commits, branches, staging, history — is ultimately built from just four fundamental object types stored inside the `objects/` folder introduced in the previous lesson. This lesson goes deep into exactly what these four object types are, how they relate to each other, and the elegant content-addressing principle that makes Git's entire integrity model work.
Learning Objectives
- Identify Git's four core object types: blob, tree, commit, and tag.
- Explain how each object type relates to and references the others.
- Understand content-addressed storage and why an object's SHA hash is derived from its content.
- Use git cat-file to directly inspect the raw content of any Git object.
Key Terms to Know Before Learning the Git Object Model
- Blob: A Git object storing the raw content of a single file, with no filename or metadata attached — just the content itself.
- Tree: A Git object representing a directory, listing the names, modes, and object hashes of the blobs (files) and other trees (subdirectories) it contains.
- Commit: A Git object referencing one tree (the project's complete state at that point), one or more parent commits, and metadata (author, date, message).
- Content-addressed storage: A storage model where an object's identifier (its SHA hash) is derived directly from its content, meaning identical content always produces the identical hash.
How the Git Object Model Actually Works
Git's entire data model rests on just four object types, each stored in `objects/` and identified by a SHA hash computed from its own content:
**Blob** ("binary large object") stores the raw content of a single file — and only the content, nothing else. Critically, a blob has **no filename, no permissions, no metadata** attached to it at all; it's purely the file's bytes. This has a fascinating consequence: if two completely different files, anywhere in your project (or even across different commits), happen to have byte-for-byte identical content, they are represented by the exact same single blob object, stored only once — Git automatically deduplicates identical content this way, entirely as a natural side effect of content-addressing.
**Tree** represents a directory. It's essentially a list of entries, where each entry has a filename, a file mode (permissions, and whether it's a file or subdirectory), and a reference to the SHA hash of either a blob (for a file) or another tree (for a subdirectory). This is what actually attaches filenames and directory structure to otherwise-anonymous blob content — a tree is where 'this blob is named `index.html`' gets recorded, since the blob itself has no idea what it's called.
**Commit** is what ties everything together into a snapshot with history and authorship. A commit object references exactly **one tree** (representing the complete state of the entire project at that moment), **one or more parent commits** (zero parents for the very first commit, one parent for a normal commit, two parents for a merge commit — directly explaining Module 3's merge commit structure), and metadata: author name/email, committer name/email, timestamp, and the commit message itself.
**Tag** (specifically an **annotated** tag, from Module 2 — lightweight tags are simply refs, not their own object type) is an object that references a specific commit, along with a tagger name, date, and message — exactly why annotated tags carry richer metadata than lightweight tags, as covered back in Module 2.
The entire system rests on **content-addressed storage**: every object's identifying hash (historically SHA-1, with newer Git versions supporting SHA-256) is computed directly from that object's own content. This has two profound consequences already touched on throughout this course: **identical content always produces an identical hash** (enabling automatic deduplication of blobs, as mentioned above), and **any change to content, anywhere, changes that object's hash**, which cascades upward — change a file (its blob's hash changes) → the tree referencing it must update (its hash changes too) → the commit referencing that tree must update (its hash changes too) — directly explaining why Module 2's rebase and Module 7's history-rewriting lessons both note that changing anything about a commit inevitably changes its hash and the hash of every commit that comes after it.
You can inspect any of these objects directly using `git cat-file`:
```
git cat-file -p <commit-hash> # shows the commit's tree, parent, author, message
git cat-file -p <tree-hash> # shows the tree's file/subdirectory entries
git cat-file -p <blob-hash> # shows the raw file content
```
This reveals, very concretely, that a commit is really just a small text object pointing at one tree and some parent commits — everything else (branches, staging, merges) is built as a layer of tooling and convention on top of this remarkably simple, elegant four-object foundation.
Blobs, Trees, and Commits: Visual Walkthrough
Draw a layered diagram bottom to top. Bottom layer: three 'Blob' boxes containing raw content ('<html>...', 'body { color: blue; }', 'console.log(...)'). Middle layer: a 'Tree' box listing entries pointing down to each blob: 'index.html -> blob hash A', 'style.css -> blob hash B', 'app.js -> blob hash C' — plus a nested 'Tree' entry for a subdirectory. Top layer: a 'Commit' box pointing to the top-level Tree, plus an arrow labeled 'parent' pointing to a PREVIOUS commit box, and metadata listed: 'Author: Asha, Date: ..., Message: feat: add homepage'. Off to the side, an 'Annotated Tag' box pointing at one specific Commit, with its own tagger/date/message metadata.
Git Object Types: Quick Reference Table
| Object Type | References | Contains |
|---|---|---|
| Blob | Nothing (it's leaf-level content) | Raw file content only — no filename or metadata |
| Tree | Blobs and/or other Trees | A directory listing: filenames, modes, and hashes of its contents |
| Commit | One Tree + parent commit(s) | Complete project snapshot reference, author, date, message |
| Tag (annotated) | One Commit | Tagger name, date, message — pointing at a specific commit |
Inspecting Git Objects: Command Syntax and Examples
# Inspect a commit object directly
git cat-file -p HEAD
# tree a1b2c3d...
# parent 9f8e7d6...
# author Asha Mehta <asha@example.com> 1719500000 +0530
# committer Asha Mehta <asha@example.com> 1719500000 +0530
#
# feat: add homepage layout
# Inspect the tree that commit references
git cat-file -p a1b2c3d
# 100644 blob 3c4d5e6... index.html
# 100644 blob 7g8h9i0... style.css
# 040000 tree 1j2k3l4... src
# Inspect a specific blob's raw content
git cat-file -p 3c4d5e6
# <html>...</html>
# Confirm the object TYPE of any hash
git cat-file -t a1b2c3d
# tree
Breaking Down the Git Object Model Example
`git cat-file -p HEAD` reveals a commit object's raw content directly: a reference to one tree, one parent commit, author/committer metadata, and the message — exactly the structure described above, made concrete. Following that tree's hash with a second `cat-file -p` call shows its listing of blobs (files) and a nested tree (subdirectory), each with a filename and mode attached. A third call on a specific blob hash shows purely raw file content, with no filename attached at all, confirming the blob/tree distinction: content lives in blobs, names and structure live in trees. `git cat-file -t` confirms an object's type directly, useful when exploring hashes found via other commands like `git log` or `git reflog`.
How Understanding the Object Model Helps in Real Engineering Work
- Genuinely understanding the object model is often what separates an intermediate Git user from someone with true depth — it explains why every operation covered throughout this entire course (branching, merging, rebasing, tagging) behaves exactly the way it does.
- Git's automatic content deduplication via blobs (identical file content across the entire history, even in different files, stored only once) is a key reason Git repositories are often surprisingly storage-efficient despite tracking extensive history.
- Advanced debugging of repository corruption or unusual behavior frequently involves directly using git cat-file to inspect specific objects, going beneath the abstraction of higher-level commands.
- Tools and scripts built on top of Git (custom tooling, some CI/CD integrations) sometimes interact directly with the object model via git's plumbing commands (like cat-file, hash-object) rather than exclusively using higher-level porcelain commands.
Git Object Model Interview Questions and Answers
Q1. What are Git's four core object types, and how do they relate to each other?
Blobs store raw file content with no filename attached. Trees represent directories, listing filenames and the hashes of the blobs and other trees they contain, attaching structure and names to otherwise-anonymous blob content. Commits reference one tree (a complete project snapshot) plus parent commit(s) and metadata like author and message. Annotated tags reference a specific commit along with tagger metadata.
Q2. What is content-addressed storage, and what are its two key consequences in Git?
It means an object's identifying hash is computed directly from its own content, rather than being assigned arbitrarily. This means identical content always produces an identical hash, enabling automatic deduplication (identical blobs are stored only once), and any change to an object's content changes its hash, which cascades upward through every tree and commit that references it.
Q3. Why does a blob have no filename, and where does a file's actual name get recorded?
A blob stores purely the raw content of a file, with absolutely no metadata like a filename attached, which is what allows identical content to be automatically deduplicated regardless of what it's called in different places. A file's actual name (and its permissions/mode) is recorded in the tree object that references that blob, not in the blob itself.
Git Object Model Quiz: Test Your Understanding
1. What does a Git blob object store?
- A filename and its content together
- Only the raw content of a file, with no filename or metadata
- A list of files in a directory
- A commit's author and message
Answer: B. Only the raw content of a file, with no filename or metadata
Explanation: A blob is purely raw file content — no filename, permissions, or any other metadata is attached to it; that information lives in the tree object referencing it instead.
2. What does a Git commit object reference?
- Only a list of changed files
- One tree (a complete project snapshot) plus parent commit(s) and metadata
- Multiple trees representing every past version simultaneously
- Nothing — a commit is a standalone object with no references
Answer: B. One tree (a complete project snapshot) plus parent commit(s) and metadata
Explanation: A commit points to exactly one tree object representing the project's full state at that point, along with its parent commit(s) and metadata such as author, date, and message.
3. What does content-addressed storage mean in the context of Git objects?
- Objects are stored in alphabetical order by filename
- An object's identifying hash is computed directly from its own content
- Objects are addressed by their creation date
- Content is stored separately from its identifying hash
Answer: B. An object's identifying hash is computed directly from its own content
Explanation: Content-addressed storage means an object's SHA hash is derived from its actual content, so identical content always produces an identical hash, and any change to content changes the resulting hash.
Common Misunderstandings About the Git Object Model
- Assuming a blob stores a filename along with its content, when filenames are actually recorded only in the referencing tree object.
- Not realizing identical file content anywhere in a repository's history is automatically deduplicated into a single shared blob.
- Confusing a tree with a commit — a tree represents directory structure at one point; a commit adds history, authorship, and parent references on top of one specific tree.
- Underestimating how directly this object model explains earlier course concepts, like why rebasing and amending change commit hashes (since content-addressing means any change cascades upward).
Git Object Model: Exam-Ready Quick Notes
- Four object types: Blob (raw file content, no filename), Tree (directory listing: names + blob/tree hashes), Commit (one tree + parents + metadata), Tag (references one commit + tagger metadata).
- Content-addressed storage: hash derived from content — identical content = identical hash (deduplication); any content change = new hash (cascades upward).
- git cat-file -p <hash>: shows an object's raw content. git cat-file -t <hash>: shows an object's type.
Git Object Model: Key Takeaways
- Every Git concept covered throughout this course is built from just four elegant object types: blobs, trees, commits, and tags.
- Content-addressed storage — hashing based on content — is the foundational principle explaining both Git's automatic deduplication and why any content change cascades into new hashes for every affected commit.
- git cat-file lets you directly inspect any object's raw content, making Git's internal mechanics concrete rather than abstract.
Frequently Asked Questions About the Git Object Model
Q1. What are the four types of objects in Git?
Blobs (raw file content, no filename), trees (directory listings, attaching filenames to blobs and other trees), commits (a project snapshot reference plus parent commits and metadata), and annotated tags (a reference to a specific commit with tagger metadata).
Q2. Why doesn't a blob store a filename?
A blob stores purely the raw content of a file. The filename and structure are recorded separately in the tree object that references that blob, which is what allows identical content (even under different filenames) to be automatically stored just once.
Q3. What does 'content-addressed' mean for Git objects?
It means each object's identifying hash is calculated directly from its own content, so identical content always produces the exact same hash, and any change to content produces a completely different hash.
Q4. How can I see the raw content of a Git object directly?
Use git cat-file -p <hash>, which prints the raw content of any object — showing a commit's tree/parent/message, a tree's file listing, or a blob's actual file content, depending on which hash you provide.
Q5. Why does changing a file always change its commit's hash, even for a tiny edit?
Because Git's content-addressed model means a changed file produces a new blob hash, which means the tree referencing it must also change (new hash), which means the commit referencing that tree must also change (new hash) — a change cascades upward through the entire chain.
Summary
Git's entire data model is built from four object types stored in `.git/objects/`. Blobs store a file's raw content with no filename or metadata attached. Trees represent directories, listing filenames, modes, and the hashes of the blobs and other trees they contain — this is where actual filenames and structure get attached to otherwise-anonymous blob content. Commits reference exactly one tree (a complete project snapshot), one or more parent commits, and metadata like author and message. Annotated tags reference a specific commit along with tagger metadata. Underlying all of this is content-addressed storage: every object's SHA hash is computed directly from its own content, meaning identical content produces an identical hash (enabling automatic deduplication) and any content change produces a new hash that cascades upward through every tree and commit referencing it — directly explaining why operations like rebasing, amending, and history rewriting (Modules 2 and 7) always produce new commit hashes. `git cat-file -p <hash>` lets you directly inspect any object's raw content, making these mechanics concrete.