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.