Menu
Home Articles About Work With Me
Git branch visualization with colorful lines diverging and converging
Technology Apr 14, 2026 • 16 min read

OMG Not Another Merge Conflict

Git merge conflicts explained with real solutions. How to resolve them in the CLI, GitHub, VS Code, and Visual Studio 2026. History, causes, best practices, and the mindset shift that stops them from happening.

Share:
Lee Foropoulos

Lee Foropoulos

16 min read

Continue where you left off?
Text size:

Contents

Merge conflicts aren't a Git problem. They're a clarity problem. And your repo just exposed it.

You know the feeling. You've been heads-down on a feature branch for three days. The code is clean. Tests pass. You open a pull request, click merge, and Git hits you with CONFLICT (content): Merge conflict in src/utils/auth.js. Your stomach drops. You stare at <<<<<<< and >>>>>>> like someone left a ransom note in your codebase.

Here's the thing most developers get wrong: they treat merge conflicts like an interruption. Something to click through. A speed bump between them and a merged PR. That's a mistake. Every merge conflict is a decision point. Handle it right, and your codebase gets stronger. Handle it wrong, and you ship bugs that don't show up for weeks.

This post is the complete guide. We'll cover where Git came from, why conflicts happen, and exactly how to resolve them in four environments: the CLI, GitHub's web editor, VS Code, and Visual Studio 2026. By the end, you'll stop dreading merge conflicts and start treating them like the quality checkpoints they actually are.

63%
of developers say merge conflicts are their most frustrating Git experience, according to the 2025 Stack Overflow Developer Survey
Team of developers collaborating around a monitor reviewing code together
Merge conflicts are a team problem disguised as a technical one. The code is just the messenger.

A Brief, Slightly Angry History of Git

To understand merge conflicts, you need to understand why Git exists in the first place. And that story starts with an argument.

The BitKeeper Incident

In 2005, the Linux kernel was the largest collaborative open-source project on Earth. Thousands of contributors. Millions of lines of code. And the version control system holding it all together was BitKeeper, a proprietary tool that gave the Linux community a free license out of goodwill.

Then Andrew Tridgell, an Australian developer, reverse-engineered parts of the BitKeeper protocol. BitKeeper's owner, Larry McVoy, revoked the free license. Overnight, the Linux kernel had no version control system.

Linus Torvalds didn't go shopping for an alternative. He built one. In two weeks. He called it Git, and it changed everything about how software teams collaborate.

14 days
the time it took Linus Torvalds to write the first working version of Git in April 2005, while managing 20,000+ Linux kernel patches per release

Why Distributed Matters for Conflicts

Before Git, most teams used centralized systems like CVS and Subversion. Everyone committed to one central server. If two developers edited the same file, the second one to commit would get a rejection. Sometimes changes were silently overwritten. The system created a race condition out of basic collaboration.

Git flipped the model. Every developer gets a full copy of the repository. You branch locally, commit locally, and merge when you're ready. Conflicts don't surprise you at commit time. They surface at merge time, when you're actively choosing to integrate changes. That distinction matters. It means conflicts happen in a controlled moment where you can think, compare, and decide.

Linus didn't build Git to avoid merge conflicts. He built it to make them survivable.

"I'm an egotistical bastard, and I name all my projects after myself. First 'Linux', now 'git'."

That attitude produced a tool that handles branching and merging better than anything before it. Twenty-one years later, Git runs over 95% of all version-controlled software projects on the planet.

What Actually Causes a Merge Conflict

Git is remarkably good at merging code automatically. Most of the time, you merge a branch and nothing goes wrong. So what triggers the dreaded CONFLICT message?

The Three-Way Merge

When you run git merge feature-branch, Git doesn't just compare your branch to the feature branch. It finds the common ancestor, the last commit both branches share, and performs a three-way comparison:

  1. Base: The common ancestor (what the code looked like before either branch changed it)
  2. Ours: Your current branch's version
  3. Theirs: The incoming branch's version

If only one side changed a particular section, Git takes that change automatically. If both sides changed the same section differently, Git can't decide which version is correct. That's your merge conflict.

Whiteboard covered in diagrams and flowcharts showing branching logic
The three-way merge: Git compares base, ours, and theirs. When both sides modify the same lines, you get a conflict.

The Anatomy of a Conflict Marker

Open a conflicted file and you'll see something like this:

javascript
1function getUserRole(user) {
2<<<<<<< HEAD
3  return user.isAdmin ? 'administrator' : 'standard';
4=======
5  return user.permissions.includes('admin') ? 'admin' : 'viewer';
6>>>>>>> feature/role-refactor
7}

The markers break down simply:

  • <<<<<<< HEAD marks the start of your version (current branch)
  • ======= divides the two versions
  • >>>>>>> feature/role-refactor marks the end of their version (incoming branch)

Your job is to delete all three markers and write the correct code. Sometimes that means keeping one side. Sometimes it means combining both. Sometimes it means writing something entirely new that accounts for both changes.

The Three Kinds of Merge Conflict

Content conflict: Two branches changed the same lines of the same file. This is the most common type and the one you'll resolve manually.

