Git Merge Conflicts: A Visual Guide to Resolution
What causes merge conflicts, how to read conflict markers, and a step-by-step process for resolving them confidently.
What Causes Merge Conflicts
A merge conflict occurs when Git cannot automatically reconcile differences between two branches. Git is remarkably good at merging: it can combine changes from different branches as long as they modify different parts of a file. But when two branches modify the same lines of the same file, Git cannot determine which change should take precedence. In that situation, it stops the merge and asks you to resolve the conflict manually.
Conflicts typically happen in three scenarios:
- Concurrent edits: Two developers modify the same line of code on different branches. This is the most common cause.
- File deletion vs. modification: One branch deletes a file while another branch modifies it. Git does not know whether to keep the modified version or respect the deletion.
- Rename conflicts: Both branches rename the same file to different names, or one branch renames a file while another modifies its original path.
Conflicts are a normal part of collaborative development. They are not errors or bugs -- they are Git asking for human judgment when the automated merge algorithm reaches its limits.
Anatomy of a Conflict Marker
When a conflict occurs, Git modifies the affected file by inserting conflict markers that delimit the competing changes. Understanding these markers is the first step to resolving any conflict.
<<<<<<< HEAD
const API_URL = "https://api.production.example.com";
=======
const API_URL = process.env.API_URL || "http://localhost:3000";
>>>>>>> feature/dynamic-configThe three marker types serve distinct purposes:
<<<<<<< HEAD: Marks the beginning of your current branch's version (the branch you are merging into). Everything between this marker and the separator is "ours."=======: The separator between the two versions. Above is "ours," below is "theirs.">>>>>>> feature/dynamic-config: Marks the end of the incoming branch's version (the branch you are merging from). The branch name is included for reference.
A single file can contain multiple conflict regions, each enclosed in its own set of markers. Every conflict must be resolved individually.
Three-Way Merge: Base, Ours, Theirs
Git uses a three-way merge strategy that considers three versions of the file:
- Base: The common ancestor -- the version of the file at the point where the two branches diverged. This is the "original" that both sides started from.
- Ours: The version on the current branch (HEAD). These are your local changes.
- Theirs: The version on the incoming branch. These are the changes you are merging in.
The three-way approach is more intelligent than simply comparing two files. By knowing the base version, Git can determine which side made a change. If only one side modified a line, Git takes that change automatically. Conflicts only arise when both sides changed the same line relative to the base.
You can see the base version by enabling diff3 conflict style:
git config merge.conflictstyle diff3With diff3 enabled, conflict markers include the base version:
<<<<<<< HEAD
const API_URL = "https://api.production.example.com";
||||||| merged common ancestors
const API_URL = "http://localhost:3000";
=======
const API_URL = process.env.API_URL || "http://localhost:3000";
>>>>>>> feature/dynamic-configNow you can see that the base had a hardcoded localhost URL. Your branch changed it to a production URL, while the incoming branch made it configurable via environment variable. This context makes the right resolution much clearer.
Step-by-Step Resolution Process
When you encounter a merge conflict, follow this systematic process:
- Identify conflicted files: Run
git status. Conflicted files appear under "Unmerged paths" with the label "both modified." - Understand both changes: Before editing anything, read both sides of each conflict carefully. Use
git log --merge -p -- filenameto see the full history of changes from both branches. - Edit the file: Remove the conflict markers (
<<<<<<<,=======,>>>>>>>) and write the correct merged version. This might be one side, the other side, or a combination of both. - Test: Run your tests to ensure the resolved code works correctly. A syntactically valid resolution can still be semantically wrong.
- Stage the resolved file: Run
git add filenameto mark the conflict as resolved. - Complete the merge: Run
git merge --continue(orgit commit) to finish the merge with a commit message.
Common Conflict Patterns and How to Resolve Them
Adjacent Line Edits
Two developers modify nearby (but not identical) lines. Git sometimes marks these as conflicts even though the changes do not overlap. The resolution is usually to keep both changes:
// Resolution: keep both changes
const timeout = 10000; // From "ours"
const retries = 5; // From "theirs"Function Signature Changes
One branch adds a parameter while another renames a parameter. The resolution requires understanding the intent of both changes and combining them:
// Ours: renamed 'callback' to 'handler'
function fetch(url, handler) { ... }
// Theirs: added 'options' parameter
function fetch(url, callback, options) { ... }
// Resolution: apply both changes
function fetch(url, handler, options) { ... }Import Statement Conflicts
Both branches add new import statements at the same location. This is extremely common and usually trivial to resolve: keep all imports from both sides.
Package Lock File Conflicts
Lock files (package-lock.json, yarn.lock) generate massive, unreadable conflicts. The standard practice is to accept one version and regenerate: resolve the conflict in package.json, then run npm install to regenerate the lock file.
Aborting and Retrying
If a merge becomes overwhelming or you realize you need more context, you can safely abort:
git merge --abort: Cancels the merge entirely and restores your branch to its pre-merge state. No changes are lost.git checkout --ours filename: Resolves a specific file by keeping your version entirely, discarding the incoming changes.git checkout --theirs filename: Resolves a specific file by accepting the incoming version entirely, discarding your changes.
These escape hatches are safe and reversible. Do not hesitate to abort a merge if you need to gather more information before proceeding.
Preventing Conflicts
While conflicts cannot be eliminated entirely, these practices dramatically reduce their frequency:
- Communicate with your team: If two people plan to modify the same file, coordinate beforehand.
- Keep pull requests small: Smaller PRs touch fewer files and lines, reducing the probability of overlap with other developers' work.
- Merge or rebase frequently: Regularly integrating changes from the main branch keeps your feature branch up to date and catches conflicts early when they are small.
- Use feature flags: Instead of long-lived feature branches that diverge significantly, use feature flags to merge code incrementally while keeping the feature hidden.
- Establish code ownership: When specific modules or files have clear owners, concurrent edits happen less often.
Using Diff Tools to Understand Conflicts
A diff tool helps you understand conflicts by visualizing the three-way comparison. Rather than reading raw conflict markers in a text editor, you can see the base, ours, and theirs versions side by side with changes highlighted.
Try using our Diff Checker to compare the "ours" and "theirs" versions of a conflicted section. The visual highlighting makes it immediately clear what each side changed, which is essential for choosing the correct resolution.
For understanding how specific patterns match in conflicted code, our Regex Tester can help you verify patterns against both versions. And for Git commit hashes referenced in conflict resolution, you can verify them with our Hash Generator.
Further Reading
- Git merge documentation
Official Git reference for merging branches and resolving conflicts.
- Atlassian — Git merge conflicts
Practical tutorial on understanding and resolving merge conflicts.
- Pro Git — Advanced Merging
The Pro Git book chapter on merge strategies and conflict resolution.