Live streamJoin us for "State of code review 2024" on July 18thRegister today
Background gradient

As a software engineer, you’ve probably wondered how Git’s reflog works under the hood. You (hopefully) won’t need to use reflog daily, but you’ve likely found yourself in a situation where your Git state is seemingly borked beyond repair at least once before. Engineers are (mostly) human, and Git mistakes can happen even with safeguards like pre-commit hooks in place. That’s where this guide comes in.

Today, we’ll be taking a look at ways to fix these mistakes with the power of git reflog. With reflog you can navigate the entire granular history of your Git repository to identify and fix any errors that find their way into your codebase.

The Git reflog is a reference log file that stores a chronological list of all changes made to the HEAD pointer in your Git repository. HEAD always points to the most recent commit, and the reflog essentially tracks every previous commit ever made in the repo.

Think of the reflog as a logbook for your Git branches and other references. It records every change made to these references, enabling you to rewind time and undo any potentially unwanted actions. This can be incredibly helpful when you:

  • Accidentally commit something you shouldn't have.

  • Delete a commit or branch by mistake.

  • Need to recall what happened to a specific reference at a particular point in time.

This list is, of course, not exhaustive - I’m sure there are thousands of other mistakes you could possibly fix using reflog.

Locating the reflog:

To start, let’s actually take a look at the reflog and its various components.

Every repo has its own reflog stored in the .git folder at the root of your repository. In the .git directory, the reflog is stored in logs folder and is updated any time you make any changes to any branch (or other reference, like HEAD)

While the raw log contains a lot of useful info, it’s pretty ugly. Let’s look at how we can sift through the log and navigate it a little better.

Every action you take in Git is timestamped in the reflog, so, when you want to explore specific moments in your Git history, you can easily filter by various periods of time with “time qualifiers”.

These tokens serve as filters for navigating your reflog and allow you to specify a timeframe and view changes made within that window.

Here are a few examples:

  • @{1.minute.ago}: See the state of your repository just a minute ago.

  • @{1.hour.ago}: Rewind your Git journey by one hour.

  • @{}: Compare your current work with yesterday's progress.

  • @{yesterday}: Travel back to yesterday's snapshot of your repository.

  • @{1.week.ago}: Analyze changes made a week ago.

  • @{1.month.ago}: Recall the state of your project a month back.

  • @{1.year.ago}: Explore the evolution of your work over a year.

  • @{2023-10-21.08:30:00}: Jump to a specific date and time in your Git history.

Combining and stacking time qualifiers**:**

  • @{}: Refine your search to a specific time within the past day.

  • @{5.minutes.ago}: Plural forms allow for even more precise navigation.

Using time qualifiers with other Git commands**:**

  • @{git diff main@{0} main@{}}: Compare your current main branch with its state one day ago.

  • @{git log master@{1.month.ago..yesterday}}: View all commits made to the master branch between one month ago and yesterday.

You can se the see a detailed history of changes made to the current branch (HEAD) or any other reference you specify using this command in your terminal:

git reflog

To check if a particular entry exists in the reflog, you can use:

git reflog exists <rev>

You can also specify a specific point in the reflog using keywords like

  • HEAD@{2} (two revisions back)

  • master@{one.week.ago} (master's state one week ago).

This allows you to checkout that specific point and view the state of the world at that point in time any subsequent changes.

Because the reflog adds entries for every git event in your repo, it can get large very quickly. Luckily however, git has garbage collection built right in. The way this works is that many common Git commands silently run additional plumbing commands under the hood to prune the reflog. Two of these plumbing subcommands are expire and delete.


Expire prunes older reflog entries.

git reflog expire

You can also manually remove old entries based on specified timeframes:

git reflog expire <time>
git reflog expire --expire-unreachable <time>

You can use the following options to customize the usage of this command:

  • --all: Process reflogs of all references.

  • --single-worktree: Limit processing to the current working tree only.

  • --expire=<time>: Prune entries older than a specified time.

  • --expire-unreachable=<time>: Prune unreachable entries older than a specified time.

  • --updateref: Update the reference to the top reflog entry if the previous top entry was pruned.

  • --rewrite: Adjust the "old" SHA-1 of reflog entries if their predecessors are pruned.

  • --stale-fix: Prune entries referencing "broken commits".

  • --dry-run: Show what would be pruned without actually deleting any entries.

  • --verbose: Display additional information on the screen.

To remove an individual entry from the reflog:

git reflog delete <entry>

You can use the following options to customize the usage of this command:

  • --updateref: Update the reference to the top reflog entry if the previous top entry was pruned.

  • --rewrite: Adjust the "old" SHA-1 of reflog entries if their predecessors are pruned.

  • --dry-run: Show what would be pruned without actually deleting any entries.

  • --verbose: Display additional information on the screen.

While these commands are important to understand, as an enduser you will typically never run them yourself as Git will run them under the hood.

Imagine you're working on a feature branch in your project, and you initiate a rebase onto the main branch. However, something goes wrong during the rebase process—perhaps due to conflicts or incorrect conflict resolutions—and you end up with a feature branch that's in a worse state than before. This is a common situation that many developers face. Fortunately, git reflog can be a lifesaver in such instances, allowing you to restore your feature branch to its state before the rebase.

Here's how to fix a bad rebase:

1. Identify the Commit Before the Rebase:

To undo the effects of a bad rebase, first find the commit hash where your feature branch was before the rebase. The git reflog command will help you with this:

git reflog show <your-feature-branch>

This command shows a list of recent commits on your feature branch, including the state before the rebase. Look for the commit right before the rebase started.

Example output of the reflog might look like this:

52896f49 (HEAD -> feature) feature@{0}: rebase: Added new feature post-rebase
7896dbe feature@{1}: commit: Pre-rebase commit
43672ac feature@{2}: commit: Another feature update
987fd12 feature@{3}: commit: Initial feature work

Here, 7896dbe is the commit before the rebase started.

2. Revert to Pre-Rebase State:

Once you’ve identified the correct commit, use the following command to reset your feature branch to that state, effectively undoing the rebase:

git reset --hard 7896dbe

Replace 7896dbe with the actual commit hash you identified. This resets your feature branch to how it was before the problematic rebase.

3. Verify the Branch State:

After running the git reset command, check that your feature branch is back to its desired state:

git log

The top commit should now be the one you identified as the last correct commit before the rebase.

Remember that while the reflog is a powerful tool, it's not a substitute for good engineering practices. Practicing safe Git habits, and by making frequent small pull requests you can minimize the need for reflog interventions.

In a worst case scenario however, know thatreflog can be your saving grace.

Give your PR workflow
an upgrade today

Stack easier | Ship smaller | Review quicker

Or install our CLI.
Product Screenshot 1
Product Screenshot 2