Resolving Merge Conflicts in GitHub: The Ultimate Guide

Merge conflicts are an inevitable part of working on a development team. As collaborators work on the same codebase simultaneously, sooner or later different changes will clash causing a merge conflict. A study by Microsoft Research found that merge conflicts occur in up to 15% of all pull requests on popular open source projects. While merge conflicts can be frustrating, they don‘t have to ruin your day.

In this guide, you‘ll learn what causes merge conflicts, how to spot them, and most importantly, how to resolve them quickly and painlessly using GitHub‘s tools and Git commands. We‘ll walk through examples of common conflicts and share best practices for avoiding them in the first place. By mastering merge conflict resolution, you and your team can collaborate smoothly and ship great code. Let‘s dive in!

Why Merge Conflicts Happen

Merge conflicts arise when competing changes are made to the same line of a file, or when one collaborator edits a file and another deletes it. For a concrete example, consider this simple index.html file:

<html>
  <body>

  </body>
</html>

Now imagine two team members, Alice and Bob, both decide to edit the heading text simultaneously:

Alice‘s change:

Bob‘s change:

If Alice and Bob make their changes in separate branches or forks and then try to merge them back to the main branch, Git will not know which change to apply. It will mark the file as having a conflict that needs to be manually resolved.

Other common causes of merge conflicts include:

  • Rewriting Git history by rebasing or cherry-picking commits
  • Force pushing branches
  • Changing file names or locations in different branches
  • Applying stashed changes to an out-of-date branch

Detecting Merge Conflicts

GitHub makes it easy to see which pull requests have merge conflicts. When you open a PR that has a conflict with the base branch, you‘ll see a warning message highlighted in red:

GitHub pull request with merge conflicts

Clicking on the "Resolve conflicts" button will open GitHub‘s web editor where you can see the exact lines that conflict and choose how to fix them (more on that in the next section).

If you‘re working with Git on the command line, attempting to merge branches with conflicts will result in an error message:

$ git merge alice-feature
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Git will also modify the conflicted file to include visual markers that show exactly where the problem lies:

<html>
  <body>
<<<<<<< HEAD

=======

>>>>>>> alice-feature
  </body>
</html>

The <<<<<<<, =======, and >>>>>>> markers delineate the start of the merge conflict, the point at which the two branches diverge, and the end of the conflict respectively. The code from the current branch (in this case the main branch) appears between <<<<<<< and =======, while the code from the branch being merged appears between ======= and >>>>>>>.

It‘s now your job to replace those markers and the code between them with the properly merged content. We‘ll look at how to do that next.

Resolving Conflicts in GitHub

In many cases, you can resolve simple merge conflicts right from the GitHub web interface. When you open a pull request that has conflicts, click the "Resolve conflicts" button as shown previously.

GitHub‘s conflict resolution tool shows you a split view with "their changes" from the branch being merged on the left and "our changes" from the base branch on the right:

Resolving a merge conflict on GitHub

To fix a conflict, click "Accept Incoming Change", "Accept Current Change", or manually edit the code to be how you want it. In our Alice vs. Bob example from earlier, we might settle on this resolution:

Once you‘ve fixed all the conflicts in a file, click "Mark as resolved". Repeat the process for any other conflicting files until you see "All conflicts resolved". Finally, click "Commit merge" to finalize the resolution and merge the pull request.

Resolving conflicts through GitHub‘s web interface is convenient for quick and simple fixes, but what about more complex conflicts? That‘s where resolving merge conflicts locally comes in handy.

Resolving Merge Conflicts Locally

For trickier merge conflicts, you‘ll want to resolve them on your local machine where you have more flexibility to test different resolutions, debug issues, and use your preferred code editing tools. Here‘s a step-by-step process for resolving merge conflicts locally with Git:

  1. Ensure your local main branch is up-to-date:

    $ git checkout main 
    $ git pull
  2. Check out the feature branch you want to merge:

    $ git checkout alice-feature
  3. Attempt to merge in the latest changes from main:

    $ git merge main

    If there are conflicts, Git will output an error message like before.

  4. Open the conflicted file(s) in your code editor. You‘ll see the same <<<<<<<, =======, and >>>>>>> conflict markers from earlier.

  5. Decide how to resolve each conflict by either picking all code from one side, the other, or mixing and matching. You can also write completely new code if needed. Just be sure to completely remove the conflict markers.

  6. After resolving all conflicts in a file, save it and stage the changes:

    $ git add index.html   
  7. Repeat steps 5 and 6 for all conflicted files until you‘ve resolved everything.

  8. Commit your resolution:

    $ git commit -m "Fix merge conflicts in index.html"
  9. Push your resolved feature branch back to GitHub:

    $ git push

The pull request on GitHub should now be marked as mergeable with no conflicts. You can merge it with confidence knowing you resolved the conflicts locally.

While resolving merge conflicts locally requires a few more Git steps than resolving through GitHub, the added control and ability to test resolutions makes it the preferred approach for most development teams.

Tips for Avoiding Merge Conflicts

