This is the chapter people wish came first, because it removes the fear. Here's the reassuring truth, and it falls straight out of Chapter 1's model:
Git almost never deletes anything. Commits are immutable snapshots, and even when you "undo" one, the snapshot usually still exists — you've just moved a label off it. There's a recovery for nearly every mistake.
Every "undo" in git is one of two moves: move a label (fast, but rewrites local history) or add a new commit that reverses an old one (safe for history others have). Keep that split in mind and the whole toolkit below makes sense.
The Playbook: Find Your Mistake, Get the Fix
Most of the time you just need the right command for the situation you're in. Here's the lookup table; the sections after explain the important ones.
| "Oh no, I…" | Fix |
|---|---|
| …staged a file I didn't mean to | git restore --staged file |
| …want to throw away my edits to a file | git restore file (discards uncommitted work) |
| …made a typo in my last commit message (not pushed) | git commit --amend |
| …forgot to include a file in the last commit | git add file && git commit --amend --no-edit |
| …want to undo the last commit but keep the changes | git reset --soft HEAD~1 |
| …want to undo the last commit and unstage, keep file edits | git reset HEAD~1 |
| …want the last commit and its changes gone | git reset --hard HEAD~1 (dangerous) |
| …need to undo a commit that's already pushed | git revert <sha> |
| …committed to the wrong branch | cherry-pick to the right one, then reset the wrong one |
| …need just one commit from another branch | git cherry-pick <sha> |
| …must drop everything and switch tasks right now | git stash … later git stash pop |
| …think I lost a commit (bad reset/rebase) | git reflog → find the SHA → recover |
restore: Working Directory and Staging
git restore is the modern, purpose-built undo for uncommitted changes (it split off from the overloaded git checkout):
git restore --staged file— unstage a file (move it from the staging area back to "changed but not staged"). Your edits are untouched; you've just changed your mind about what's in the next commit.git restore file— discard your edits and restore the file to its last committed state. This throws away uncommitted work permanently — there's no commit to recover it from. The one genuinely unrecoverable command in this chapter, so pause before running it.
reset: Moving the Branch Label (the Three Modes)
git reset HEAD~1 means "move the current branch label back one commit." HEAD~1 is shorthand for "one commit before HEAD." Because branches are just labels (Ch 1), this un-commits by sliding the label backward. The three modes differ only in which of the three trees they also touch:
| Mode | Moves branch label? | Resets staging? | Resets working dir? | Net effect |
|---|---|---|---|---|
--soft | ✅ | ❌ | ❌ | Changes stay staged, ready to re-commit |
--mixed (default) | ✅ | ✅ | ❌ | Changes stay in working dir, unstaged |
--hard | ✅ | ✅ | ✅ | Changes gone from everywhere |
Figure 1 — The three resets, mapped to the three trees from Chapter 1. --soft touches only the label; --mixed also clears staging; --hard also overwrites your files. The further right, the more it undoes — and --hard is the only one that can lose work.
revert: The Safe Undo for Pushed Commits
git revert <sha> does not move any label or rewrite history. Instead it creates a brand-new commit that is the exact inverse of the target commit — if the old commit added a line, the revert commit removes it. The bad change is undone, but the history stays intact and forward-only.
Figure 2 — revert adds an inverse commit R on top rather than deleting C. History only ever moves forward, so it's safe even on main that everyone has pulled. This is the right tool when a bad change already shipped.
The rule of thumb:
resetto undo local commits nobody has seen;revertto undo pushed commits other people already have.
cherry-pick: Grab One Commit
git cherry-pick <sha> copies a single commit onto your current branch (as a new commit with a new SHA). Two classic uses:
- "I committed to the wrong branch." Switch to the right branch,
git cherry-pick <sha>the commit over, then go back to the wrong branch andgit reset --hard HEAD~1to remove it from there. - "I need just this one fix from another branch" without merging the whole thing.
stash: Park Work Without Committing
Sometimes you're mid-edit and have to switch tasks now — but your changes aren't ready to commit. git stash sweeps all your uncommitted changes into a safe holding area and gives you a clean working directory:
git stash # set aside all uncommitted changes
git switch main # do the urgent thing on a clean tree
# ...later...
git switch my-branch
git stash pop # bring your changes back and remove them from the stashThink of it as a clipboard for "work in progress I'm not ready to snapshot." git stash list shows what you've parked; you can stash multiple times.
reflog: The Time Machine That Saves You
Here's the safety net almost nobody knows about, and it's why --hard and a botched rebase are rarely fatal. Git logs every move of HEAD — every commit, checkout, reset, rebase, merge — in the reflog. Even when a commit is no longer reachable from any branch (you "lost" it), the reflog still remembers where you were, and the snapshot still exists for weeks before git garbage-collects it.
git reflog
# 8367b9b HEAD@{0}: reset: moving to HEAD~1
# a1b2c3d HEAD@{1}: commit: the work I thought I destroyed
# ...
git reset --hard a1b2c3d # or: git checkout a1b2c3d — you're backSo the recovery for "I ran reset --hard and lost a commit" or "my rebase went wrong" is: git reflog, find the SHA from before the mistake, and reset/checkout to it. The label moves back onto the snapshot that was never actually deleted.
Bonus: Stop Tracking a File You Already Committed
Committed node_modules/ or a .env by accident? Add it to .gitignore, then untrack it without deleting it from disk:
echo ".env" >> .gitignore
git rm --cached .env # stop tracking; keep the local file
git commit -m "Stop tracking .env"(If the file held a secret that was ever pushed, also rotate the secret — it's in history forever. .gitignore only prevents future tracking.)
Mental Model — Three Sentences
- Almost every "undo" is either moving a branch label (
reset— fast, rewrites local history) or adding an inverse commit (revert— safe for pushed history); pick based on whether others have the commit. reset's three modes map to the three trees:--softmoves the label only,--mixedalso unstages,--hardalso wipes your files — only--hardcan lose work.reflogrecords every move of HEAD, so committed work survives even a hard reset or a broken rebase — the only truly unrecoverable loss is uncommitted changes you discarded.
Try It Yourself (15 Minutes)
In a throwaway repo (so you can be reckless):
- Make a commit. Run
git reset --soft HEAD~1andgit status— the changes are staged again. Re-commit. - Make a commit.
git reset --hard HEAD~1— gone. Nowgit reflog, find the commit,git reset --hard <sha>— back. Feel the safety net work. - Make and push a commit to a test branch, then
git revert HEAD— see a new inverse commit instead of a rewrite. - Start editing, then
git stash, confirm a clean tree, thengit stash popto get your edits back. git cherry-picka commit from one branch onto another and watch it arrive with a new SHA.
Where This Lands in the Series — and What's Next
That covers the core mechanics of the Git & GitHub Pro series:
- How git actually works — the snapshot/DAG/label model.
- Merge, rebase, or squash — reshaping history.
- GitHub like a pro — issues, branch protection, releases, the
ghCLI. - Undo anything — the recovery toolkit (this chapter).
You now understand not just the commands you've been typing, but the model underneath them — which means you can reason about any git situation you hit, including the ones this series didn't name. Commit early, commit often, and nothing can really hurt you.
One practical chapter remains. You've used GitHub on the free plan this whole time — so Chapter 5 answers the money question: what do Pro, Team, Enterprise, and Copilot actually unlock, what are those "usage-based" charges (Actions, Codespaces, and friends), and what's genuinely worth paying for versus what you already get for free?
Ship your apps faster
When you're ready to publish your Swift app to the App Store, Simple App Shipper handles metadata, screenshots, TestFlight, and submissions — all in one place.
Try Simple App Shipper