DEV Community

Cover image for The Git Recovery Guide: How to Undo Anything (Without Panic)
James Anderson
James Anderson

Posted on AI-assisted

The Git Recovery Guide: How to Undo Anything (Without Panic)

There's a specific feeling every developer knows. You run a git command, hit Enter, and half a second later your stomach drops because you realize what you just did. Three hours of work, gone from git log. The wrong branch, obliterated. A rebase that turned your history into soup.

Take a breath, because here's the truth that this entire guide rests on:

Git almost never actually deletes your work.

When you "lose" a commit, it's usually not destroyed — it's just orphaned, meaning nothing points to it anymore. The commit is still sitting in git's database, and its hash is still recorded in a log of everything you've done. Recovery is almost always possible. You just need to know which command brings it back.

This guide is organized by what you just did. Find your situation, copy the fix, breathe. Bookmark it now — you will need it someday, probably at 2am.

The one idea that makes all of this make sense

Before the recipes, thirty seconds of theory that turns this from magic into something you understand.

Git rarely destroys anything. It moves references — the little pointers (branches, HEAD) that say "the current state is here." When you reset, delete a branch, or botch a rebase, you're usually just moving a pointer, not deleting commits. The commits stay in git's object database until garbage collection eventually clears the unreferenced ones.

And git keeps two safety nets:

  • The reflog — a log of every move your HEAD and branches have made. Think of it as git's security-camera footage of your own actions. Even "lost" commits show up here with their hashes. Run git reflog and you can see everywhere you've been.
  • git fsck — finds orphaned ("unreachable") objects when even the reflog isn't enough.

Nearly every recovery below is really just: find the hash of where you want to be, and point something at it again. That's it. Now the recipes.


Part 1 · Undoing uncommitted changes

Discard changes to one file (before staging):

git restore <file>
Enter fullscreen mode Exit fullscreen mode

Throws away your uncommitted edits to that file, restoring it to the last commit. (This is the modern command; you may have muscle memory for git checkout <file>, which still works but restore is clearer.)

Unstage a file but keep the changes:

git restore --staged <file>
Enter fullscreen mode Exit fullscreen mode

Removes it from staging; your edits stay in the working directory.

Discard ALL uncommitted changes:

git restore .
Enter fullscreen mode Exit fullscreen mode

⚠️ This is destructive — it permanently throws away uncommitted work, and since it was never committed, the reflog can't save you. Make sure you mean it.

Recover a file you deleted but hadn't committed:

git restore <file>
Enter fullscreen mode Exit fullscreen mode

As long as the deletion wasn't committed, the file comes right back from HEAD.


Part 2 · Fixing commits

Fix a typo in your last commit message:

git commit --amend
Enter fullscreen mode Exit fullscreen mode

You forgot to include a file in the last commit:

git add forgotten-file.js
git commit --amend --no-edit
Enter fullscreen mode Exit fullscreen mode

--no-edit keeps the existing message.

Undo the last commit but KEEP the changes (put them back as uncommitted work):

git reset --soft HEAD~1
Enter fullscreen mode Exit fullscreen mode

The commit is undone; your changes are safe and staged. This is the gentle, safe undo.

Undo the last commit AND discard the changes:

git reset --hard HEAD~1
Enter fullscreen mode Exit fullscreen mode

⚠️ --hard throws away the changes too. If you didn't mean to lose them, don't panic — the reflog can recover this (see Part 3). But run it carefully.

Undo a commit that's already pushed / shared — use revert, not reset:

git revert <commit-hash>
Enter fullscreen mode Exit fullscreen mode

This creates a new commit that reverses the old one. Nothing is rewritten, so it's safe on shared branches — nobody's history breaks.

The reset vs. revert rule, once and for all:

  • reset rewrites history — great for local, private commits nobody else has.
  • revert adds a new undo-commit — the polite way to undo shared commits without ruining your teammates' day.
  • Rule of thumb: if you've pushed it and others might have it, revert.

Part 3 · The "oh no" recoveries (meet the reflog)

This is the heart of the guide. Almost every panic below is fixed the same way: git reflog, find the hash, point something at it.

You ran git reset --hard and lost commits:

git reflog
# find the entry from BEFORE the reset — e.g. abc1234 HEAD@{1}
git reset --hard abc1234
Enter fullscreen mode Exit fullscreen mode

Your commits come right back. The reflog recorded where HEAD was before you reset.

You deleted a branch that had commits on it:

git reflog
# find the last commit that was on the branch, then recreate it:
git branch recovered-branch abc1234
Enter fullscreen mode Exit fullscreen mode

Deleting a branch only removes the label — the commits are still there. This re-attaches a label to them.

You botched a rebase:

git reflog
# look for entries around "rebase (start)" / "rebase (finish)"
# find the commit from BEFORE the rebase and reset to it:
git reset --hard HEAD@{5}
Enter fullscreen mode Exit fullscreen mode

Rebasing rewrites commits, but the originals are still in the reflog.

You made commits in a detached HEAD and switched away (they now have no branch pointing to them):

git reflog
# find your commit's hash, then give it a branch before it ages out:
git branch rescued abc1234
Enter fullscreen mode Exit fullscreen mode

