Skip to content

Solving git rebase conflicts

Let’s see how to solve merge conflicts that may arise when applying git rebase.

Why git rebase?

One of the main uses of git rebase is to keep a linear commit history. Let’s say that you branch off of master and make new modifications in the new branch feature. During this process, new changes could be added to master.

In the example below, we created the branch feature at commit initial commit. We submitted two commits to the feature branch. Meanwhile, master has diverged from its initial state and was added a new commit: master: new commit.

* e6b1706 (HEAD -> master) master: new commit
| * d05f6db (feature) feature: second commit
| * f787560 feature: first commit
|/  
* f578e2b initial commit

If we wanted to merge feature into mater, we would get the following commit history:

*   e03514e (HEAD -> master) Merge branch 'feature'
|\  
| * d05f6db (feature) feature: second commit
| * f787560 feature: first commit
* | e6b1706 master: new commit
|/  
* f578e2b initial commit

This commit history isn’t very clear. Specially if master has diverged too much from the branch point of feature.

A linear commit history

Let’s go back to our previous example, right before merging feature into master.

* e6b1706 (HEAD -> master) master: new commit
| * d05f6db (feature) feature: second commit
| * f787560 feature: first commit
|/  
* f578e2b initial commit

We can rebase the feature branch to achieve a linear commit history when merging.

What git rebase does is to move all of the commits in feature to a new base commit. In this case, we want to change the feature base to master‘s current reference (e6b1706). We can do so with the following command.

$ git rebase master feature

Or:

$ git rebase master

If the current branch is feature.

After doing so, the new commit history will look as follows:

* 1c4b251 (HEAD -> feature) feature: second commit
* b7c3fbb feature: first commit
* e6b1706 (master) master: new commit
* f578e2b initial commit

feature now branches off of master‘s most recent commit.

If we merge feature to master (using the --no-ff option), we’ll get the following commit history:

*   5f2d014 (HEAD -> master) Merge branch 'feature'
|\  
| * 1c4b251 (feature) feature: second commit
| * b7c3fbb feature: first commit
|/  
* e6b1706 master: new commit
* f578e2b initial commit

This commit history is much clearer. Additionally, if a bug was introduced during merging, we can be sure that it was introduced by the commits in feature. Without a linear commit history, we don’t know if the bug was introduced by the feature branch or by the commit e6b1706:

*   e03514e (HEAD -> master) Merge branch 'feature'
|\  
| * d05f6db (feature) feature: second commit
| * f787560 feature: first commit
* | e6b1706 master: new commit
|/  
* f578e2b initial commit

git rebase merge conflicts

While it’s great to have a linear commit history, we can run across merge conflicts when running git rebase. Let’s see an example.

We start on branch master that contains file mycode with the following content and commit history:

// mycode
a
* f578e2b (HEAD -> master) initial commit

Next, we create the new branch feature and modify mycode with the following changes.

// mycode
a
b
c
* d05f6db (feature) feature: second commit
* f787560 feature: first commit 
* f578e2b (master) initial commit

The second line b was added by commit f787560 and c was added by commit d05f6db. At this point, we’re ready to merge!

However, during that period a co-worker has submitted new changes to master, modifying mycode:

// mycode
a
aa

The commit history now looks like so:

* e6b1706 (HEAD -> master) master: new commit
| * d05f6db (feature) feature: second commit
| * f787560 feature: first commit
|/  
* f578e2b initial commit

When trying to rebase, we’ll get a conflict message.

$ git rebase master feature
Auto-merging file
CONFLICT (content): Merge conflict in mycode
error: could not apply f787560... feature: first commit

What happened here is that both master and feature added modifications to the same part of mycode and git does not know which modification to choose. Rebase applies all commits that were present in feature to a new branch with base master. In case of conflict, the process is halted before applying the conflicting commit.

Opening file mycode we’ll see its contents have changed:

// mycode
a
<<<<<<< HEAD
aa
=======
b
>>>>>>> f787560 (feature: first commit)

It may look a bit cryptical at first, but it’s quite simple. The current state of the conflicting portion of mycode starts just after <<<<<<< HEAD and continues all the way down to =======. Below the = line and all the way down to >>>>>>> f787560, we have the new changes that git rebase wants to apply.

Basically, git is asking, which modification should I choose aa or b?

The answer depends on what you want your code to do. Maybe we have to choose either aa or b. Or maybe we want both to coexist.

Let’s imagine we want to keep aa, but we need to modify b to bb to be compatible with it. To resolve the conflict, we need to erase all of the lines added by git and then leave mycode in the state that we want.

// mycode
a
aa
bb

After that’s done, be sure to save the new changes to mycode. Then, we run git commit -a.

What we’re doing here is applying a new commit instead of the conflicting commit f787560. We can lave the commit message as it was or modify it to reflect the new changes.

Once that’s done, the rebase operation can be resumed by running git rebase --continue.

The rebase operation will apply the new commit that we just created and continue with the remaining commits that we had in feature.

New merge conflicts may arise, and they are solved in the same way. This process is continued until you see a successful rebase message:

$ git rebase --continue
Successfully rebased and updated refs/heads/feature.

Verifying changes and modifications with git range-diff

A handy command to check how the commits changed after the rebase operation is git range-diff:

$ git range-diff feature@{1}...feature

feature@{1} is the previous reference of feature (that is, before rebasing). It is compared to the current reference of feature.

The output displays how the commits between both references differ:

1:  f787560 < -:  ------- feature: first commit
2:  d05f6db < -:  ------- feature: second commit
-:  ------- > 1:  9ab5dc4 master: new commit
-:  ------- > 2:  b1c4fd1 feature: first commit
-:  ------- > 3:  34a3583 feature: second commit

In this case, it shows that the two original commits have been replaced.

In the hypothetical case that the rebase operation had not raised any conflicts, the output would look like so:

-:  ------- > 1:  020882b master: new commit
1:  f787560 = 2:  1091dbd feature: first commit
2:  d05f6db = 3:  8e94b28 feature: second commit

This is very helpful because it verifies for us that the two commits that were part of the feature branch did not change after the rebase operation. Or, in case of changes, it shows us how they are different.

Published inProgramming
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments