Using the Reflog to Restore Lost Commits

Avatar of Tobias Günther
Tobias Günther on

This article is part of our “Advanced Git” series. Be sure to follow us on Twitter or sign up for our newsletter to hear about future articles!

The “Reflog” is one of Git’s lesser-known features—but one that can be extremely helpful. Some people refer to it as a “safety net,” while I like to think of it as Git’s “diary.” That’s because Git uses it to keep a journal about every movement of the HEAD pointer (i.e. every time you commit, merge, rebase, cherry-pick, reset, etc.). Git logs your actions in the Reflog which makes it a valuable logbook and a good starting point when something went wrong.

In this last part of our “Advanced Git” series, I’ll explain the differences between git log and git reflog, and I’ll show you how to use the Reflog to recover deleted commits as well as deleted branches.

git log or git reflog: what’s the difference?

In previous articles, I’ve recommended you use the git log command to inspect previous events and look at your commit history, and that’s exactly what it does. It shows the current HEAD and its ancestors, i.e. its parent, the next parent in line, etc. The log goes all the way back in the commit history by recursively printing every commit’s parent. It’s part of the repository which means it gets replicated after you push, fetch, or pull.

git reflog, on the other hand, is a private and workspace-related recording. It doesn’t go through the list of ancestors. Instead, it shows an ordered list of all commits which HEAD has pointed to in the past. That’s why you can think of it as some kind of “undo history” like you might see in word processors, text editors, etc.

This local recording technically isn’t part of the repository and it’s stored separately from the commits. The Reflog is a file in .git/logs/refs/heads/ and it tracks the local commits for every branch. Git’s diary usually gets cleaned up after 90 days (that’s the default setting), but you can easily adjust the expiration date of the Reflog. To change the number of days to 180, simply type the following command:

$ git config gc.reflogExpire 180.days.ago
Screenshot of a Terminal with a light yellow background and black text. The content shows the output from the git config gc.reflogExpire 180.days.ago command.
The repository’s configuration file (.git/config) now includes the variable reflogExpire with the value 180.days.ago

Alternatively, you can decide that your Reflog should never expire:

$ git config gc.reflogExpire never

Tip: Remember that Git makes a distinction between the repository’s configuration file (.git/config), the global per-user configuration ($HOME/.gitconfig), and the system-wide settings (/etc/gitconfig). To adjust the Reflog’s expiration date for the user or the system, add the --system or --global parameter to the commands shown above.

Enough theoretical background—let me show you how to work with git reflog to correct mistakes.

Recovering deleted commits

Imagine the following scenario: After looking at your commit history, you decide to get rid of the last two commits. You courageously perform a git reset, the two commits disappear from the commit history… and a while later, you notice that this was a mistake. You’ve just lost valuable changes and start to panic!

Do you really have to start from scratch again? You don’t. In other words: keep calm and use git reflog!

So, let’s mess things up and make this mistake in real life. The next image shows our original commit history in Tower, a graphical Git client:

Screenshot of the Tower application interface. On the left is a navigation with the History item selected. In the center panel is a visual outline of the commit history showing the avatars of people who made the commits. The second commit is selected and on the right is a panel showing more detail about the commit, including the author, date, committer, refs, hashes, and changed files.

We want to get rid of two commits and make the “Change headlines for about and imprint” commit (ID: 2b504bee) our last revision on the master branch. All we need to do is copy the hash ID to the clipboard and then use git reset on the command line and enter that hash:

$ git reset --hard 2b504bee
Screenshot of the Tower application interface. The master branch is selected in the right navigation, the first commit is selected in the center panel, and the detail for that commit is displayed in the right panel. The commits in the center panel are clean and linear without any additional commits or branches.

Voilà. The commits have disappeared. Now, let’s assume this was a mistake and take a look at the Reflog to recover the lost data. Type git reflog to view the journal in your terminal:

Screenshot of an open Terminal window with a light yellow background. The text is mainly black, but some words are highlighted in red, light blue and bright green. The top line is the git reset --hard 2b504bee command. The second line says the head is now at that commit ID. The third line is the git reflog command, which outputs the history.