When the reflog isn't enough — the deeper safety net:

git fsck --unreachable | grep commit
# inspect each candidate to find the one you want:
git show <hash>
# then recover it:
git branch recovered <hash>
Enter fullscreen mode Exit fullscreen mode

How to read the reflog (so it's not intimidating):

git reflog --date=relative
Enter fullscreen mode Exit fullscreen mode

Entries look like HEAD@{2} — that means "where HEAD was 2 moves ago." You can use those references directly, e.g. git reset --hard HEAD@{2}.


Part 4 · Undoing merges and shared-history scares

You merged into the wrong branch, or merged by accident (and haven't pushed):

Git automatically saves where you were right before a merge in a reference called ORIG_HEAD, which makes this a one-liner:

git reset --hard ORIG_HEAD
Enter fullscreen mode Exit fullscreen mode

That snaps you back to exactly your pre-merge state. ORIG_HEAD is your best friend after any merge.

If the merge wasn't the very last thing you did, find the pre-merge commit in git reflog and reset to it instead.

You merged into the wrong branch entirely (meant to merge into feature, hit main):

# on the wrong branch (e.g. main):
git reset --hard ORIG_HEAD      # undo the accidental merge
git switch feature              # go to the branch you meant
git merge your-branch           # merge properly this time
Enter fullscreen mode Exit fullscreen mode

The merge is already pushed / shared — don't reset, revert it:

git revert -m 1 <merge-commit-hash>
Enter fullscreen mode Exit fullscreen mode

-m 1 tells git which parent to keep (the mainline branch, usually parent 1). This undoes the merge with a new commit, safely, without rewriting shared history.
⚠️ Gotcha worth knowing: after reverting a merge, git considers that branch "already merged." If you later want to genuinely re-merge it, you'll have to revert the revert first. Just be aware.

You need to undo a git push:

  • Safe way (shared branches): git revert <hash> and push the revert.
  • Rewriting way (only if you're sure no one else has pulled):
git reset --hard <good-hash>
git push --force-with-lease
Enter fullscreen mode Exit fullscreen mode

⚠️ Always use --force-with-lease, never plain --force. --force-with-lease refuses to overwrite if someone else has pushed in the meantime — it stops you from silently destroying a teammate's work. Plain --force is how you cause an incident.

Someone force-pushed over your work:

git reflog
# find your last good commit before their overwrite:
git reset --hard HEAD@{n}
Enter fullscreen mode Exit fullscreen mode

Your local reflog still has your version, even if the remote was overwritten. (This is one more reason the reflog is a lifesaver.)


Part 5 · Other lifesavers

You dropped a stash you needed:

git fsck --unreachable | grep commit
# check candidates with git show <hash>, then:
git stash apply <hash>
Enter fullscreen mode Exit fullscreen mode

Dropped stashes are orphaned, not gone — for a while.

You're mid-merge / mid-rebase / mid-cherry-pick and it's a mess — bail out:

git merge --abort
git rebase --abort
git cherry-pick --abort
Enter fullscreen mode Exit fullscreen mode

These return you cleanly to the state before you started the operation.

You want to peek at an old version of a file without losing your current work:

git restore --source=<commit-hash> <file>
Enter fullscreen mode Exit fullscreen mode

Or view it without changing anything: git show <commit-hash>:<file>.

You ran git clean and deleted untracked files:
Here's the one honest hard limit in this guide: git clean deletes untracked files for good. They were never in git, so git can't bring them back. (Editor local-history or your OS trash are your only hope.) This is the command to run carefully — always preview first with git clean -n.


Before you panic: the survival rules

Print these on the inside of your eyelids:

  • Stop. Don't run more commands blindly. Panicked flailing is how people lose the work a second time, for good. Read this, then act deliberately.
  • Recover fast. Reflog entries expire — roughly 30 days for unreachable commits, 90 for reachable ones — and git gc can prune them earlier. Don't sit on it for a week.
  • The reflog is local and per-clone. It only knows what this copy did. It won't help in a fresh clone, and you can't recover a teammate's mistake from your machine.
  • When in doubt, make a backup branch before trying anything risky:
  git branch backup-before-i-do-something-scary
Enter fullscreen mode Exit fullscreen mode

Costs nothing, and it's a guaranteed anchor to come back to.


The takeaway

The scariest thing about git was never the mistakes. It's not knowing they're reversible — that gap between hitting Enter and remembering the reflog exists, where it feels like you just deleted hours of your life.

But now you know the secret the whole guide is built on: git moves references, it rarely destroys work, and almost everything is recoverable if you act before garbage collection does. A bad reset, a deleted branch, a botched rebase, an accidental merge, even a force-push — they're one git reflog away from being fixed.

Bookmark this. The next time your stomach drops, you'll have the fix in ten seconds instead of ten frantic browser tabs. And you'll be the person in the room who calmly says the one word everyone needs to hear:

Reflog.


What's the git disaster that taught you the reflog exists? Mine was a git reset --hard on the wrong branch — three hours of work vanished from the log, heart in my throat — until a senior dev glanced over and said one word that brought it all back. What's your story?

Top comments (0)