Resolving merge conflicts

Status: 🟩 COMPLETE Last updated: 2026-06-20 Plain-English tagline: When two branches change the same lines, Git can’t auto-pick. You have to. Here’s how to do it without panic.


In plain English

Most of the time, Git can combine changes from two branches automatically. But when both branches changed the same lines of the same file, Git has no way to know which version you want — yours, theirs, or a mix. So Git asks you.

That’s a merge conflict. It feels scary the first few times. It shouldn’t. You’re just picking which version of disputed lines to keep. Five minutes after your first one, it becomes routine.


Why it matters

Merge conflicts happen routinely:

  • Pulling main into your feature branch
  • Merging a long-running feature back to main
  • Cherry-picking commits across branches
  • Rebasing

They’re a normal part of Git life. The fear of them is what keeps people from using Git well. Once you know the resolution flow, they’re a 1–5 minute task per conflict.


What a conflict looks like

When Git can’t merge a file, it inserts conflict markers showing both versions:

function greeting() {
<<<<<<< HEAD
  return "Hello, world!";
=======
  return "Hi, world!";
>>>>>>> feature/casual-greeting
}
  • <<<<<<< HEAD — start of “ours” (the branch you’re merging into, usually main)
  • ======= — separator
  • >>>>>>> feature/casual-greeting — end of “theirs” (the branch you’re merging in)

Between <<<<<<< and ======= is the current version. Between ======= and >>>>>>> is the incoming version.

Your job: edit the file to be what you actually want, then remove the conflict markers, save, and tell Git you’ve resolved it.


The basic resolution flow

When a merge produces conflicts:

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

Run git status to see what’s conflicted:

git status
# Unmerged paths:
#   (use "git add <file>..." to mark resolution)
#         both modified:   file.js

For each conflicted file:

  1. Open the file. Find the conflict markers.
  2. Decide what you want. Keep one version, the other, a combination, or rewrite.
  3. Delete the markers. The <<<<<<<, =======, >>>>>>> lines should not remain.
  4. Save the file.
  5. git add <file> to mark it resolved.

Once all conflicts are resolved:

git commit
# Editor opens with a default merge commit message; usually accept it

For a rebase instead of a merge:

# After resolving conflicts:
git add <files>
git rebase --continue
# (Repeats per commit in the rebase)

A concrete example

Suppose main has:

function calculateTotal(items) {
  return items.reduce((sum, item) => sum + item.price, 0);
}

And feature/add-tax has:

function calculateTotal(items) {
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);
  return subtotal * 1.10;  // include tax
}

You merge feature into main. Git can’t decide and produces:

function calculateTotal(items) {
<<<<<<< HEAD
  return items.reduce((sum, item) => sum + item.price, 0);
=======
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);
  return subtotal * 1.10;  // include tax
}
>>>>>>> feature/add-tax

Since you want the tax-including version, you edit to:

function calculateTotal(items) {
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);
  return subtotal * 1.10;  // include tax
}

Save, git add file.js, git commit. Done.

If you wanted a hybrid:

function calculateTotal(items, includeTax = false) {
  const subtotal = items.reduce((sum, item) => sum + item.price, 0);
  return includeTax ? subtotal * 1.10 : subtotal;
}

That’s fine too. Conflict resolution is just “the file should look like X” — you decide X.


Using VS Code’s built-in resolver

VS Code (and most modern editors) detect conflict markers and offer inline buttons:

  • “Accept Current Change” (keep yours)
  • “Accept Incoming Change” (keep theirs)
  • “Accept Both Changes” (concatenate)
  • “Compare Changes” (open a 3-way diff view)

For simple conflicts, one click resolves them. For complex ones, the 3-way merge view shows main’s version, your branch’s version, the common ancestor, and the result side by side.

This is much nicer than editing markers by hand. Use it.


Git’s merge tools

git mergetool

Opens whichever merge tool you have configured (KDiff3, p4merge, Beyond Compare, VS Code, etc.). Useful for big or visually complex conflicts.

For VS Code as your merge tool:

git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

“Theirs” vs “Ours” — quick resolution

If you don’t actually need to merge (you just want one side wholesale):

# Keep mine, discard theirs:
git checkout --ours <file>
 
# Keep theirs, discard mine:
git checkout --theirs <file>
 
git add <file>

Important caveat: during a rebase, “ours” and “theirs” are reversed from intuition (because you’re applying your commits ONTO the other branch, so “ours” is the other branch). Read the docs carefully if you’re unsure.


Conflict types beyond content conflicts

Add/add conflict

Both branches created a file with the same name but different contents.

Delete/modify conflict

One branch deleted the file; the other modified it. Git asks which you want.

Rename/rename

Both branches renamed the same file to different names.

For each, git status describes the situation. The resolution is the same: pick what you want, git add, git commit.


When to abort

Sometimes you start a merge or rebase and realize it’s going badly. You can back out:

git merge --abort     # cancel an in-progress merge
git rebase --abort    # cancel an in-progress rebase

You’re back to where you were before. Try a different approach (e.g. handle the integration differently, talk to whoever made the conflicting changes, or do it in smaller chunks).


Reducing conflicts in the first place

The best resolution is one you don’t have to do:

  • Short-lived branches. A 1-day branch has fewer conflicts than a 1-month branch.
  • Pull main into your branch often. Surface conflicts in small batches as they arise.
  • Communicate. “I’m refactoring the auth module” lets others avoid editing it simultaneously.
  • Small, focused commits. Easier to merge.
  • Avoid sprawling diffs. A formatting change across 50 files conflicts with everyone.

Common gotchas

  • Forgetting to remove the markers. A file still containing <<<<<<< is broken. CI usually catches it; sometimes it slips through. Search the diff: grep -n "<<<<<<<" .

  • Resolving by accepting one side blindly. Read both sides before deciding. Auto-accepting “theirs” can wipe out important changes.

  • git add . after partial resolution. If you’re not done resolving, this stages your half-fixed state. Resolve completely first.

  • Conflicts in generated files. package-lock.json, yarn.lock, build outputs. Often easier to regenerate than resolve: delete it, git rm (or just delete locally), then re-run the tool (npm install) to regenerate, then commit.

  • Conflict markers in binary files. Git can’t textually merge binaries (images, PDFs). You have to pick one wholesale, usually via git checkout --ours or --theirs.

  • The conflict reappears after a rebase. Each commit in the rebase may re-introduce the same conflict. Use git rerere to remember resolutions: git config --global rerere.enabled true.

  • Resolved files still showing as conflicted. Often you forgot git add <file>. Run git status to verify.

  • CI breaks after merge. A successful merge doesn’t mean the result is correct. Run tests after every non-trivial merge.

  • “There are unmerged paths.” Means you haven’t git add-ed all the resolved files. Finish the resolution.

  • git diff after a conflict shows the changes you’d get from each side. Not always intuitive. git diff --base <file> shows changes relative to the common ancestor.


See also


Sources