ReadbackLesson 02In progress
2 steps · 20 minutes · one terminal

BRANCHESARE POINTERS

A branch is not the commits you added to it. It is one file, forty-one bytes long, containing a single hash — and almost everything confusing about branching follows from that.

The model you carry

Claim · no terminal needed

In lesson 01 you opened a commit and found a snapshot with a pointer to its parent. This lesson opens the other half: the names. Once you can read a branch, you can say exactly what an agent moved when it said “switched to a new branch and pushed”.

Almost everyone carries the same picture of a branch: a line that peels off main, containing the commits you added on it, and belonging to the branch it came from.

Here is the part everyone has backwards

A branch is not a series of commits. A branch is one small file containing one hash — the commit at its tip. That is the entire branch.

Two things follow, and they are the two that bite. First, a branch does not contain "the commits you added" — it contains everything reachable from its tip by walking parent pointers backwards, all the way to the root. Your feature branch contains the whole history of main too. Second, git has no concept of a parent branch. Nothing anywhere records that your branch "came from" main, which is why every merge and every rebase makes you name the other branch explicitly.

Julia Evans makes an important concession here that is worth keeping: the intuitive model is not useless. It matches how merges, rebases and pull requests behave — see git branches: intuition & reality. What it gets wrong is what a branch is, and that is the part that matters the moment something goes sideways.

What you can check after this step

Nothing yet. The next step hands you the file.

Open the branch

Claim + proof · cat .git/refs

A branch is stored as a file under .git/refs/heads/, named after the branch. Read it directly — no plumbing command required, it is just text on disk:

One branch, on disk, in fullStep 02
$ cat .git/refs/heads/main
$ wc -c < .git/refs/heads/main
d04677a77d21c4b17aa595207d5db1de547dffa8
41

Forty hexadecimal characters and a newline. Forty-one bytes. There is no more branch than this.

Creating a branch, then, is writing a 41-byte file. It is why git branch returns instantly on a repository of any size — and why creating branches costs you nothing. Make a new one and both files hold the same hash, because both names point at the same commit:

A new branch is a new file
$ git branch fix
$ ls .git/refs/heads/
$ cat .git/refs/heads/main
$ cat .git/refs/heads/fix
fix
main
d04677a77d21c4b17aa595207d5db1de547dffa8
d04677a77d21c4b17aa595207d5db1de547dffa8

Two names, one commit. Nothing was copied and nothing was branched — a name was written down.

Predict before you read on

You are on main, with fix pointing at the same commit. You make one commit. What happens to the two files on disk?

One file moved; the other did not
$ git commit -qm "Extend a"
$ cat .git/refs/heads/main
$ cat .git/refs/heads/fix
d674dc699481af999191ee7c9e40cbb84c67cafc
d04677a77d21c4b17aa595207d5db1de547dffa8

The branch you were on advanced. The other name stayed exactly where it was.

Run this on a repo an agent touched

git rev-parse main prints the same hash the file holds, without you needing to know where git put it. Compare it with git rev-parse on the branch the agent was working on — if they differ, the agent’s branch has moved somewhere main has not followed.

2 of 4 steps remain

HEAD is a pointer to a pointer

You have read the first 2 steps in full — a claim and the verification that settles it. The rest of this lesson, and the 9 lessons after it, open when subscriptions do. Nothing that is free today will be taken away then.

Read the free reference
Subscriptions open soon
the free preview stays