Undoing Safely: Revert, Reset & Restore¶
Mistakes are routine in Git — a bad commit, a file staged by accident, a change you want gone. What matters is picking the command that undoes the right thing without silently discarding work someone else already has, or work you didn't mean to touch.
TL;DR — in 30 seconds:
git revertundoes a commit by making a new commit that reverses it — history keeps growing, nothing is rewritten. Safe on shared branches, includingmain.git resetmoves your current branch's pointer backward (optionally also touching staged/working files) — it rewrites what your branch points at. Safe only on branches nobody else has.git restoreworks on files, not commits — it discards uncommitted changes or unstages a file, without touching history at all.
1. git revert — undo with a new commit¶
git revert <commit> creates a brand-new commit whose changes are the exact opposite of the target
commit. The bad commit stays in history exactly as it was — it's just followed by a commit that cancels its
effect.
flowchart LR
C1["c1"] --> C2["c2 (bad change)"] --> C3["c3"] --> C4["c4 = revert of c2<br/>(new commit)"]
Because it only adds a commit and never removes or rewrites one, revert is the correct choice for
undoing something that's already shared — pushed, merged, or on main — since nobody else's history
gets rewritten out from under them.
2. git reset — move the branch pointer¶
git reset <commit> moves your current branch's pointer to point at a different (usually earlier)
commit. Depending on the flag, it also decides what happens to the changes in between:
| Mode | Branch pointer | Staged changes | Working directory files |
|---|---|---|---|
--soft |
moves | changes from the undone commits become staged | untouched |
--mixed (default) |
moves | changes become unstaged | untouched |
--hard |
moves | discarded | discarded — matches the target commit exactly |
flowchart LR
Before["main: c1 → c2 → c3"] -->|"git reset --hard c1"| After["main: c1<br/>(c2, c3 no longer pointed at)"]
reset rewrites what your branch points at — the commits you reset past aren't instantly deleted (they
survive briefly, recoverable via git reflog, until garbage-collected), but your branch no longer leads to
them. That's why reset is unsafe on a branch anyone else has already pulled: their copy still expects
those commits to exist there.
3. git restore — undo file changes, not commits¶
git restore is scoped to files in your working directory or staging area — it never touches commit
history at all.
git restore <file>— discard uncommitted changes to a file, reverting it to match the last commit.git restore --staged <file>— unstage a file (undogit add) without touching its actual content or discarding the edit — the change stays in your working directory, just no longer staged for the next commit.
4. Which one, when¶
| Situation | Command |
|---|---|
Undo a commit that's already pushed/shared/on main |
git revert <commit> |
| Throw away your own, unpushed recent commits entirely | git reset --hard <commit> |
| Uncommit recent work but keep the changes to redo/re-split | git reset --soft <commit> |
Accidentally git added a file you're not ready to commit |
git restore --staged <file> |
| Discard uncommitted edits to a file you don't want | git restore <file> |
The one question that decides between revert and reset --hard: has anyone else already seen this
commit? If yes, revert (adds a correction, doesn't rewrite). If no — it's still only on your machine —
reset is fine.
Common gotchas
- Using
git reset --hardon a commit that's already pushed. Fix: once a commit is shared,revertit instead —reset --hardon a shared commit forces everyone else into a broken, diverged state the next time they try to push or pull. - Panicking after
git reset --hardand assuming the commits are gone forever. Fix: they usually aren't —git reflogrecords where your branch pointer has recently been, andgit reset --hard <old-hash-from-reflog>(orgit branch rescue <hash>) can bring them back, until Git eventually garbage-collects genuinely unreachable commits. - Confusing
git restore(files) withgit reset/git revert(commits). Fix: ask "am I undoing something in a commit, or something I haven't committed yet?" — uncommitted →restore; already committed →revertorresetdepending on whether it's shared. - Running
git reset --hardwithout checkinggit statusfirst. Fix:--hardsilently discards all uncommitted changes, not just the commits — always check what you'd lose before running it.
Check yourself — a bad commit was merged into main an hour ago and two teammates have already pulled it. How do you undo it, and why not the other option?
git revert <commit> — it adds a new commit that cancels the bad one, so both teammates' next pull
just picks up the correction cleanly. git reset --hard would rewrite what main points at, which
would put their local main out of sync with the remote in a way pull can't cleanly resolve.
Check yourself — you ran git add on a file by mistake and aren't ready to commit it. Which command, and does it delete your edit?
git restore --staged <file> — it unstages the file (undoes the add) but leaves the actual edit
intact in your working directory; you can keep editing or restage it later. Nothing is deleted.
5. How this connects¶
- This course's page 1,
01-the-git-model.md—resetis a direct application of "a branch is just a pointer": it works by moving that pointer, which is also why the commits it moves past aren't immediately deleted, just unreferenced. - This course's page 3,
03-remotes-and-the-github-flow.md— "has anyone else already seen this commit?" is the same shared-vs-private distinction that decides merge vs. rebase; once a change is pushed and part of a PR, treat it as shared. - Module 00 (Setup & ways of working) — its 3-step "committed on
mainby mistake" recovery is a safe, guided use of exactly this page's tools: branch off the current commit first (preserving it), thenreset --hardmainback to the remote — safe specifically because the commit survives on the new branch before anything moves.
You can defend this when you can pick the right undo command from the "which one, when" table without
looking it up, and explain in one sentence why git reset --hard is dangerous on a shared branch but
git revert isn't.