Talking in Pictures

A blog about visual communication

ScreenSteps Desktop 2.7 Now Available! Download ScreenSteps 2.7

Recovering From a Disasterous Git-rebase mistake

Yesterday was adding the finishing touches to a marketing redesign of our screenstepslive.com site. This had been through many iterations but I felt that I finally had things the way I wanted them. ScreenSteps Live is entirely programmed in Ruby on Rails, both the web application and the marketing/sign-up area. We use Git for all of our version control.

Following standard Git practice, I was working on "marketing" branch that I had created from our "master" branch. Earlier in the day I had made a small update to the master branch to fix a small bug. In preparing to launch the updates to the marketing site I decided to do a git-rebase.

I’ll go into the details of how rebase works in a second, but basically the idea is to update the changes with a child branch (marketing) with changes that have been made to the parent branch (master). I ran into a few problems with the rebase and decided to abort, which has always worked before, but not this time. At least it didn’t work the way I expected it to. After aborting the rebase my marketing branch was at the same state it had been the day before. I had done a LOT of work in that 24 hour period. Basically all of my changes were gone.

Extreme panic set in. I had lost my css, my copy, my design, everything. I looked frantically for a way to recover from my disaster.

Option 1: Time Machine

Time Machine has saved me from myself more than once in my life so that was the first place I went to. Checking the backup I could see that I had a backup from about an hour ago. That was much better than losing an entire day’s work, but I had still done a lot of work in that last hour. What was more, I had finished the work. There is nothing worse than having to finish something twice. So I began to look for different options.

How Git Works

how-git-works.png

To understand the problem you need to understand how Git works internally. Whenever you make a commit with Git you are taking a snapshot of how your repository looks at that moment. The commit is assigned a sha1 hash (basically, a unique id), a message and a parent. The parent is the sha1 hash of the direct ancestor of the selected commit.

A git branch really doesn’t know anything more about its lineage than its direct parent. It also doesn’t know anything about its children. Branches are put together by stringing together a line of parent ids.

So What Happened?

Here is how things went. Oftentimes when doing a rebase you will run into conflicts that you have to resolve before the rebase will complete. It usually isn’t a big deal. But for some reason git had been acting funny because I had replaced a file titled "Movie.png" with a different image file called "Movie.png". I don’t know exactly what the problem was but it caused an issue with the rebase. Instead of resolving the conflict I decided to just to git-rebase -skip for that commit. Apparently this was a bad idea.

Right after that another conflict popped up. I decided that I really didn’t need to do a rebase and that this was going to be more of a pain than it was worth. I was just going to do a merge so I issued a git-rebase -abort command. The way this has always worked for me in the past is that all of the updating of the child branch with the parent branch is undone and you are right back where you started before you issued that "git-rebase master" command.

But not this time. Apparently issuing the git-rebase -skip command changed the point that my branch would rollback to, which was about 24 hours ago. Since git commits don’t know anything about their children there was no way for me to "fast forward" the branch to where I needed to be.

The Solution

Even though things looked bleak I was pretty sure of one thing – Git doesn’t usually delete commits from its database. That meant that my finished version was somewhere in there, I just had to find it.

I quickly searched the internet for ways to find commits that were no longer attached to a branch but didn’t find anything. So then I just started digging around in the .git directory of my project.

And I found a solution.

In .git/logs/refs is a list of text files. These text files have a history of your commits and merges on a branch.

media-1238070241727.png

I opened up the log for my marketing branch. Above is the relevant part of the log. I have highlighted the rebase that went bad.

media-1238070336956.png

But here is the interesting part. The log lists the commit’s sha1 (1) and the commit’s parent (2). You can see that in the image above.

So the solution was simple:

git checkout -b marketing_recovery 0b4354e5b95394c43

This created a new branch based off of my finished marketing update (note: You don’t have to enter the full sha1 hash of a branch when referencing it, just enough so that Git knows which one you are talking about).

There is one caveat: I lost the history of the branch but in this case it didn’t really matter. I was just happy to have retrieved my changes.

Lesson Learned

I still don’t know exactly what went wrong. It seems that git-rebase -abort should have taken me back to where I wanted to be. But I do know that I will not be using git rebase-skip again anytime soon.

Bookmark and Share

Tags:

8 Responses to “Recovering From a Disasterous Git-rebase mistake”

  1. Chris
    April 13th, 2009 at 4:47 pm

    I came across you posting after I had a similar mishap with git-rebase -i/git-rebase -abort that left me with a lot of missing commits.

    Your tip on peeking at the .git/logs/refs file pointed me in the right direction. But instead of doing a “git-checkout -b” with the any of dangling commits, I was feeling lucky and tried a git-rebase from my master branch. This restored my entire branch history to the state it was in before the debacle. So thanks for the post!

  2. Greg DeVore
    April 13th, 2009 at 5:14 pm

    So Chris, I’m not clear on what you did. Did you checkout the master branch and then do “git-rebase idofbranchfromgit_logs” ?

  3. oglcm
    June 15th, 2009 at 8:01 pm

    I… I can’t thank you enough.

    To clarify, what you probably want is to open the log for the branch (head) which contained the changes you’ve lost.

    You’ll see a couple of rebase messages toward the bottom, it’s the line above that which is the last commit you made. The fields seem to be ‘(new ID) (parent ID) (action): (action specific fields)’, so you can “git checkout -b recovery (new ID)” to get back to where you were.

    Hope this helps someone else as much as it did me…

  4. lucas
    July 10th, 2009 at 10:37 pm

    thank you so much. i did exactly what you had done and was near tears when I stumbled upon your blog. saved me 3 days.

  5. Soleone
    July 21st, 2009 at 2:46 pm

    Yea, thanks a lot indeed! I lost a pretty heavy commit today, when accidently doing git rebase –skip in the middle of a rebase.

    You saved my day man!

  6. Como recuperar um git rebase mal feito « Tino Gomes
    July 23rd, 2009 at 12:51 am

    [...] pelo famosíssimo Guanabara em um dos guanacasts sobre SEO (parte I e parte II), onde encontrei um post de Greg DeVore, explicando como recuperar uma cagada bagunça dessa. E com meus MAD SKIILS, ainda usei o git [...]

  7. Vijay
    December 22nd, 2009 at 2:28 pm

    Thanks much for this! Saved me from losing a heavy commit! :)

  8. Jorge
    January 29th, 2010 at 8:25 am

    You can use git reflog instead of looking at the log manually :)

Leave a Reply

Screensteps Support Suite

ScreenSteps is the most effective tool for creating and delivering customer tutorials and guides.
Learn about the ScreenSteps Support Suite