Undoing changes — restore, reset, and revert
"Undo" in Git isn't one operation — it's a different command depending on exactly what needs undoing and whether it's already been shared, and picking the wrong one is a real, common source of lost work.
4 min read
The question that decides which command to use
Every "undo" scenario in Git maps to one of these three tools, and the deciding factor is always the same: how far has this change traveled. Using the wrong one is the actual source of most real Git accidents — not the individual commands themselves, which are each safe when used for what they're meant for.
git restore: undoing uncommitted changes
git restore app.py # discard uncommitted changes to this file, back to last commit
git restore --staged app.py # unstage a file, keeping the actual edits in place
git restore (the modern replacement for older, more overloaded uses of git checkout) operates entirely before a commit exists — git restore given a filename throws away uncommitted edits to that file, reverting it to match the last commit; git restore --staged given a filename un-stages it without touching its actual content, moving it back from the staging area to the working directory. Neither of these touches commit history at all, since nothing has been committed yet — this is the safest category of undo, reversible right up until it's run (the discarded edits themselves are genuinely gone once restore runs without --staged, so it's worth a git diff check first).
git reset: rewriting local history that hasn't been shared
git reset --soft HEAD~1 # undo the last commit, keep changes staged
git reset --mixed HEAD~1 # undo the last commit, keep changes unstaged (the default mode)
git reset --hard HEAD~1 # undo the last commit, DISCARD the changes entirely
git reset moves the current branch's pointer backward (from the branching lesson's "a branch is just a pointer" model) — effectively un-committing one or more commits. The three modes differ in what happens to the changes those commits contained: --soft keeps them staged, ready to re-commit differently; --mixed (the default) keeps them in the working directory, unstaged; --hard discards them completely, with no recovery through ordinary means. reset is genuinely safe only on commits that haven't been pushed anywhere — rewriting history that only exists locally doesn't affect anyone else, since nobody else has that history yet.
Why reset on already-pushed commits is the actual danger
You: A -- B -- C (pushed to remote, then git reset --hard to A locally)
Teammate: still has A -- B -- C, and builds D on top of C
Your next push: rejected, or (with --force) silently erases B, C, and D from the remote
If a commit has already been pushed and someone else might have already pulled it (or built more commits on top of it), reset rewrites history that other people's local copies still expect to exist — pushing afterward requires --force, which can silently erase commits a teammate is actively relying on. This is exactly why reset is scoped to local, unshared commits: rewriting history is fine when only one copy of it exists; it becomes destructive the moment other copies have already diverged from it.
git revert: undoing a shared commit without rewriting history
git revert a3f8e21
main: A -- B -- C -- D(revert of B)
git revert, given a commit hash, doesn't remove the target commit or rewrite history at all — it creates a new commit that applies the exact opposite of the target commit's changes. This is the safe way to undo something that's already been pushed and shared: the original commit (B above) stays in history exactly as it was, and a new commit (D) sits on top, undoing its effect — everyone else's copy of history stays valid, since nothing already-shared was removed or rewritten, only added to.
The actual decision, stated plainly
| Scenario | Command to use |
|---|---|
| Uncommitted mistake | git restore |
| Committed, but only local | git reset |
| Committed AND already shared | git revert |
This is the entire decision, and it maps directly onto the "how far has this change traveled" question from the top of this lesson. Reaching for reset --hard on a commit that's already been pushed and pulled by someone else is the single most common way Git accidents actually happen — not because reset is inherently dangerous, but because it's being used on history that isn't safely local anymore.
Further reading
Check your understanding
A quick comprehension check — not tracked, not graded, just for you.
1. What single factor decides whether to use restore, reset, or revert to undo a change?
2. What's the key difference between git reset --soft and git reset --hard?
3. Why is git reset genuinely risky on a commit that's already been pushed and possibly pulled by a teammate?
4. How does git revert undo a shared commit without the danger that git reset has in that situation?