Before I can explain the concept of resolving a merge conflict during a rebase, I need to elaborate a little about how and why merge conflicts occur.
What happens during a rebase?
The reason we want to rebase, is to move our commits to the top of a TARGET branch. Let’s take this history as a scenario:
* c293e3a updated library `Y` dependency version
* 339ac3c moved and renamed foo.js to src/bar.js
| * 9e538b9 renamed class `Foo` to `Bar`
| * 71c8156 added new external library `X`
| * 448624b `baz` function now uses the validation provided via `X`
|/
* 2b3b44a baz now submits records via XHR
After a rebase we should achieve the following history:
* 9e538b9 renamed class `Foo` to `Bar`
* 71c8156 added new external library `X`
* 448624b `baz` function now uses the validation provided via `X`
* c293e3a updated library `Y` dependency version
* 339ac3c moved and renamed foo.js to src/bar.js
* 2b3b44a baz now submits records via XHR
When rebasing you will see the message:
First, rewinding head to replay your work on top of it...
What it actually does is resetting the HEAD to point to the hash you want to rebase on:
$ git reset --hard TARGET_HASH
Now that the HEAD is the same as your TARGET, all commits that do not exists on the TARGET will be re-applied.
When performing an interactive rebase, you will be presented with a list of commits that will be re-applied:
pick 9e538b9 renamed class `Foo` to `Bar`
pick 71c8156 added new external library `X`
pick 448624b `baz` function now uses the validation provided via `X`
What actually happens are cherry-pick's, picking the commits from the list, top to bottom, one by one:
$ git cherry-pick 9e538b9
$ git cherry-pick 71c8156
$ git cherry-pick 448624b
Which results in a happy case as:
Applying: renamed class `Foo` to `Bar`
Applying: added new external library `X `
Applying: `baz` function now uses the validation provided via `X`
Merge conflicts, HELP?!
There are several types of conflicts. Unfortunately there is no silver bullet. Let’s identify them and resolve them accordingly.
File content conflict
This is the most common merge conflict. In a git merge
the end result of the two targets will be presented as one big blob of changes.
There is no information on how the changes got to this point. This makes merging very knowledge heavy.
If you are not involved with the changes causing the merge conflicts, you’ll probably have a difficult time resolving the conflicts alone.
Now git rebase
applies commits one by one. This also means you will be resolving conflicts 1 commit at a time.
This gives you a context for the conflict. Our first commit is a rename of the class Foo
to Bar
.
This means other files might have updated the reference to this class as well. If in the TARGET branch the constructor has changed, you’ll get a conflict.
<<<<<<<<<<<<<
// changes applied by target branch
const foo = new Foo('baz');
==============
// how the code looked like from their common ancestor
const foo = new Foo();
==============
// the changes applied by `renamed class Foo to Bar`
const bar = new Bar();
>>>>>>>>>>>>>>
Now the conflict has a lot of context. Even when looking at these changes for the first time, it is simple to deduct how this conflict should be resolved:
const bar = new Bar('baz');
Let's continue our rebase:
git add
git rebase --continue
The next conflict that occur is with the commit: baz
function now uses the validation provided via X
<<<<<<<<<<<<<
// changes applied by target branch
foo.baz(1, 2, value);
==============
// how the code looked like from their common ancestor
foo.baz(1, 2);
==============
// the changes applied by `baz` function now uses the validation provided via `X`
bar.baz(1, 2);
>>>>>>>>>>>>>>
Once again, we have a context around our conflict. We can reason what the result should be.
This demonstrates how commits that are sensible can help resolving conflicts.
Changes already applied
These are conflicts are are easiest to resolve. However the message provided by git might seem scary at first.
First, rewinding head to replay your work on top of it...
Applying: added new external library `X`
Using index info to reconstruct a base tree...
M package.json
Falling back to patching base and 3-way merge...
No changes -- Patch already applied.
Since the changes of this commit are already applied, we can skip this commit all together:
$ git rebase --skip
Often conflicting files
Some files are prone for conflicts. Imagine a package.json
dependency that has a dependency that gets updated frequently.
"some-uber-cool-dep": "1.3.44"
It could happen that your branch has the dependency updated, but on the TARGET branch the dependency is even further:
<<<<<<<<<<<<<
// changes applied by target branch
"some-uber-cool-dep": "1.3.44"
==============
// how the code looked like from their common ancestor
"some-uber-cool-dep": "1.3.40"
==============
// the changes applied by
pick 71c8156 added new external library `X`
"some-uber-cool-dep": "1.3.43"
>>>>>>>>>>>>>>
Now we could simply skip this commit, as the changes that would be applied make no sense anymore.
$ git rebase --skip
File moved and renamed conflict
This is probably the most difficult to resolve conflict, a file that you've edited has moved and renamed. The description is also most cryptic to understand:
Merging:
foo.js
Deleted merge conflict for 'foo.js':
{local}: deleted
{remote}: modified file
Use (m)odified or (d)eleted file, or (a)bort?
Git is unable to relate the changes to the same file. We can help git by performing a rename in our branch before our other changes. To do this we are going to first abort the current rebase:
$ git rebase --abort
The first commit where we edited foo.js
was 3 commits ago. To edit this commit, we have to point the rebase at least one commit before the to be edited commit.
Git provides a shortcut with HEAD
pointing to the current commit and ~n
for the amount of commits you want to go back:
$ git rebase -i HEAD~4
We would want to mark the first commit to be edited. Keep in mind, the changes from the following commits will be applied after.
To mark a commit to be edited, change the pick
to e
:
e 9e538b9 renamed class `Foo` to `Bar`
pick 71c8156 added new external library `X`
pick 448624b `baz` function now uses the validation provided via `X`
The rebase will apply the commit and wait for your changes:
Stopped at 9e538b9... renamed class `Foo` to `Bar`
You can amend the commit now, with
git commit --amend
Once you are satisfied with your changes, run
git rebase --continue
Now rename the file to what it has be renamed to on the TARGET. Now we amend the commit and continue with the rebase:
$ git commit -a --amend
$ git rebase --continue
The following commits will be applied on the renamed file. Now git will be able to associate the changes to the move.
So the rebase on the target should now apply our commits without the cryptic error message!