Git Reflogs: A Guide to Rescuing Your Lost Work

Introduction

Have you ever experienced one of those heart-stopping moments when something goes horribly wrong with your Git history? Maybe you accidentally deleted a branch, performed a hard reset, or messed up a rebase, and now it feels like all your hard work has vanished into thin air. Panic sets in as you scramble to recover what seems irrecoverably lost.

But fear not, for Git has an amazing, yet overlooked feature that can come to your rescue: git reflog. This Git command can be your saving grace. It can help you to recover lost commits, deleted branches, and changes that you thought were gone forever.

In this post, we’ll delve into the git reflog command, explore how it works, and demonstrate how it can save your life (or at least your code) when disaster strikes. Let’s dive in!

What is git reflog?

git reflog is a powerful Git command that manages the information recorded in the so-called "reflogs" (reference logs). Reflogs keep track of when the tips of branches and other references, such as branches or tags, were updated in the local repository. Essentially, every time a reference like HEAD changes, it’s recorded in the reflog.

You might be familiar with the git log command, which shows a list of commits on the current branch. git reflog, instead, provides a list of times when a reference was changed. This includes actions such as checking out from a branch, performing a merge or rebase, resetting commits, pushing a branch to a remote repository, or making changes that updated HEAD or other references.

The main purpose of git reflog is to track changes that aren’t part of the regular commit history. This can be incredibly helpful when you need to recover a lost commit, redo a change after an "undo", or track down the state of your repository at a particular point in time.

Note: git reflog is an alias to the git log -g --abbrev-commit --pretty=oneline command. Although git log lists commits by default, it can be used to retrieve the reference logs as well. See the git log documentation.

Using git reflog

The git reflog command takes various subcommands, but in this post, we’ll focus on the show subcommand. show is the default subcommand when you run the command git reflog without any subcommands. This command takes a reference as an argument. If you don’t provide any reference, it defaults to HEAD. Additionally, git reflog show accepts any of the options accepted by git log.

Example usages:

$ git reflog # lists all recent actions performed on HEAD
$ git reflog refs/remotes/origin/main # lists all recent actions performed on the remote main branch

Reflogs specify the old value of a reference, making them useful to various other Git commands. They allow us to perform different actions such as navigating to a specific state in the repository history or recovering lost commits. For example, HEAD@{3} means "where HEAD was two moves ago". If you want, you can check out this reference value by using the command git checkout HEAD@{3}, or even hard reset the current state to that point by using git reset --hard HEAD@{3}.

Below, we’ll explore some common scenarios where reflogs might come in handy and how to use them to recover from accidental disasters.

How to use git reflog

Let’s understand how to use reflogs by describing a few common scenarios where you might need them. First, let’s create a simple Git repository to play around with.

$ mkdir git-reflog-example && cd git-reflog-example # create directory and enter it
$ git init # creates a new Git repository in the current directory
$ echo "example file in main" > example.txt # creates example file in main branch
$ git add . && git commit -m "add example.txt" # adds and commits the file
$ git checkout -b example-branch # creates a new example branch
$ echo "very important content" > very-important-file.txt
$ git add . && git commit -m "very important commit" # adds a very important file
$ git checkout main

Recovering from Accidental Branch Deletion

Imagine you’re pruning long-stale branches in your local repository and end up deleting a very important branch that you forgot to push. Don’t panic! git reflog can help you recover it.

First, let’s [accidentally] delete the example-branch:

$ git branch -D example-branch

Now, with git reflog, we can look for the commit hash where your branch pointer was last visible. The output will look like this:

$ git reflog
b58145c (HEAD -> main) HEAD@{0}: checkout: moving from example-branch to main
b5b3286 HEAD@{1}: commit: very important commit
b58145c (HEAD -> main) HEAD@{2}: checkout: moving from main to example-branch
b58145c (HEAD -> main) HEAD@{3}: commit (initial): add example.txt

The commit hash (b5b3286 in this example) is the one we’re looking for. It’s the last commit we made to the deleted branch.

Once we’ve identified the commit hash, we can recreate the branch at that specific commit:

$ git checkout -b example-branch b5b3286
# Alternatively, we can use the following command
$ git checkout -b example-branch HEAD@{1}

Now, let’s verify the commits with git log:

$ git log --oneline
b5b3286 (HEAD -> example-branch) very important commit
b58145c (main) add example.txt

And it’s back!

By using git reflog, we’ve successfully recovered a deleted branch and restored access to its commits and history.

Hard Reset Recovery: Redo an Undo

Now, imagine you’ve made a few commits and then decided that you no longer needed them. So, you use the git reset --hard command to undo your changes to a previous commit. Let’s run the following commands in our example:

$ git checkout example-branch
$ touch one-more-file.txt
$ git add . && git commit -m "one more commit"

Now, our commit history should look similar to this:

