Ten years ago, I was just starting out in my career as a developer. Back then, I was using subversion for my version control—then I came across Git. I remember how thrilled I was to find that Git worked way better than subversion. Subversion requires a workaround just to have branches. In Git, branching is a first-class citizen: explicitly available without your having to use weird workarounds. Merging code is a lot smoother in Git as well.
In other words, Git was an awesome invention, one that spawned the business GitHub shortly thereafter. GitHub became everybody’s remote Git repository starting in early 2008.
Not too long after Git and GitHub emerged, this question appeared on Stack Overflow:
With more than 4,000 votes and 33 different answers, this is clearly a popular question for developers. In this article, I will explore how to handle merge conflicts in these common scenarios:
- Sending pull requests in GitHub
- Pulling remote changes to a local repository
- Performing a merge and rebase
At the end, I’ll wrap up by going through some simple ways to keep merge conflicts from happening in the first place.
1. Conflicts From Sending Pull Requests in GitHub
In this scenario, I deliberately created a merge conflict (it’s harder than you might think!) with two separate feature branches. Both feature branches (I’m calling them section1 and section2) branched off from the same master branch but got merged back to the master branch via pull requests at different times. As you might expect, the pull request that got merged first had no issue. But when I tried to merge the second pull request, I got a merge conflict.
You should expect to see something like the image below at the pull request when you’re facing a merge conflict.
Notice how GitHub disabled the merge pull request button. There’s also an additional message about conflicts in the branch.
There are two possible situations at this point:
- The Resolve conflicts button is available.
- The Resolve conflicts button is NOT available. Usually this happens because the conflicts are more complicated.
I will proceed assuming the first scenario. The second has the same solution as when you fetch remote changes locally and experience the merge conflict. In that scenario, the resolution has to be done locally first. (I’ll talk more about how to resolve merge conflicts locally later on.)
Resolve within GitHub’s Web Editor
- Click on Resolve conflicts and you should see the entire display of the changed files in the pull request. Notice that GitHub has disabled the Mark as resolved button.
- Resolve the conflicts in the first file you see.
- Ensure that all traces of <<<<<<, >>>>>>, and ====== are removed.
- If you do this correctly, you should see the button Mark as resolved become available for that particular file.
- If you have multiple files with conflicts, select the next file to resolve. Repeat steps two through four until you’ve resolved all of your pull requests’ merge conflicts.
- Now the Commit merge button is available.
- Click Commit merge and carry on with your merge pull request.
2. Conflicts From Pulling Remote Changes to a Local Repository
Now that you know how to resolve merge conflicts when sending pull requests to GitHub, it’s only right that you also learn how to resolve merge conflicts that arise when you fetch remote changes from GitHub. This section will also cover how to deal with the more complicated merge conflicts that GitHub does not let you resolve, as we touched on in the first section.
Let’s get started:
- Fetch all the remote changes from GitHub and switch to <branch-to-merge-into>. Let’s assume the same procedure as in the previous section and try to merge feature/add-section2 back into master. So <branch-to-merge-into> is master.
git fetch origin git checkout <branch-to-merge-into> git pull
- Trigger the merge conflict by git merge feature/add-section2.
- Now you have basically two choices to resolve your conflict:
- You can open up your favorite IDE or code editor and go through the conflicts one at a time. Some editors might even help you by flagging the actual files.
- You can use native mergetools available in your system (I will cover this in the next section).
- Essentially, you are doing the same thing here as with the GitHub web editor example by removing the >>>> and <<<< and then changing the code for all affected files.
- Typing git status will render a statement about unmerged paths.
- Typing git commit -a will render a commit message about merging conflicts.
- You may add more comments or you can simply keep it as it is. Stage the change and you are done.
Setting up Mergetools
If you are using a mac, you have a range of available mergetools for you. These include meld, opendiff, vimdiff, and tortoisediff.
To activate these tools, simply type git mergetool the moment after you’ve triggered a local merge conflict.
You can choose to configure your mergetool. Typing “`git config merge.tool vimdiff`” will configure vimdiff as the mergetool of choice. You can also install other mergetools if you like.
Finally, to trigger the mergetool, simply type git mergetool again.
Here’s a look at the mergetool I use. I prefer FileMerge, which opendiff is responsible for triggering.
3. Conflicts from Performing a Merge and Rebase
Sometimes, you’ll want to merge and rebase at the same time and you’ll fail due to a merge conflict. You might still be able to perform the regular merge on its own, or you might not. But let’s say you insist on doing it with the rebase. What do you do?
Once again, we’re trying to merge and rebase the feature/add-section2 branch into the master branch. You can only do this locally.
Let’s dive in:
- Fetch all the remote changes from GitHub for your <branch-to-merge-into> and <feature-branch>. In this case, remember that your feature branch is feature/add-section2.
git fetch origin git checkout master git pull git checkout feature/add-section2 git pull
- Perform the rebase inside your feature branch with git pull origin master –rebase.
- Resolve the merge conflict as per normal.
- Force push your newly rebased feature branch back to remote git push -u origin feature/add-section2 -f. (Warning! Be absolutely certain nobody else has made any new changes to the remote version of your feature branch. The forced push will override those new changes.)
- Now you can go to GitHub to perform the merge and rebase.
How to Reduce Merge Conflicts
So far, I have covered various ways to resolve merge conflicts under the three most common scenarios:
- Sending pull requests to GitHub
- Fetching remote changes from GitHub
- Attempting to merge and rebase
I have also added some helpful information about how to set up your mergetool, should you desire to do so. Before I conclude, I want to save you some future headaches by showing you how to reduce the number of merge conflicts you generate in the first place. As the saying goes, an ounce of prevention is worth more than a pound of cure. Here are three useful steps to help reduce merge conflict headaches you may have. Bear in mind, though: merge conflicts are inevitable. You can decrease them, but you can never fully eliminate them.
Fetch Remote Changes Frequently to Avoid Big Conflicts
Fetch remote changes frequently from the main branches and then handle the changes upstream. While you may need to resolve merge conflicts more frequently, this means that you’ll be resolving smaller conflicts each time. Resolving merge conflicts can get pretty hairy, especially for big projects with dozens of collaborators where the codebase runs into millions of lines.
Have Fewer Developers Working off the Same Branch
Merge conflicts increase tremendously when you have many people working on separate features and trying to merge back to the same branch. This is where your project manager can help. He or she will likely plan release branches from the master branch and then break the release branches into smaller feature branches, which in turn can be further subdivided. Good old divide and conquer, I say.
Implement Feature Flags Management Solution with Trunk-Based Development
Instead of handling multiple feature branches, a more straightforward method may be to implement feature flags. Employ a feature flag management solution that allows for trunk-based development. This is especially crucial when it comes to early-stage development, when features are often dropped or changed drastically based on feedback. It’s worth it for your velocity and developer sanity to have a cleaner way to reduce merge conflicts. Sometimes, resolving merge conflicts can feel like writing the same code twice. Avoid unnecessary complications; create an environment where your developers (and you!) can be more productive.
That’s it! You are now fully equipped with all the knowledge you’ll need to handle those pesky merge conflicts. If you think your colleagues will find this guide useful, do share it with them. They will thank you for it.