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.jsFor each conflicted file:
- Open the file. Find the conflict markers.
- Decide what you want. Keep one version, the other, a combination, or rewrite.
- Delete the markers. The
<<<<<<<,=======,>>>>>>>lines should not remain. - Save the file.
git add <file>to mark it resolved.
Once all conflicts are resolved:
git commit
# Editor opens with a default merge commit message; usually accept itFor 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-taxSince 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 mergetoolOpens 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 rebaseYou’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 --oursor--theirs. -
The conflict reappears after a rebase. Each commit in the rebase may re-introduce the same conflict. Use
git rerereto remember resolutions:git config --global rerere.enabled true. -
Resolved files still showing as conflicted. Often you forgot
git add <file>. Rungit statusto 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 diffafter 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
- Branches & merging đźź©
- Rebase vs merge đźź©
- Git rescue moves đźź©
- Git basics đźź©
- Git gotchas 🟥
Sources
- Pro Git book — Basic merging
- Atlassian — Resolving merge conflicts
- GitHub — Resolving conflicts on the command line
- Oh Shit, Git!?! — for when something goes really wrong