You’ll notice that all entries are ordered chronologically. That means: the most recent—the newest—commits are at the top. And, if you look closely, you will notice the fatal git reset action from a few minutes ago right at the top.

The journal seems to work—that’s good news. So, let’s use it to undo that last action and restore the state before the reset command. Copy the hash ID (which is e5b19e4 in this specific example) to the clipboard, like before. You could use git reset again, which is totally valid. But in this case, I’m going to create a new branch based on the old state:

$ git branch happy-ending e5b19e4

Let’s take a look at our graphical Git client again:

A screenshot of the terminal with a light yellow background and the output for git reflog in it, on top of a screenshot of the Tower application window, showing the updated commit history following the command.

As you can see, the new branch, happy-ending, has been created and it includes the commits we deleted earlier—awesome, nothing is lost!

Let’s look at another example and use the Reflog to recover an entire branch.

Recovering deleted branches

The next example resembles our first scenario: we’re going to delete something—this time, it’s an entire branch that has to go. Maybe your customer or your team leader has told you to get rid of a feature branch, maybe it was your own idea to clean up. To make things worse, a commit (C3 in the picture) is not included in any of the other branches, so you’re definitely going to lose data:

Illustration showing the commit history flow of a feature/login branch with ID C2 being deleted from a C2 branch that is off the master branch. Beside the diagram is a list of the steps taken to deleted the branch, ending with step 4: you panic next to a screaming emoji.

Let’s actually do this and then recover the branch later:

Screenshot of the Tower app interface showing the login branch selected in the left panel, the commit history of that branch in the center panel with the first commit selected and highlighted in blue, then details for the commit in the right panel, including the author, date, refs, cases, and modified files.

Before you can delete the branch feature/login, you need to step away from it. (As you can see in the screenshot, it’s the current HEAD branch, and you can’t delete the HEAD branch in Git.) So, we’re going to switch branches (to master) and then we’re going to delete feature/login:

Screenshot of an open Terminal window with a light yellow background and mostly black text, though the branch and committer names are highlighted in bright green. The first command is git status, the second is git checkout master, the third is git branch -vv, the fourth is git branch -D feature/login, and the last command is git branch -vv.

Okay… now let’s say our customer or team lead had a change of heart. The feature/login branch (including its commits) is wanted after all. What should we do?

Let’s take a look at Git’s diary:

$ git reflog
776f8ca (HEAD -> master) HEAD@{0}: checkout: moving from feature/login to master
b1c249b (feature/login) HEAD@{1}: checkout: moving from master to feature/login

Turns out we’re lucky again. The last entry shows our switch from feature/login to master. Let’s try to return to the state right before that and copy the hash ID b1c249b to the clipboard. Next, we’re going to create a branch called feature/login based on the desired state:

$ git branch feature/login b1c249b
$ git branch -vv
  feature/login b1c249b Change Imprint page title
* master        776f8ca Change about title and delete error page

Great—the branch is back from the dead and also includes that valuable commit we thought we had lost:

Screenshot of the Tower application interface. The feature/login branch is selected in the left panel, the commit history for the branch is in the center panel with the first commit selected, and the left panel displays more information ab out the commit, including the author, date, refs, hashes, and modified files.

If you’re using Git in a desktop GUI like Tower, you can simply press CMD+Z to undo your last action, just like a text editor or word processor when you make a typo.

Keep calm and keep track

Git’s Reflog can be a real lifesaver! As you can see, it’s quite easy to bring lost commits or even entire branches out from the grave. What you need to do is find the correct hash ID in the Reflog—the rest is a piece of cake.

If you want to dive deeper into advanced Git tools, feel free to check out my (free!) “Advanced Git Kit”: it’s a collection of short videos about topics like branching strategies, Interactive Rebase, Reflog, Submodules and much more.

This was the last part in our series on “Advanced Git” here at CSS-Tricks. I hope you enjoyed the articles. Happy hacking!