May 3, 2016

How to Rebase in Git


Learn how and why to rebase in Git.

In previous articles, I have showed how to find things in Git and how to fix mistakes in Git. Here, I’ll be looking at why and how to rebase in Git.

What Is Rebasing?

Rebasing is when you take a set of patches from one Git branch and apply them to another branch. As with so many Git operations, whether or not to use rebasing is more a question of policy than technical correctness, because you can achieve the same results with merging. We'll do a bit of comparing and contrasting of merging vs. rebasing and then look at a couple of rebasing scenarios, one of which is rather unusual.

Rebase vs. Merge

Merging is always the safest option, because it preserves all history. Rebasing rewrites history, which can do real damage to a project by making it impossible to go back in time to see what happened. It can disrupt the work of contributors using the old history, and it may invalidate any previous testing. Introducing new bugs via rebasing is unlikely to win friends.

You can't go wrong with merging. The disadvantage, however, is being drowned in clutter and not being able to easily find what you want, because merging preserves everything. However,  this could be strong motivation to get really good with advanced usage of git log commands.

The (greatly simplified) policy for the Linux kernel is: Rebase only private history to make nice clean pull requests, and never rebase work pushed by anyone else. Rebasing private history is good. Rebasing public history is bad.

Two Rebase Scenarios

The projects I am involved in that use Git are fairly simple. Most work starts in working branches created from master, and occasionally other stable branches if the changes are specific to those versions. There are two scenarios where I rebase rather than merge.

The first scenario is a bit unusual, because it happens only with production trees that use Git submodules. When the submodules are updated, they mess up existing pull requests (Figure 1).


Figure 1: GitHub gives me sad news.

One way to fix this is to click the Update Branch button on GitHub. If you're not using GitHub, or want to test your changes locally first, follow these steps.

$ git fetch
$ cd [working-branch]
$ git rebase [production branch]
$ git submodule update
$ git add --all
$ git commit -a
$ git push origin [working-branch]

This breaks the policy of "Rebasing public history is bad," because it's rewriting history after I have pushed changes upstream. However, it's the project's policy, so I use it.

Making Things Tidy

I like to use an interactive rebase to clean up my work before opening a pull request, so that I have a simplified commit history instead of a giant mucky mess all full of reverts, commits to fix previous commits, and extra commits because I kept forgetting things. This is a more routine use of rebasing.

To start, update your production branch, then change to your working branch and launch an interactive rebase against the production branch.

$ git checkout [production branch]
$ git pull
$ git checkout [working-branch]
$ git rebase --interactive [production branch]

This opens your editor with a list of commits:

pick 7758944 example json config
pick 2584c53 sample htaccess
pick f89c54f add readme
pick 51d9f5a describe bridged networking
pick 1e1088c remove readme

# Rebase 46f1974..1e1088c onto 46f1974 (5 command(s))
# Commands:
# p, pick = use commit
# r, reword = use commit, but edit the commit message
# e, edit = use commit, but stop for amending
# s, squash = use commit, but meld into previous commit
# f, fixup = like "squash", but discard this commit's log message
# x, exec = run command (the rest of the line) using shell

Now you can edit your commits. In the above example, I can safely delete f89c54f add readme and 1e1088c remove readme. There is no value in leaving them in there.

Another option is to squash the commits you want to keep into a more compact form. As you can see in your editor, s melds the commits together while retaining the commit messages, and f melds and then discards the commit message.

I usually don't squash commits, but when it's a bunch of small changes, I'll use s to squash and retain the commit messages. Commits are listed in order from oldest to newest and are processed in order. After deleting the two readme commits, my squash example looks like this:

pick 7758944 example json config
s 2584c53 sample htaccess
s 51d9f5a describe bridged networking

If there are any error messages, you can run git status to see what the problem is. Most likely it's a merge conflict, so you can resolve the conflict, add the file (git add [filename]) after fixing the conflicts, and then finish with git rebase --continue.

You may enjoy my previous Git how-tos:

Click Here!