Personal blog of Gunnari Auvinen. I enjoy writing about software engineering topics.

Picture of the author, Gunnari

How to undo a git rebase? Hint: git reflog and git reset

February 12, 2015

The other day at work a group of us were asked if any of us were Git masters, at which point my ears perked up and I asked him to explain what exactly he needed help with at that moment. He then told me that a teammate had lost roughly a half-days worth of work after a bad git rebase and he wanted to know if it was possible to recover their lost work. Now while I'm not a Git master, I vaguely remembered something from a lecture and told him that I would look into it and get back to him in a few minutes.

After a couple of minutes of searching I managed to find the information that I was searching for to "save the day." What I had discovered was that Git keeps track of the tips of branches and when they're changed and this information can be accessed via the git reflog command. In this case the git reflog command allowed us to find the reference to the commit that had been made prior to the bad git rebase. The reflog had the various messages associated with the branch tip changes and in our case we looked for the last commit message that had been entered prior to the rebase, which had a reference similar to HEAD@{7}. Having this reference allowed us to use the git reset --hard HEAD@{7} command to go back to that specific commit. With a little bit of searching we fortunately saved hours worth of work!

As this concept can be a little bit nebulous in just text I've provided an example below to help flush things out.

Example of How to Undo a git rebase

For this example I made a little git repository that uses master and the amazing-feature branch for us. Here is a printout of a git log --oneline command before the git rebase is done, while in the amazing-feature branch.

98008d5 Add the divide function to amazing-feature.
93c18de Add multiply function to amazing-feature.
abdc941 Add amazing-feature.
18244f8 Add the add function to first-feature.
3a01fc1 Add the amazing first-feature.

Next I did:

➜  git-reflog-git-rebase-example git:(amazing-feature) git rebase master

First, rewinding head to replay your work on top of it...

Applying: Add amazing-feature.
Applying: Add multiply function to amazing-feature.
Applying: Add the divide function to amazing-feature.
➜  git-reflog-git-rebase-example git:(amazing-feature)

This added the other commits from master that this branch didn't have. Following that I did a git log --oneline.

b13c3f1 Add the divide function to amazing-feature.
4fdbc95 Add multiply function to amazing-feature.
085c6a5 Add amazing-feature.
2c0a30c Add the square function to best-feature file.
18244f8 Add the add function to first-feature.
3a01fc1 Add the amazing first-feature.

Oh no, we don't actually want that information from master merged into the amazing-feature branch! We need to figure out a way to undo that rebase. Fortunately we can look up the HEAD reference that we want to go to by using the git reflog command.

b13c3f1 HEAD@{0}: rebase finished: returning to refs/heads/amazing-feature
b13c3f1 HEAD@{1}: rebase: Add the divide function to amazing-feature.
4fdbc95 HEAD@{2}: rebase: Add multiply function to amazing-feature.
085c6a5 HEAD@{3}: rebase: Add amazing-feature.
2c0a30c HEAD@{4}: rebase: checkout master
98008d5 HEAD@{5}: commit: Add the divide function to amazing-feature.
93c18de HEAD@{6}: checkout: moving from master to amazing-feature
2c0a30c HEAD@{7}: commit: Add the square function to best-feature file.
18244f8 HEAD@{8}: checkout: moving from amazing-feature to master
93c18de HEAD@{9}: commit: Add multiply function to amazing-feature.
abdc941 HEAD@{10}: commit: Add amazing-feature.
18244f8 HEAD@{11}: checkout: moving from master to amazing-feature
18244f8 HEAD@{12}: commit: Add the add function to first-feature.
3a01fc1 HEAD@{13}: commit (initial): Add the amazing first-feature.

Looking at the reflog printout, I know that to undo the git rebase that I need to go to the reference HEAD@{5} when I do a git reset, as that reference is the last time the HEAD pointed to the proper place.

➜  git-reflog-git-rebase-example git:(amazing-feature) git reset --hard HEAD@{5}
HEAD is now at 98008d5 Add the divide function to amazing-feature.
➜  git-reflog-git-rebase-example git:(amazing-feature)

One last git log --oneline to make sure that the rebase has been undone.

98008d5 Add the divide function to amazing-feature.
93c18de Add multiply function to amazing-feature.
abdc941 Add amazing-feature.
18244f8 Add the add function to first-feature.
3a01fc1 Add the amazing first-feature.

Phew it has been fixed! Here's what git reflog shows after the git reset command from immediately prior.

98008d5 HEAD@{0}: reset: moving to HEAD@{5}
b13c3f1 HEAD@{1}: rebase finished: returning to refs/heads/amazing-feature
b13c3f1 HEAD@{2}: rebase: Add the divide function to amazing-feature.
4fdbc95 HEAD@{3}: rebase: Add multiply function to amazing-feature.
085c6a5 HEAD@{4}: rebase: Add amazing-feature.
2c0a30c HEAD@{5}: rebase: checkout master
98008d5 HEAD@{6}: commit: Add the divide function to amazing-feature.
93c18de HEAD@{7}: checkout: moving from master to amazing-feature
2c0a30c HEAD@{8}: commit: Add the square function to best-feature file.
18244f8 HEAD@{9}: checkout: moving from amazing-feature to master
93c18de HEAD@{10}: commit: Add multiply function to amazing-feature.
abdc941 HEAD@{11}: commit: Add amazing-feature.
18244f8 HEAD@{12}: checkout: moving from master to amazing-feature
18244f8 HEAD@{13}: commit: Add the add function to first-feature.
3a01fc1 HEAD@{14}: commit (initial): Add the amazing first-feature.

As you can see, the reflog shows the HEAD pointer moves back to the reference that was at HEAD@{5} and is now at HEAD@{6} in the last reflog printout.

Always Push to Your Remote Prior to a git rebase

While I was able to find a solution to the student's problem and we were able to save a half-days worth of work, there is an additional takeaway from this experience. My recommendation is that prior to performing a git rebase always push your work up to a remote if you haven't already. This way even if your git local git history gets messed up, you can always just clone down the remote again.

It really is great to know that you can go back in time and fix those seemingly unfixable mistakes with a few key strokes!