DEV Community

Cover image for Git Isn't a Diff Tracker: How Blobs, Trees, DAG Commits, and the Index Actually Work Under the Hood

Git Isn't a Diff Tracker: How Blobs, Trees, DAG Commits, and the Index Actually Work Under the Hood

S M Tahosin on October 10, 2026

Ask most developers how Git works, and you will hear a familiar explanation: "Git tracks changes. When you edit a file and commit, Git calculates ...
Collapse
 
koda2026 profile image
Harun - solo dev • • Edited

@smtahosin , this is a masterclass in demystifying git. the shift from thinking of git as a "diff tracker" to a "content-addressed snapshot database" is the exact mental model that makes commands like rebase and reset intuitive rather than terrifying.

your point about trap 1 (accidentally committing a large file) hits home. as someone obsessed with keeping my app's codebase at exactly 142kb, accidentally committing a single large asset or a bloated node_modules folder instantly ruins the repo size because that blob is permanently in the history. git filter-repo is an absolute lifesaver for keeping the .git folder lean.

quick question on the 3-way merge: you mentioned setting merge.conflictstyle zdiff3 to see the common ancestor. have you found that this style causes any friction when using automated merge tools or IDE diff viewers like VS Code, or does it integrate seamlessly?

fantastic, deeply practical breakdown. the reflog section alone will save so many developers from panic! 🐯🌳

Collapse
 
smtahosin profile image
S M Tahosin •

Great catch on "--force-with-lease"! You're right that a background fetch can update the remote-tracking ref, which means the default lease might not protect against a commit you haven't actually reviewed. Pinning the expected SHA makes that check much more explicit.

Regarding "zdiff3", it generally works fine with VS Code and other editors because it uses standard conflict markers with an additional base section. However, the editor's visual conflict-resolution UI may not expose that base as a separate resolution option in every version. When that happens, viewing the conflict markers directly is still useful. I should have highlighted that distinction in the post. Thanks for adding this important nuance!

Collapse
 
koda2026 profile image
Harun - solo dev •

tahosin, glad the --force-with-lease catch was helpful! that background fetch race condition is such a silent killer, and pinning the expected sha is the only way to be truly certain you aren't overwriting unseen work.

and you're spot on about zdiff3 in vs code: the visual merge editor can sometimes hide or awkwardly render that middle "base" column depending on the version, so falling back to the raw text markers is still the ultimate source of truth. thanks for writing such a deeply practical breakdown! 🐯🌳

Thread Thread
 
smtahosin profile image
S M Tahosin •

😄 I really appreciate you adding those practical details to the discussion. These are exactly the kinds of edge cases that make Git worth understanding beyond just memorizing commands. The background-fetch issue is especially easy to overlook until it causes a real problem. Thanks for making the discussion even more useful for everyone reading along!

Collapse
 
amorizz profile image
Amorizz •

On Trap 3, one catch with plain --force-with-lease: with no value it compares against your remote-tracking ref, and editors that run git fetch in the background keep moving that ref. The lease then matches whatever was just fetched, so a teammate's commit you never looked at can still get overwritten. Two fixes: pin the lease to the tip you rebased onto, git push --force-with-lease=my-branch:<sha>, or add --force-if-includes (Git 2.30+), which only forces when the remote-tracking tip is already in your local history. I set git config --global push.useForceIfIncludes true once and stopped thinking about it.

Collapse
 
smtahosin profile image
S M Tahosin •

Thanks for stopping by! Appreciate you taking the time to read the post and join the conversation. 🙌

Collapse
 
smtahosin profile image
S M Tahosin •

Yes, that's exactly the distinction I wanted to highlight in section 2. Git's logical model is built around immutable snapshots and content-addressed objects, while packfiles use delta compression as a storage optimization. The important point is that storing objects as deltas inside a packfile doesn't mean Git's commit model is based on tracking diffs. The two operate at different layers. Great question!

Collapse
 
mudassirworks profile image
Mudassir Khan •

the object store framing is what finally made rebase click for me. once you see that a commit is just a tree pointer and a parent pointer, rebase is just replaying those trees onto a new parent — the 'rebase versus merge' confusion disappears. had a production incident years back where someone lost two days of work because they thought git reset --mixed was a full undo and were confused when the blobs were still in objects/. this article would have saved that afternoon. are you planning a follow up on reflog and how GC interacts with loose objects?

Collapse
 
smtahosin profile image
S M Tahosin •

That's a great idea, Mudassir! The interaction between reflog, unreachable objects, and garbage collection deserves its own deep dive. git reset --mixed moves HEAD and resets the index, but it leaves the working tree untouched, which is why the old commit's objects can still exist even when they're no longer referenced by the branch. The interesting part is how long those objects remain recoverable before GC eventually prunes them. I'll definitely consider covering that lifecycle in a follow-up!

Collapse
 
dev_in_the_fog profile image
Jason Y. (dev_in_the_fog) •

Really thoughtful post! Documenting real-world engineering hurdles and actionable solutions like this brings immense value to the community.

Collapse
 
smtahosin profile image
S M Tahosin •

Thanks, Jason! That's exactly what I hope these deep dives can do: connect the internals to the problems developers actually face in real projects. Understanding what's happening underneath makes debugging and recovery much less intimidating. Really appreciate the kind words!

Collapse
 
dev_in_the_fog profile image
Jason Y. (dev_in_the_fog) •

Spot on! The moment junior or even mid-level devs realize commits are immutable snapshots pointing to tree objects rather than diff deltas, commands like git reflog, git cherry-pick, and detached HEAD stop feeling like dark magic.

The classic lightbulb moment for our team was walking through a botched rebase where someone thought their commits were "deleted forever". Inspecting .git/lost-found and recovering dangling tree hashes through git fsck --lost-found blew everyone's mind. Looking forward to your next internal deep dive!

Thread Thread
 
smtahosin profile image
S M Tahosin •

That botched rebase story is such a good example of why understanding Git's internals matters! 😄 A commit disappearing from the current branch doesn't necessarily mean the underlying objects are gone. The reflog is usually the first place I'd look, and "git fsck --lost-found" can help uncover dangling objects when the usual references no longer point to them.

I especially like that you mentioned recovering dangling tree hashes. People often focus on commits, but understanding how commits, trees, and blobs relate makes recovery much less mysterious. Thanks for sharing your team's experience! Really glad the post resonated with you. 🙌