How to resolve merge conflicts in Git
A merge conflict isn’t an error. Git is telling you that two branches changed the same lines, and it won’t guess which version you meant. Here’s why that happens and the exact steps to fix it.
Why conflicts happen: the three-way merge
Git doesn’t merge by comparing your branch with theirs directly. It finds the merge base, the last commit both branches share, and asks two questions for every region of the file: did my side change it since the base, and did their side? If only one side changed a line, git takes that change. If neither did, the line stays. Only when both sides changed the same region in different ways does git stop and write conflict markers.
Base (merge-base)
title: Quarterly reportowner: danaformat: pdfpages: 12draft: true
Your branch (HEAD)
Their branch (incoming)
Merge result 1 conflict
title: Q3 reportonly your branch changed itowner: samonly their branch changed itformat: htmlboth made the identical change<<<<<<< HEADpages: 14=======pages: 16>>>>>>> their-branchboth changed it differentlydraft: truenobody changed itIn the starting setup, pages conflicts because each branch wrote a different number, while format merges cleanly even though both branches edited it, because the edits are identical. Tick draft on both sides and it merges cleanly too. And a line changed by one branch only never conflicts, however much the other branch changed elsewhere. Git actually works in hunks rather than single lines, so edits on adjacent lines can also collide.
Step by step: resolving a conflict
- See what conflicted. Run
git status. Files under “Unmerged paths” need attention. - Open each file and find the blocks between
<<<<<<<and>>>>>>>. The top half is yours (HEAD), the bottom half is incoming. - Decide per block: keep one side, keep both, or write a combination. Delete all three marker lines. Paste the file into the merge conflict resolver if you’d rather click than edit markers by hand.
- Check it still works. Build and run the tests. A textually clean merge can still be logically broken.
- Mark it resolved with
git add <file>. - Finish:
git commitfor a merge, orgit rebase --continue/git cherry-pick --continue.
$ git merge feature/security-headers
Auto-merging server.js
CONFLICT (content): Merge conflict in server.js
Automatic merge failed; fix conflicts and then commit the result.
$ git status --short
UU server.js
# …edit server.js, remove the markers…
$ git add server.js
$ git commit # git pre-fills "Merge branch 'feature/security-headers'"Make conflicts easier to read
By default git shows only your side and theirs. Switch on the zdiff3 style (Git 2.35+) and every conflict also shows the base version, so you can see what each side actually changed:
git config --global merge.conflictStyle zdiff3With the base visible, “both changed the port” becomes “I read it from the environment, they changed the default to 8080”, and the correct merge, doing both, is obvious. DiffKit’s resolver shows this “common ancestor” block automatically when it’s present.
Avoiding conflicts in the first place
- Merge or rebase from the main branch often, so your branch never drifts far from it.
- Keep pull requests small and focused. Fewer touched lines means fewer collisions.
- Agree on a formatter (Prettier, Black, gofmt) so whitespace-only edits don’t collide.
- Regenerate lock files with the package manager rather than merging them by hand.
- Enable
rerereif you rebase long-lived branches repeatedly.
Resolved, but want to double-check the result? Compare the merged file with either parent in the diff checker, or read how diffs work under the hood.