- git revert creates a new commit that undoes an earlier one. History is kept, so it is safe on shared branches.
- git reset moves the branch pointer backwards, removing commits from the branch. Use it on local work you have not pushed.
--softkeeps changes staged,--mixedkeeps them unstaged,--hardthrows them away.- Committed work lost to a reset can usually be recovered with
git reflog. Uncommitted work cannot.
You committed a bug, or a whole wrong approach, and you want it gone. Git gives you two main tools, and choosing the wrong one on a shared branch can disrupt the whole team. The difference comes down to one question: do you want to add an undo to history, or rewrite history as if the mistake never happened?
We built a small repository with Git 2.56 and ran every command below. The commit hashes and outputs are from that run, so yours will differ. New to Git? Start with our Git tutorial.
The short answer
| git revert | git reset | |
|---|---|---|
| What it does | Adds a new commit with the opposite changes | Moves the branch to an earlier commit |
| History | Preserved | Rewritten |
| Safe after pushing? | Yes | Not on shared branches |
| Can target an old commit only | Yes | No, it removes everything after the target |
| Typical use | Undo a change already on main | Tidy up local commits before pushing |
Our test repository
We made four commits, each adding one file:
$ git log --oneline
17e3cba D: add cart
e48227b C: enable debug (bug)
74369ab B: add login
0cbab47 A: add home page
Commit C turned on debug mode by mistake. Commit D, which came after it, is good work we want to keep. This is the realistic case: the bad commit is not the latest one.
git revert: undo by adding a commit
$ git revert --no-edit HEAD~1
$ git log --oneline
5d8c654 Revert "C: enable debug (bug)"
17e3cba D: add cart
...
$ git show --stat HEAD
config.js | 1 -
1 file changed, 1 deletion(-)
Git worked out the opposite of commit C, removing config.js, and recorded it as a new commit. Commit D and its cart.js file stayed untouched. Nobody’s history changed, so anyone who already pulled can simply pull again. That is why revert is the right tool for anything already pushed.
If later commits changed the same lines as the reverted one, Git stops with a conflict, just as in a merge. Resolve it, then run git revert --continue, or git revert --abort to back out.
git reset: undo by moving the branch
Reset moves your current branch to another commit. Everything after that commit disappears from the branch. Unlike revert, it cannot surgically remove C while keeping D; it removes both. What happens to their file changes depends on the mode.
Soft, mixed and hard
We reset two commits back, to B, in each mode, restoring the repository in between.
–soft: keep changes staged
$ git reset --soft HEAD~2
$ git log --oneline
74369ab B: add login
0cbab47 A: add home page
$ git status --short
A cart.js
A config.js
The commits are gone, but their changes are still staged and ready to commit again. This is perfect for squashing several messy local commits into one clean commit.
–mixed (the default): keep changes unstaged
$ git reset HEAD~2
$ git status --short
?? cart.js
?? config.js
The changes stay in your working folder but are no longer staged. Use this to redo commits differently, for example to split one large commit into several smaller ones.
–hard: discard everything
$ git reset --hard HEAD~2
$ git log --oneline
74369ab B: add login
0cbab47 A: add home page
$ ls
index.html login.js
cart.js and config.js were deleted from the folder too. This is the dangerous mode. It also discards uncommitted edits: we added a line to a tracked file, ran git reset --hard, and the line was gone without any warning.
| Mode | Branch moves | Staging area | Working files |
|---|---|---|---|
--soft |
Yes | Kept | Kept |
--mixed |
Yes | Reset | Kept |
--hard |
Yes | Reset | Reset |
Recovering with reflog
After our hard reset, commits C and D looked lost. They were not. Git keeps a log of where HEAD has pointed:
$ git reflog -4
74369ab HEAD@{0}: reset: moving to HEAD~2
17e3cba HEAD@{1}: reset: moving to HEAD@{1}
...
$ git reset --hard HEAD@{1}
$ git log --oneline
17e3cba D: add cart
e48227b C: enable debug (bug)
...
Both commits and their files came back. The reflog is local to your machine, and entries expire after a while (by default, 90 days for reachable entries and 30 days for unreachable ones), so recover sooner rather than later. It cannot save edits that were never committed.
Tip: Before any risky reset or rebase, create a backup branch with git branch backup-before-reset. It costs nothing and gives you an obvious way back.
Why reset is dangerous on shared branches
We pushed our branch to a remote, reset it back one commit locally, and tried to push again:
$ git push origin main
! [rejected] main -> main (non-fast-forward)
error: failed to push some refs to '../remote.git'
Git refused because the remote has a commit that our branch no longer contains. Forcing it with git push --force would overwrite the remote, and every teammate who already pulled that commit would have a history that no longer matches. If you truly must rewrite a pushed branch, such as your own feature branch, use git push --force-with-lease, which refuses if someone else pushed in the meantime. On main or any branch others use, use revert instead. Many teams enforce this with branch protection rules.
Reverting a merge commit
Merges have two parents, so Git needs to know which side to keep:
$ git revert HEAD
error: commit a166a4b... is a merge but no -m option was given.
$ git revert -m 1 HEAD
$ git log --oneline
6eeb57d Revert "M: merge payments"
a166a4b M: merge payments
d28bd4b E: add payments
-m 1 means “keep the first parent”, which is the branch you merged into, usually main. The payments file was removed. One catch to know: if you later try to merge the same feature branch again, Git considers those commits already merged and will not bring the changes back. You must revert the revert first. Our guide to merge vs rebase explains how merge history works.
Undoing files, not commits
Sometimes you only want to undo changes to a file:
- Unstage a file:
git restore --staged notes.txt, or the oldergit reset notes.txt. In our test, a staged file (A) became untracked (??) again. - Discard edits to a file:
git restore notes.txt. This cannot be undone. - Fix the last commit message or add a forgotten file:
git commit --amend, only before pushing. - Get an old version of one file:
git restore --source=HEAD~3 config.js.
Which one to use
| Situation | Command |
|---|---|
| Bad commit is already on main | git revert <commit> |
| Squash local commits before a pull request | git reset --soft, then commit |
| Split a commit you have not pushed | git reset HEAD~1 |
| Throw away local experiments | git reset --hard (after a backup branch) |
| Undo a merged feature on main | git revert -m 1 <merge> |
| Recover after a bad reset | git reflog, then git reset --hard HEAD@{n} |
Our Git commands cheat sheet lists these alongside other everyday commands.
Reverting several commits at once
A feature built over three commits, B, C and D, needs to come out of main. You can revert a range and record it as a single commit:
$ git revert --no-commit HEAD~2..HEAD
$ git commit -m "Revert feature"
$ git log --oneline
2917743 Revert feature
eedf9f0 D: finish feature
8d1ef91 C: more feature
85b42e8 B: start feature
945b819 A: base
$ ls
a.txt b.txt
Look at the files. b.txt is still there. We reverted C and D, but not B, because a range like X..Y means “commits after X, up to Y”. HEAD~2..HEAD therefore covers only the last two commits. To include B, the range must start one commit earlier, at HEAD~3, or you can list the commits by hash. Check with git log --oneline HEAD~3..HEAD before reverting, so you can see exactly which commits the range includes.
--no-commit stages all the undo changes together so you can review them and commit once, which keeps history tidy. Git applies the reverts newest first, which reduces conflicts when the commits touch the same files.
Final thoughts
Revert is the polite undo: it records the fix in history and keeps everyone in sync. Reset is the private undo: powerful for cleaning up local work, harmful on shared branches. Learn the three reset modes, make a backup branch before anything risky, and remember that the reflog can usually rescue committed work.
Frequently asked questions
What is the difference between git reset and git revert?
git revert adds a new commit that undoes an earlier commit, keeping history. git reset moves the branch back to an earlier commit, removing later commits from it.
Is git revert safe on a pushed branch?
Yes. It only adds a commit, so teammates can pull it normally. It is the recommended way to undo changes on shared branches.
What does git reset –hard do?
It moves the branch and discards all staged and unstaged changes, making your files match the target commit. Uncommitted work is lost permanently.
Can I undo a git reset –hard?
For committed work, usually yes. Find the old commit in git reflog and reset to it. Edits that were never committed cannot be recovered.
How do I revert a merge commit?
Use git revert -m 1 <merge-commit>, where -m 1 keeps the main line. Remember to revert the revert if you later merge the same branch again.
Keep learning
Need help with testing, WordPress or SEO?
We help teams ship better software and grow their sites. Tell us what you are working on and we will suggest a clear next step.