Every earlier lesson in this domain treated a commit hash as an opaque ID. This is what it actually is — a content hash of a real, inspectable object, which is why so many of Git's behaviors (immutable history, cheap branches, why rebasing changes hashes) fall out of one underlying mechanism.
4 min read
blob - the raw content of one file, nothing else (no filename, no permissions)
tree - a directory listing: names, permissions, and which blob/tree each one points to
commit - a snapshot: which tree it points to, its parent commit(s), author, message
tag - (annotated tags) a pointer to a commit, plus a message
Every one of these earlier lessons' concepts — a file's content, a commit's snapshot, a commit's history — is stored as one of exactly four object types, all living in the same object database inside .git. A blob stores a file's raw content. A tree stores a directory's structure — a list of names, each pointing at either a blob (a file) or another tree (a subdirectory). A commit points at one tree (the root of the project at that snapshot) plus its parent commit(s), author, and message. This is the concrete, literal version of the "commits are snapshots" claim from the very first lesson in this domain.
commit content includes:
- the tree hash (which snapshot)
- the parent commit's hash (or hashes, for a merge commit)
- author, committer, timestamp
- the commit message
SHA-1 (or SHA-256, in newer Git) hash of all of that -> the commit's own hash
A commit's hash isn't an arbitrary ID assigned by Git — it's a cryptographic hash computed from the commit's actual content, including its parent's hash. This one fact explains several behaviors from earlier lessons directly: changing a commit's message, its content, or its parent produces a completely different hash, because any of those changes the input to the hash function — which is exactly why rebase (from its own lesson) replaying a commit onto a new parent necessarily produces a new hash, even when the actual code changes are identical.
Because each commit's hash incorporates its parent's hash, the commits form a chain where altering anything anywhere breaks every hash after that point — this is what makes Git history tamper-evident: two people comparing the same commit hash can be confident they're looking at bit-for-bit identical history, all the way back, without needing to manually diff every file. Changing history isn't literally forbidden, but doing so is exactly what rebase, reset, and commit-amending all do explicitly — and it visibly changes hashes, rather than silently rewriting content while keeping the same identifier.
$ cat .git/refs/heads/main
a3f8e21f9c2d1049c8e7...
The branching lesson's claim that a branch is "a lightweight, movable pointer" isn't a simplification — it's literally true: a branch is a small text file inside .git/refs/heads/, containing nothing but one commit hash. Moving a branch (committing, merging, resetting) means overwriting that one line with a different hash; nothing about a branch involves copying any actual file content, which is the concrete reason branch creation and switching are as fast as they are.
git gc and the reflog's expiry make sense, given this modelAn object with no reference pointing to it (no branch, no tag, no reflog
entry) is genuinely unreachable — nothing in the repository can find it
by walking from any starting point
The reflog lesson described commits eventually being cleaned up by git gc after no longer being referenced — this is now the concrete mechanism: gc walks every reachable object starting from every branch, tag, and reflog entry, and anything not reachable that way is safe to delete, since nothing in the repository could ever find it again anyway. This is also exactly why the reflog is what makes recently-orphaned commits recoverable: as long as some reflog entry still points at a commit, gc considers it reachable and leaves it alone.
None of the behavior covered across this entire domain is arbitrary or memorized trivia — every one of these observations is a direct, mechanical consequence of "everything is a hashed object, and refs are just pointers to hashes." Understanding this underlying model is what turns Git's behavior from a list of commands to memorize into something predictable, reasoned out from one consistent mechanism.
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. Which Git object type is the literal mechanism behind 'a commit is a snapshot, not a diff'?
2. Is a commit's hash an arbitrary identifier Git assigns, or is it derived from something?
3. Why does a commit including its parent's hash make Git history 'tamper-evident'?
4. What is a Git branch, literally, on disk?
Git & Version Control