Git Rebase Explained

Introduction

Rebase is a helpful git command that can be used to turn a branchy two-headed version history into an easy to follow linear one. A linear history is much easier for a human eye to follow and a human brain to understand. Rebase is a very safe command to use, despite some fear that people have about it.

This post should explain how rebase works and clear up any misunderstandings about it.

Rebase Basics

The short summary of rebase is that it cuts commits out of the git history and reconnects them somewhere else.

Let's say the common scenario comes up: you commit a change to the main branch of a repository, you then run git fetch, and in come a couple changes that other people made on the main branch. If your commit is version 46f and the others that just came in on the fetch are versions ef9 and 4a2, your version history might look like this:


The main branch now has two heads, 46f and 4a2. You can run git rebase to disconnect your commit from version d88 and connect it to 4a2. In one simple command the above version history morphs into this nice linear version history:


It's called rebase because 46f was based on d88 and now it is based on 4a2. Simple. Multiple head problem solved. That's all you need to know. If you really want to know more then read on.

Automating rebase

Most git users have been trained to run git pull instead of git fetch. That's because git pull does a git fetch and then usually follows that by a git merge if needed. You can tell git pull to do a rebase instead of a merge like so:

git pull --rebase

Or, you can configure git to always use rebase for git pull by running this command, which is highly recommended:

git config pull.rebase true

Then you don't need to add --rebase to your git pull command.

More Detail

You might be wondering how the rebase command does what it does. You might also have other questions like:

  • Does rebase change your commit?
  • Does it change the other commits (ef9 and 4a2, in our example)?
  • Can anything go wrong?
  • If so, what if something does go wrong?

If so, read on.

How It Works

I'm sure there are some gory details of how it works that I'm leaving out, but essentially rebase just does a merge and then deletes part of the history of the merge so that you get a simple linear history. If we start again with this:


And run rebase, it first does a merge to get this:


Then it removes the original 46f and any pointers to it. That's how you end up with this:


Knowing that, you can see that rebase does in fact change your original commit. If nothing else, your commit gets a new parent pointer. If 46f changed some of the same files that ef9 or 4a2 changed then those changes will have to be merged and there can even be merge conflicts. If that happens you can resolve the merge conflicts however you are used to doing that (I prefer kdiff3). Most rebases (like most merges) don't have any conflicts and it's very nearly a no-op. Just the parent pointer changes.

One thing to note is that the parent pointer change is still significant and that's why there is an asterisk on the new 46f commit. The commit ID will actually not stay the same after a rebase. Git computes the commit id based on the diff, your comments, and the parent(s) of the commit. The rebase command changed the parent of 46f so it will calculate a new commit ID. I left it the same in the diagram so it'd be easy to follow where the commit actually went.

Also note that only the commit(s) being rebased get changed. In our example, ef9 and 4a2 are the destination of the rebase but they aren't touched themselves at all.

If Things Go Wrong

Just like with some merges, some rebases can result in a multitude of merge conflicts or broken compiles or failing tests due to subtle code bugs. Sometimes when that happens you just want to get back to where you were before the merge or rebase. If a rebase goes horribly wrong and you want to abort, your old commits are all still there, they are just hidden by git. If you haven't finished resolving conflicts you can simply type git rebase --abort and everything will be restored. If you have completed the rebase but you remember the commit id of your original commit, you can run git checkout <commit-id> to get git to show you the original commit. If you don't remember the commit id, you can use the git reflog command to find the commit id and use git checkout to make git show it to you again.

Conclusion

Rebase is a very useful and safe git tool that keeps our version history clean and easy to understand. You can automatically use rebase for every git pull that you do by configuring git with this command:

git config pull.rebase true

Comments

Popular posts from this blog

SystemVerilog Fork Disable "Gotchas"

'git revert' Is Not Equivalent To 'svn revert'

SystemVerilog Streaming Operator: Knowing Right from Left