Every Commit Is The Whole Project

Almost everyone assumes a commit stores the lines you changed. It stores the entire project, and once that lands, half of git's weirder commands stop being weird.

July 25, 20265 min read3 / 4

The last post ended with git described as a graph of commits pointing at parents. That leaves an obvious question unanswered.

What is actually inside one of those nodes?

I had a confident answer to this for years, and it was wrong.

The Thing Almost Everyone Gets Wrong

Ask most developers what a commit contains and you will get some version of: the changes. The diff. The lines added and removed since last time.

It is a reasonable guess. Every tool that shows you a commit shows you a diff, so the diff feels like the content.

A commit is a complete snapshot of the entire project at a point in time.

Not the delta. Not the lines that moved. The whole thing, every file, in full.

Two rows comparing models of a commit. The top row shows the common assumption, a chain where each commit holds only the lines changed. The bottom row shows what git actually stores, three commits each containing the complete project, with arrows pointing backwards at parents. ExpandTwo rows comparing models of a commit. The top row shows the common assumption, a chain where each commit holds only the lines changed. The bottom row shows what git actually stores, three commits each containing the complete project, with arrows pointing backwards at parents.

Why That Sounds Wasteful and Is Not

The first reaction is usually that this cannot possibly work. Storing the entire codebase for every commit, thousands of commits deep, sounds absurd.

The reason it works out fine is that any single commit can regenerate the project on its own. You do not walk the chain. You do not replay history from the beginning. You take that one commit and you have the project as it stood.

Compare that with the diff model. To reconstruct the state at commit 400, you would need commit 1 plus 399 patches applied in exactly the right order, and every one of them would have to be intact.

Snapshots turn an O(n) replay into a direct lookup. That is not a small difference when you are checking out a branch in a repository with a decade of history.

The diffs you see are computed on demand by comparing two snapshots. The diff is a view, not the storage.

Those 40 Characters Are Not Random

Every commit is named by a 40 character string of hexadecimal, so characters a through f and digits 0 through 9:

Bash
9c4fd2a8b1e73f05a6d92c1748be3f0a5d817e2b

You will see these constantly, usually shortened to the first seven characters because that is enough to be unique in practice.

The important thing is where that name comes from. It is derived from the commit's own contents: the state of the project, the author, the time, and the parent it points at.

That has a consequence worth pausing on. Change any of those inputs, even slightly, and you get a completely different name. A commit's identity is inseparable from what is in it.

This is why commands that rewrite history produce new identifiers rather than editing old ones in place. They are not modifying commits. They cannot. They are creating new commits that happen to contain similar work.

Every confusing thing about rebase traces back to that sentence.

More Than One Parent, Never a Cycle

The pointers between commits run backwards, from child to parent. A commit knows where it came from. It has no idea what came after it.

A commit can have two parents. That is precisely what a merge is: a single commit whose parents are the two lines of work being joined.

What a commit can never do is participate in a cycle. You cannot arrange commits so that following parents long enough brings you back to where you started, and it is hard to even describe what that would mean. The structure only points backwards, into the past, which is why it is always safe to walk.

Why This Is the Whole Point

I want to be direct about why this one fact deserves its own post.

Nearly everything that makes git feel unpredictable comes from operating on a model that does not match what git is doing.

If you think commits are diffs, then rebase is magic. Cherry-pick is magic. Revert is a mystery, and the difference between reset and revert is unlearnable trivia.

If you think of commits as snapshots in a graph, all of those become obvious operations. Cherry-pick takes the difference between one snapshot and its parent and applies it somewhere else, producing a new commit with a new name. Revert produces a new snapshot that undoes an earlier one. Rebase rebuilds a series of snapshots on a different base, which is why every rebased commit comes out with a different identifier.

None of that requires memorisation once the structure is in your head. You can reason your way to it.

That is the actual promise of learning git from the data structure up, and it is why the next several posts spend time in the plumbing rather than collecting command flags.

You can poke at real commit objects in the git playground, where the object store is genuine and the hashes are the ones git itself would produce.

What This Sets Up

There is one piece of setup left before making a repository. Git needs to know who you are, because your name and email get stamped into every commit you create, and those values feed directly into the identifier we just talked about.

Next: configuring git before you use it, and why the config is layered rather than global.

The Essentials

  1. A commit is a complete snapshot of the project, not a set of changes.
  2. Any single commit can rebuild the project on its own, with no replay of history required.
  3. The diffs you see are computed by comparing snapshots, not read out of storage.
  4. The 40 character identifier is derived from the commit's contents, author, time, and parent.
  5. Change any input and you get a different commit, which is why rewriting history always produces new identifiers.
  6. A commit can have multiple parents but never forms a cycle.
  7. Understanding this structure is what makes rebase, revert, and cherry-pick reasonable instead of memorised.

Further Reading and Watching