git reset vs revert vs checkout: undo without fear
Git reset vs revert: reset moves the branch and can destroy work, revert adds a commit and destroys nothing. Each measured on the same repository.
Three commands undo things in git, and they operate on different parts of the repository:
| Command | Moves the branch? | Changes history? | Can lose work? |
|---|---|---|---|
git revert |
No, adds a commit | No | No |
git reset |
Yes | Yes | With --hard, yes |
git restore / checkout |
No | No | Yes, uncommitted work |
The one rule worth memorising: revert is the only one that is safe on history you have pushed. Everything below was measured on real repositories with git 2.50.0.
The three trees
These commands make sense once you know there are three places a version of your files can live:
- HEAD is the last commit, the committed state.
- The index (staging area) is what will go into the next commit.
- The working directory is the files on disk.
git reset differs only in how many of those three it moves back. That is the entire distinction between its three flags, and it is why the flags feel arbitrary until you know what they touch.
reset --soft, --mixed and --hard, measured
The same repository, three commits deep, reset back one commit three different ways.
git reset --soft HEAD~1
commits: 2
status: M a.txt (staged)
file: one two three
The branch moved back one commit. The change from that commit is now staged, ready to commit again. Nothing was lost.
git reset --mixed HEAD~1 (the default, when you write no flag)
commits: 2
status: M a.txt (not staged)
file: one two three
Branch moved back, change is now unstaged but still in your working directory. Nothing lost.
git reset --hard HEAD~1
commits: 2
status: (clean)
file: one two
Branch moved back, and the file content is gone. three is no longer on disk. This is the only one of the three that destroys anything.
Read the file contents row again. --soft and --mixed both kept three; --hard did not. The commits count is identical in all three cases, which is why people who only check git log think the flags do the same thing.
Which reset you actually want
--soft when you want to redo the commit: wrong message, forgot a file, split it in two. Everything is staged and ready.
--mixed when you want to restage selectively. This is the default, so git reset HEAD~1 gets you here.
--hard when you genuinely want the work gone. Before running it, be sure the work is either committed elsewhere or something you are happy to lose, because this is the command that shows up in "git deleted my work" stories.
There is one safety net. --hard moves the branch, and the reflog remembers where it was:
git reflog
git reset --hard HEAD@{1}
That recovers a --hard you regret, as long as the work was committed at some point. Uncommitted changes destroyed by --hard are genuinely unrecoverable, because git never had a copy.
revert adds a commit instead of removing one
git revert HEAD
Measured on a two-commit repository:
3 commits:
af944d4 Revert "second"
c5e8929 second
955e9cf first
file contents: one
The file is back to its earlier state, and the history got longer. The original commit is still there, and a new commit sits on top undoing its changes.
That is the point. Nothing was rewritten, so anyone who already has these commits is unaffected. Their history and yours still agree; yours just has one more commit on the end.
This is why revert is the correct tool for anything already pushed. Resetting a pushed branch changes commits other people have, and fixing that afterwards requires a force push and a conversation.
restore and checkout: the file-level undo
git checkout used to do two unrelated jobs, which is why it confused everyone. Modern git split them:
git switch main # move between branches
git restore file.txt # throw away changes to a file
git checkout main and git checkout -- file.txt still work and still do both jobs. The new names exist because one of those is navigation and the other is destruction, and they should never have shared a command.
git restore file.txt discards uncommitted changes to that file. Like --hard, it cannot be undone, for the same reason: git has no record of work it never saw.
Choosing, in one question
Has this been pushed?
Yes → use revert. It is the only option that does not rewrite shared history.
No → reset is available and usually better, because it leaves a clean history rather than a commit that says "Revert". Pick the flag by how much you want moved back.
For undoing just the most recent commit, which is by far the most common case, undoing your last commit walks through each scenario.
Situations, and what to run
Most of the time you arrive here with a specific problem rather than a general question:
| Situation | Command |
|---|---|
| Wrong commit message, not pushed | git commit --amend |
| Forgot a file in the last commit | git add file then git commit --amend |
| Want to uncommit but keep the work | git reset --soft HEAD~1 |
| Committed to the wrong branch | cherry-pick it across, then reset here |
| Bad commit already pushed | git revert <hash> |
| Want to throw away today entirely | git reset --hard origin/main |
| Discard changes to one file | git restore file.txt |
| Undo a reset you regret | git reflog then git reset --hard HEAD@{1} |
Two entries in that table are the ones people miss.
git reset --hard origin/main resets your branch to exactly what the remote has, discarding every local commit and change. It is the "start again from a known-good state" command, and it is destructive in both senses: local commits go, and uncommitted work goes.
git commit --amend is technically also a rewrite, since it replaces the previous commit with a new one that has a different hash. The same pushed-history rule applies to it, which is why amending something already pushed requires a force push.
What rewriting does to other people
The reason revert exists is worth stating concretely rather than as a warning.
When you reset a branch you have pushed and force-push the result, the commits other people have are no longer in the branch's history. Their next pull sees a history that diverges from theirs, and git cannot reconcile the two automatically, because from its point of view both are valid.
What follows is a colleague with a branch that appears to be ahead, a merge that reintroduces the commits you removed, or a force-push race that loses somebody's work. None of it is subtle, and all of it takes longer to fix than the original problem.
revert sidesteps the whole category. It only ever adds commits, so everyone's history stays a prefix of everyone else's, and a normal pull brings the undo across like any other change.
The practical line: once a commit is on a shared branch, it is part of the record. Undo it by adding to the record, not by editing it.
The confusing cases
Reverting a merge commit needs -m 1 to say which parent is the mainline, because a merge has two and git cannot guess which side to keep. If you get error: commit is a merge but no -m option was given, that is this.
Reverting a revert is legal and does exactly what it sounds like: it reapplies the original change as a third commit. There is nothing special about a revert commit, it is a normal commit that happens to undo something.
git reset with no flag and no commit unstages files rather than moving the branch, which is why git reset file.txt appears in undoing git add. Same command, different job depending on whether you name a path.
Quick reference
git revert <commit> # safe undo, adds a commit
git revert -m 1 <merge> # undo a merge commit
git reset --soft HEAD~1 # uncommit, keep changes staged
git reset HEAD~1 # uncommit, keep changes unstaged
git reset --hard HEAD~1 # uncommit and DISCARD changes
git restore <file> # discard changes to one file
git switch <branch> # move between branches
git reflog # find where the branch used to be
Want to try all three where nothing is at stake? Work through Git basics in a real repository in the browser.
More from the blog
How to resolve merge conflicts in Git
A Git merge conflict happens when two branches change the same lines. How to read the markers, resolve each file, and the check that stops a bad commit.
Read moreGit vs GitHub: they are not the same thing
Git vs GitHub: Git is the version control tool on your computer, GitHub is a website that hosts Git repositories. What each one does and how they fit.
Read moreReady to write some code?
Put this into practice - start your first free lesson. No setup, no credit card.