Quick answer: which command to use
Start by checking where you are with git log --oneline -5 and git status. Then pick the row that matches your situation. HEAD~1 means "the commit before the current one"; HEAD~3 would go back three commits.
- Not pushed, keep the changes staged:
git reset --soft HEAD~1. - Not pushed, keep the changes but unstage them:
git reset HEAD~1(the default--mixedmode). - Not pushed, throw the changes away completely:
git reset --hard HEAD~1. - Already pushed to a shared branch:
git revert HEAD, thengit push. - Only the message is wrong or a file is missing:
git commit --amend. - You reset and regret it:
git reflog, thengit reset --hard HEAD@{1}.
# See the last few commits and the state of your working tree
git log --oneline -5
git statusgit reset: remove the last commit locally
git reset moves the current branch pointer back to an earlier commit, so the last commit is no longer part of the branch history. It is the right tool when the commit exists only on your machine. The three modes differ only in what happens to the changes that were in the removed commit.
Because reset rewrites history, never use it on commits that other people have already pulled unless your team explicitly agrees and you force-push afterwards (see below).
Tip
On Windows cmd, ^ is an escape character, so HEAD^ can behave oddly. HEAD~1 works the same in every shell.
git reset --soft HEAD~1 (keep changes staged)
The soft reset removes the commit but leaves every change from it in the staging area, exactly as it was just before you ran git commit. Use it when you committed too early, want to split the work into several commits, or want to commit to a different branch. It is the safest way to undo the last commit because nothing is lost.
git reset --soft HEAD~1
git status # changes are listed under "Changes to be committed"
git commit -m "A better commit message"git reset --mixed HEAD~1 (keep changes, unstaged)
Mixed is the default, so git reset HEAD~1 does the same thing. The commit is removed and its changes stay in your working directory as unstaged modifications. Use it when you want to review and re-stage files selectively, for example with git add -p.
git reset HEAD~1 # same as: git reset --mixed HEAD~1
git add -p # stage only the hunks you want
git commitgit reset --hard HEAD~1 (delete the commit and its changes)
A hard reset removes the commit and also overwrites your staging area and working directory to match the previous commit. Use it only when you are sure the work is worthless. It also discards any uncommitted changes you had, which is the part that catches people out: those were never committed, so reflog cannot bring them back.
git stash # optional: park uncommitted work first
git reset --hard HEAD~1Warning
git reset --hard permanently deletes uncommitted changes in tracked files. The removed commit itself can be recovered from the reflog for a while, but unsaved edits cannot. Run git status first and stash anything you might want.
git revert: undo a commit that is already pushed
git revert HEAD does not delete anything. It creates a new commit that applies the exact opposite of the last commit, so the branch history keeps both the mistake and its correction. This is the correct way to undo a pushed commit on a shared branch such as main, because everyone who already pulled the original commit can simply pull the revert commit too. Nobody's history is rewritten and nobody needs to force-pull.
You can revert any commit, not only the latest one, by passing its hash. If the revert conflicts with later changes, Git stops and lets you resolve the conflict just like a merge, then you run git revert --continue. Reverting a merge commit needs -m 1 to tell Git which parent is the mainline (usually the first parent, the branch you merged into).
# Undo the most recent commit with a new "Revert ..." commit
git revert HEAD
git push
# Revert an older commit by hash, without opening the editor
git revert --no-edit 3f2a91c
# Revert a merge commit (keep the first parent's side)
git revert -m 1 <merge-commit-sha>
# Revert several commits but make a single commit at the end
git revert --no-commit HEAD~2..HEAD
git commit -m "Revert last three commits"Undo a pushed commit: revert vs force-push
If the commit is already on the remote you have two choices. Reverting is always safe. Resetting and force-pushing actually removes the commit from the remote history, which is cleaner but breaks the clone of anyone who already pulled it: their next pull will try to bring the deleted commit back, or produce confusing conflicts.
Force-pushing is reasonable on a branch only you work on, such as your own feature branch before review, or when you accidentally pushed a secret and need it gone from the branch (in that case, also rotate the secret immediately, because it has already been exposed). Use --force-with-lease instead of --force. It refuses to overwrite the remote if someone else pushed in the meantime, so you cannot silently destroy a colleague's commits.
# Only on a branch nobody else uses
git reset --hard HEAD~1
git push --force-with-lease origin my-feature-branchWarning
Never force-push to main, master or any shared release branch without agreeing it with your team. Many repositories block it with branch protection rules, and that is a good thing. Use git revert there instead.
git commit --amend: fix the last commit instead of removing it
Often you do not want to undo the commit at all, you just want to change it. git commit --amend replaces the last commit with a new one that combines the previous commit's content with whatever you have staged now. Use it to fix a typo in the message, add a forgotten file, or remove a debug line.
Amending creates a new commit hash, so it is a history rewrite. That is fine before pushing. If you already pushed, amending means you have to force-push with lease, with the same caveats as above.
# Change only the message
git commit --amend -m "Fix login redirect loop"
# Add a forgotten file and keep the same message
git add src/forgotten-file.ts
git commit --amend --no-editRecover a commit with git reflog
Git keeps a private log of every position HEAD has pointed to, called the reflog. When you reset away a commit, the commit object still exists and the reflog still points to it, so you can get it back. Reflog entries for unreachable commits are kept for 30 days by default (90 days for reachable ones) before garbage collection may remove them.
Find the entry just before your mistake, then either reset to it or create a new branch at that commit if you would rather not move your current branch.
git reflog
# a1b2c3d HEAD@{0}: reset: moving to HEAD~1
# 9f8e7d6 HEAD@{1}: commit: Add payment webhook handler
git reset --hard HEAD@{1} # move the branch back to the lost commit
# or, without touching the current branch:
git branch recovered-work 9f8e7d6Undo the last commit on GitHub
GitHub's web interface has no button to delete a commit from a branch, because that would require a force-push. What you can do depends on how the commit arrived:
- If the commit came from a merged pull request, open the pull request and click Revert. GitHub creates a new branch with a revert commit and opens a pull request for it.
- In GitHub Desktop, the most recent unpushed commit shows an Undo button at the bottom of the Changes tab. This is a soft reset: the changes return to your working copy.
- For a direct commit that is already pushed, run
git revert HEADlocally and push, or use the force-push approach on your own branch. - To remove an older commit in the middle of a branch, use an interactive rebase locally (
git rebase -i HEAD~5, changepicktodropon that line) and then force-push with lease. See our rebase vs merge guide for how interactive rebase works.
Special cases
A few situations do not fit the standard commands. If the commit you want to undo is the very first commit in the repository, HEAD~1 does not exist. Use git update-ref -d HEAD to delete the branch pointer while keeping your files staged.
If you committed to the wrong branch, keep the work and move it: create or switch to the correct branch, cherry-pick the commit, then go back and reset the original branch. If you only want to unstage a file without touching commits, you do not need reset at all: git restore --staged <file> does it.
# Undo the initial commit (keeps files staged)
git update-ref -d HEAD
# Move the last commit from main to a new branch
git branch feature/login # new branch points at the commit
git reset --hard HEAD~1 # main goes back one commit
git switch feature/loginUndo last commit: FAQ
Quick answers to common questions
How do I undo the last commit but keep my changes?
Run git reset --soft HEAD~1. The commit is removed and all of its changes stay staged, ready to commit again. Use git reset HEAD~1 instead if you want the changes unstaged.
What is the difference between git reset and git revert?
git reset moves the branch pointer back and removes commits from history, which is only safe for commits that have not been shared. git revert adds a new commit that undoes an earlier one, leaving history intact, which makes it the safe choice for pushed commits.
How do I undo a commit that I already pushed?
On a shared branch, run git revert HEAD and push the new commit. On a branch only you use, you can run git reset --hard HEAD~1 followed by git push --force-with-lease.
Can I recover a commit after git reset --hard?
Usually yes. Run git reflog, find the commit hash from before the reset and run git reset --hard <hash> or git branch rescue <hash>. Uncommitted changes discarded by the hard reset cannot be recovered this way.
How do I delete the last two or three commits?
Use git reset --soft HEAD~2 or HEAD~3 (or --hard to discard the changes). For pushed commits on a shared branch, use git revert --no-commit HEAD~2..HEAD and commit once instead.

