Git Internals: Blobs, Trees, and Commits Explained
Learn how Git stores versions using blobs, trees, and commits instead of diffs. Understand the content-addressable filesystem and why this knowledge helps with merges, rebases, and recovery.
Git Internals: Beyond Just git add and git commit
If you've used Git for a while, you know the drill: git add, git commit, and maybe git push when you're feeling brave. But what actually happens inside that .git folder when you type these commands? Understanding Git's internals is like discovering the engine under the hood of your favorite car—it makes you a much better driver.
Most developers treat Git as a black box, but the moment you understand how it tracks versions, conflicted merges stop feeling like magic and start making perfect sense. Let me walk you through the three core objects that make Git tick.
The Content-Addressable Filesystem
At its heart, Git is a key-value store. Every piece of content—every file, every directory, every commit—gets hashed with SHA-1 and stored as a blob. The hash becomes the address you use to retrieve that content later.
When you run git init, Git creates that .git/objects directory. This is where everything lives. Run this yourself and you'll see subdirectories with two-character names—those are just the first two hex digits of the hash. Git does this to keep lookups fast, since no directory has more than 256 subdirectories.
Here's the interesting part: Git doesn't store diffs. Not the way you probably think. It stores complete snapshots of your entire project at each commit. The reason Git feels fast is because these snapshots are smartly compressed using a technique called "packing," but the default mode is "loose objects"—each blob is a separate file.
Try this in your terminal:
echo "hello" | git hash-object --stdin -w
That command hashes the string "hello", stores it in .git/objects, and prints the hash. The -w flag writes it. Now look inside .git/objects—you'll see a new file. That single string is stored as a compressed blob, and the filename is its SHA-1 hash.
Blobs, Trees, and Commits
Three object types form the backbone of Git's version history:
Blobs store file content. A blob has no metadata—no filename, no permissions, just the raw bytes. This is why Git can detect file renames so naturally: it just compares blob hashes, not paths.
Trees represent directories. A tree contains pointers to blobs (files) and other trees (subdirectories), each with a mode, filename, and hash. This is where directory structure lives. When you modify a file and commit, Git creates a new tree that reuses all the unchanged blobs and only records the new blob for the changed file.
Commits are the top-level objects that anchor your history. A commit points to a tree (the full snapshot of your project at that moment), a parent commit (or multiple for merges), an author, a committer, a timestamp, and a message. That's it. No diff calculations stored anywhere.
When you run git log, Git walks backward through these commit objects, showing you each parent's metadata. The actual content changes are computed on the fly by comparing the trees—and this is where Git's speed shines, because it only needs to compare the hashes of unchanged blobs rather than re-reading entire files.
How Version History Actually Works
Let's say you have a file called app.py. You make a change and commit it. Here's the sequence:
- Git reads
app.pyand hashes its content. If the hash already exists in the object store, nothing new is created—your file just references an existing blob. - If the content changed, Git stores the new blob, creates a new tree object that points to this new blob (but reuses all other unchanged blobs from the previous tree), and then creates a new commit object pointing to that tree, with the previous commit as its parent.
So every commit is a full snapshot of your project, but Git doesn't duplicate the unchanged content. This is the key insight: Git is storing snapshots, but it's doing it efficiently by reusing object hashes.
Your working directory is just a checkout of one of these snapshots. When you switch branches, Git rewrites the working directory to match the tree of the new commit. If the tree contains a blob with the same hash as the file currently on disk, Git doesn't touch that file—it knows it's identical.
The Reflog: Your Safety Net
One of the most misunderstood Git internals is the reflog. The reflog records every change to HEAD and branch references—every commit, reset, checkout, and rebase. Even "lost" commits stay in the reflog for 90 days by default.
Here's how to think about it: Git never actually deletes objects immediately. When you git reset --hard to a previous commit, the later commits still exist in the object store. They just become unreachable from any branch or tag. The reflog holds a reference to them. Run git reflog and you'll see a timeline of every HEAD movement.
This saved me last month when I accidentally force-pushed a rebased branch without realizing I'd lost three commits. The commits were still in my local reflog. git reflog show showed me the hash, and I just cherry-picked them back onto the branch. No disaster, no panicked calls to the team.
Why This Matters for Your Daily Work
Understanding Git's internals changes how you use it. When you know Git stores snapshots, not diffs, you stop worrying about "too many commits" slowing things down—they won't. When you know trees are content-addressed, you understand why git rebase is so powerful: it just creates new commit objects with different parents, reusing all the same trees and blobs.
The next time you're in the middle of a messy merge conflict, remember: a commit is just a snapshot of your project. The conflict is between two trees. Git is comparing blobs, not lines. And if something goes wrong, the reflog has your back.
Git's simplicity—just blobs, trees, and commits—is what makes it both powerful and reliable. Once you see it that way, version control stops feeling like magic and starts feeling like logic. And that's the kind of understanding that makes you a stronger developer, whether you're working alone on a side project or collaborating on code with a team of twenty.
Comments
Questions, corrections, and tips stay visible for everyone reading this page.
Join the discussion
No comments yet
Be the first to leave a note — it helps the next reader.