Rename/delete conflict: One branch renamed or moved a file while the other branch modified it. Git can't reconcile structural disagreements automatically.

Binary file conflict: Two branches modified the same image, PDF, or compiled file. Git can't diff binary data, so you pick one version or the other.

Resolving Conflicts: The CLI Way

The terminal is where you learn what's actually happening. GUI tools abstract the process. The CLI shows you every moving part.

Step by Step

Start the merge:

bash
git merge feature/role-refactor

Git reports the conflict:

1Auto-merging src/utils/auth.js
2CONFLICT (content): Merge conflict in src/utils/auth.js
3Automatic merge failed; fix conflicts and then commit the result.

Check which files are conflicted:

bash
git status

Open the conflicted file in your editor. Find the <<<<<<< markers. Read both versions carefully. Decide what the correct code should be. Delete the conflict markers and write the resolution:

javascript
1function getUserRole(user) {
2  if (user.permissions.includes('admin')) {
3    return 'administrator';
4  }
5  return user.permissions.includes('edit') ? 'editor' : 'viewer';
6}

Stage the resolved file and commit:

bash
git add src/utils/auth.js
git commit -m "resolve merge conflict in getUserRole"

That's it. The merge is complete.

The terminal doesn't hide anything from you. That's either comforting or terrifying, depending on how well you understand what you're looking at.

Using git mergetool

If you prefer a visual diff, Git has you covered:

bash
git mergetool

