How to have a developer help resolve merge conflicts
This how-to article will describe a workflow that can allow an integrator make a developer help resolve merge conflicts that they don't know how to deal with.
As a summary of why you might care about this, imagine the situation where you (as an integrator) are attempting to merge a specific feature branch into next (or master) and you encounter endless merge conflicts. You take a look at them, but have no idea how to resolve the conflicts and are overall confused about how to proceed. However, the developer knows how to resolve the conflicts, since they wrote the code. One possibility is to have the developer sit next to you and help you resolve the conflicts, but that ends up wasting a lot of both of your time. Alternatively, you could task the developer with providing example resolutions, and providing them to you so that you can use them to perform the actual merge. This how-to will go over this process and help you understand how to instruct a developer to provide you with an example resolution, and how to make use of the example resolution in your merge.
To begin, the concepts this article will cover are:
- History examination
- rerere cache usage (Building / using a cache)
- Test merges
One of the most useful concepts for this specific practice is the use of a rerere (stands for reuse recorded resoluiton) cache. For more information on what a rerere cache is please read this: https://git-scm.com/blog/2010/03/08/rerere.html
The tl;dr is that a rerere cache provides methods for git to automatically resolve merge conflicts using recorded resolutions. To build up a rerere cache you first need to resolve merge conflicts, which implies you need to know how to resolve merge conflicts.
By the end of this how-to you should know to:
- Enable your rerere cache
- Help a developer provide resolutions
- Use provided resolutions to build your rerere cache
- Use your rerere cache to make your merge easier
Enable your rerere cache
Before building a rerere cache, you need to enable this option within git. To enable your rerere cache, run the following command: `git config rerere.enabled true`
This will allow a rerere cache to be placed in: .git/rr-cache
All of the resolutions that are recorded will be placed in this directory.
NOTE: The developer does not need to have a rerere cache enabled, but if they want to that's fine.
Help a developer provide conflict resolutions
After you have decided that the develop should provide you with example resolutions, you need to tell the developer how to make example resolutions and how to provide them to you.
These example resolutions are most easily provided through a resolution branch. The steps for creating this branch are below, but you as the integrator would pick what the <feature> and <merge> branches are (note <merge> should be a permanent branch, like master, next, or a maint branch, depending on where the <feature> branch has issues merging into).
The developer will:
- Create a new branch at the HEAD of their feature branch: git checkout -b <feature>-resolved <feature>`
- Make sure renaming is caught in the merge: git config merge.renamelimit 99999
- Merge the <merge> branch into their new branch: git merge origin/<merge>
- NOTE: You can replace origin/<merge> with a specific hash to make sure the merge is what you were attempting.
- Resolve the merge conflicts
- Edit the conflicting files (listed with git status)
- Add the resolved files: git add <files>
- Commit the resulting merge: git commit
- Push the resolution branch: git push origin <feature>-resolved
Using provided resolutions to build a rerere cache
Before actually building your rerere cache, you might choose to flush your rerere cache. The main reason here is that sometimes your rerere cache can be built with bad resolutions (for example, if you resolved the same thing as a developer already in a different way).
You can see the resolutions that are already recorded in .git/rr-cache, but you could choose to flush your entire rerere cache. To do either of these, just delete the contents of .git/rr-cache.
Now, the developer has provided you with a merge of their branch and the branch you selected (i.e. master). However, the merge was done in the wrong order (i.e. master was merged into their branch, not the other way around). So what we're going to do now, is build a rerere cache using the merge they provided. Again, as the integrator, you picked what <feature> was, so you should know what <feature>-resolved is.
Perform the same merge as the developer, in the same way:
(Make sure your renamelimit is set properly before beginning git config merge.renamelimit 99999)
To begin with, you're going to perform the same exact merge that the developer did. To do this, you'll need to figure out the two parent commits to the merge the developer did. To find these out, use the following two commands:
- Find the first parent commit: git log -n 1 <feature>-resolved^1
- Find the second parent commit: git log -n 1 <feature>-resolved^2
NOTE: The merge commit could have more parents than just two, so keep in mind you need to get all of the parent commits for this merge to work. The list of parents can also be found by looking at `git log -n <feature>-resolved`. Specifically the "Merge:" line lists all of the parents in order.
From the instructions above, you're going to checkout the first parent commit of the merge the developer provided you with
- git checkout <hash>
After that, you are going to merge all of the other parent commits into the commit you're currently on:
- git merge <parent_list>
Here, <parent_list> is simply a list of all of the hashes from the list of parents aside from the first parent.
Now, after you perform this merge, you'll get a list of conflicts just like you did before (what motivated you to do this in the first place). However, you already have resolutions for how to perform this merge (from the developer). To make use of the developer's resolution branch, simply check it out over the top of all of your existing files:
- git checkout <feature>-resolved -- .
At this point, the files should not have any conflicts. To check (this might fail, if someone has comments that look like conflict markers) you can `git grep -l "<<<<<"`
Finally, you need to add the files, so git will let you commit:
- git add .
And commit the resolved versions:
- git commit
It really doesn't matter what the commit message looks like here, since this is a throw away commit. The sole purpose of creating this is so your rerere cache has the resolutions the developer provided you with. After this commit, you should see messages like the following:
Recorded resolution for '<file>'
This means that git has now recorded the resolution you provided. If you don't get this message for every file that was in conflict, it likely means the resolution was incomplete (there are still merge markers somewhere that were not resolved).
Using a rerere cache to make your merge easier
The nice thing about rerere caches is that they are automatically applied, but not staged, into a merge.
As an example, if you now perform the exact same merge as before, but in the other direction (i.e. you're on master instead of <feature>, and you merge in <feature>) you'll see messages like:
Resolved '<file>' using previous resolution.
This means that git found a resolution in your rerere cache, and applied it. Note though, if you type git status you'll still see the files in conflict listed, but if you open them up, you won't see any conflicts. Git forces you to manually stage them, to ensure you actually looked at them and made sure the merge was done properly (additionally, it won't resolve any new conflicts if you're merging slightly different commits).
Guided example on the SE telecon:
- fcf7a63 (master)
- 47a7c54 (next)
- origin/bishtgautam/lnd/vsfm (branch, hash: 469f4cb)