Undoing Things Safely
Everyone commits to the wrong branch, stages the wrong file, or loses work in a rebase. Git can almost always recover, as long as you don't make it worse in a panic. This guide shows how to pick the right undo command for your situation, and how to rescue work that seems to be gone.
Step 1: Stop and Ask Two Questions
Before typing any undo command, run git status and answer:
- Is the work committed? Committed work is almost impossible to lose. Uncommitted work can be deleted permanently by a few commands, so be most careful there.
- Has it been pushed to a shared branch? Local, unpushed commits can be rewritten freely. Once others may have pulled them, you must add a commit that undoes them, never rewrite history.
If you're unsure, make a safety bookmark first. It costs nothing and gives you a guaranteed way back:
git branch backup/before-undo
Step 2: Find Your Situation
Is the change committed?
├── No
│ ├── Discard my edits to a file ............... git restore <file> (Step 3)
│ ├── Unstage a file, keep the edits ........... git restore --staged <file> (Step 3)
│ └── Set everything aside for later ........... git stash (Step 3)
└── Yes
├── Not pushed yet
│ ├── Fix the last commit's message/files .. git commit --amend (Step 4)
│ ├── Uncommit, keep the changes ........... git reset --soft HEAD~1 (Step 4)
│ └── Committed on the wrong branch ........ (Step 6)
├── Pushed to a shared branch
│ └── Undo it for everyone ................. git revert <hash> (Step 5)
└── "My commits disappeared" ................. git reflog (Step 7)
Step 3: Undo Uncommitted Changes
git restore path/to/file.cs # discard edits; the file returns to its last committed state
git restore --staged path/to/file.cs # unstage; your edits stay in the file
git restore --source=Dev path/to/file.cs # replace the file with Dev's version
git restore <file> permanently deletes uncommitted edits in that file; there is no undo for it. If there's any chance you want them, stash instead. A stash is a shelf where you can put changes aside, switch branches, and bring them back later:
git stash push -m "half-done tooltip work" # shelve all tracked changes
git stash push -u -m "including new files" # -u also shelves untracked files
git stash list # stash@{0}, stash@{1}, ...
git stash pop # re-apply the latest stash and remove it
git stash apply stash@{1} # re-apply a specific stash and keep it
git stash drop stash@{1} # delete a stash
New files Git has never tracked are removed with git clean. Always preview first with -n (dry run), because deleted untracked files are gone for good:
git clean -n # list what would be deleted
git clean -f # delete untracked files (add -d to include folders)
Step 4: Undo Commits You Haven't Pushed
Amend replaces the last commit. Use it to fix a typo in the message or add a forgotten file:
git add forgotten-file.cs
git commit --amend # opens the editor to adjust the message too
git commit --amend --no-edit # keep the message as it is
Reset moves your branch label back to an earlier commit. HEAD~1 means "one commit before the current one"; HEAD~3 means three. The mode decides what happens to the changes in the commits you remove:
| Command | The removed commits' changes end up… | Risk |
|---|---|---|
git reset --soft HEAD~1 | Staged, ready to commit again. | Safe |
git reset HEAD~1 (mixed, the default) | In your files, unstaged. | Safe |
git reset --hard HEAD~1 | Deleted, along with all uncommitted changes in your working tree. | Dangerous |
The commits removed by any reset can still be recovered with the reflog (Step 7). Uncommitted changes wiped by --hard cannot.
Step 5: Undo Commits That Are Already Shared
Once a commit is on a shared branch, don't reset it away; teammates already have it, and protected branches block force-pushes anyway. Instead, revert it: Git creates a new commit that does the exact opposite. History stays intact, and the undo goes through a pull request like any other change.
git switch -c fix/issue-61-revert-broken-sorting Dev
git revert 3f2a9c1 # new commit undoing 3f2a9c1
git push -u origin fix/issue-61-revert-broken-sorting
To revert a merge commit, tell Git which side to keep with -m 1 (the branch that was merged into): git revert -m 1 <merge-hash>. Reverting a merge has knock-on effects if the same branch is merged again later, so ask a lead before doing it.
Step 6: Move Commits to the Right Branch
Committed on local Dev instead of a feature branch (not pushed)
Create the feature branch where you are (it keeps your commits), then move your local Dev back to match GitHub:
git switch -c feature/issue-42-inventory-sort # new branch keeps your commits
git switch Dev
git fetch origin
git reset --hard origin/Dev # local Dev matches GitHub again
git switch feature/issue-42-inventory-sort
Commit or stash any uncommitted work before running reset --hard.
Need one commit from another branch
Cherry-pick copies a single commit onto your current branch:
git cherry-pick 3f2a9c1
Step 7: Rescue "Lost" Work with the Reflog
The reflog is Git's private diary of every commit your HEAD has pointed at recently, including commits no branch points at anymore after a reset, a bad rebase, or a deleted branch. Entries are kept for about 90 days by default.
git reflog
# 9c4e2b7 HEAD@{0}: reset: moving to HEAD~3
# a81f03d HEAD@{1}: commit: feat(inventory): sort by rarity
# ...
git branch rescue/inventory a81f03d # put a branch on the lost commit
git log --oneline rescue/inventory # check it's the work you wanted
The reflog is local to your machine. It can't recover commits that only ever existed on someone else's computer, or changes that were never committed. That's the best argument for committing early and often on your feature branch.
Step 8: Know the Commands That Can Destroy Work
These commands are useful, but they can delete work permanently. Pause before running any of them, and never copy one from the internet without understanding it.
| Command | What can be lost | Safer alternative |
|---|---|---|
git reset --hard | All uncommitted changes. | git stash first |
git restore . | All unstaged edits in every file. | Restore specific files |
git clean -fd | All untracked files and folders. | git clean -n to preview |
git push --force | Teammates' commits on the remote branch. | git push --force-with-lease |
git branch -D | Unmerged commits on that branch (recoverable via reflog for a while). | git branch -d, which refuses if unmerged |
git stash drop / clear | Shelved changes. | git stash pop after checking git stash list |