Basis Lab

How a Repository Remembers

A Hard Reset Deletes Nothing, and You Have About Thirty Days

Last timeRewriting What Happened

A reset moves a label and leaves every commit exactly where it was. Here is the local record that still names them, how to point a label back, and the real window before collection.

The label is the only thing that moved

The frightening version of this goes as follows. You are standing on the side

branch, three commits deep into it, and you run a hard reset back to where that

branch started. The history now shows two commits where it showed five. Nothing

in the output mentioned deleting anything, and nothing was deleted.

That is not reassurance, it is a consequence. A commit is stored under a name

computed from its own contents, and those contents include the name of its

directory listing and the name of its parent. So there is no operation that

edits a commit, because changing anything inside it would change the name it is

filed under, which would make it a different commit sitting beside the old one.

The same holds for every object in the store. Nothing in there can be modified,

only added.

What then can change? Only the labels. A branch is a small file holding one

object name, and moving a branch means writing a different name into that file.

Everything a reset does is on one side of that line.

The repository worked through here has three files, readme.md, settings.ini and

render.py. Its main line runs 6f74b13, 5c79a65, 2c74519, a2f7109, and a side

line of three commits leaves the second of those, running 042a329, 5c18714 and

f512f47 under the label tint. Those are the first seven characters of the real

names, and the arithmetic behind them is not arbitrary: the same contents, laid

out the same way, produce the same name on anyone's machine.

Reset that label back to 5c79a65 and two things happen. The file holding the

label gets a new name written into it, and the working copy and the staged list

are made to match the commit now named. The three commits 042a329, 5c18714 and

f512f47 are precisely where they were, under precisely the names they had.

FIG 1What a hard reset writes and what it leaves alone
Follow the two arrows out of the reset. One of them changes a label, the other appends to a log, and neither one goes anywhere near an object. That is why the fix is symmetrical with the mistake rather than being a rescue.

The claim is checkable without any recovery command. Ask the store directly what

type of object f512f47 is and it answers that it is a commit, because it is. The

question that feels like "where did my commits go" is really "what name do I

type", and the rest of this is about getting that name back and knowing how long

it stays available.

The log of where a label has been

The name is forty characters you never wrote down, so something has to have kept

it. Something did. Every update to a label is appended to a log of that label,

held inside the repository, recording the value before, the value after, who did

it, when, and which command. The default is to keep this log for every branch,

every remote-tracking name and for the record of where you are standing, in any

repository that has a working copy.

Two properties of that log matter and both get missed. It is strictly local: it

is never sent anywhere and never arrives from anywhere, so a fresh copy of a

repository has a log with one line in it no matter how long the original had

been worked in. And it is per label, so a commit appears in the log of whichever

label moved onto it. Commits made while standing on no branch at all are in the

log of where you were standing and in no branch's log, which is exactly the case

people assume is unrecoverable.

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 Stagedopening only
  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 Daysyou are here
  8. 08Why Your Push Was Rejected, and the One Push That Destroys Workopening only

Read alongside