Skip to content
LogluneStart free

git reflog: Find and Recover Lost Commits

By Loglune

Reflog preserves local records of where HEAD and branches pointed, helping recover a commit no branch currently references.
What each history records
git log
Commits reachable from a branch, shared when you push
git reflog
Every position HEAD and each branch pointed to, local to your clone
Loglune flag history
Every flag edit with time and actor; any time T can be rebuilt

git reflog is a Git command that shows the reference log, a local record of every position HEAD and your branches pointed to, including positions that no branch points to anymore. You use it to find a commit that seems lost after git reset --hard, a rebase, git commit --amend, or a deleted branch, and then you go back to it with git reset, git branch, or git cherry-pick. Each entry has a name such as HEAD@{2} or main@{1.day.ago} that other Git commands accept.

The reflog lives in your clone and is never pushed, and Git prunes entries after 90 days by default, or 30 days for entries no longer reachable from the branch.

This guide is for developers who lost commits in their own clone and need them back. It covers reading the output, HEAD@{n} and time-based names, the recovery steps, git reflog vs git log, and how long entries last. Edits that were never committed are out of scope, because the reflog does not record them.

The last section applies the same idea, looking up a past state by time, to feature flags with Loglune, using output recorded on October 6, 2026.

Reproduce a customer’s flag state

Start free

What git reflog does

Reflogs record when the tips of branches and other references were updated in the local repository. Every time a commit, checkout, reset, merge, rebase, pull, or amend moves HEAD or a branch, Git appends an entry with the old position, the new position, the time, and a short message.

Two examples of the names you can use: HEAD@{2} means where HEAD used to be two moves ago, and master@{one.week.ago} means where master pointed one week ago in this local repository. Git also keeps a separate reflog for the stash, and that reflogs are stored under .git/logs/, for example .git/logs/HEAD and .git/logs/refs/heads/.

Because the reflog records positions rather than branch ancestry, it still lists a commit after git reset, git rebase, or a branch deletion removed that commit from every branch. That is why the reflog is the first place to look when work seems gone.

How to read git reflog output

Run git reflog with no arguments. The default subcommand is show, and it shows HEAD when you name no ref, so git reflog and git reflog show HEAD are the same command.

Each line of the output has 3 parts, newest first. The short hash is the commit HEAD pointed to after that move. The selector, such as HEAD@{1}, is the position counted back from the newest move. The action and message, such as commit: Add search, reset: moving to HEAD~1, or checkout: moving from main to feature, say what moved HEAD and give the commit message or target.

HEAD@{0} is the current position, HEAD@{1} is the position before the last move, and so on. A line such as rebase (start) or reset: moving to marks the operation you may want to undo, and the line just below it shows where HEAD was before that operation.

To quit the reflog, press q. Git shows long output in a pager, and q closes the pager and returns you to the shell. To print without a pager, run git --no-pager reflog.

git reflog commands and options

git reflog show accepts every option of git log, because it is an alias for git log -g --abbrev-commit --pretty=oneline.

Command What it does
git reflog The HEAD reflog
git reflog show main The reflog of the branch main
git reflog show --all The reflogs of all refs
git reflog stash The stash reflog, one line per stash entry
git reflog --date=iso Each selector shown as a timestamp instead of a number
git reflog -n 20 The newest 20 entries
git log -g -p The reflog walk with each commit’s patch
git reflog list Every ref that has a reflog
git reflog exists <ref> Exit status 0 if the ref has a reflog
git reflog expire Prunes old entries; normally run for you by git gc
git reflog delete <ref>@{n} Deletes one entry
git reflog drop <ref> Removes the whole reflog of a ref

End users typically do not run expire and delete, and both can lose data. The list, write, and drop subcommands are in the current reference page and in Git 2.56.0, the version we checked; older Git releases may not have them.

Reflog references: HEAD@{n} and time-based names

Every reflog entry has a name you can pass to other Git commands. Git defines 3 forms.

Form Example Meaning
<ref>@{<n>} main@{1}, HEAD@{5} The n-th prior value of that ref
<ref>@{<date>} main@{yesterday}, HEAD@{5 minutes ago} The value of that ref at a prior point in time
@{-<n>} @{-1} The n-th branch or commit checked out before the current one

