DevPlainly
Developer Productivity

How to Undo Almost Anything in Git (Without Losing Work)

Undo the last commit, unstage files, discard changes, fix a bad merge or recover deleted commits. Every Git undo scenario with the exact command to use.

· 7 min read

Contents
  1. Before you undo anything: pushed or not?
  2. Quick reference
  3. Undoing uncommitted changes
  4. Discard edits to a file
  5. Unstage a file
  6. Remove untracked files
  7. Undoing the last commit (not pushed)
  8. Change the message or contents
  9. Remove the commit but keep the work
  10. Remove the commit and its changes
  11. Understanding the three reset modes
  12. Undoing a commit that is already pushed
  13. Revert it
  14. Reverting a merge commit
  15. If you really must rewrite pushed history
  16. Undoing merges and rebases
  17. Merge in progress with conflicts
  18. Rebase in progress
  19. Merge or rebase already finished (not pushed)
  20. Recovering "lost" commits and branches
  21. Recover a deleted branch
  22. Undoing a single file to an older version
  23. FAQ
  24. What is the difference between git reset and git revert?
  25. Can I undo git reset --hard?
  26. How do I undo git add .?
  27. How do I undo a git stash pop that caused conflicts?
  28. Further reading

Git almost never deletes your work for good. Commits you "lost" in a bad reset or rebase are usually still there for weeks. The trick is knowing which undo command fits which situation — and which ones are safe once you have pushed.

Use this page as a lookup table: find your situation, copy the command, and read the short explanation so you know what it does.

Before you undo anything: pushed or not?

This is the one question that decides which command to use.

  • Not pushed yet (only on your machine): you can rewrite history freely — reset, --amend, interactive rebase.
  • Already pushed to a shared branch: do not rewrite history. Use git revert, which adds a new commit that cancels the old one.

Check with git status — it tells you if your branch is "ahead of" the remote — or with git log --oneline origin/main..HEAD to list commits that exist only locally.

Quick reference

I want to…Command
Discard changes in one filegit restore file.txt
Discard all uncommitted changesgit restore .
Unstage a file but keep changesgit restore --staged file.txt
Fix the last commit messagegit commit --amend -m "New message"
Add a forgotten file to the last commitgit add file.txt then git commit --amend --no-edit
Undo last commit, keep changes stagedgit reset --soft HEAD~1
Undo last commit, keep changes unstagedgit reset HEAD~1
Undo last commit and throw away changesgit reset --hard HEAD~1
Undo a commit that is already pushedgit revert <commit>
Abort a merge in progressgit merge --abort
Abort a rebase in progressgit rebase --abort
Recover a "deleted" commit or branchgit reflog then git branch rescue <hash>
Delete untracked filesgit clean -n (preview) then git clean -fd

The sections below explain each one.

Undoing uncommitted changes

Discard edits to a file

git restore app.py

This replaces the file with the version from your last commit. It cannot be undone — Git never saw those edits, so they are not in the reflog. If you are unsure, stash instead:

git stash push app.py     # keeps a copy you can bring back with git stash pop

Unstage a file

You ran git add on something you did not mean to commit:

git restore --staged secrets.env

The file stays modified on disk; it is just no longer staged. On Git versions older than 2.23, the equivalent is git reset HEAD secrets.env.

Remove untracked files

git clean -n      # dry run: shows what would be deleted
git clean -fd     # delete untracked files and directories
git clean -fdx    # also delete ignored files (build output, node_modules…)

Always run the dry run first.

Undoing the last commit (not pushed)

Change the message or contents

git commit --amend -m "Fix typo in pricing page"

Forgot a file?

git add forgotten.css
git commit --amend --no-edit

--amend replaces the last commit with a new one, so only do it before pushing.

Remove the commit but keep the work

git reset --soft HEAD~1

The commit disappears and its changes go back to the staging area, ready to be committed again — useful when you committed to the wrong branch:

git reset --soft HEAD~1
git stash
git switch correct-branch
git stash pop
git commit -m "Same work, right branch"

Remove the commit and its changes

git reset --hard HEAD~1

This discards the commit and resets your files. The commit is still recoverable through the reflog for a while (see below), but uncommitted changes in your working directory are gone.

Understanding the three reset modes

ModeMoves branchStaging areaWorking files
--softYesKeptKept
--mixed (default)YesResetKept
--hardYesResetReset

HEAD~1 means "one commit before the current one". Use HEAD~3 to go back three, or a specific commit hash.

Undoing a commit that is already pushed

Revert it

git revert a1b2c3d
git push

git revert creates a new commit that applies the opposite of a1b2c3d. History stays intact, so teammates who already pulled are not affected.

To revert several commits at once without creating one revert commit per commit:

git revert --no-commit a1b2c3d..f6e5d4c
git commit -m "Revert the experimental search feature"

Reverting a merge commit

Merge commits have two parents, so you must tell Git which side to keep — usually 1, the branch you merged into:

git revert -m 1 <merge-commit-hash>

If you really must rewrite pushed history

Sometimes you pushed a secret or a huge file to your own feature branch. Fix it locally, then push with:

git push --force-with-lease

--force-with-lease refuses to overwrite the remote if someone else pushed in the meantime, which makes it much safer than --force. Tell anyone working on the branch.

If you pushed a password or API key, rotate it immediately. Removing it from history does not help if someone already cloned or a bot already scraped it.

Undoing merges and rebases

Merge in progress with conflicts

git merge --abort

You are back to where you were before git merge.

Rebase in progress

git rebase --abort

Merge or rebase already finished (not pushed)

Use the reflog to find where your branch was before:

git reflog
# a1b2c3d HEAD@{0}: rebase (finish): returning to refs/heads/feature
# ...
# 9f8e7d6 HEAD@{5}: commit: Add search filters   <- before the rebase
git reset --hard 9f8e7d6

After a merge, ORIG_HEAD also points to the commit before it, so git reset --hard ORIG_HEAD is a shortcut.

Recovering "lost" commits and branches

The reflog records every position HEAD has been at. By default, entries stay for 90 days (30 days for commits that are no longer reachable from any branch).

git reflog

Find the commit you want and create a branch pointing to it:

git branch rescue 9f8e7d6

Recover a deleted branch

git branch -D feature/payments   # oops

Git printed the last commit hash when it deleted the branch (Deleted branch feature/payments (was 4c5d6e7).). Recreate it:

git branch feature/payments 4c5d6e7

If you no longer have that output, search the reflog for the branch name.

Undoing a single file to an older version

git log --oneline -- config.yaml        # find the commit you want
git restore --source=a1b2c3d config.yaml

This changes only that file; commit it like any other change.

FAQ

What is the difference between git reset and git revert?

reset moves your branch backwards and rewrites history — use it for local commits. revert adds a new commit that cancels an old one — use it for anything already shared.

Can I undo git reset --hard?

The commits, yes: find them in git reflog and reset back to them. Uncommitted changes that were in your working directory, no — Git never stored them.

How do I undo git add .?

Run git restore --staged . to unstage everything while keeping your edits.

How do I undo a git stash pop that caused conflicts?

The stash is not dropped when a pop conflicts. Run git restore . (or git reset --hard if you have nothing else to keep) to clear the conflicted state; the stash is still in git stash list.

Further reading

Keep reading