Jul 11 2013

Git Tips & Tricks, Part 3–Changing History with Rebase

Category: gitMatt @ 05:01

In my last post, I alluded to combining commits before pushing them to the shared repository.  The magic command that can make that happen is “rebase,” and I’m going to show you how I use it every day.

What is Rebase?

First, let’s get something out of the way: history cannot be changed.  That’s true even in Git.  While you will hear people say “rebase let’s you rewrite history!” that’s not really what it’s doing.  You’ll see why soon. 

What’s the point of rebase?  Perhaps it’s best illustrated with, well, an illustration.  It turns this:

image

Into this:

image

A standard ‘git rebase master’ will take your changes and re-play them on top of the current tip of the master branch. 

A rebase is a bit like a merge, but a merge is more like this:

image

While a rebase is more like this:

In a best-case scenario, a ‘git merge’ and a ‘git rebase’ will yield the exact same results.  When things have diverged though (such as when team members commit code to the shared master branch while you are committing locally), ‘git rebase’ will yield much cleaner results than ‘git merge,’ which could produce something like this (or worse):

image

My Workflow

I always do a ‘git rebase’ to prepare my local feature branches before I push them to the shared repository.  Here’s my workflow:

git checkout -b SomeFeatureBranch
#DO WORK
git add -A
git commit -m "Fixed some stuff!"
#DO MORE WORK
git add -A
git commit -m "Fixed MORE stuff!"

#Pull down latest changes from remote into local master
git checkout master
git pull

#Rebase my changes onto the latest revision
git checkout SomeFeatureBranch
git rebase master

#push my changes
git push origin SomeFeatureBranch:master

When I commit a feature, my goal is to make the changes I made easy to review and comprehend for someone else.  In situations where I’ve made multiple commits in my feature branch, I’ll usually do an interactive rebase next to combine commits: 

git checkout -b SomeFeatureBranch
#DO WORK
git add -A
git commit -m "Fixed some stuff!"
#DO MORE WORK
git add -A
git commit -m "Fixed MORE stuff!"

#Pull down latest changes from remote into local master
git checkout master
git pull

#Rebase my changes onto the latest revision
git checkout SomeFeatureBranch
git rebase master

#Now do an interactive rebase to squish things, fixup commits, or even reorder commits
git rebase master -i

#push my changes
git push origin SomeFeatureBranch:master

Assuming you are using Git Extensions, the ‘git rebase master -i' command will display a dialog to let you alter your commit history:

image

These commands will turn this:

image

Into this:

image

This cuts down on noise in the main repository and makes the change easier for someone else to review and comprehend.  That said, there are times where I will choose not squash commits, such as when I’m making a simple refactoring in one of my commits.  That refactoring is easier to review on its own without my real changes mixed in.

You Can’t Really Change The Past

Rebase allows you to create new history;  an alternate timeline, if you will.  The original timeline is still accessible for a bit.  It’s hanging out there, “detached,” like this:

image

Eventually it will be garbage-collected and destroyed unless you attach a branch to it, but it’s there long enough for you to undo a bad rebase should you screw something up.  To do that, just run the ‘git reset --HARD ORIG_HEAD’ command.

If it’s been a while since you did the rebase, you might have to dip in to the reflog.  That’s beyond the scope of this article, but it’s not nearly as daunting to recover your missing changes as you might think.

The ‘git rebase’ command alone is worth the cost of switching to git.  It is powerful though, and powerful things can bring pain.  Use it wisely.

Tags:

blog comments powered by Disqus