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.
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 contentsThis 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