Undo a Push / Revert Commit
Everyone makes mistakes in Git. You committed the wrong file, pushed a broken change, or need to undo a commit that's already been shared. This guide teaches you how to safely undo commits using git revert and git reset, with special attention to the dangers of force push.
There are many scenarios where you might need to undo a commit. You committed a file that contained sensitive information like an API key or password. You accidentally pushed code that breaks the build. You made a commit with the wrong message or forgot to include a file. Or you simply committed changes that you later realized were incorrect.
The approach you take depends on whether the commit has been pushed to a shared repository or not. If the commit exists only on your local machine, you have many options to undo or amend it. If the commit has been pushed and others may have pulled it, you need to be much more careful. Rewriting history that others have already seen is dangerous and should be avoided.
git revert for shared commits, git reset for local commits only.
git revert is the safest way to undo a commit that has already been pushed to a shared repository. Unlike reset, which rewrites history, revert creates a new commit that undoes the changes from a previous commit. This means the original commit remains in the history, but a new commit is added that reverses its effects. This is safe because it doesn't rewrite history—it just adds new history.
To revert a commit, you need its hash. You can find the hash using git log --oneline. Then run git revert . Git will open an editor for you to write a commit message (the default is usually fine), and then create a new commit that undoes the changes. Finally, push this new commit to the remote repository.
# View recent commits to find the hash
$ git log --oneline
a1b2c3d Fix login bug
e4f5g6h Add new feature
i7j8k9l Update documentation
# Revert the commit "Fix login bug"
$ git revert a1b2c3d
# Git creates a new commit. Push it to GitHub
$ git push origin main
git reset rewrites history by moving the branch pointer backward. This is safe only if the commits you're resetting have never been pushed to a shared repository. Reset has three modes: --soft, --mixed (the default), and --hard. Each mode determines what happens to your working directory and staging area.
git reset --soft moves the branch pointer but leaves all changes in the staging area. This is useful when you want to combine multiple commits into one or fix a commit message. Your files are unchanged, but Git thinks they are ready to be committed again.
git reset --mixed (default) moves the branch pointer and unstages changes. Your files are unchanged, but they are no longer staged for commit. This is useful when you want to keep your changes but re-commit them differently.
git reset --hard moves the branch pointer AND discards all changes in your working directory. This is dangerous because any uncommitted changes are permanently lost. Only use this when you're certain you don't need the changes.
# Undo the last commit but keep changes staged
$ git reset --soft HEAD~1
# Undo the last commit and unstage changes (keep files)
$ git reset HEAD~1
# Completely remove the last commit and all changes
$ git reset --hard HEAD~1
# Undo the last 3 commits (local only!)
$ git reset --hard HEAD~3
git reset --hard permanently deletes uncommitted changes. Always run git status before using it to see if you have any unsaved work.
After using git reset locally, you might want to update the remote branch. However, a normal git push will be rejected because the remote branch has commits that your local branch doesn't. This is where force push comes in. Force push tells Git to overwrite the remote branch with your local version, discarding any commits that are only on the remote.
Force push is dangerous because it discards commits. If someone else has pulled those commits, their local history will diverge from the remote, causing confusion and potential data loss. Before force pushing, make sure no one else is working on the branch. Communicate with your team. And always use --force-with-lease instead of --force—it checks that no one else has pushed changes to the branch before overwriting.
# After a local reset, force push to update remote
$ git push --force-with-lease origin main
# For specific branch
$ git push --force-with-lease origin feature-branch
# NEVER use plain --force without a very good reason
$ git push --force origin main # Not recommended
| Scenario | Recommended Command | Why |
|---|---|---|
| Commit pushed to shared branch | git revert | Safe for teams, doesn't rewrite history |
| Commit only on local machine | git reset | Cleaner history, no negative impact |
| Need to fix last commit message | git commit --amend | Simplest for most recent commit |
| Forgot to add a file to last commit | git add file && git commit --amend | Amends the previous commit |
| Multiple commits to undo (local only) | git reset --hard HEAD~N | Removes N commits completely |
| Multiple commits to undo (pushed) | git revert OLD..NEW | Creates revert commits for the range |
For the most recent commit that hasn't been pushed yet, git commit --amend is the simplest solution. It allows you to change the commit message or add forgotten files to the previous commit without creating a new commit.
To amend a commit message, run git commit --amend -m "New message". To add a forgotten file, stage the file with git add then run git commit --amend --no-edit (which keeps the existing message). If the commit has already been pushed, you would need to force push after amending—which is dangerous on shared branches.
# Change the last commit message
$ git commit --amend -m "Fix login bug with proper validation"
# Add a forgotten file to the last commit
$ git add forgotten-file.js
$ git commit --amend --no-edit
# If the commit was already pushed, force push after amend
$ git push --force-with-lease origin main
git commit --amend freely for commits that are only local. For pushed commits, consider if the change is worth the complexity of a force push.
Sometimes you don't want to undo a whole commit—just revert a specific file to a previous version. git restore (or the older git checkout --) lets you discard uncommitted changes to a file. For committed changes, you can check out a file from a previous commit.
To discard uncommitted changes to a file, use git restore filename. This reverts the file to its last committed state. To restore a file from a specific commit, use git restore --source=. This is useful when you want to keep other changes in the commit but revert one file.
# Discard uncommitted changes to a file
$ git restore config.js
# Restore a file from 2 commits ago
$ git restore --source=HEAD~2 app.js
# Older syntax (still works)
$ git checkout -- config.js
$ git checkout HEAD~2 -- app.js
Scenario 1: You committed a secret (API key) and pushed it. First, revoke the secret immediately at the service provider. Then use git revert to create a commit that removes the secret. Finally, use git filter-repo or BFG to completely remove the secret from history—though this requires force pushing and team coordination.
Scenario 2: You made a commit with the wrong message, not pushed yet. Use git commit --amend -m "Correct message". Simple and safe.
Scenario 3: You committed to the wrong branch and haven't pushed. Use git reset HEAD~1 to undo the commit while keeping changes, then git stash to save them, switch to the correct branch, and git stash pop then commit.
Scenario 4: You want to undo the last 3 commits on a shared branch. Don't use reset. Use git revert HEAD~3..HEAD to create three revert commits (one for each commit being undone). This is safe and keeps history intact.
# Scenario 3: Move commit to correct branch
$ git reset HEAD~1 # Undo commit, keep changes
$ git stash # Save changes
$ git checkout correct-branch
$ git stash pop # Apply changes
$ git add . && git commit -m "Feature complete"
# Scenario 4: Revert range of commits on shared branch
$ git revert HEAD~3..HEAD
$ git push origin main
git revert with the commit hash from a week ago. Since others may have built on top of that commit, revert is safer than reset. The revert commit will undo the changes from that old commit.git reflog to find the commit hash you were on before the reset, then git reset --hard . The reflog tracks all branch movements and can save you from accidental resets.git revert -m 1 . The -m 1 tells Git which parent to revert to (usually the main branch). Then push the revert commit as normal.git filter-repo or BFG Repo-Cleaner. This rewrites history and requires a force push. All collaborators must re-clone. This is the nuclear option—use only for security emergencies like exposed passwords.git branch temp-branch, then git checkout main to return. Your changes are safe.Mistakes in Git are fixable. The key is knowing which tool to use: revert for shared commits, reset for local work, and force push only when absolutely necessary and coordinated with your team.