Arezgitfield notes / engineering
Git workflowsUPDATED AUG 18, 2026

Recover Lost Commits with Git Reflog

A recovery-first method for finding displaced Git commits, preserving them with a rescue branch, and verifying the result without rewriting more history.

AREZGIT / FIELD NOTEGIT WORKFLOWS
A reset, rebase, or deleted branch can make a commit disappear from the normal log even though the commit object still exists locally. The safest response is not another reset. It is to stop
READ / VERIFY / APPLYTECHNICALLY REVIEWED

A reset, rebase, or deleted branch can make a commit disappear from the normal log even though the commit object still exists locally. The safest response is not another reset. It is to stop changing references, identify the displaced object through the reflog, attach a new branch to it, and only then decide how to integrate the recovered work.

This process recovers commits that Git recorded. It does not promise recovery of untracked files, ignored files, or edits that were never staged or committed.

Distinguish a lost reference from lost content

Branches are movable names that point to commits. Deleting or moving a branch changes the name; it does not immediately rewrite the commit object. Git also keeps local reference logs that record where branch tips and HEAD pointed during recent operations. The official git reflog documentation describes entries such as HEAD@{2} as earlier local values of a reference.

That boundary matters. The reflog can help after operations such as:

  • resetting a branch to the wrong commit;
  • rebasing and then wanting the pre-rebase tip;
  • deleting a local branch whose commits were not merged;
  • switching away from a detached HEAD commit;
  • amending a commit and needing its previous version.

It cannot reconstruct bytes that Git never stored. Before doing anything else, determine whether the missing work was committed, staged, merely edited, or untracked. A reflog investigation is appropriate for the first category and sometimes helps with staged content if another Git object captured it, but it is not a general filesystem undelete tool.

Freeze the repository state before searching

Run the inspection in the affected repository and avoid commands that expire objects, rewrite references, or clean files. Start by recording the current state:

git status --short --branch
git log --oneline --decorate -n 12
git reflog --date=iso --all

git reflog --all includes reflogs for available local references instead of showing only HEAD. The ISO dates help correlate an entry with the operation that displaced the commit, but the object ID is the identity that matters. Messages such as reset: moving to, rebase (finish), commit (amend), and checkout: moving from provide context; they are not proof that an entry contains the desired change.

Do not run git gc, manually expire reflogs, reclone over the directory, or continue a history rewrite while investigating. Reflog entries and unreachable objects are retained for limited periods governed by repository configuration and maintenance. Recovery should be treated as time-sensitive.

Inspect candidates without moving a branch

Use read-only commands against a candidate object ID before attaching any name to it. Replace the synthetic placeholder below with an abbreviated ID printed by your reflog:

git show --stat --oneline a1b2c3d
git show --format=fuller --no-ext-diff a1b2c3d
git diff a1b2c3d^ a1b2c3d

The first command confirms the commit summary and affected paths. The second displays authorship, commit timestamps, message, and patch without invoking an external diff tool. The third makes the parent comparison explicit. For a root commit or merge commit, the parent expression needs different handling, so inspect git show --summary a1b2c3d before assuming a single parent.

Check the content, not just the commit message. A familiar message may belong to an earlier attempt, while the desired version may be an amended successor or a commit created during a detached-HEAD session.

Preserve the object with a rescue branch

Once the candidate is verified, create a new branch rather than moving the original branch immediately:

git branch rescue/payment-timeout a1b2c3d
git show-ref --verify refs/heads/rescue/payment-timeout
git log --oneline --decorate rescue/payment-timeout -n 8

This adds a durable local reference to the commit without changing the working tree, index, or current branch. A rescue branch is deliberately boring: it turns a time-sensitive investigation into an ordinary branch comparison.

If several candidates might contain useful work, preserve each under a distinct name. Storage is less costly than guessing which history is correct. Avoid git reset --hard <candidate> as a recovery shortcut because it simultaneously moves the current branch and replaces tracked working-tree and index content. That creates a second recovery problem if the candidate is wrong.

Compare recovered history with the intended branch

Fetch remote state only if network access and the remote are expected, then compare without merging:

git fetch origin
git log --left-right --cherry-pick --oneline origin/main...rescue/payment-timeout
git diff --stat origin/main...rescue/payment-timeout
git diff origin/main...rescue/payment-timeout

The three-dot comparison uses the merge base and shows what the rescue branch would contribute relative to the target. Confirm that the recovered commits do not include obsolete experiments, secrets, generated artifacts, or changes already integrated under different commit IDs.

Run the project checks from a clean checkout of the rescue branch or from a separate worktree. Passing tests do not prove that the recovered history is the intended history, but failures can reveal missing dependent commits.

Integrate only after the recovery is stable

Choose the least surprising integration method for the repository:

  • Merge the rescue branch when preserving its topology is acceptable.
  • Cherry-pick a small, independent commit when only that patch belongs on the target.
  • Open a normal review from the rescue branch when shared controls should evaluate it.
  • Move a private local branch only when no collaborator or automation depends on its current identity.

Do not force-push recovered history merely because the local branch now looks correct. First compare it with the remote-tracking branch, identify who may have based work on the published history, and use the repository's normal review and protection rules.

Know when reflog recovery has ended

The reflog is local. A fresh clone does not inherit another clone's reflog, and an entry may already have expired or its unreachable object may have been pruned. If the commit is absent locally, check another developer's clone, a CI workspace that is authorized to retain source, an open review, a fork, or a remote branch. Handle those systems according to their access and retention policies.

Recovery is complete when the desired object has a stable reference, its patch and ancestry have been inspected, project checks pass at the appropriate boundaries, and the integration method does not erase someone else's published work. Until all four conditions hold, keep the rescue branch and avoid further rewriting.

READER SIGNAL

Was this guide useful?

Your rating helps us prioritize clearer, more practical technical content.

AREZGIT / DESKTOP WORKSPACEFROM FIELD NOTE TO RELEASE

Review diffs, run checks, and prepare the release in Arezgit.

Keep Git review, security scanning, API checks, database inspection, and release preparation together in one local desktop application.

Explore Arezgit