Prevention is the best medicine when it comes to merge conflicts. While you can‘t avoid conflicts entirely, you can greatly reduce them with a few proactive habits:

  • Pull from main often: The more frequently you merge upstream changes from main into your feature branch, the less opportunity there is for serious divergence and conflicts. Get in the habit of pulling from main and merging those changes into your branch at least once per day.

  • Keep PRs small and focused: Giant 1000+ line pull requests are much more likely to generate gnarly merge conflicts that are a pain to untangle. Strive to keep PRs focused on a single feature or bug fix and try to limit the size to no more than a few hundred line changes. Smaller PRs are not only easier to merge, but easier to review too.

  • Communicate with your team: If you‘re working on a feature that touches files other people are also changing, let them know ahead of time. A quick chat IRL or via Slack can save hours of tedious merge conflict resolution. Regular pair programming or code reviews are also great opportunities to catch and address potential conflicts before they happen.

  • Agree on code conventions: Seemingly trivial things like spaces vs tabs, indent width, or line endings can all cause pesky merge conflicts. Avoid these nitpicks by establishing and enforcing team-wide code style conventions with tools like ESLint or Prettier.

  • Delete stale branches: Branches that hang around unmerged for weeks or months accumulate more and more conflicts that become harder to resolve. Make it a team habit to promptly review PRs and delete stale branches as soon as they‘re no longer needed.

Advanced Merge Conflict Scenarios

Beyond the basic "both branches changed the same line" type conflicts we‘ve covered so far, you may occasionally encounter more advanced merge conflict situations:

Resolving conflicts from a Git rebase

Rebasing is a way to re-apply a series of commits on top of a new base commit, effectively re-writing the Git history of a branch. Unlike regular merges, rebasing can cause conflicts to appear on commits in the middle of a branch‘s history instead of just the most recent commit.

If you encounter a conflict during a rebase, follow these steps:

  1. When Git prompts you with a conflict message during the rebase, run git status to see which files need attention.

  2. Open those files and resolve the conflicts using the same <<<<<<<, =======, and >>>>>>> marker method described earlier.

  3. After resolving conflicts in a file, stage it with git add [file].

  4. Once all conflicts for a particular commit are resolved, continue the rebase with:

    $ git rebase --continue
  5. Repeat steps 1-4 if additional conflicts appear deeper in the rebase.

  6. When all conflicts are resolved and the rebase completes, your branch should be conflict-free and re-written on top of the new base.

Resolving conflicts from a stash

Git‘s stash command lets you temporarily shelve changes you‘ve made to your working copy so you can switch contexts to something else, like an urgent bug fix on another branch. When you later apply those stashed changes, you may run into merge conflicts if the codebase changed since you first stashed.

To resolve stash conflicts:

  1. Attempt to re-apply your stashed changes with:

    $ git stash pop

    If there are conflicts, Git will let you know which files are affected.

  2. Open the conflicted files and manually resolve the issues using the same techniques covered earlier.

  3. Stage the resolved files with git add [file].

  4. Commit the resolution with git commit -m "Resolve stash conflicts".

  5. Your working copy should now have your stashed changes minus the conflicts.

Resolving Merge Conflicts Like a Pro

Merge conflicts can seem daunting at first, but with the right tools and approach you can resolve them like a seasoned pro. The next time you see that scary "merge conflict" error message, take a deep breath and follow these steps:

  1. Identify which files have conflicts using GitHub‘s UI or the output of git status.

  2. Decide whether to resolve the conflict(s) through GitHub‘s web editor or on your local machine.

    • Use GitHub‘s web tool for simple conflicts that only require picking one side or the other
    • Resolve locally for more complex, multi-line conflicts that need custom resolution code
  3. Walk through each conflicted file and resolve issues either by choosing code from one branch, the other, writing new code, or some mix of all three. Be sure to remove all <<<<<<<, =======, and >>>>>>> conflict markers.

  4. After resolving all conflicts, commit your changes and merge the pull request.

  5. If resolving locally, push your feature branch up to GitHub and merge the PR from there.

  6. Take a moment to celebrate, then get back to writing awesome code!

With practice and patience, resolving even the gnarliest merge conflicts can become second nature. Don‘t let fear of merge conflicts keep you from collaborating closely with your team or contributing to open source. Embrace the merge conflict resolution process as an opportunity to problem solve and produce the best possible code.

Additional Resources

Hungry for even more merge conflict mastery? Dig in to these additional resources:

Key Takeaways

  • Merge conflicts happen when Git can‘t automatically merge branches because code changed in conflicting ways. Common causes include two people editing the same line, deleting files someone else modified, and rewriting Git history.
  • GitHub makes merge conflicts easy to spot by marking PRs as "conflicting" and providing a web-based conflict editor for simple resolutions.
  • For more complex conflicts, resolve merge conflicts locally using Git on the command line and your code editor of choice.
  • Avoid merge conflicts proactively by keeping branches up-to-date with main, making small focused PRs, communicating with teammates, and establishing code style conventions.
  • Practice makes perfect when it comes to resolving merge conflicts. The more you do it, the less painful it becomes.