Time-based names answer “where was my branch at that time”. Time qualifiers include 1.minute.ago, 1.hour.ago, 1.day.ago, yesterday, 1.week.ago, and an absolute timestamp like 2011-05-17.09:00:00. They can be combined, as in 1.day.2.hours.ago. Use git diff to compare a branch with its state one day ago.

git diff main@{0} main@{1.day.ago}

A date name has a limit: it looks up the state of your local ref at a given time, for example what was in your local master branch last week. To find commits created during a time window, use git log --since and --until instead.

How to use git reflog to go back

Every recovery follows the same 3 steps.

  1. Run git reflog and find the entry just before the operation you want to undo.
  2. Check that entry with git show HEAD@{n} or git log --oneline HEAD@{n}.
  3. Move to it with git reset, or keep your current state and create a branch at it.

Creating a branch first is the safer default, because it changes nothing on your current branch:

git branch rescue HEAD@{2}

Undo a git reset –hard

After git reset --hard HEAD~1, the newest reflog entry is the reset itself, and HEAD@{1} is where the branch was before it.

git reset --hard HEAD@{1}

Right after a reset, git reset --hard ORIG_HEAD does the same, as long as no other command changed ORIG_HEAD since. The git reset guide covers the modes of git reset in detail.

Undo a rebase

A rebase writes a rebase (start) entry and a rebase (finish) entry. The entry just below rebase (start) is your branch before the rebase. Reset to that entry to bring back commits that were squashed.

git reset --hard HEAD@{4}

Replace 4 with the number of the entry below rebase (start) in your output.

Recover a deleted branch

When you delete a branch with -d or -D, its reflog is deleted too. The HEAD reflog still lists the commits you made while that branch was checked out. Find the last commit of the branch in git reflog and create the branch again at that commit.

git branch feature-search <hash>

Undo git commit –amend

An amend replaces the last commit with a new one, and the reflog keeps the old one as HEAD@{1}. git reset --soft HEAD@{1} moves the branch back to the commit before the amend and keeps the amended changes staged.

Restore one dropped commit

To bring back one commit without moving your branch, cherry-pick it. This works after dropping the wrong commits in an interactive rebase: take the hash from the reflog line and run git cherry-pick <hash>.

git reflog vs git log

git log follows commit ancestry from a commit back to the first commit. git reflog follows the moves of one ref over time in your clone.

git log git reflog
What it lists Commits reachable from HEAD or the refs you name Every position HEAD or a ref pointed to, newest first
Shows commits no branch points to No Yes, until the entries expire
Records checkouts, resets, rebases No Yes
Shared with other clones Yes, commits are pushed and fetched No, the reflog stays in your .git directory

Use git log to read what the project history contains. Use git reflog to find where you were before something went wrong.

How long the reflog keeps entries, and its limits

The git-reflog page gives the defaults for git reflog expire: entries older than gc.reflogExpire are pruned, which “defaults to 90 days”, and entries older than gc.reflogExpireUnreachable and not reachable from the current tip are pruned after 30 days. git gc runs this pruning for you, and after the entries are gone, garbage collection can remove the commits too.

The reflog has 3 more limits. The reflog is local: a new clone does not copy the reflog of the repository it came from, so you cannot recover a teammate’s lost commit from your clone. Edits you never committed are not in the reflog, so a git reset --hard over uncommitted changes cannot be undone with git reflog. Changes you stashed are in the stash reflog, which git reflog stash lists. And the reflog shows where refs pointed, not who changed them in a shared repository. For a pushed commit that needs undoing, use git revert, which adds a commit instead of rewriting history.

A git reflog for feature flags, read by time

The time-based reflog names answer a question that also comes up in production: what was the state at a given time. During an incident, the question is often about configuration rather than code. “What did checkout-v2 serve at 9:00 for this user?” is the reflog question main@{2026-10-06 09:00} asked of feature flags. git reflog cannot answer it for flags that live outside the repository, and it keeps entries for 90 days in one clone.

