arthrop0d

Brief articles on varied topics.

Git Reflog and Recovering Lost Commits

git

Git’s reflog is a local record of where Git references—such as branches and HEAD—have pointed over time. It records the previous and new commit IDs for each movement, allowing you to find commits that are no longer reachable from a current branch.

The problem the reflog solves

A Git branch is not a container of commits. It is a reference, or ref: a movable name that points to one commit, called the branch tip. The commits before that tip are found by following each commit’s parent links.

For example, a branch might point to this history:

A -- B -- C   main

If you reset the branch to B, the branch now points somewhere else:

A -- B   main
     \
      C

Commit C still exists in Git’s object database, but no longer belongs to the history reached by walking from main. Without another reference to it, finding C by its full commit ID could be difficult.

Operations such as reset, rebase, deleting a branch, and checking out a different branch can all make a commit disappear from the history you normally see. The reflog exists as a local safety net for these movements.

The commit history answers “which commits are ancestors of this reference?” The reflog answers “where has this reference pointed recently?”

What a reflog records

A reflog entry records at least:

HEAD has a reflog in an ordinary working repository. Branches can have reflogs too. HEAD is Git’s name for the commit currently checked out; when you are on a normal branch, it usually points indirectly to that branch.

Display the reflog for the current HEAD with:

git reflog

A typical entry looks conceptually like this:

9fceb02 HEAD@{0}: reset: moving to HEAD~1
3a7bd91 HEAD@{1}: commit: Add request timeout

The value on the left is the new commit ID for that entry. The selector HEAD@{1} means “the value of HEAD one reflog entry ago.” Reflog selectors can also use time descriptions, such as HEAD@{yesterday}, when Git can match the requested time.

To inspect a branch’s reflog directly, name its ref:

git reflog show main

The entries are shown newest first. A normal commit moves the branch tip forward, while a reset or rebase may move it to a commit that is not a descendant of its previous tip. The reflog records both kinds of movement; it does not judge whether the movement was sensible.

Recovering a moved branch tip

Suppose you run:

git reset --hard HEAD~3

The branch now points three commits earlier, and --hard also changes the working files to match. First inspect the reflog:

git reflog

You might find that the previous tip is available as HEAD@{1}. Rather than immediately changing the branch again, create a temporary recovery branch at that position:

git branch recovered HEAD@{1}

Now recovered points to the old tip. You can inspect its history, compare it with the current branch, and decide whether to merge it, reset the original branch, or copy selected commits.

If you know the commit ID directly, the equivalent is:

git branch recovered 3a7bd91

A reflog is especially useful after a rebase. A rebase creates rewritten commits with new IDs and moves the branch to the rewritten history. The old branch tip and its older commits may no longer be reachable from that branch, but the reflog usually contains the position from before the rebase. Creating a branch at that old position lets you recover or compare the original history.

The same technique can recover a branch that was deleted, provided the commit still appears in a remaining reflog—for example, the HEAD reflog from when you last had that branch checked out. Searching all visible reflogs can help:

git reflog show --all

Reflogs are local and temporary

Reflogs are normally stored in the local repository. They are not part of commits and are not normally transferred when you push. A reflog on your workstation does not mean a colleague or a remote Git server has the same record.

Reflogs also do not preserve commits forever. Git eventually removes old reflog entries, and garbage collection may delete commit objects that are no longer reachable from any ref or retained by a reflog. Expiration is configurable; typical Git installations retain reachable reflog entries longer than entries for commits that are otherwise unreachable.

That makes the reflog a recovery mechanism, not a permanent archive. If a reflog entry still names the lost tip, create a normal branch or tag pointing to it as soon as you find it:

git branch rescue HEAD@{4}

Once rescue points to the commit, that history is reachable through an ordinary ref and will no longer depend only on the reflog’s retention period.

The practical rule is simple: when a branch tip moves unexpectedly, do not start by hunting through every commit object. Run git reflog, identify the earlier tip, and give it a durable name before Git’s cleanup process has a chance to remove it.

← All articles