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
- Before you undo anything: pushed or not?
- Quick reference
- Undoing uncommitted changes
- Discard edits to a file
- Unstage a file
- Remove untracked files
- Undoing the last commit (not pushed)
- Change the message or contents
- Remove the commit but keep the work
- Remove the commit and its changes
- Understanding the three reset modes
- Undoing a commit that is already pushed
- Revert it
- Reverting a merge commit
- If you really must rewrite pushed history
- Undoing merges and rebases
- Merge in progress with conflicts
- Rebase in progress
- Merge or rebase already finished (not pushed)
- Recovering "lost" commits and branches
- Recover a deleted branch
- Undoing a single file to an older version
- FAQ
- What is the difference between git reset and git revert?
- Can I undo git reset --hard?
- How do I undo git add .?
- How do I undo a git stash pop that caused conflicts?
- 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 file | git restore file.txt |
| Discard all uncommitted changes | git restore . |
| Unstage a file but keep changes | git restore --staged file.txt |
| Fix the last commit message | git commit --amend -m "New message" |
| Add a forgotten file to the last commit | git add file.txt then git commit --amend --no-edit |
| Undo last commit, keep changes staged | git reset --soft HEAD~1 |
| Undo last commit, keep changes unstaged | git reset HEAD~1 |
| Undo last commit and throw away changes | git reset --hard HEAD~1 |
| Undo a commit that is already pushed | git revert <commit> |
| Abort a merge in progress | git merge --abort |
| Abort a rebase in progress | git rebase --abort |
| Recover a "deleted" commit or branch | git reflog then git branch rescue <hash> |
| Delete untracked files | git 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
| Mode | Moves branch | Staging area | Working files |
|---|---|---|---|
--soft | Yes | Kept | Kept |
--mixed (default) | Yes | Reset | Kept |
--hard | Yes | Reset | Reset |
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
25 Git Commands Every Developer Should Know (With Examples)
The Git commands you will use every week, grouped by task, with real examples and the safe way to undo mistakes before they reach your team.
SSH Keys Explained: Generate, Add and Use Them Safely
Create an SSH key with ssh-keygen, add it to a server and to GitHub, use ssh-agent and a config file, and fix the most common SSH permission errors.
Regex Cheat Sheet: Syntax and Real Examples You Can Copy
A practical regular expression cheat sheet: characters, quantifiers, groups, lookarounds and flags, plus tested patterns for emails, dates, logs and more.