Engineer's toolkit
Git's object store
The four object types Git stores, how a hash of the contents becomes the object's ID, and why that gives Git deduplication, integrity and cheap comparisons.
Git keeps everything it knows in a folder called .git/objects. Each object is stored under an ID, and that ID is a hash of the object's contents. Understanding this one idea explains most of Git's behavior, from why branches are cheap to why history cannot be changed silently.
Concept
Before you start: a hash is a fixed-length fingerprint of some bytes. The same bytes always give the same hash. A tiny change gives a completely different one. Git uses SHA-1 by default.
The four object types
| Object | Stores | Analogy |
|---|---|---|
| blob | The raw bytes of one file. No name, no permissions | A file's contents |
| tree | A list of entries: mode, type, hash, name. Points at blobs and other trees | A directory listing |
| commit | One root tree hash, zero or more parent commit hashes, author, committer and message | A snapshot plus its history |
| tag (annotated) | An object hash, a tag name, tagger, message and optional signature | A signed label on a commit |
A commit is a snapshot, not a diff. It points to a complete tree of the project. Git computes diffs on demand by comparing two trees.
Walking through it: an ID you can reproduce
The ID of a blob is the hash of a short header plus the contents. You can compute it yourself:
$ echo 'hello world' | git hash-object --stdin
3b18e512dba79e4c8300dd08aeb37f8e728b8dad
$ printf 'blob 12\0hello world\n' | shasum # "blob <size>\0<content>"
3b18e512dba79e4c8300dd08aeb37f8e728b8dad -Same bytes in, same ID out, on every machine. Now commit a file and look inside:
$ git cat-file -p HEAD
tree 68aba62e560c0ebc3396e8ae9335232cd93a3f60
author Ada <ada@example.com> 1767225600 +0000
committer Ada <ada@example.com> 1767225600 +0000
Add hello
$ git cat-file -p 'HEAD^{tree}'
100644 blob 3b18e512dba79e4c8300dd08aeb37f8e728b8dad hello.txtThe commit points to a tree, and the tree points to the blob with the file's contents. Notice that the commit has no diff in it.
What content addressing buys you
- Deduplication. Two identical files anywhere in history are one blob.
- Integrity. A commit's hash covers its tree, which covers every blob, and its parents, which cover their trees, all the way back. Change one byte in history and every descendant hash changes. This structure is a Merkle DAG, the same idea used in certificate transparency and blockchains.
- Cheap comparisons. If two trees have the same hash, everything under them is identical.
git diffandgit statusskip whole directories.
Storage on disk
A new object is written as one zlib-compressed file. The first two hex characters of its hash become a folder name, so 3b18e512... lives at .git/objects/3b/18e512.... Later, git gc packs objects into packfiles. A packfile stores similar objects as deltas against each other and compresses them, so history stays small.
Note
Git uses SHA-1. After the 2017 SHAttered collision, Git switched to a hardened SHA-1 implementation that detects collision attacks. It has supported a SHA-256 object format since version 2.29 (git init --object-format=sha256). Most repositories still use SHA-1, because hosting support is uneven as of 2025 to 2026.
Common mistakes
- Thinking a commit stores a diff. It stores a full tree. Diffs are computed when you ask for them.
- Assuming hashes are for secrecy. They are for identity and integrity, not for hiding content.
- Expecting a rewrite to leave history unchanged. Any change to a commit gives it a new hash, and every descendant gets a new hash too.
Check yourself
- You change one line in a file three commits back. Which hashes change? That blob, the trees above it, the commit that contains it, and every later commit.
- Why is storing ten copies of the same file cheap? Each identical file is the same blob, stored once.
Key takeaways
- Git has four object types: blob, tree, commit and tag. Each one is stored under the hash of its contents.
- A commit is a snapshot that points to a complete tree and its parents. Diffs are computed on demand.
- Content addressing gives deduplication, integrity and fast comparisons. A change to history rewrites every hash after it.
Further learning
Next, see how branches and HEAD are just pointers into this store, in refs, HEAD and the reflog.
Inside .git (Julia Evans) walks through the same folder with examples you can run.
Related
Refs, HEAD and the reflog
Branches, tags and HEAD are small files that point at commits, and the reflog records every move, which is why almost any committed work can be recovered.
Merge, rebase and squash
The three ways to combine a branch into main, what each one does to the commit graph, how to resolve conflicts without guessing, and the one rule about rewriting shared history.
Code review
Author small, reviewable PRs and give reviews that improve both the code and the author — design first, tone that teaches, turnaround that unblocks.