"Git doesn't store file versions — it stores a graph of snapshots linked by fingerprint. The magic is just maths."
The Filing Cabinet Inside .git
Every git add and git commit you've run wrote a file into .git/objects/. Open that folder after a commit and you'll find something like this:
.git/objects/
├── a3/
│ └── f7b21c9e4d5f8b1a2c3d4e5f6789012345678
├── 4b/
│ └── 825dc642cb6eb9a060e54bf8d69288fbee4904
└── f2/
└── ba8f84ab5c1bce84a7b441cb1959cfc7093b7f
Each file is a Git object — data compressed and named by its SHA-1 hash. The first two characters become the directory; the remaining 38 become the filename. Git's entire history is a folder of compressed files, each named after a fingerprint of its own content.
Two consequences follow immediately:
- Same content always produces the same hash. If two files contain identical bytes, they share one object in storage. Git never stores the same content twice.
- You can't silently edit a past object. Change one byte and the hash changes. Anything referencing the old hash now points at nothing.
Three Object Types — and How They Chain
Git stores exactly three types of objects.
Blob — raw file content and nothing else. No filename. No timestamp. No permissions. Just bytes. The association between a blob and a filename lives in the tree, not the blob.
Tree — a directory listing. Each row maps a filename and permission mode to a blob or another tree hash:
100644 blob f2ba8f... notes.txt
One tree per directory per commit. Nested directories produce nested trees.
Commit — the snapshot and its metadata: a pointer to the top-level tree, zero or more parent commit hashes (root commits have none), author, committer, and your message:
tree 4b825d... parent a3f7b2... ← absent on the very first commit author Your Name <email> 1723027425 +0530 committer Your Name <email> 1723027425 +0530 Add initial notes file
The chain from any commit: commit → tree → blobs. Following those three pointers gives you the exact state of every file at that snapshot.
Reading Objects With git cat-file
You don't need a special tool to inspect this. Two flags cover everything:
git cat-file -t <hash> # show the type: commit / tree / blob git cat-file -p <hash> # show the content: pretty-printed for the type
You'll run both of these on your own Part 2 commit in Hands-On Part 3 — using the actual hash from your real git log output, not a placeholder.
Why the Chain Is the Guarantee
A commit's hash is computed from its content, its tree hash, and its parent hash. If anyone secretly edits commit a3f7b21 — changing the author, the message, even a single character in a tracked file — the hash changes. The child commit still references the old hash. The old hash no longer exists at that address. The chain breaks.
This is not encryption. It's arithmetic. A valid, unbroken chain of hashes is tamper-evidence baked into the data structure itself. It's why you can trust that a repository's history hasn't been quietly rewritten.
Key Takeaways
- Git's object store is a folder of compressed files, each named by the SHA-1 hash of its own content
- Three types: blob (file bytes), tree (directory listing), commit (metadata + tree pointer + optional parent)
- The chain commit → tree → blobs gives you the complete snapshot at any point in history
- Content-addressable storage means same content = same hash, and any edit changes the hash — making history tamper-evident