How Git Stores Commits, Trees, and Blobs as Content-Addressable Objects in a Directed Acyclic Graph
Beneath its version-control interface, Git operates as an immutable key-value database where files and directories are stored as cryptographic hashes mapped across a directed acyclic graph.
- Porcelain Users
- Focuses on the high-level version control interface and chronological diffs.
- Plumbing Engineers
- Focuses on the content-addressable storage and immutable object database.
- Graph Theorists
- Focuses on the mathematical properties of the Directed Acyclic Graph and distributed consensus.
Perspectives this story doesn't cover
- Alternative VCS Architects
- Large File Storage (LFS) Maintainers
In a traditional filesystem like Windows NTFS or macOS APFS, a document is retrieved by asking the operating system for a specific file path, such as `C:\project\main.py`. Git, however, discards the concept of location entirely. At its core, the world's most ubiquitous version control system is not actually a version control system at all, but rather a pure content-addressable database. When a developer asks Git for a file, they are not asking for a location; they are asking for a 40-character cryptographic hash that represents the exact contents of that file at a specific millisecond in time.[1][4]
While modern developer platforms market themselves with promises of seamless collaboration and intelligent versioning, the actual capability driving the global software industry is remarkably unglamorous. Most developers interact with Git through porcelain commands like `git diff` or `git log`, creating the illusion that the software stores a chronological sequence of changes—a base file followed by a list of additions and deletions. This is how older systems from the 1990s, such as Subversion and CVS, operated.[2][5]
The reality of Git's architecture is entirely different. Git stores snapshots, not diffs. Every time a user commits a change, Git takes a complete picture of what all files look like at that exact moment. If a file has not changed, Git does not store a redundant copy; it simply links to the previous identical file already stored in its database. This approach trades the computational overhead of generating diffs on the fly for absolute data integrity and instantaneous snapshot retrieval.[4][5]
This mechanism relies entirely on content-addressable storage. "Git is a content-addressable filesystem," the official Git SCM documentation explains. "It means that at the core of Git is a simple key-value data store." In this model, the name of a piece of data is derived directly from the data itself using a cryptographic hashing function, specifically the 160-bit SHA-1 algorithm.[1][2]
When a file is added to a Git repository, the system calculates a 40-character hexadecimal string based on the file's contents and a brief header. This hash becomes the file's permanent identity. If a single comma is altered in a 10,000-line codebase, the hash changes completely, ensuring 100 percent data integrity. Because the hash is derived from the content, two identical files in different directories will produce the exact same hash and be stored only once.[2][3]
This brings us to the four fundamental object types that populate Git's hidden `.git/objects` directory. The most basic of these is the "blob," an acronym for Binary Large Object. A blob stores nothing but raw file data. It does not know its own file name, its creation date, or its permissions. It is purely the raw bytes of the content, compressed using zlib and named by its SHA-1 hash. Because blobs lack metadata, two files with different names but identical contents will hash to the exact same blob, saving significant storage space.[1][5]
Because blobs lack structural context, Git requires a second object type to reconstruct a project's directory hierarchy: the "tree." A tree object functions much like a folder in a standard operating system, organizing blobs and sub-trees. A tree contains a list of pointers. Each entry in a tree object includes a file mode, an object type (either another tree or a blob), the SHA-1 hash of that object, and the human-readable file name. By linking trees to blobs and other nested trees, Git maps out the entire directory structure of a project at a given point in time.[1][3]
Each entry in a tree object includes a file mode, an object type (either another tree or a blob), the SHA-1 hash of that object, and the human-readable file name.
Yet, trees and blobs alone only describe a static state frozen in time. To track history and attribute changes to specific authors, Git introduces the third and most familiar object type: the "commit." A commit object is surprisingly lightweight, acting merely as a wrapper around a specific state. It contains a pointer to the top-level root tree object that represents the project's entire directory structure, metadata such as the author's name and email, a timestamp, and a commit message explaining the context of the change.[2][4]
Crucially, a commit also contains a pointer to its parent commit. The initial commit in a repository has zero parents. A standard sequential commit has exactly one parent, while a merge commit has two or more parents. This parent-child linkage is what transforms isolated snapshots into a continuous, navigable timeline. Without these parent pointers, a repository would just be a disconnected pile of historical states with no chronological order.[4][5]
Together, these commits form a Directed Acyclic Graph (DAG). In graph theory, a DAG is a network of nodes connected by edges that flow in one direction and never loop back on themselves. In Git's DAG, the nodes are the commit objects, and the directional edges are the parent pointers. Because cryptographic hashes are immutable, history can only flow forward. You cannot alter a past commit without fundamentally changing its hash, which would sever its connection to all subsequent commits and require rewriting the entire downstream graph.[3][5]
This strict immutability is the bedrock of Git's reliability across distributed teams. When a developer attempts to rewrite history using commands like `git rebase` or `git commit --amend`, they are not actually editing existing commits. Instead, the software is generating entirely new commit objects with new hashes and abandoning the old ones. The original commits remain untouched in the local database, completely orphaned, until they are eventually permanently deleted by Git's internal garbage collection routines. This ensures that destructive operations rarely result in immediate data loss.[4][5]
The final piece of the core architecture is the "tag" object, which provides a permanent, human-readable label to a specific commit hash, often used to mark release versions like `v1.0.0`. What developers commonly refer to as "branches" are not complex structural entities within the DAG. A branch in Git is simply a 41-byte text file containing the 40-character SHA-1 hash of a commit, plus a newline character. When a new commit is made on a branch, the text file is simply overwritten with the new hash.[1][5]
This architectural separation between the immutable object database—comprising blobs, trees, and commits—and the mutable pointers like branches and tags is what allows Git to perform complex operations with near-instantaneous speed. Creating a new branch does not duplicate files or require heavy computation; it merely creates a 41-byte pointer to an existing node in the graph. Switching branches simply updates the working directory to match the tree referenced by that pointer, making context switching a trivial operation rather than a heavy filesystem copy.[4][5]
While the software industry often hypes new AI-assisted coding tools and cloud-based collaboration platforms, the underlying mechanics of Git have remained largely unchanged since Linus Torvalds designed the system in 2005. The true capability driving modern development lies not in complex diffing algorithms, but in the elegant simplicity of the Directed Acyclic Graph. By treating code history as a mathematical graph of immutable snapshots rather than a fragile sequence of edits, Git solved the distributed collaboration problem permanently.[2][6]
Understanding this hidden plumbing layer strips away the magic of the user-facing porcelain commands. When a merge conflict occurs or a repository enters a detached HEAD state, it is not a catastrophic system failure, but simply a matter of graph traversal and pointer alignment within a content-addressable key-value store. Developers who grasp the mechanics of the Directed Acyclic Graph stop memorizing opaque command syntax and start manipulating the graph directly, transforming a confusing version control system into a highly predictable, transparent database.[5][6]
Key points
- Git operates as a content-addressable filesystem, identifying data by a 40-character cryptographic hash rather than a file path.
- The system stores complete snapshots of a project rather than a chronological list of diffs or changes.
- Four fundamental object types—blobs, trees, commits, and tags—make up the entirety of Git's immutable database.
- Commits are linked together via parent pointers, forming a strict Directed Acyclic Graph (DAG) that maps the project's history.
- Branches are not structural copies of data, but simply 41-byte text files pointing to a specific commit hash.
Key terms
- Content-Addressable Storage
- A storage mechanism where data is retrieved based on a cryptographic hash of its contents, rather than a specific file path or location.
- Directed Acyclic Graph (DAG)
- A network of nodes connected by directional edges that never form a closed loop, used by Git to map the history of commits.
- Blob
- Binary Large Object; the Git object type that stores the raw, unformatted contents of a tracked file.
- Tree
- A Git object that represents a directory structure, containing pointers to blobs (files) and other trees (subdirectories).
- SHA-1
- A 160-bit cryptographic hashing algorithm used by Git to generate a unique 40-character identifier for every object in its database.
- Porcelain Commands
- The high-level, user-friendly Git commands (like `git add` and `git commit`) that abstract away the underlying database operations.
Sources
[1]Git SCMPlumbing Engineers10.2 Git Internals - Git Objects
Read on Git SCM →
[2]GeeksforGeeksPorcelain UsersGit Internals
Read on GeeksforGeeks →
[3]SubstackPlumbing EngineersA Nibble of Git's Object Store
Read on Substack →
[4]MediumPorcelain UsersUnderstanding the Magic Behind Git: A Deep Dive into Git Internals
Read on Medium →
[5]Career DastakGraph TheoristsBeyond Commit and Push: A Deep Dive into Git Internals (Blobs, Trees, and Refs)
Read on Career Dastak →
[6]Factlen Editorial TeamGraph TheoristsSynthesis by Factlen editorial team
Read on Factlen Editorial Team →
Comments
More in Technology
See all →Platform Policy
Meta Expands Test Limiting Facebook Page Link Posts to Two Per Month Unless Subscribed to 'Meta One'
6 sources
AI Infrastructure
SK Hynix Subsidiary Solidigm Weighs $150 Billion US IPO and First American NAND Fab
7 sources
GDPR Compliance
The Legal Bases for Processing Personal Data Under GDPR
7 sources
Robot Kinematics
The Engineering Trade-off Between Position Control and Impedance Control in Robotics
8 sources
Every angle. Every day.
Get Technology stories with full source coverage and perspective breakdowns delivered to your inbox.




