Rebasing a Branch in Git Explained

Rebasing a Branch in Git Explained

Rebasing??? What the heck???

We've all been there. You've just submitted a pull request that adds an absolutely amazing feature to your favorite open source project and the maintainer asks you to rebase your PR to master. CRAP! Now you're going to have to type in a bunch of arcane git commands and deal with merging code. And who can remember all of the steps involved? Well, this post will help you do just that.

First, we have to remember that a commit can be thought of as a snapshot of your repository at a certain point in time. It contains everything in your code at the time of the commit. Git doesn't work off of "diffs." This ends up making our life much easier and makes the process of rebasing much simpler both in its implementation, but (thankfully) in how we can mentally model what is happening during a rebase. A new branch in Git is simply a spot where we decided to split off from whatever line of commits we had been working from and start creating a separate set of commits, so when we decide to rebase, we are simply picking a new commit to start our branch from. Once I started thinking in this fashion, dealing with rebases became a much more simple mental exercise.

Let's figure this out!

I am going to work through the a simple scenario to explain the steps involved. You can play along at home if you would like. This scenario is probably the simplest possible rebasing example that still includes the need to merge changes during the rebase. We need to create some commits to work from, so let's do that first.

To start, open your favorite shell program and create a directory. For our purposes, I am going to call the folder learnrebase. Change to that directory and initialize a git repo by typing git init

Now, we will go back to the master branch and change our file. This simulates someone else committing changes to our parent branch (such as what could happen if a pull request is pulled in after we created our branch). This will create a situation where we need to merge the changes during our rebase. First, checkout master.

Now we will rebase the NewFeature branch so that it will branch off from "Third commit (from master)" instead of "First commit (from master)." This will require working with git's rebasing functionality. To continue, checkout the NewFeature branch.

Now, we will kick off the rebasing process. Git will tell us that Auto-merging failed.

~/learnrebase (NewFeature)
$ git rebase master
First, rewinding head to replay your work on top of it...
Applying: Second commit (from NewFeature branch)
Using index info to reconstruct a base tree...
M file1.txt
Falling back to patching base and 3-way merge...
Auto-merging file1.txt
CONFLICT (content): Merge conflict in file1.txt
Failed to merge in the changes.
Patch failed at 0001 Second commit (from NewFeature branch)
The copy of the patch that failed is found in:
c:/Users/Ashley/learnrebase/.git/rebase-apply/patch
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
~/learnrebase (NewFeature|REBASE 1/1)
$

Notice that the branch name changed from "NewFeature" to "NewFeature|REBASE 1/1". We are now in a temporary branch where we can resolve our merge conflicts. So, how do we do the merge? Well, git has helpfully updated the file with conflicts to include the changes from both the master and NewFeature branches.

Please note that in a real merge, this is a spot where you will quite possibly need to put real thought in to how you merge the changes. Actually doing the merge is as simple as editing the file to make it look how you want it to look. Simply use your text editor of choice and resolve the conflicts as you see fit!

So, now that we've resolved our conflicts, it's time to continue with the rebase. We can think of the rebase as a more convoluted commit (because that's what it really simply is: a commit). Thus, we need to stage our changes just like with any other commit. Then, we follow the instructions that git gave us when we started the rebase operation and enter git rebase --continue.

Now, if we go check out our git log, something interesting has happened.
Notice that the commit hash for the commit "Second commit (from NewFeature branch)" has changed. Before the rebase, this commit had a hash of c18ec05, but now it is fdba04a. Why is this? Well, remember that a git commit represents a snapshot of the the repo at the time of the commit. Well, the snapshot at the time of the 'old' commit is no longer valid. The commit in question now represents a different state for the repo, and thus it has a new hash. This is a new commit, it just shares a commit message! When I started thinking about git branching in this fashion, it became a lot easier for me to understand what is going on.

We can now merge our branch back in to the master branch. This is what happens when a pull request is accepted and merged. To do this, we must switch back to the master branch and issue the git merge command, passing the name of the branch to be merged. If there are merge conflicts, then the conflicts can be resolved in the same way discussed above; however, in our case there are no conflicts to resolve, and our merge will be done automatically using the "fast-forward" technique.

If we now check our log, we can again see how thinking of git commits as snapshots can help to understand what git is doing.
Notice that the commit hash fdba04a has not changed. After rebasing our branch to the most recent commit on master, no further changes to master occured, so our final checkin on NewFeature branch is simply inserted in the commit history for the master branch. If changes had occurred on the master branch after the rebase for the NewFeature branch occurred, then a new commit would have been created to represent this new snapshot, even if no merge conflicts existed (for example if all the changes occurred in a different file).

Conclusion

Rebasing a branch is not very difficult, once you understand the concept of how git handles commits. Remember that a git commit is simply a snapshot of your repo at the time and your relationship with git will likely improve. I hope I've helped your understanding of how to work with git.