How to Resolve Simple and Complex Merge Conflicts in Git

Merge conflicts are an inevitable part of working with Git. They occur when two or more contributors modify the same part of the codebase, and Git is unable to automatically merge these changes. In this detailed post, we'll cover how to resolve both simple and complex conflicts in Git.

How to Resolve Simple and Complex Merge Conflicts in Git

Overview

Merge conflicts are an inevitable part of working with Git in collaborative environments. They occur when two or more contributors modify the same part of the codebase, and Git is unable to automatically merge these changes. In this detailed post, we'll cover how to resolve both simple and complex conflicts in Git, as well as best practices for conflict resolution.

This post will explore:

  1. What are simple and complex merge conflicts?
  2. How to resolve simple conflicts manually.
  3. How to resolve complex conflicts involving multiple files or changes.
  4. Tools and techniques to help manage conflict resolution.
  5. Best practices for minimizing merge conflicts.

1. What Are Simple and Complex Merge Conflicts?

Simple Conflicts

A simple conflict typically occurs when two or more developers make changes to the same line or section of code. The conflict is confined to a single file, and resolving it typically requires choosing between the changes or manually merging them.

Example of a simple conflict:

  • Developer A changes line 25 of a file to const apiUrl = "https://dev.api.com";.
  • Developer B changes the same line to const apiUrl = "https://staging.api.com";.

When Git attempts to merge these changes, it will flag a conflict, as it cannot decide which value should be used. However, this is considered a simple conflict because it only affects one line of code and is easily resolvable.

Complex Conflicts

A complex conflict, on the other hand, occurs when multiple files or different parts of the same file are changed in conflicting ways. These conflicts often arise in larger projects where multiple contributors are working on interconnected components, making conflict resolution more challenging. Complex conflicts can involve:

  • Multiple lines or sections of code.
  • Files that have been deleted, renamed, or moved.
  • Incompatible changes to critical logic or configuration files.

Resolving complex conflicts requires a deeper understanding of the project and the changes being made. Sometimes, this process involves communicating with other developers to understand the intent behind their modifications.

2. How to Resolve Simple Conflicts

Let's start by addressing simple conflicts, which are usually limited to one file or a few lines of code. Here's a step-by-step guide to resolving them.

Step 1: Identify the Conflict

When Git encounters a conflict, it will stop the merge process and notify you that a conflict needs to be resolved. The conflicted file(s) will be flagged in the Git status output, and you'll see a message like this:

Auto-merging file.js
CONFLICT (content): Merge conflict in file.js
Automatic merge failed; fix conflicts and then commit the result.

Step 2: Open the File with the Conflict

Open the conflicted file in your text editor or IDE. Git marks the conflicting areas with conflict markers like this:

<<<<<<< HEAD
// Your changes (current branch)
const apiUrl = "https://dev.api.com";
=======
 // Incoming changes (other branch)
const apiUrl = "https://staging.api.com";
>>>>>>> feature-branch
  • The HEAD section shows your current branch's changes.
  • The incoming section shows changes from the branch you are merging into your current branch.

Step 3: Resolve the Conflict Manually

There are three common strategies for resolving simple conflicts:

Merge the Changes: If both changes are needed, you can combine them.Example:

const apiUrl = process.env.ENV === "staging" ? "https://staging.api.com" : "https://dev.api.com";

Keep Incoming Changes: If the other branch's changes are better, delete your changes and keep the incoming changes.Example:

const apiUrl = "https://staging.api.com";

Keep Your Changes: If your changes are correct, delete the incoming changes and keep the code from your current branch.Example:

const apiUrl = "https://dev.api.com";

Step 4: Mark the Conflict as Resolved

After manually resolving the conflict, remove the conflict markers (<<<<<<<, =======, and >>>>>>>) and save the file.

Step 5: Add and Commit the Resolved File

Once the conflict is resolved, you need to tell Git that the file has been updated. Stage the file using git add and commit the changes.

git add file.js
git commit -m "Resolved merge conflict in file.js"

3. How to Resolve Complex Conflicts

When dealing with complex conflicts, you may need to manage conflicts across multiple files or deal with deleted/renamed files. Here’s how to handle more advanced scenarios.

Step 1: Identify the Conflicts

As with simple conflicts, Git will notify you about the conflicts that need to be resolved. For complex conflicts, you may see multiple files flagged in the Git status output:

Auto-merging config.json
CONFLICT (modify/delete): config.json deleted in feature-branch and modified in HEAD.
Auto-merging file.js
CONFLICT (content): Merge conflict in file.js

This message indicates that there are both content conflicts and a delete/modify conflict.

Step 2: Resolve Content Conflicts Across Multiple Files

If you have content conflicts in multiple files, open each file and resolve the conflicts manually as described in the previous section.

You can use Git tools like git diff to compare the changes in each file and better understand the nature of the conflict.

git diff --merge

Step 3: Resolve Rename/Delete Conflicts

When one branch modifies a file that was deleted in another branch, Git cannot automatically decide whether to keep the file or discard it. You’ll need to decide:

Delete the File: If the deletion should be preserved, remove the file from the working directory and stage the removal:

git rm config.json

Keep the File: If the file should remain, stage the modified file:

git add config.json

Step 4: Use git mergetool to Assist with Complex Merges

Git offers the git mergetool command, which launches an external merge tool to help you resolve conflicts visually. If you’re dealing with a large number of conflicts or need help visualizing changes, this tool can make conflict resolution easier.

git mergetool

Git will open the files with conflicts in the configured merge tool (such as KDiff3, Beyond Compare, or Meld), where you can easily compare changes and merge them interactively.

Step 5: Mark All Conflicts as Resolved

After resolving all the conflicts across the files, use git status to check the status of the merge. Stage each resolved file using git add, and then commit the merge:

git add file1.js file2.js
git commit -m "Resolved complex merge conflicts"

4. Tools and Techniques to Help Manage Conflict Resolution

Resolving conflicts manually can become tedious in larger projects, especially when dealing with multiple contributors. Luckily, there are several tools and techniques that can help streamline conflict resolution.

Git's Built-In Tools

  • git diff: Helps compare the differences between branches or commits.
  • git mergetool: Opens a graphical tool to assist with conflict resolution.
  • git log: Helps understand the history of commits and changes to a file.

External Tools

  • Meld: A graphical diff and merge tool that visually shows the differences between files.
  • Beyond Compare: A paid tool for comparing files and folders and resolving conflicts.
  • KDiff3: An open-source tool for visualizing and resolving merge conflicts.

Automating Conflict Resolution with git rerere

git rerere stands for "reuse recorded resolution." When enabled, Git will remember how you resolved a conflict for a specific section of code, and if the same conflict arises again in the future, Git can automatically apply the recorded resolution.

git config --global rerere.enabled true

This feature is especially useful when dealing with recurring conflicts in a project.

5. Best Practices to Minimize Merge Conflicts

While it’s impossible to completely avoid merge conflicts, following these best practices can help reduce their frequency and simplify conflict resolution.

5.1. Merge Frequently

Keep your feature branches in sync with the main or develop branch by merging frequently. This reduces the chance of conflicts arising from long-lived branches diverging significantly from the main branch.

git pull origin main
git merge feature-branch

5.2. Break Down Large Changes

Make smaller, more atomic commits instead of large, sweeping changes. The smaller the changes, the easier it will be to merge them and resolve any potential conflicts.

5.3. Communicate with Your Team

If you know you’re making changes to critical parts of the codebase, communicate with your team to avoid overlapping changes. Let others know when you’re modifying shared files or key functions.

5.4. Use Feature Flags

In projects with long-lived branches, consider using feature flags to reduce the need for lengthy, isolated development. This allows you to merge work into the main branch earlier without deploying incomplete features.

5.5. Rebase Before Merging

To ensure your feature branch is up to date with the latest changes from the main branch, rebase it before merging.

This can help identify and resolve conflicts ahead of time.

git pull --rebase origin main

Conclusion

Resolving simple and complex merge conflicts is an essential skill for any developer working with Git in collaborative environments. By understanding the nature of conflicts, using Git's built-in tools, and following best practices, you can resolve conflicts efficiently and keep your workflow running smoothly.

Read next

Understanding Why Merge Conflicts Happen

Working with Git, in collaborative environments, often results in multiple developers working on the same codebase. As a result, merge conflicts are a natural part of version control systems like Git. Understanding why they occur is critical for resolving them efficiently.

GitLab Flow for Continuous Deployment and Issue Tracking

GitLab Flow offers a streamlined workflow that integrates CI/CD, and issue tracking to support modern software development. It's designed to bridge the gap between development and operations. GitLab Flow simplifies branching strategies and focuses on delivering software quickly and reliably.

Simplifying Workflows with GitHub Flow

GitHub Flow offers a more streamlined and lightweight workflow for CI/CD. Created by GitHub, this workflow is designed to fit modern software development practices where speed, agility, and simplicity are paramount. It is commonly used by teams that release updates frequentlyy.