This launches your configured merge tool (vimdiff, opendiff, meld, or whatever you've set up). Configure it once:

bash
1git config --global merge.tool vscode
2git config --global mergetool.vscode.cmd \
3  'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'

The Nuclear Options

Sometimes you just need to back out:

bash
1# Abort the merge entirely. Go back to where you were.
2git merge --abort
3
4# Take your version for every conflict in a specific file
5git checkout --ours src/utils/auth.js
6
7# Take their version for every conflict in a specific file
8git checkout --theirs src/utils/auth.js

Quick Reference: CLI Conflict Commands

  • git status : See which files have conflicts
  • git diff : Show the conflict markers in context
  • git merge --abort : Cancel the merge and go back to pre-merge state
  • git checkout --ours <file> : Accept your version wholesale
  • git checkout --theirs <file> : Accept their version wholesale
  • git mergetool : Launch your configured visual merge tool
  • git add <file> : Mark a file's conflicts as resolved
  • git commit : Complete the merge after all conflicts are resolved

Resolving Conflicts: Directly on GitHub

Sometimes you don't want to touch the terminal at all. GitHub's web-based conflict editor handles simple conflicts directly in the browser.

When It Works

Navigate to your pull request. If there are merge conflicts, GitHub shows a banner: "This branch has conflicts that must be resolved." Click the "Resolve conflicts" button.

GitHub opens a browser-based editor showing each conflicted file. The conflict markers appear inline, just like in your local files. Edit the code, remove the markers, and click "Mark as resolved" for each file. When all files are resolved, click "Commit merge."

Developer working on a laptop with a code interface visible on screen
GitHub's web editor lets you resolve simple conflicts without ever opening a terminal. For complex multi-file conflicts, you'll still want a local tool.

When It Doesn't Work

GitHub's editor is limited. It can't handle conflicts in more than about 20 files at once. There's no syntax highlighting in the merge view. You can't run tests or linters before committing the resolution. And binary file conflicts are completely out of scope.

For quick, straightforward text conflicts in one or two files, the GitHub editor is genuinely convenient. For anything more complex, pull the branch locally and resolve with a proper tool.

~60%
of simple merge conflicts on GitHub can be resolved directly in the browser without touching a terminal, based on GitHub's conflict editor capabilities

Resolving Conflicts: VS Code

VS Code is the most popular code editor among developers for good reason. Its built-in merge conflict tools have matured significantly, and the three-way merge editor is now one of the best visual resolution interfaces available.

The Merge Editor

When you open a file with conflict markers in VS Code, you'll see inline annotations: Accept Current Change, Accept Incoming Change, Accept Both Changes, and Compare Changes. These one-click buttons appear directly above each conflict block.

For a more structured view, VS Code's dedicated Merge Editor shows three panes: the incoming changes on the left, your current changes on the right, and the merged result at the bottom. You can cherry-pick specific changes from either side with checkboxes.

Enable it in your settings if it's not already active:

json
1{
2  "git.mergeEditor": true
3}
Close-up of a code editor showing colorful syntax-highlighted code on a dark screen
VS Code's merge editor gives you three panes: incoming, current, and result. No more manually hunting for conflict markers.

Accept Both: The Trap

The "Accept Both Changes" button sounds diplomatic. In practice, it usually means you just pasted two conflicting implementations into the same function and hoped for the best. Both versions of a return statement in the same function will crash your app. Both versions of a CSS class with different values will produce unpredictable styling.

Use "Accept Both" only when the changes are genuinely additive, like two different import statements or two new test cases that don't interact.

"Accept Both" sounds diplomatic. In practice it usually means you pasted two conflicting implementations into the same function and hoped for the best.

Extensions Worth Knowing

GitLens adds blame annotations and commit history directly in your editor. During a merge conflict, it shows you who changed each version and when, which helps you understand the intent behind each side.

Git Graph gives you a visual branch history. Seeing where two branches diverged and what happened on each one makes conflict resolution feel less like guesswork.

Resolving Conflicts: Visual Studio 2026

If you're working in the .NET ecosystem or on large C++ projects, Visual Studio 2026 has dedicated merge tooling that goes beyond what lighter editors offer.

The Merge Toolbar

Visual Studio's merge view shows three panes: Source (incoming changes), Target (your changes), and Result (the merged output). Checkboxes next to each change let you accept individual hunks from either side. The result pane updates live as you toggle selections.

The toolbar also includes Previous Conflict and Next Conflict navigation buttons, which is a small thing that saves real time when you're resolving ten conflicts across a large file.

Three-Way Diff

Right-click any conflicted file in Solution Explorer and select "Merge..." to open the full three-way diff. The base version (common ancestor) appears in the center, so you can see exactly what each branch changed relative to the original.

Visual Studio 2026: What's New for Merges

AI-assisted conflict resolution: GitHub Copilot integration suggests resolutions based on the context of both changes and the surrounding code. It doesn't auto-resolve, but it proposes a starting point you can accept or modify.

Improved diff performance: Large files with hundreds of conflicts render smoothly. Previous versions would choke on files over 10,000 lines.

Inline blame during merge: Hover over any line in the merge view to see who last modified it, the commit message, and the date. Context without switching tools.

When to Use It

Visual Studio is the heavyweight option. If you're already in it for your .NET or C++ work, the merge tooling is excellent and deeply integrated with the Solution Explorer, Test Explorer, and build pipeline. If you're not already in Visual Studio, VS Code is lighter and faster for the same merge operations.

Best Practices: Stop Fighting the Same Fire

Knowing how to resolve conflicts is necessary. Knowing how to prevent them is better. Most merge conflicts are symptoms of a process problem, not a technical one.

Keep Pull Requests Small

This is the single most effective thing you can do. Smaller diffs mean fewer lines that can overlap with someone else's work. Target 200 to 400 changed lines per pull request. If your PR touches 2,000 lines across 30 files, you're not submitting a pull request. You're submitting a surprise.

Rebase Before You Merge

Running git pull --rebase before you push keeps your branch current with main. Conflicts surface earlier, in smaller chunks, when your branch is fresh. A conflict between your work and one new commit on main is straightforward. A conflict between your two-week-old branch and 47 new commits on main is a nightmare.

bash
1# Before pushing your feature branch
2git fetch origin
3git rebase origin/main
4# Resolve any conflicts one commit at a time
10x
increase in merge conflict probability when a feature branch lives longer than 5 days, based on analysis of 1,000+ GitHub repositories

Talk to Each Other

If two people are editing the same file and neither knows about it, that's not a Git problem. That's a communication problem. Daily standups, shared Kanban boards, even a quick message in Slack: "Hey, I'm refactoring the auth module this sprint" prevents more conflicts than any technical trick.

Every merge conflict is a post-mortem in miniature. It tells you exactly where your team stopped communicating.

Use CODEOWNERS

GitHub's CODEOWNERS file assigns reviewers based on file paths. When someone opens a PR that touches src/auth/*, the designated owner gets notified. This creates natural awareness of who's working in which part of the codebase.

1# .github/CODEOWNERS
2/src/auth/       @security-team
3/src/api/        @backend-team
4/src/components/ @frontend-team

Trunk-Based Development

Long-lived feature branches are conflict factories. The longer a branch diverges from main, the more it accumulates changes that overlap with other people's work. Trunk-based development keeps feature branches short-lived (one to three days) and merges frequently. Small, frequent integrations are far easier to reconcile than one massive merge after two weeks of divergence.

Group of professionals collaborating at a table with laptops and notebooks
The best merge conflict prevention tool isn't a Git command. It's a five-minute conversation with your teammates about who's working where.

The Takeaway

Merge conflicts don't break your code. They expose what was already broken.

Every conflict tells you something. Your branches lived too long. Your PRs were too big. Two people were working in the same file without a conversation. The code was already drifting before Git flagged it.

Stop treating merge conflicts like random bad luck. Start treating them like feedback. Fix the process, and the conflicts get rare. Understand the tools, and the ones that do appear become a two-minute fix instead of a two-hour headache.

The scariest merge conflict you'll ever face is the one you don't understand. After reading this, that shouldn't be a problem anymore. Go fix your repo.

Your Merge Conflict Action Plan 0/6
How was this article?

Share

Link copied to clipboard!

You Might Also Like

Lee Foropoulos

Lee Foropoulos

Business Development Lead at Lookatmedia, fractional executive, and founder of gotHABITS.

🔔

Never Miss a Post

Get notified when new articles are published. No email required.

You will see a banner on the site when a new post is published, plus a browser notification if you allow it.

Browser notifications only. No spam, no email.

0 / 0