Basis Lab

How a Repository Remembers

Why the Same File Is Reported as Both Staged and Not Staged

Last timeThe Graph of Commits

One path can hold three different versions at once, and the two letters in a status line are two independent comparisons between them rather than one verdict on the file.

Three places, not two

A tracked file has a version on disk and a version in the last commit, and

almost every confusing message comes from there being a third version in

between. The repository used here has three files and four commits on its main

line. Read the middle layer out directly.

FIG 1The staged list and the last commit, side by side, for the same three paths
bash
$ git ls-files --stage
100644 33769db0109a55e7ca266e647c5b6ae6b7df93ad 0    readme.md
100644 5b6cf77b6348d72977eab587467cbf306a1bc31c 0    render.py
100644 04f61ebb94a49f4b9e30a0d840b49c0a9b655e90 0    settings.ini
$ git cat-file -p 'HEAD^{tree}'
100644 blob 33769db0109a55e7ca266e647c5b6ae6b7df93ad    readme.md
100644 blob 5b6cf77b6348d72977eab587467cbf306a1bc31c    render.py
100644 blob 04f61ebb94a49f4b9e30a0d840b49c0a9b655e90    settings.ini
$ wc -c < .git/index
289
$ cat readme.md render.py settings.ini | wc -c
143
The first command prints the staged list and the second prints the tree of the last commit. Right now the object names match line for line, which is what having nothing to report means. Note the sizes: the staged list is 289 bytes for a working copy of 143 bytes of actual file content, because it holds names and not contents.

Each line of the staged list is four things: a mode, a forty character object

name, a stage number which is 0 unless a merge is unresolved, and the path. That

is the entire structure. There is no copy of any file in it, no record of what

changed, and no partial commit waiting to be finished.

Two consequences follow immediately, and both are usually learned the hard way

instead. Staging is fast because it writes one object and rewrites one short

line, no matter how large the file is. And what you staged is not disturbed by

editing the file afterwards, because the staged line holds the name of the bytes

as they were when you staged them, and those bytes are in the store under that

name whatever the file on disk says now.

So the three places are the working copy, which is the files themselves, the

staged list, which is one line per path naming an object, and the last commit,

which is a tree in the store naming an object per path. Every state a path can

be in is some arrangement of three object names, and every command is a move

between two of the three.

FIG 2The three places and the moves between them
Follow the arrows in order and notice where the risk is. Three of the four moves either write new objects or rewrite one line of one file. The fourth overwrites a file on disk from an object name, and unstaged edits are the only thing in the picture that no object name can bring back.

Adding writes the object there and then

The claim that staging writes an object is worth checking rather than believing,

because the whole behaviour of the staged list depends on it. The committed

version of the settings file is the object 04f61eb. Change the width in it and

ask for the name of the new content without storing anything.

The lesson stops here

7 more paragraphs to go

You have read the opening. The rest of the argument, the problems that check whether it landed, and the lines worth keeping at the end all come with a plan.

The first lesson of every course in the library reads the whole way through, free, so you can see exactly what the rest of them are.

See the planThe contents

This is the reading half

Starting the course gives you your own copy of it. Every idea on every page has problems standing under it, marked with a reason rather than a tick, and any sentence you do not believe can be opened and argued with. None of that can happen on a page nobody owns.

The contents

The rest of this course

  1. 01What a Git Hash Is Actually a Hash Of
  2. 02Why a Commit Is a Whole Snapshot and Still Costs Almost Nothingopening only
  3. 03Every Commit Names Its Parent, and a Branch Is One Line in a Fileopening only
  4. 04Why the Same File Is Reported as Both Staged and Not Stagedyou are here
  5. 05Why a Merge Only Stops to Ask About Some of Your Changesopening only
  6. 06Why Your Commits Get New Names When You Rebaseopening only
  7. 07A Hard Reset Deletes Nothing, and You Have About Thirty Daysopening only
  8. 08Why Your Push Was Rejected, and the One Push That Destroys Workopening only

Read alongside