How to do a git rebase, and why

Quick tutorial on why you need to rebase instead of merge and how to do it

Bahadir Balban

Buzz Founder

@gettingstartedwithgit38547

Problem: Merge commits will complicate your git history.

If you don’t know what a merge or fast-forward is, check out this tutorial first.

When you develop a feature in a side branch, you need to get your changes merged into the main branch. The problem is merge commits will complicate your main branch commit history:

  • It creates an extra merge commit in the commit history.

  • The merge commit does some background merge magic in your files, meaning you will find them in a different state than you left before the merge.

  • The commit history will have a mixed order of commits from the original branch and the merged branch. Commits may not be in a coherent, sequential order.

If you do merge commits on a regular basis your commit history will be hard to follow.

Solution: Rebase

1. The feature developer rebases their changes on top of the master branch

2. The main branch maintainer pulls the changes in the side branch as a fast-forward.

Let’s assume you have a series of commits on a feature branch:

A feature branch with 3 commits on top of master.

A rebase re-applies all commits in a feature branch on top of the HEAD commit of the branch you want to merge into (such as master). After a rebase, the feature branch can be trivially applied with a fast-forward.

Your project maintainer will be happy, because after pulling your changes her git logs will show a natural succession of commits with your changes and no merge.

How do we do a rebase? Lets do it

Easy No Conflict Scenario

You simply type:

git checkout feature/new-feature-branch

git rebase <branch-to-be-merged-into>

git rebase - the easy, no-conflict scenario

As mentioned above, this rewinds the feature branch HEAD back to the common ancestor, and re-apply all changes on top of the top commit of master.

Here is the end result:

See how the feature branch is now on top of the latest commit of master, instead of just the common ancestor somewhere below it.

The project maintainer can now merge the feature branch to master trivially with a fast-forward and no merge:

That was easy. Why do people avoid rebasing? Because: They don’t know how to resolve conflicts.

A merge conflict occurs when commits in two git branches modify the same contents in the same file, and there is no clear evidence which change is the right one to pick.

Let’s create a conflict scenario

Let us look at the scenario where re-winding and re-applying all commits in a feature branch causes a conflict with the main branch.

git checkout -b feature-branch
[Modify line 3 in file & save]
git add file
git commit

git checkout master
[Modify line 3 in file & save]
git add file
git commit

Merge options now:

Option 1: Maintainer pulls, merges, resolves conflict:

Now, if the maintainer does:

git pull . feature-branch 

he has to resolve the conflict. This is a no go option. No maintainer wants to resolve conflict of others.

Option 2: Feature developer merges, resolves conflict, Maintainer pulls merge commit:

1. If the feature developer does:

git pull . master

on the side branch, he will get a conflict, and will create a merge commit.

  1. The maintainer pulls it:

    git pull . feature-branch

The maintainer now can merge the change, but he will have to merge a “merge commit”. A less than ideal situation.

Option 3: Feature developer rebases, maintainer pulls the rebase as a simple "fast-forward":

This is the best option. Maintainer simply does a fast-forward to get the feature and sees a natural progression of commits in the main branch.

Feature developer does:

git checkout -b backup

(This is to save the current branch, as rebase modifies the branch we are on permanently)

git checkout feature-branch
git rebase master

Result:

Bahadirs-iMac:new-repo bahadir$ git rebase master

First, rewinding head to replay your work on top of it...
Applying: Commit #1 in feature branch
Using index info to reconstruct a base tree...
M	a.c
.git/rebase-apply/patch:12: new blank line at EOF.
+
warning: 1 line adds whitespace errors.
Falling back to patching base and 3-way merge...
Auto-merging a.c
CONFLICT (content): Merge conflict in a.c
error: Failed to merge in the changes.
Patch failed at 0001 Commit #1 in feature branch
Use 'git am --show-current-patch' to see the failed patch

Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".

Resolve conflict:

The 2 versions of the file’s portion with conflict is nicely presented to you by Git:

<<<<<<< HEAD
Hello New World! Master branch!
Line 4
Line 5
=======
Hello world in feature branch
Line 4
Line 5

>>>>>>> Commit #1 in feature branch

First version is between the portion <<<< HEAD and =====

The second version is between the portion ===== and >>>>> Commit name

A simple resolution is to delete those markers, and pick only one of the versions.

Hello world in feature branch
Line 4
Line 5

In this scenario, we deleted markers, and picked the 2nd version, done by the feature branch.

In a frequent scenario, you may need to mix and decide on a change that includes parts of both versions:


Hello New World! Master branch! Hello world in feature branch
Line 4
Line 5


Don’t forget to delete markers: <<<<<<<, =====, >>>>>>>> Save and type:

git add <conflicted-file>
git rebase --continue

More conflicts?

You repeat the above conflict resolution step multiple times, until all conflicts are resolved.

If you find that there are too many and you want to attempt it in the future, you can type:

git rebase --abort

To try this again later in the future.




Join The Discussion