$ git log --oneline
dd7c37e (HEAD -> example-branch) one more commit
b5b3286 very important commit
b58145c (main) add example.txt

First, we hard reset some commits that seem unimportant:

$ git reset --hard HEAD~2 # HEAD~2 means we are going two commits back
$ git log --oneline
b58145c (HEAD -> example-branch, main) add example.txt

Now, we notice that those commits we just undid were actually important and now they’re gone! Similar to the deleted branch case, we can use git reflog to save us:

$ git reflog
b58145c (HEAD -> example-branch, main) HEAD@{0}: reset: moving to HEAD~2
dd7c37e HEAD@{1}: commit: one more commit
b5b3286 HEAD@{2}: checkout: moving from main to example-branch
b58145c (HEAD -> example-branch, main) HEAD@{3}: checkout: moving from example-branch to main
b5b3286 HEAD@{4}: commit: very important commit

So, how can we "redo" a previously "undone" commit or commits? It depends on what you want to accomplish:

  • Revert the entire repository state to what it was before the hard reset:

    $ git reset --hard <commit hash> # This will reset the current branch to the specified commit, restoring the repository state.
  • Recover specific files from a lost commit in your working directory without altering history:
    $ git checkout <commit hash> -- </path/to/your/file> # This will restore the specified files from the specified commit to your working directory.
  • Replay specific commits onto your current branch without changing the state of the entire repository:
    $ git cherry-pick <commit hash> # This will apply the changes from the specified commits to your current branch.

For our example, let’s go with the first option:

$ git reset --hard dd7c37e
# Alternatively, you can use the following
$ git reset --hard HEAD@{1}

Finally, checking our commit history:

$ git log --oneline
dd7c37e (HEAD -> example-branch) one more commit
b5b3286 very important commit
b58145c (main) add example.txt

They’re back again!

Undoing a rebase

Imagine you’re working on a feature branch and you decide to rebase it onto main to incorporate the latest changes. During the rebase, some conflicts occur, and after resolving them, you realize that the rebase didn’t go as expected and you want to undo it, restoring the current branch to its previous state. Reflogs can also help you in this scenario.

Let’s run the following commands to update our example repository:

$ git checkout main
$ touch example2.txt
$ git add . && git commit -m "one more commit in main"
$ git checkout example-branch

First, we do the rebase and check our commits history:

$ git rebase main
$ git log --oneline
9c706b2 (HEAD -> example-branch) one more commit
1ada882 very important commit
e48b7f6 (main) one more commit in main # this commit was added by the rebase
b58145c add example.txt

Then, we realize we must undo the rebase. First, we need to identify the commit before the rebase. We can check our reflogs for that:

$ git reflog
d8983f9 (HEAD -> example-branch) HEAD@{1}: rebase (pick): one more commit
7160dea HEAD@{2}: rebase (pick): very important commit
e48b7f6 (main) HEAD@{3}: rebase (start): checkout main
dd7c37e HEAD@{4}: checkout: moving from main to example-branch

dd7c37e is the commit hash we’re looking for.

Now, we can reset the branch state to the commit before the rebase:

$ git reset --hard dd7c37e
# Alternatively, you can do the following
$ git reset --hard HEAD@{4}

Checking our commit history:

$ git log --oneline
dd7c37e (HEAD -> example-branch) one more commit
b5b3286 very important commit
b58145c add example.txt

We’ve successfully undone our rebase and restored the repository to its previous state before the rebase.

Caveats

While git reflog is a powerful tool for recovering from mistakes, there are some important caveats to keep in mind:

  • HEAD changes only: HEAD changes when you make commits, check out branches, and undo commits with commands like git reset. That means reflogs can’t help to recover uncommitted changes.

  • Reflog entries don’t last forever: by default, Git prunes reachable reflog entries older than 90 days. Entries that are not reachable from the current tip are pruned when they turn older than 30 days. So, don’t expect to find months-old commits lying around forever. Although it’s possible to turn off the reflog entries expiration, it’s not recommended.

  • Your reflog entries are yours and yours alone: it’s not possible to use git reflog to recover another developer’s unpushed commits.

Conclusion

Mistakes are inevitable. Whether it’s a misjudged rebase, a branch deleted too soon, or a commit that gets lost in the shuffle, git reflog can be your lifesaver. By keeping a record of reference changes, git reflog provides a powerful way to track and recover your work.

In this post, we’ve explored how git reflog can help you undo a rebase, recover lost commits, and even restore deleted branches. We also highlighted important caveats, such as the necessity of reference changes for reflog entries, the expiration of these entries, and the fact that reflog is local to your repository.

Understanding and leveraging git reflog can significantly improve your ability to manage and recover your work. So next time you find yourself in a Git-induced panic, remember that git reflog might just be the tool you need to save the day.

We want to work with you. Check out our "What We Do" section!