Snapshots, hashes & objects
Something about the repo isn't adding up — and the answer changes how you'll think about every Git command from here on.
Anvesha's been committing steadily — new content, a fixed typo, then the club's rocket logo dropped into assets/, then three more commits that don't touch the logo at all. You're curious how big the project's history has gotten, so you check:
du -sh .git/ — and it's tiny. Kilobytes, not megabytes, even though that logo file alone is a few hundred KB and it's now sitting inside six different commits.
"Shouldn't that be six copies of the logo by now?" you ask Rachit. He shrugs — he doesn't know either. You're both about to learn the single idea that makes every other Git command make sense.
It doesn't store copies. It stores content.
The most common wrong mental model of Git is "it stores the difference between versions, like a diff tool." That's not it. Every commit records a complete snapshot of every tracked file, as it existed at that moment. But — and this is the part that explains your tiny .git/ folder — if a file's content is identical to something Git has already seen, it isn't stored a second time. It's referenced.
Think about how a courier warehouse could work, if it were clever. Every parcel gets a barcode generated from its contents and packaging — not assigned in order, but computed, so that two parcels with byte-for-byte identical contents always get the exact same barcode. Now: a hundred people send the club's standard event T-shirt, identically boxed. The warehouse doesn't build a hundred shelf slots. It builds one, and prints ninety-nine address labels pointing at it. Storage grows with how much genuinely different stuff exists, not with how many times someone touched a box.
That's what happened to Anvesha's logo. Commit two, three, four, five, six — none of them changed assets/rocket.png. So none of them need their own copy. They all just point at the one copy from commit two.
The barcode is real, and you can print it yourself
Git's "barcode" is a SHA-1 hash — a fixed-length fingerprint computed from a file's exact bytes. Same bytes in, same fingerprint out, every single time, on every computer on Earth:
Change one character — one pixel in the logo, one semicolon in the CSS — and the fingerprint changes completely, unrecognizably, every time. There's no "close enough." Content is either exactly what it was, in which case it's the same object and gets reused, or it's different, in which case it's a new object.
Four kinds of object, and what each one is actually for
Blob — a file's raw content
Just bytes. No filename attached, no folder, no permissions. The blob for rocket.png doesn't know it's called rocket.png — that name is recorded one level up.
Tree — a folder listing
An ordered list of (name, type, hash) entries. This is what says "the file at assets/rocket.png has this content" — it connects a name to a blob. A tree can also point to other trees, which is how subfolders work.
Commit — a snapshot with a story attached
Points to exactly one tree (the whole project's state at that moment), points back to its parent commit(s), and carries the author, the timestamp, and the message. This is the object git log shows you.
Tag — a permanent name for one commit
Useful for marking "this exact commit is what we submitted to the buildathon judges." Branches move; tags don't. More in Chapter 10.
├─ tree a91c02f
│ ├─ blob b3d4e5f index.html
│ ├─ blob c1e2f3a style.css
│ └─ tree d7f8a9b assets/
│ └─ blob e4a5b6c rocket.png
├─ parent (none — this is the first commit)
└─ author Rachit <rachit@example.com>
Now picture commit six, the one after Anvesha's third content edit. Its tree still has an entry for assets/rocket.png — but instead of a new blob, that entry points at blob e4a5b6c, the exact same one from commit two. Nothing about the logo was re-stored. That's the kilobytes you saw instead of megabytes.
Commits remember what came before them
Every commit — except the very first one Rachit made — carries a pointer back to its parent. Follow those pointers backward and you get the whole story of the project, in order:
HEAD is Git's name for "the commit your working files currently match." Move HEAD to a different commit and your files change to match it instantly — that's what "checking out" a commit actually does under the hood.
Why any of this matters to you, specifically
Three consequences fall straight out of what you just watched happen to the logo file:
- Tampering is loud, not silent. If someone edited even one byte of an old commit, its hash would change — which means the commit after it (which references the old hash as "parent") would also have to change to still make sense, and so on down the whole chain. There's no way to quietly alter history without every commit hash after it visibly breaking.
- Two people at the same commit have byte-identical files. When Sahil later sets up automated deployment (Chapter 18), this is the entire reason it can be trusted: if the deploy server checks out commit
8f2a1b3, it is running exactly what was tested in CI at that hash — not "probably the same, roughly." - Rewriting history changes hashes. This sounds abstract now, but it's the reason rebasing a branch that someone else has already built on causes real pain (Chapter 9) — the "same" commit, replayed onto a different parent, gets an entirely new fingerprint.
8f2a1b3. Whose copy of rocket.png is more "current"?You close the laptop lid satisfied, but there's already a new problem waiting for tomorrow. You fixed a real bug in index.html today — and left two debug console.log lines in the same file that you don't want committed yet. Right now, as far as Git's concerned, that file is just "changed." How do you commit only part of it?