Skip to content

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 revert undoes a commit by making a new commit that reverses it — history keeps growing, nothing is rewritten. Safe on shared branches, including main.
  • git reset moves 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 restore works 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 (undo git 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 --hard on a commit that's already pushed. Fix: once a commit is shared, revert it instead — reset --hard on a shared commit forces everyone else into a broken, diverged state the next time they try to push or pull.
  • Panicking after git reset --hard and assuming the commits are gone forever. Fix: they usually aren't — git reflog records where your branch pointer has recently been, and git reset --hard <old-hash-from-reflog> (or git branch rescue <hash>) can bring them back, until Git eventually garbage-collects genuinely unreachable commits.
  • Confusing git restore (files) with git reset/git revert (commits). Fix: ask "am I undoing something in a commit, or something I haven't committed yet?" — uncommitted → restore; already committed → revert or reset depending on whether it's shared.
  • Running git reset --hard without checking git status first. Fix: --hard silently 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.mdreset is 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 main by mistake" recovery is a safe, guided use of exactly this page's tools: branch off the current commit first (preserving it), then reset --hard main back 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.