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:
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:
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):
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:
These commands will turn this:
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:
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.