Skip to article

Undo git reset --hard: Recover Commits with Reflog

Recover committed work after git reset --hard. Find the lost tip with git reflog and restore it on a rescue branch in a disposable browser VM.

You ran git reset --hard, and the last two commits disappeared from your branch's log. The working files also went back to an older version. If those changes were committed, the branch moving backward does not necessarily mean the commits have vanished from the local repository.

Use git reflog to find the previous tip, inspect that commit, and create a rescue branch pointing at it. Creating the branch preserves the snapshot without resetting whatever branch you currently have checked out.

Reflog records local reference updates. The HEAD reflog includes movements caused by operations such as commit, checkout and reset. That gives you a short history of where your local HEAD pointed, which is a different question from “which commits are ancestors of my current branch?”

The VM starts with three small commits in recovery-lab. You will deliberately reset this disposable repository, recover the old tip and inspect both restored files. This exercise affects only the guest VM. It does not provide a recovery tool for files on your computer, and it is not a promise that uncommitted work is recoverable.

01

Make the committed snapshots disappear from main

First check that the working tree is clean, then inspect the last three commits. The newest commit adds tests.txt; its parent adds shipping.txt; the initial commit contains only README.md.

Resetting to HEAD~2 moves main to the initial commit and updates tracked files to that snapshot. The new log shows only the initial commit because the other two are no longer ancestors of main. They are not shown by --all either: no remaining branch or tag points to their newer tip in this fixture.

The important distinction is between a reference becoming unreachable through a branch and an object being removed from the repository. Reset changes the reference immediately. Object cleanup is a separate process with retention rules. That separation is what makes this local recovery possible.

Do not use the hard reset command below as a diagnostic step in your own damaged repository. We run it here only to create a known accident in a fresh VM. If you are already recovering real work, skip directly to inspecting the reflog, avoid cleanup commands, and preserve a copy of the repository before experimenting further.

↳ TRY IT / CURRENT IMAGELive Git
cd /home/student/recovery-lab
git status --short
git log --oneline -3
git reset --hard HEAD~2
git log --oneline --all

Expected: main now shows Initial checkout service. shipping.txt and tests.txt are absent from its working tree.

 

02

Find, inspect and name the old tip

Read the reflog's action labels, not just its row numbers. The newest entry records the reset. In this controlled sequence, HEAD@{1} is the tip immediately before that reset, whose subject is Add checkout tests. git show --stat lets you inspect it before attaching a new branch name.

Then create rescue at that entry. Branch creation does not switch the working tree, so main stays at the initial commit. You have now given the recovered tip a stable, descriptive name. Its ancestor containing shipping.txt comes with it; recovering the latest snapshot does not require rebuilding each parent commit separately.

The quotes around 'HEAD@{1}' keep the shell from interpreting the revision notation. In a real recovery, copy the hash of the verified commit once you identify it. Reflog positions change as new actions are recorded, so HEAD@{1} is not a universal shortcut for “the commit I lost.” A checkout or another reset changes what that position means.

You can also inspect a branch's own reflog with git reflog show main. If the loss followed a rebase, identify the pre-rebase tip and inspect its content rather than assuming the most recent entry is correct. If cherry-picking a recovered commit onto current work is more appropriate, keep the rescue branch first and perform that integration as a separate step.

↳ TRY IT / CURRENT IMAGELive Git
cd /home/student/recovery-lab
git reflog -6
git show --stat 'HEAD@{1}'
git branch rescue 'HEAD@{1}'
git log --oneline rescue -3

Expected: rescue points to Add checkout tests and also contains Add shipping calculator.

 

03

Verify the recovered files before changing any other branch

Switch to rescue and read both files. The contents should match the snapshots committed before the reset. The final status should be clean. You have recovered a usable branch without moving main again.

At this point you can review the changes, run the project's tests and decide how to integrate them. You might keep rescue as the working branch, merge selected work into a new branch, or cherry-pick individual commits. None of those decisions requires immediately hard-resetting an important branch to the recovered hash.

Reflog has limits. Entries are local to this repository, are not transferred by push or clone, and can expire. Objects that become unreachable can eventually be pruned. Another clone may have different evidence, or none. Avoid git gc --prune=now, reflog-expiration commands and other cleanup while you are trying to recover a lost snapshot.

Most importantly, reflog records reference movements, not a continuous backup of your working directory. A hard reset can destroy tracked edits that were never committed. Staged blobs or an editor's local history sometimes provide separate recovery avenues, but reflog alone cannot reconstruct arbitrary unsaved work. An untracked file is not automatically protected either, particularly if its path obstructed the checkout.

After verifying recovery, keep important work on a named branch and use a remote backup where appropriate. A reflog is a useful safety net, but its retention and local scope make it unsuitable as your only backup strategy.

↳ TRY IT / CURRENT IMAGELive Git
cd /home/student/recovery-lab
git switch rescue
git log --oneline -3
cat shipping.txt tests.txt
git status --short

Expected: The recovered files say Shipping is free above 50. and Check the free-shipping boundary.; status is clean.

 

Recover first, integrate second

Find the old tip in reflog, inspect the snapshot, create a rescue branch, and verify the files. Move or merge other branches only after you know what you recovered. Repeat the exercise with Reset if you want to watch the same sequence again; rerunning only the destructive card changes the reflog positions.

Git documents reflog behavior and expiration, reset modes and a complete data-recovery walkthrough. For a different kind of branch mistake, try moving commits to another branch. The essential habit is to give recovered work a name before making the next repair.