Git · commonly confused
Git reset vs revert
Short answer
reset --hard also throws away uncommitted work: changes you never staged are gone for good.Reproduced on Git 2.50, . Where the text says “reproduced”, the behaviour was run on that version, not taken from documentation.
First published
On this page · 10 sections
In simple terms
Both are ways to undo a commit, which is why they get swapped. They undo different things. Reset changes where your branch points. Revert changes the files, with a new commit that says so.
Reset is moving a bookmark back. The commits after it still exist for a while, but the branch no longer includes them, and anyone who already has them now has a different history from yours.
Revert is writing a correction. The original commit stays in the history, followed by a commit that applies its exact opposite. Everyone's history keeps moving forward, so it pushes and pulls like any other commit.
Reset also comes in three strengths, and the strongest is the one that loses work: --hard rewrites your files to match the commit you reset to.
Words used on this page
- commit
- A saved snapshot of the project with a message and a link to the commit before it, its parent.
- branch
- A name that points at one commit. Committing moves it forward;
git resetcan move it anywhere, including backwards. - HEAD
- The commit you are on now, usually through the current branch.
HEAD~1is its parent. - working tree
- The files you see and edit on disk.
- index
- Also called the staging area: what
git addhas prepared for the next commit. - ORIG_HEAD
- Where the branch pointed before the last reset, kept by Git so
git reset --hard ORIG_HEADcan undo it. Other commands, such as merge, move it too. - reflog
- Your local record of where
HEADand each branch have pointed. It is not pushed, and its entries expire: by default after 90 days, or 30 for commits no longer on the branch. - fast-forward
- A push that only adds commits on top of what the remote already has, so the remote accepts it without force.
- force push
git push --force: replace the remote branch with yours even when that removes commits from it.- mainline parent
- For a merge commit, the parent that counts as the branch's own history.
git revert -m 1names the first parent.
What actually happens
git reset moves the current branch to another commit, then optionally updates the index and the working tree. Reproduced on Git 2.50, undoing one commit three ways from the same state: --soft kept the undone changes staged; --mixed, the default, kept them as unstaged edits; --hard made the files match the earlier commit. A new file that had been staged but never committed was left untracked by --mixed and deleted by --hard. Git's manual: with --hard, any changes to tracked files in the working tree since the commit are discarded.
What reset removes from the branch is recoverable for a while. Git sets ORIG_HEAD to the old tip and the reflog records every move, so git reset --hard ORIG_HEAD put the branch back exactly. What it removes from the working tree is another matter: uncommitted work was never in a commit, so neither ORIG_HEAD nor the reflog has it. Staged changes were written to Git's object store, though, and git fsck --lost-found can recover them until garbage collection runs. Reproduced on Git 2.50: after reset --hard, a staged file came back from .git/lost-found/other/, and an unstaged edit did not.
git revert makes a new commit whose parent is your current tip and whose changes are the inverse of the commit you name. Nothing is rewritten. Reproduced: the history read Revert "bad", bad, two, one; the file was back to its earlier content; and the push was an ordinary fast-forward. Resetting the same pushed commit instead made the next push fail with ! [rejected] main -> main (non-fast-forward).
Merge commits need one more decision. A merge has two parents, so revert has to be told which one is the mainline, and without it Git refuses: "is a merge but no -m option was given". With -m 1 it undoes what the merge brought in. Git's manual states the consequence: reverting a merge declares that you will never want those changes, so later merges only bring in commits that are not ancestors of it. Reproduced: merging the same branch again said "Already up to date" and brought nothing back, until the revert was itself reverted.
Undoing a commit, each way
# Undo the last commit, keeping its changes staged / as edits / not at all. git reset --soft HEAD~1 git reset HEAD~1 # --mixed, the default git reset --hard HEAD~1 # also discards uncommitted work in tracked files # Changed your mind straight after a reset? The old tip is kept. git reset --hard ORIG_HEAD git reflog # every earlier position of HEAD, once ORIG_HEAD has moved on # Undo a commit that is already pushed: a new commit, history intact. git revert <commit> git push # Undo a merge: name the parent that was the mainline. git revert -m 1 <merge-commit> # Bring a reverted merge's changes back later. git revert <the-revert-commit>
Run git status before any --hard. Changes in tracked files are discarded and a staged new file is deleted; none of it was ever committed, so the reflog cannot return it.
Try it yourself
Before you start
- Git installed (
git --version). Everything on this page was reproduced on Git 2.50.1. - A clean starting point.
git statusshould saynothing to commit, working tree clean; if it does not, commit orgit stashfirst. Git's manual says revert needs a clean working tree. In practice, reproduced on Git 2.50.1, it refused staged changes and edits to files the revert touches, and let an unrelated unstaged edit through, so start clean rather than rely on that. - The ID of the commit you want to undo, from
git log --oneline, and whether anyone else already has it.git branch -r --contains <commit>lists the remote branches that include it: if any are listed, use revert. - Push access to the branch. If the branch is protected, push the revert to a new branch and open a pull request like any other change.
- For a merge commit, which parent is the branch's own history.
git show --no-patch --format=%P <merge>prints the parents in order; the first is usually the branch you merged into.
Undoing a pushed commit with revert
Reproduced on Git 2.50.1 (Apple Git-155) in a scratch repository whose remote was a local bare repository, with global and system config switched off and the time zone set to UTC. Three commits had been pushed; the last changed the port in app.conf from 8080 to 9090. The author is a placeholder and the repository path is replaced with <path>.
Command
git log --oneline git revert --no-edit HEAD git log --oneline cat app.conf git push origin main
Output
$ git log --oneline 5a3dd0d Change port to 9090 b2df759 Set request timeout 92d604d Add app config $ git revert --no-edit HEAD [main f6e7366] Revert "Change port to 9090" Date: Mon Oct 12 09:06:25 2026 +0000 1 file changed, 1 insertion(+), 1 deletion(-) $ git log --oneline f6e7366 Revert "Change port to 9090" 5a3dd0d Change port to 9090 b2df759 Set request timeout 92d604d Add app config $ cat app.conf PORT=8080 TIMEOUT=30 $ git push origin main To <path>/origin.git 5a3dd0d..f6e7366 main -> main
How to read it
git revert --no-edit HEADmade a new commit,f6e7366, whose message names what it undoes.--no-editaccepts that message as it is; without it Git opens your editor first so you can explain why.- The log still contains
Change port to 9090. Nothing was removed, so anyone who already pulled it has nothing to reconcile: their next pull simply adds the revert. app.confis back toPORT=8080, andTIMEOUT=30from the earlier commit is untouched. A revert undoes one commit's changes, not everything since.- The push printed
5a3dd0d..f6e7366, two dots and no+flag: a fast-forward. Git's manual uses three dots and a+for a forced update, which is what rewriting pushed history needs. - In the same repository,
git reset --hard HEAD~1on that pushed commit made the next push fail with! [rejected] main -> main (non-fast-forward), andgit statussuggestedgit pull, which would have fetched the commit straight back.
The mistake this causes
Using git reset --hard to undo a commit that has already been pushed, or to undo a commit while there are uncommitted changes in the working tree.
Why people do it Reset rewrites where the branch points. A pushed commit is already in other people's history, so the branch now disagrees with the remote. And --hard rewrites the working tree as well, so uncommitted work goes with it.
What you see The next push is rejected as non-fast-forward. Forcing it removes the commit from the shared branch and leaves everyone who pulled it holding a commit the remote no longer has. Locally, edits made since the last commit are gone, and git reflog does not list them, because they were never committed.
If it goes wrong
error: your local changes would be overwritten by revert., orYour local changes to the following files would be overwritten by merge- You have uncommitted work. Both messages end with
fatal: revert failed, and nothing was changed. Commit the work or rungit stash, revert, thengit stash pop. If your edits touch the lines the revert changed, the pop stops with a conflict to resolve, and Git keeps the stash until you do. error: commit <id> is a merge but no -m option was given.- Check which parent is the mainline with
git show --no-patch --format=%P <merge>, then rungit revert -m 1 <merge>(or-m 2if the second parent is your branch). CONFLICT (content): Merge conflict in <file>followed byerror: could not revert <id>- A later commit changed the same lines. Edit the file to the result you want,
git add <file>, thengit revert --continue. To give up and return to where you started, rungit revert --abort. - The push is rejected with
(non-fast-forward)straight after a reset - You reset a commit the remote already has. Do not force the push. Put the branch back with
git reset --hard ORIG_HEAD(this also discards uncommitted edits), then undo the commit withgit revert. - Work is missing after
git reset --hard - Committed work is in
git reflog: find the entry and rungit reset --hard <entry>, or keep it on a new branch withgit branch rescue <entry>. Staged changes can sometimes be found withgit fsck --lost-found. Edits that were never staged were never in Git, and Git cannot bring them back.
How it shows up in production
A bad commit reaches main, and the deploy that follows breaks.
The quickest-looking fix is git reset --hard HEAD~1 and a force push. It works on the server: main no longer contains the commit, and the next deploy is clean.
Meanwhile other people have pulled main. Their next pull reports divergent branches, one of them merges, and the bad commit comes back with their next push.
git revert would have produced the same files with a commit that records what happened, pushed as a fast-forward, with nothing for anyone else to reconcile.
How to tell which one you are hitting
- Has the commit been pushed anywhere others pull from?
- Then revert. Reset is for commits only you have.
- Do you have uncommitted changes?
- Then not
--hard. Use--softor--mixed, or commit or stash first;--harddiscards them. Staged changes can sometimes be dug out withgit fsck --lost-found; unstaged ones cannot. - Is the commit a merge?
- Revert needs
-m 1to know which parent was the mainline, and once it is reverted, merging that branch again brings nothing back until the revert is reverted. - Did you just reset the wrong thing?
git reset --hard ORIG_HEADstraight away. If other commands have moved ORIG_HEAD since, find the old position ingit reflog.
Security considerations
- Neither command deletes anything from history. A revert leaves the original commit in place, and a reset leaves it in your repository until its reflog entry expires (30 days by default for commits no longer on the branch) and garbage collection prunes it (loose objects older than two weeks by default), as well as in every clone and fork that already fetched it. If the commit contained a password or key, rotate it: removing it from history is a separate job, covered on the page about a push declined for a detected secret.
- Force-pushing after a reset removes commits from the shared branch for everyone, without telling them. If you really must rewrite a shared branch, use
--force-with-lease --force-if-includes, which refuse when the remote has commits you have not integrated, and keep branch protection that blocks force pushes on main. - Read what you are undoing with
git show <commit>before you revert it. Reverting a fix brings back the problem it fixed, including a security fix, and the revert message is the place to say why. - On a branch that requires signed commits, the revert is a new commit and must be signed as well:
git revert -S <commit>.
In an interview
How this gets asked in an interviewpreparation
Usually phrased as What is the difference between git reset and git revert?
What a strong answer contains Reset moves the branch to another commit and, depending on the mode, rewrites the index and working tree; it changes history, so it belongs on commits nobody else has. Revert adds a new commit that applies the inverse of an earlier one; history only moves forward, so it is the safe way to undo something already pushed. A strong answer adds the sharp edges: reset --hard discards uncommitted work the reflog cannot recover, and reverting a merge needs -m, after which re-merging that branch brings nothing back until the revert is reverted.
The follow-up "You ran reset --hard and lost a commit. Can you get it back?" Yes, if it was committed: ORIG_HEAD or git reflog has the old position. Not for uncommitted changes, which were never in a commit.
Sources
The behaviour this page describes, as documented by the projects themselves.
Did this get you to an answer?
No text box on purpose — please do not paste production logs anywhere
Something wrong or out of date? Report it
Where to go next
- When it goes wrongfatal: refusing to merge unrelated histories
- When it goes wrongfatal: detected dubious ownership in repository
- When it goes wrongfatal: Need to specify how to reconcile divergent branches
- What comes nextGit merge vs rebase
- What comes nextPush rejected (non-fast-forward)
- What comes nextPush declined: secret detected