Code Safari

Chapter 110·Intermediate·9 min read

Undoing Anything in Git: Reset, Revert, and Reflog

A plain-English guide to undoing mistakes in Git — the difference between reset and revert, how to unstage or discard changes, why revert is safe for shared history, and how the reflog lets you recover work that seemed lost. The chapter that makes Git feel safe.

July 19, 2026

The deepest fear people carry into Git is that one wrong command will destroy their work. It's a reasonable fear and a mostly unfounded one. Git is built to be safe: it keeps snapshots, and it quietly records everywhere your branches have been. Once you know the handful of ways to undo things — and, crucially, how to recover when an undo goes too far — Git stops feeling like a minefield and starts feeling like a place you can experiment freely. This chapter is about that safety.

Two questions before you undo

When you want to undo something, two questions decide which tool you reach for:

  • Have you committed it yet? Undoing an uncommitted edit is a different, simpler operation than undoing a commit.
  • Has anyone else seen this commit? This is the same private-vs-shared boundary from the rebase chapter, and it decides whether you can safely rewrite history or should undo without touching it.

Hold those two questions in mind and the right tool almost picks itself.

Undoing before you commit

Not every undo involves history. Often you just want to walk back changes you haven't committed yet, and the staging area from earlier gives you two clean levels of undo.

  • Unstage a change — you staged something but don't want it in the next commit after all. You can pull it back out of the staging area; the edit stays in your working files, just no longer staged. Nothing is lost.
  • Discard an uncommitted edit — you want to throw away a change entirely and return a file to its last committed state. This does destroy the uncommitted edit (it was never saved to history), so it's the one undo here to run deliberately.

Revert: the safe undo for shared history

Now for undoing an actual commit. Suppose a commit is already on main and other people have pulled it, but it turned out to be wrong. You want to undo its effect — but you learned in the rebase chapter that rewriting shared history causes chaos. So how do you undo without rewriting?

Revert is the answer. Instead of removing the bad commit, revert creates a new commit that is its exact opposite — if the original added a line, the revert removes it, and vice versa. The bad change is neutralized, but nothing in history is deleted or rewritten. The original commit still sits there, followed by a new commit that cancels it out.

Bad commit is on main
Revert creates an inverse commit
Effect of the bad commit is undone
History preserved — safe for everyone
Revert cancels a commit by adding its inverse, leaving history intact

Because revert only adds a commit and never rewrites existing ones, it's completely safe on shared branches. Everyone else just pulls the new revert commit and their history stays consistent. When in doubt on shared history, revert.

Reset: rewinding the branch label

Reset is the more powerful — and sharper — tool. Remember that a branch is just a label pointing at a commit. Reset moves that label backward to an earlier commit, effectively rewinding your branch as if the later commits never happened. It comes in degrees: a gentler reset keeps your changes as uncommitted edits (handy for redoing a commit differently), while a harder reset also discards them.

Reset is perfect for private, unshared work: you made a few local commits, decided they were wrong, and want to rewind before anyone saw them. But because it rewinds the branch and abandons commits, using it on shared history has the same danger as rebase — you'd be rewriting commits others already have. So the rule mirrors what you already know: reset freely on private commits; prefer revert once commits are shared.

Reflog: the safety net under everything

Here's the feature that makes Git genuinely hard to lose work in, and almost nobody discovers until they need it: the reflog. Git keeps a private, local log of every place your branch tips have pointed — every commit, reset, rebase, merge, and checkout you've made. Even when a commit is no longer reachable from any branch — say a reset "threw it away" — it isn't actually gone. It's still in your repository, and the reflog remembers where it was.

That means the classic panic — "I reset too far and lost my commits!" — usually has a happy ending. You look in the reflog, find the commit you were on before the mistake, and point a branch back at it. The "lost" work reappears. Git didn't delete it; it just stopped referring to it, and the reflog kept the reference you needed.

A reset seems to discard commits
The commits still exist in the repo
Reflog remembers where they were
Point a branch back at them — recovered
How the reflog rescues 'lost' commits

Why safety makes you better

Once you internalize that Git is reversible — unstage and discard before committing, revert to safely undo shared commits, reset to rewind private ones, and the reflog to recover almost anything you feared was gone — a change comes over how you work. You stop tiptoeing. You try the risky refactor, make the experimental branch, commit the half-formed idea, because you know none of it is permanent unless you want it to be. That confidence is the real gift of understanding undo. With it, we reach the final chapter, where all of this — commits, branches, remotes, clean history — comes together in the workflow that turns individual work into a team's shared, reviewed product: pull requests and code review.

Undoing Anything in Git: Reset, Revert, and Reflog | Code Safari