Lesson 03 · solo safety

The Undo Playbook

Every "I made a mistake" fix, sorted by which tree or pointer it touches. This is the lesson that kills the panic.

This is the payoff of the first mission goal: never lose work, undo anything without googling. It works because you already have the map. Every command below is just moving a pointer or overwriting one of the three trees — nothing new, only recombination. The trick is matching the mistake to the tree it lives in.

The one habit that makes all of this safe Before any undo you're unsure about, run git status and git log --oneline --graph --all. Know where you are before you move. Every fix below is reversible except throwing away uncommitted working-directory changes — so that's the only category to slow down for.

The playbook, by mistake

"I staged a file I didn't mean to."

Change lives in the staging area. Move it back to working dir — edits on disk untouched.

git restore --staged <file>        # unstage, keep the edit

"I want to throw away my edits to a file and go back to the last commit."

Change lives in the working directory. This one destroys the edit — there's no undo. Be sure.

git restore <file>                  # discard working-dir edits — irreversible

"My last commit message has a typo / I forgot to add a file."

The commit is fine, you just want to redo it. --amend replaces the top commit with a new one.

git commit --amend -m "better message"
# or: stage the forgotten file first, then:
git add forgotten.js
git commit --amend --no-edit       # keep the old message

"I committed too early — I want the commit undone but my work kept."

Move the branch pointer back one, leave the trees. That's reset — covered in full next.

git reset --soft HEAD~1            # uncommit, changes stay STAGED
git reset HEAD~1                   # uncommit, changes go to working dir (unstaged)

"I already pushed / shared the commit and want to reverse it."

Don't rewrite shared history. Add a new commit that undoes the old one. Safe on a team.

git revert <sha>                    # new commit that inverts <sha>; history preserved
reset vs. revert — the team rule reset rewrites history (moves the pointer, old commits fall off). Great before you push. revert adds a new inverse commit, leaving history intact — the right tool after a commit is shared. Rewriting history someone else has already pulled is how you create the merge nightmares your teammates blame Git for.

reset, demystified

reset is the command people fear, because --soft, --mixed, --hard look arbitrary. They're not. Pro Git frames reset as walking down three steps, stopping where you tell it:

1 Move the branch HEAD points to --soft stops here
2 Make the index (staging) look like HEAD --mixed (default) stops here
3 Make the working directory look like the index --hard goes all the way
Pro Git, 7.7 — Reset Demystified reset "overwrites these three trees in a specific order, stopping when you tell it to: (1) Move the branch HEAD points to — stop here if --soft. (2) Make the index look like HEAD — stop here unless --hard. (3) Make the working directory look like the index."
Reset Demystified →
CommandBranch ptrStagingWorking dirUse when
reset --softmovesUncommit; keep everything staged, ready to recommit.
reset (--mixed)movesresetUncommit and unstage; edits stay on disk. The default.
reset --hardmovesresetresetNuke it all back to a commit. Destroys uncommitted work.
⚠ The only dangerous one Pro Git: --hard is "the only way to make the reset command dangerous, and one of the very few cases where Git will actually destroy data." --soft and --mixed never touch your working directory — your edits are safe. Say the flag out loud before you hit enter.

The safety net: reflog

Here's the fact that should let you sleep. Git almost never truly deletes a commit. When you reset "away" a commit, the commit object still exists — you've only moved the branch label off it. The reflog is a log of everywhere HEAD has been, so you can find that orphaned commit and jump back.

git reflog                          # every position HEAD has held, newest first
# find the line before your mistake, e.g. "d4e5f6 HEAD@{2}: commit: good work"
git reset --hard d4e5f6             # or: git switch -c rescue d4e5f6
Why this ends the panic A bad reset --hard feels like disaster. But the commit you reset off is still in reflog for weeks (until garbage collection). "I lost a commit" is almost always "I moved a pointer and can move it back." The one thing reflog can not save: uncommitted edits that were never a commit. Which is the whole argument for committing early and often.

The same thing in VS Code

CLIVS Code
git restore --staged <f> on the staged row (Unstage Changes)
git restore <f>right-click a changed file ▸ Discard Changes
git commit --amendSource Control ▸ ⋯ ▸ Commit ▸ Commit (Amend)
git revert <sha>right-click a commit in history ▸ Revert Commit
reset / reflogno safe GUI button — use the terminal for these

Do this now (5 minutes, in a scratch repo)

git init undo-drill && cd undo-drill
echo one > f.txt && git add f.txt && git commit -m "c1"
echo two >> f.txt && git commit -am "c2 — the mistake"

git log --oneline                   # two commits
git reset --soft HEAD~1             # undo c2
git status                          # "two" change is STAGED again — soft kept it

git commit -m "c2 redone"          # recommit cleanly
git reset --hard HEAD~1            # nuke back to c1 — the change is gone from disk
cat f.txt                          # just "one"

git reflog                         # see c2 still listed — pick its SHA...
git reset --hard <sha-of-c2>       # ...and it's back. Nothing was ever truly lost.

The whole lesson is in those last two lines: you destroyed a commit with --hard, then walked it back out of reflog. Do that once and reset stops being scary forever.

Your win You have a mistake-to-command lookup table, you know the one dangerous flag by name, and you know the reflog safety net exists. That's the solo-safety half of the mission essentially handled. Next we turn to the team half — branching and merging for real.

Go deeper

Primary source — read this one: Pro Git §7.7, "Reset Demystified". The best explanation of reset that exists, with tree-by-tree diagrams. Worth a slow read — it's the command most worth truly understanding.

Also skim §2.4, "Undoing Things" for --amend and restore in the book's own words.