Commits: Are They Diffs, Snapshots, or Something Else?
Everyone who uses Git has a mental model of what a commit is, but those models often diverge sharply. A recent, highly unscientific poll on Mastodon asked developers how they primarily think about commits. The split was surprisingly close: 51% said a diff, 42% said a snapshot, while a small remainder saw a commit as a history of every previous version or something else entirely.
This near-even division suggests the answer isn't as clear-cut as it might seem. Defining the terms helps sharpen the question. A “diff” is what you see when you run git show COMMIT_ID — the patch of changes. A “snapshot” is the complete state of all files you get after running git checkout COMMIT_ID. Git internally refers to this file listing as the “tree.”
What Git Actually Stores
Internally, Git represents commits as snapshots. A commit object stores metadata alongside a pointer to a tree object, which lists every file and subdirectory in the repository at that point in time. Because the full state is saved, checking out any commit — yesterday's or the millionth ancestor — is equally fast. Git never needs to replay a chain of diffs to reconstruct a directory tree.
The Truth About Compression: Packfiles
That said, the intuition behind the “commits-as-diffs” crowd is not baseless. Git does compress snapshot data using packfiles. During garbage collection or a fresh git clone, Git reduces disk usage by storing objects as an “original file” reference plus a delta. These deltas are markedly different from human-readable diffs. They are essentially instructions that copy byte ranges from the original and splice in new text; there is no concept of a deletion, only copies and additions.
The original file chosen as a base isn't necessarily the previous commit. Git uses heuristic algorithms to pick an efficient base object, and the resulting layers of deltas are generally shallow. Packfiles are effectively a behind-the-scenes optimization — they make snapshots feasible without consuming excessive disk space.
Weirdness in the git show Pipeline
The process for displaying a commit reveals an interesting workflow:
- Git reconstructs the tree objects for the target commit and its parent by applying deltas from packfiles.
- Git then diffs the two trees. This is fast in most cases because unchanged files can be skipped by comparing their hashes.
- Finally, the calculated diff is rendered for you.
So a request starts with delta-like data, converts it into a full snapshot, and ends with a calculated diff. Despite appearing circuitous, it makes sense because packfile deltas and displayed diffs serve completely different purposes.
A Useful, “Wrong” Mental Model
Despite the internal reality, a common and surprisingly coherent mental model treats commits as follows:
- Commits are stored as diffs from their parent, along with metadata.
- To inspect an old commit, Git replays all prior commits from the beginning.
This model is factually incorrect, yet it is intuitive because presenting a commit as a diff matches how users interact with Git most frequently. It frames the working mental model of a repository around the change itself. Merge commits are the main wrinkle, but one can gloss over that by thinking of a merge as a diff from its first parent.
This philosophy extends to other “wrong” ideas that don't hinder daily use. You might think you can edit a commit message or move a commit to a new base. In reality, Git copies the commit, creates a new one, and leaves the original intact. For routine work, these misconceptions cause few problems — the trouble usually surfaces only during complex undo operations or error recovery.
When Diff-Thinking Makes Sense
Thinking of a commit as a diff is often the most practical approach. When you're altering a single line, your focus is on that one change, not the entire codebase. It's also how tools present commits: clicking on a commit on GitHub or running git show shows a patch. For developers who frequently use rebase, which revolves around replaying changes, the diff view is the natural way to reason about operations.
The Case for Thinking in Snapshots
Snapshot-centric thinking has its own wins. Git's handling of file moves can be misleading — when you move and edit a file, Git might flag it as a delete and an add because it only stores snapshots. The “renamed” status is just a guess based on content similarity. Thinking in snapshots also makes operations like git checkout COMMIT_ID seem less conceptually stressful; you're not replaying thousands of commits, just hopping to a new state. This model makes merge commits clearer, too: a merge isn't a calculated combination but a brand new state that can technically contain arbitrary change made during conflict resolution — which explains why careful review is critical.
Alternate Perspectives on Commits
Beyond the primary diff/snapshot dichotomy, developers attach other meanings to commits. Some treat them as bundles of out-of-band metadata, such as links to a pull request or context from a conversation with a coworker. Others frame a diff as a paired “before state plus after state.” For many, the definition shifts depending on the task at hand, and terminology can help disambiguate: “revision” leans transactional toward snapshots, while “patch” strongly implies a diff.
The takeaway is that there is no single right way to think about commits. Both principal models have significant utility, and their mix in practice is a matter of context. Recognizing the underlying internal structure of a commit as a snapshot, while appreciating the pragmatic value of the diff-based model, provides a robust toolkit for navigating Git's complexities.



