All posts
·12 min read· undo· git· reset· revert· beginners

How to undo a Git commit (reset vs revert, and when to use each)

How to undo a Git commit safely. Choose between git reset and git revert, keep or discard your changes, and learn the one rule that decides which to use once you have pushed.

You just ran git commit. A second later you see it. The wrong files are in there. Or the message is wrong. Or you committed to main when you meant to be on a branch. Or the whole change was a bad idea and you want it gone.

Your first instinct is to search "how to undo a git commit" and paste whatever the top answer says. Don't do that yet. The top answer is often git reset --hard, and that command can throw away work you can't get back.

There is no single "undo" in Git. There are a few different undos, and they do different things. Picking the wrong one is how people turn a small mistake into a real one. The good news: choosing the right one is easy once you ask yourself two questions.

  1. Do I want to keep the changes, or throw them away?
  2. Have I pushed this commit yet?

That is the whole decision. This post walks through both questions, shows the exact commands, and explains the single rule that matters most: if you have already pushed, you almost always want git revert, not git reset.

The two tools: reset and revert

Git gives you two main ways to undo a commit. They sound similar. They are not.

git reset moves your branch pointer backward. It rewinds. After a reset, the commit you undid is no longer in your branch's history. It is as if you never made it. (The commit object is not deleted from disk right away — more on that later — but your branch no longer points at it.)

git revert does the opposite. It leaves the old commit in place and adds a new commit on top that cancels out the change. The official docs describe it as: "revert the changes that the related patches introduce, and record some new commits that record them" (git-revert). Nothing is rewound. History grows forward.

Here is the difference in one line each:

That difference is the heart of everything below. reset is for commits that only live on your machine. revert is for commits other people might already have.

Question 1: keep the changes or discard them?

Say you made a commit and you want to undo the commit but not necessarily the work. Maybe the code is fine but the message was wrong. Maybe you committed too early and want to split it into two. Maybe the whole thing was wrong and you want it gone.

This is what the three git reset modes are for. They all move your branch back one commit. They differ only in what happens to your files and your staging area.

The command shape is the same each time:

git reset --soft HEAD~1
git reset --mixed HEAD~1   # this is the default
git reset --hard HEAD~1

HEAD~1 means "one commit before where I am now." So all three say "move my branch back by one commit." The flag decides what happens to the changes from that commit.

--soft: uncommit, keep everything staged

git reset --soft HEAD~1

This is the gentlest undo. The docs say --soft will "leave your working tree files and the index unchanged" (git-reset). In plain terms: your files are untouched, and the changes from the undone commit stay staged, ready to commit again.

Use this when the commit's content was right but something about the commit itself was wrong — bad message, wrong grouping, committed too soon. Run the reset, then run git commit again with the message or grouping you actually wanted.

A common use is squashing. The docs give this exact example: "if you have no staged changes, you can use git reset --soft HEAD~5; git commit to combine the last 5 commits into 1 commit" (git-reset).

--mixed: uncommit and unstage (the default)

git reset HEAD~1          # same as --mixed
git reset --mixed HEAD~1

If you run git reset HEAD~1 with no flag, you get --mixed. The docs describe it as: "Leave your working directory unchanged. Update the index to match the new HEAD, so nothing will be staged" (git-reset).

So your files keep their changes, but those changes are now unstaged. You are back to the state you were in just before you ran git add. This is useful when you want to re-stage the changes in a different way — for example, splitting one commit into two by staging some files now and the rest in a second commit.

The key thing to hold onto: both --soft and --mixed keep your actual file changes. The work is safe. You are only undoing the commit, not the code.

If you want to undo more than one commit but keep the work staged, the same idea scales: git reset --soft HEAD~3 uncommits the last three and leaves all their changes staged as one block. You can try this in a live terminal in the undo N commits and keep them staged scenario.

--hard: discard the changes too

git reset --hard HEAD~1

This is the destructive one. The docs say --hard will "overwrite all files and directories with the version from <commit>" and "tracked files not in <commit> are removed so that the working tree matches <commit>" (git-reset). It moves your branch back and throws away the file changes from the undone commit, in your working tree and your staging area.

Use --hard only when you are sure you want the work gone. There is no "are you sure?" prompt. If you had uncommitted edits mixed in with the commit you are undoing, those edits are gone too.

There is a real safety net here, but it has limits. Because --hard only discards committed work (the commit object still sits in .git/objects), you can usually get a hard-reset commit back with git reflog for about 30 days. But any changes that were never committed — edits you hadn't committed yet — were never given a SHA, so the reflog can't help with those. They are simply gone. Treat --hard as a one-way door for anything uncommitted.

Here is a quick comparison:

ModeMoves branch backWorking tree (your files)Staging area
--softYesUnchangedChanges kept, staged
--mixed (default)YesUnchangedChanges kept, unstaged
--hardYesReset to target — changes discardedReset to target — changes discarded

Question 2: have you pushed it?

Everything above assumes the commit only exists on your machine. That assumption is doing a lot of work. The moment you push, the rules change.

