Undoing Almost Anything in Git
A lookup table for Git recovery — uncommitting, unstaging, recovering deleted branches, fixing the wrong branch, and why reflog makes almost everything reversible.
Table of contents
- I committed too early
- I want to fix the last commit
- I committed to the wrong branch
- I need to undo a commit that is already pushed
- I want to throw away local changes
- I deleted a branch I still needed
- I need one commit from another branch
- I need to stash work temporarily
- I broke something and do not know which commit did it
- I need to know who changed a line and why
- reset vs revert vs restore
- Frequently asked questions
- How long does the reflog keep things?
- Can I recover a git reset --hard that lost uncommitted work?
- What is the difference between git switch and git checkout?
- How do I remove a secret from history?
- Related reading
- References
Almost everything in Git is recoverable. This is the lookup table, ordered by what you just did.
I committed too early#
# Keep the changes staged, undo the commit
git reset --soft HEAD~1
# Keep the changes but unstage them
git reset HEAD~1
# Discard the commit AND the changes — destructive
git reset --hard HEAD~1The three modes differ only in what happens to your files: --soft keeps them staged, mixed (the default) keeps them unstaged, --hard deletes them.
I want to fix the last commit#
# Change the message only
git commit --amend -m "Better message"
# Add a forgotten file to the previous commit
git add forgotten.ts
git commit --amend --no-edit--amend rewrites the commit, so the same rule applies as rebasing: not on anything already shared.
I committed to the wrong branch#
# The commits are on main; they should be on a feature branch
git branch feature # bookmark the current state
git reset --hard origin/main # move main back
git switch feature # your commits are hereI need to undo a commit that is already pushed#
git revert <sha>revert creates a new commit that inverts the change. This is the correct tool for shared history — unlike reset, it does not rewrite anything, so nobody else's clone breaks.
# Reverting a merge commit needs the parent number
git revert -m 1 <merge-sha>I want to throw away local changes#
# One file
git restore path/to/file.ts
# Everything tracked
git restore .
# Unstage but keep the edits
git restore --staged path/to/file.ts
# Remove untracked files too — preview first
git clean -nd # dry run: shows what would be deleted
git clean -fd # actually deleteAlways run git clean -nd before -fd. Untracked files are not in Git, so there is nothing to recover them from.
I deleted a branch I still needed#
git reflog
# find the last commit that was on it
git switch -c recovered-branch <sha>The reflog records every position HEAD has held. Commits are retained for 90 days by default even with no branch pointing at them.
I need one commit from another branch#
git cherry-pick <sha>
git cherry-pick <sha1>..<sha2> # a rangeI need to stash work temporarily#
git stash push -m "half-done refactor"
git stash list
git stash pop # apply and remove
git stash apply stash@{1} # apply and keep
# Include untracked files — the flag people forget
git stash push -u -m "wip"Without -u, new files are left in your working tree and can end up in the wrong commit.
I broke something and do not know which commit did it#
git bisect start
git bisect bad # current state is broken
git bisect good v1.2.0 # this tag was fine
# Git checks out a midpoint; test and mark it:
git bisect good # or: git bisect bad
# repeat until Git names the culprit
git bisect resetBinary search over history. Twenty commits takes about five tests. It is the fastest way to find a regression in a large range, and it can be automated with git bisect run <test-command>.
I need to know who changed a line and why#
git log -S "functionName" # commits that added/removed this string
git log -p -- path/to/file.ts # full history of one file
git blame -L 40,60 file.ts # who last touched these linesgit log -S (the "pickaxe") is the underused one: it finds when a specific piece of code appeared or disappeared, which blame cannot tell you once a line has been reformatted.
reset vs revert vs restore#
| Command | Scope | Rewrites history? | Use for |
|---|---|---|---|
git restore | working tree / index | no | discard uncommitted changes |
git reset | branch pointer | yes | undo local commits |
git revert | new commit | no | undo pushed commits |
Choosing correctly is mostly one question: has anyone else seen these commits? If yes, revert. If no, reset is fine.
Frequently asked questions#
How long does the reflog keep things?#
90 days for reachable commits, 30 for unreachable, by default. git gc can prune earlier.
Can I recover a git reset --hard that lost uncommitted work?#
Uncommitted changes are not in Git's object database, so generally no. Committed work is always recoverable via reflog. This is the argument for committing early and often.
What is the difference between git switch and git checkout?#
switch changes branches; restore changes files. They were split out of checkout, which did both and was confusing. Prefer the newer pair.
How do I remove a secret from history?#
git filter-repo (not the deprecated filter-branch). And rotate the secret regardless — assume anything that reached a remote is compromised.