CHAPTER 03

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.

Thursday, a week later

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:

proving it to Rachit
$ echo "accelerate" | git hash-object --stdin
6d5a1a2f7b3c8e9012a3b4c5d6e7f8901a2b3c4d
 
# run it again — same input, same fingerprint, forever
$ echo "accelerate" | git hash-object --stdin
6d5a1a2f7b3c8e9012a3b4c5d6e7f8901a2b3c4d

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.

RACHIT'S SECOND COMMIT, TAKEN APART
commit 8f2a1b3  "Hero section: initial layout and styles"
├─ 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:

THE WEBSITE'S HISTORY SO FAR
Rachit1
Anvesha2
3
HEAD6

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:

CHECK
You clone the website repo onto a lab computer. A friend clones it onto their laptop. You both check out commit 8f2a1b3. Whose copy of rocket.png is more "current"?
The commit's hash is computed from its tree, which is computed from its blobs — a fingerprint of every byte in the project at that moment. Two people at the same commit hash are, by construction, looking at the identical file. This is the property everything else in Git leans on.

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?