git reset rewrites history. On your own machine, that is fine — nobody else has seen those commits, so nobody is affected. But once you push a commit to a shared branch, other people may have pulled it. They may have based their own work on it. If you then reset the branch and force-push the rewritten version, their history and yours no longer agree.

The Pro Git book is blunt about this: "Do not rebase commits that exist outside your repository and that people may have based work on." The same logic applies to any history rewrite, including a reset followed by a force-push. The book continues: "If you push commits somewhere and others pull them down and base work on them, and then you rewrite those commits... and push them up again, your collaborators will have to re-merge their work and things will get messy" (Pro Git: Rebasing).

So the rule of thumb is simple:

This is the fork that matters most. When in doubt about whether others have your commit, prefer revert. It is the safe choice. The worst it does is add an extra commit to the log.

How git revert works

git revert HEAD          # undo the most recent commit
git revert a1b2c3d       # undo a specific commit by its SHA

git revert looks at the commit you name, works out the changes it made, and creates a new commit that applies the exact opposite. If the old commit added a line, the revert commit removes it. If the old commit deleted a file, the revert commit brings it back.

The docs confirm it "automatically creates some commits with commit log messages stating which commits were reverted" (git-revert). Git opens an editor with a pre-filled message like Revert "your original message". Save and close it, and you have a new commit that undoes the change — with the original commit still right there in your history above it.

This is exactly why revert is safe for shared branches. You are not removing anything. You are adding a correction. Your teammates pull one new commit, same as any other commit. No re-merging, no messy history.

Reverting a merge commit

Reverting a normal commit is straightforward. Reverting a merge commit needs one extra flag, because a merge has two parents and Git can't guess which side you want to keep. The docs explain: "Usually you cannot revert a merge because you do not know which side of the merge should be considered the mainline. This option specifies the parent number (starting from 1) of the mainline" (git-revert).

git revert -m 1 <merge-sha>

-m 1 means "keep the first parent" — usually the branch you merged into (for example, main). One warning from the docs: reverting a merge "declares that you will never want the tree changes brought in by the merge," which can complicate re-merging that branch later (git-revert). Reverting merges is its own topic; just know the -m flag exists and why.

Fixing, not undoing: git commit --amend

Sometimes you don't want to undo the last commit at all. You want to fix it — change the message, or add a file you forgot to stage. That is git commit --amend.

git add forgotten-file.ts
git commit --amend

The docs describe --amend as: "Replace the tip of the current branch by creating a new commit" (git-commit). Read that carefully — it creates a new commit with a new SHA. It does not edit the old one in place. The old commit is replaced, and the replacement carries whatever you staged plus the message you keep or change.

Because amend creates a new SHA, it is a history rewrite, just like reset. The same push rule applies. The docs warn: "You should understand the implications of rewriting history if you amend a commit that has already been published" (git-commit). Amend freely before you push. After you push, amending means force-pushing a rewritten commit, with all the same problems as a reset on a shared branch.

amend is for fixing the most recent commit. It is not really an undo, but people reach for it in the same panic, so it belongs here. We cover it in more depth in its own post.

A worked example

You committed three files. Two belong together; the third was a mistake. You have not pushed.

git reset --soft HEAD~1

Now the commit is gone but all three files' changes are staged. Unstage the wrong one and recommit the right two:

git restore --staged wrong-file.ts
git commit -m "feat: the two files that belong together"

Then deal with wrong-file.ts however you like — commit it separately, or discard it.

Now the pushed version. You committed and pushed a change that broke production. Other people have already pulled.

git revert HEAD
git push

A new commit undoes the broken change. Production is fixed. The history shows both the bad commit and its revert, which is honest and easy to follow. Nobody has to re-sync.

Common myths

Myth 1: "reset --hard is how you undo a commit." It is one way, and often the wrong one. --hard discards your file changes along with the commit (git-reset). If you only wanted to undo the commit and keep the work, --soft or --mixed does that without risk. And on a pushed branch, reset of any kind rewrites shared history. Reach for --hard only when you have decided the work itself should be gone — and remember that uncommitted edits caught in the blast are not recoverable through the reflog.

Myth 2: "git revert deletes the commit." No. Revert never removes anything. It "records some new commits" that undo the change (git-revert). The original commit stays in your history, with a new commit above it that cancels its effect. If you expected the old commit to vanish from git log, that is reset's job, not revert's. Revert keeps the full story visible, which is exactly why it is safe to share.

Myth 3: "You can't undo a commit once it's pushed." You can. You just shouldn't use reset to do it. git revert is built for this case: it undoes the change by adding a new commit, so the shared history stays intact and your teammates pull a normal commit (git-revert). Pushed commits are not stuck forever. They are simply undone forward, with revert, instead of rewound with reset.

When undo isn't the real answer

A few situations look like "undo a commit" but need a different tool.

What to read next

You now have the decision: keep or discard (the three reset modes), and pushed or not (reset vs revert). These posts cover the cases that sit right next to it.

When you want to feel the difference instead of reading about it, the Revert vs Reset lesson and the Reset Variations lesson open a live terminal where you run each command on a real seeded repo and watch exactly what it does.