Loglune stores flag changes as a history that every flag state is rebuilt from. Every flag edit is appended as a change event with a seq number, the actor, the time, and the state before and after. Every publish creates an immutable version with the next number in a sequence that publishing alone advances. The state at any time T is rebuilt from the events up to T, and the same input always gives the same version.

That gives you the 3 reflog moves for flags.

Reflog move in Git Loglune
git reflog lists every move The audit log lists every change event, each with the version that first published it
main@{<date>} names the state at a time Reproducing a time T serves the state live at T and evaluates it for the user you name
git reset --hard HEAD@{n} goes back Rolling back to a published version N publishes a new version with the same contents, and the versions in between stay

The history is an append-only hash chain, and Loglune rejects rewrites and deletions of it. Unlike git reflog expire, nothing prunes the chain except the retention period of your plan.

What the output looks like

We ran the shipped control route on a local test database on October 6, 2026, with an editorial example flag. The run created checkout-v2 with default off and published version 1. It switched checkout-v2 to on and published version 2, then rolled back to version 1, which published version 3.

Version publishedAt Label checkout-v2 default
1 2026-10-06T09:00:02.000Z checkout-v2 created, off off
2 2026-10-06T09:00:04.000Z checkout-v2 on for everyone on
3 2026-10-06T09:00:05.000Z rollback to v1 off

Next, the run reproduced 2 times for the context key user-42, half a second after each publish. At 2026-10-06T09:00:04.500Z, Loglune served version 2 and returned on.

{
  "version": 2,
  "count": 1,
  "evaluations": [
    {
      "flagKey": "checkout-v2",
      "variationId": "on",
      "value": true,
      "reason": {
        "kind": "default"
      },
      "missing": []
    }
  ]
}

At 2026-10-06T09:00:05.500Z, Loglune served version 3 and returned "variationId": "off" with "value": false. The empty missing list means the evaluation read no input that the request left out.

The audit for checkout-v2 listed each change event with the version that first published it, the way git reflog lists each move of HEAD.

seq at default before default after publishedIn
1 2026-10-06T09:00:01.000Z null off 1
2 2026-10-06T09:00:03.000Z off on 2
3 2026-10-06T09:00:05.000Z on off 3

Going back to on worked like resetting to a reflog entry, without losing the entries in between. Rolling back to version 2 returned "version": 4 and "label": "rollback to v2", appended seq 4 setting the default from off to on, and the publish check against version 3 flagged the change.

{
  "kind": "defaultFlip",
  "flagKey": "checkout-v2",
  "before": "off",
  "after": "on",
  "severity": "high"
}

A diff between version 2 and version 4 returned "flags": [], "segments": [], and "configValues": [], so version 4 has the same contents as version 2. The audit after the rollback listed all 4 events, seq 4 with "publishedIn": 4, and ended with the hash chain check.

"chain": {
  "verified": true,
  "count": 4,
  "legacy": 0,
  "brokenAtSeq": null,
  "head": {
    "seq": 4,
    "hash": "2b58c0432041854e4c0babc3d1cddd1bd0077c5f441d5f38a82fb2b259427e46"
  },
  "anchor": "absent"
}

The anchor is absent because the daily anchor had not run on this local test database. A time T outside the retention period of your plan returns 403.

Choosing the recovery

Use git reflog to find where HEAD or a branch was, git reset --hard HEAD@{n} to move back, git branch <name> HEAD@{n} to keep a lost state without moving, and git cherry-pick to bring back one commit. Use a time-based name such as main@{1.day.ago} when you know when rather than how many moves ago. When the state you need is a set of feature flags rather than a commit, Loglune rebuilds the flags live at any time T from its change history and rolls back by publishing a new version, so no entry is lost.

A git reflog for feature flags, read by time

Loglune appends every flag edit to the history as a change event with the actor, the time, and the state before and after, and it rebuilds the flag state live at any time T from that history. Reproducing a time T serves that state and evaluates it for a user you name. Rolling back to a published version N publishes a new version with the same contents, so the versions in between stay. The Free plan costs $0 for 1 seat, keeps 7 days of history, and includes 5 reproductions.