Great work!

XP to next level

BugEater
EN

Recovering a Lost Commit or a Deleted Branch

Learning Objectives

By the end of this lesson you will be able to:

  • Recover commits removed by a git reset --hard
  • Restore a branch deleted with git branch -D
  • Undo a completed rebase or merge
  • Rescue commits made in a detached HEAD

Recovery 1: A Bad reset --hard

You meant HEAD~1 and typed HEAD~3. Three commits are off the branch.

git reflog
e2b90d1 HEAD@{0}: reset: moving to HEAD~3
1a77c60 HEAD@{1}: commit: test: add timezone cases      ← the branch you want
9d3e072 HEAD@{2}: commit: test: add currency cases

Verify, then restore:

git log --oneline 1a77c60 -5
git reset --hard 1a77c60

Done. All three commits are back, and the branch is exactly as it was.

If it happened just now with nothing in between, the short form works too:

git reset --hard ORIG_HEAD

Recovery 2: A Deleted Branch

git branch -D test/PAY-4417
Deleted branch test/PAY-4417 (was 1a77c60).

Notice Git prints the commit ID in the confirmation. If that line is still in your terminal, you already have everything you need:

git branch test/PAY-4417 1a77c60

The branch is back, pointing at exactly the same commit.

If the terminal is gone, the reflog has it. git reflog shows HEAD moves, so look for where you last left that branch:

git reflog | grep PAY-4417
1a77c60 HEAD@{7}: commit: test: add timezone cases
e2b90d1 HEAD@{8}: checkout: moving from test/PAY-4417 to main

The checkout: moving from test/PAY-4417 to main line is the moment you left it. The commit above is the last thing you did on it:

git branch test/PAY-4417 1a77c60
git switch test/PAY-4417
git log --oneline -5

Recovery 3: An Unwanted Rebase or Merge

Both write ORIG_HEAD before they start:

git reset --hard ORIG_HEAD

That is the whole recovery, if nothing else has run since. ORIG_HEAD is overwritten by the next big operation, so if you have merged or rebased again in between, go to the reflog and find the entry below rebase (start) or merge.

Recovery 4: Commits Made in Detached HEAD

You checked out a tag to reproduce a bug, made two commits while investigating, then switched back to main:

Warning: you are leaving 2 commits behind, not connected to
any of your branches:

  4c1f8ab test: minimal reproduction of PAY-4417
  1a77c60 test: add failing assertion

Git warns you and prints the IDs, which is generous. If you missed it:

git reflog
e2b90d1 HEAD@{0}: checkout: moving from 4c1f8ab to main
4c1f8ab HEAD@{1}: commit: test: minimal reproduction of PAY-4417   ← there they are

Give them a branch:

git branch repro/PAY-4417 4c1f8ab

Now they are anchored and safe. This is the one recovery that is genuinely time-limited — detached commits are unreachable from the moment you leave them, so they fall under the 30-day unreachable expiry rather than 90.

The Universal Method

Every recovery in this lesson is the same four steps:

  1. git reflog — find the operation that lost it
  2. Take the entry directly below that operation
  3. git log --oneline <sha> -5 — verify it is what you think
  4. git branch <name> <sha> or git reset --hard <sha> — recover it

Prefer git branch when you are unsure. It creates a new name pointing at the commits and changes nothing else — the safest possible way to bring something back while you check whether it is right. reset --hard when you are certain and want the current branch itself moved.

When the Reflog Has Nothing

A last resort, for the rare case where the commit is not in the reflog at all — a stale clone, an expired entry:

git fsck --lost-found
dangling commit 4c1f8ab8e94b7d0c1f5a2836b4c9d0e1f2a3b4c5d

This scans the object database for objects nothing references. It gives you IDs with no context, so you inspect each with git show. Slow and clumsy, and it works when nothing else does.

Pro Tip: Before deleting a branch you are only fairly sure is finished, git tag archive/PAY-4417 test/PAY-4417. Tags never expire and are not garbage-collected, so the commits are permanently safe. Delete the tag in six months when nobody has asked.

Key Takeaways

  • Four steps every time: reflog, the entry below, verify, recover
  • git reset --hard ORIG_HEAD undoes a just-finished rebase, merge or reset
  • git branch -D prints the commit ID — recover with git branch <name> <sha>
  • Find a deleted branch in the reflog by the checkout: moving from <branch> line
  • Detached-HEAD commits are recovered by giving them a branch, and expire sooner (30 days)
  • Prefer git branch over reset --hard while you are still verifying
  • git fsck --lost-found is the last resort when the reflog has nothing

Quiz

git branch -D test/PAY-4417 prints "(was 1a77c60)". What is the fastest recovery?

Which reflog line tells you where a deleted branch was last left?

Why do commits made in detached HEAD expire sooner than others?

While verifying a recovery candidate, why prefer git branch over git reset --hard?