When Git Refuses to Choose
A conflict is not an error and not a failure — it is Git declining to guess which of two edits to the same lines you meant. Resolve one by hand, marker by marker, and see why the answer is sometimes neither side.
Worth reading first: Bringing Two Histories Back Together
A conflict is not an error and not a failure — it is Git declining to guess which of two edits to the same lines you meant. Resolve one by hand, marker by marker, and see why the answer is sometimes neither side.
A conflict means two commits changed the same lines
Git merges most things without asking. Two people editing different files, different functions, or even different parts of the same function merge silently and correctly.
A conflict happens in one narrow situation: both sides changed the same lines since the merge base, and Git has no basis for preferring one. Rather than pick, it stops and hands the decision to you.
Auto-merging src/config.tsCONFLICT (content): Merge conflict in src/config.tsAutomatic merge failed; fix conflicts and then commit the result.Notice the first line. Files that merged cleanly are still merged — the merge is partly done. You are in a paused merge, and git status will now tell you exactly what is left.
You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Changes to be committed: modified: src/routes.ts Unmerged paths: (use "git add <file>..." to mark resolution) both modified: src/config.tsThe markers are a literal edit of your file
This is the part that startles people. Git does not show you conflicts in a special interface — it writes them into the file on disk. Open it in any editor and the markers are really there, as text.
Both branches changed the page size. Git stopped and wrote this into your file — the markers are real text, sitting on disk right now.
export function pageSize() {<<<<<<< HEAD return 25;======= return 50;>>>>>>> feature/bigger-pages}<<<<<<< HEAD — everything until the next marker is what the branch you are standing on says.
======= — the divider. Not a change; just the boundary between the two versions.
>>>>>>> feature/… — what the branch you are merging in says, and the name of that branch.
Pick one to see the file you would end up committing.
Whichever you choose, the merge is not finished until you git add the file — that is how you tell Git the conflict is handled — and then git commit. Git does not check your answer. It checks only that the markers are gone.
Start of the conflict. Everything until the divider is the version from the branch you are standing on — labelled HEAD.
The divider. It is not a change; it is the boundary between the two versions.
End of the conflict, labelled with the branch or commit being merged in.
Resolving means producing the file you want
Git does not check your answer. It checks only that you claimed to have one, and you make that claim with git add.
- 1
Open every file git status lists as unmerged
There may be several, and each may have several conflicted regions. Work through them one at a time.
- 2
Edit the file until it is the code you want
Delete all three markers. Keep one side, the other, both, or write something new — whatever is actually correct.
- 3
git add the file
This is how you tell Git the conflict is resolved. Nothing is verified; adding IS the claim.
- 4
Run the tests before committing
A resolved merge is new code that has never existed anywhere. It compiled on both branches and may not compile now.
- 5
git commit
With no -m, Git offers a pre-written merge message listing the conflicted files. Accepting it is fine.
If you know one side is simply correct — a lockfile, a generated file, something you know was regenerated — you can say so without editing:
$ git checkout --ours package-lock.json # the branch you are standing on$ git checkout --theirs package-lock.json # the branch being merged in$ git add package-lock.jsonAbort is always available and always safe
Nothing about a conflicted merge is permanent until you commit. If the conflict turns out to be larger than you thought, or you started the merge in the wrong direction, or you just want to look at something first — back out completely.
$ git merge --abort # a conflicted merge$ git rebase --abort # a conflicted rebase$ git cherry-pick --abort # a conflicted cherry-pickEvery one of these restores the state from immediately before the command. No commits are lost, no edits made before the merge are lost, and there is no cleanup to do.
Knowing that is what makes conflicts stop being frightening. The worst realistic outcome of attempting a merge is that you type six more words and are exactly where you started.
Most conflicts are prevented, not resolved
Conflicts scale with how long a branch has been away and how much of the file it touches. Both of those are things you control.
What actually reduces conflicts
- →Merge main into your branch regularly rather than once at the end. Five small conflicts across a week beat one enormous one on Friday.
- →Keep branches short-lived. A two-day branch rarely conflicts; a three-week branch reliably does.
- →Do reformatting in its own commit, on its own, merged immediately. A formatting change mixed into a feature conflicts with everything.
- →Agree a formatter and commit its config. Half of all conflicts in some codebases are two editors disagreeing about indentation.
- →Split files that everybody edits. A single 2,000-line routes file is a conflict generator; the fix is architectural.
Key takeaways
- A conflict means both sides changed the same lines since the merge base. Everything else merges silently.
- Files that merged cleanly are already staged; only the conflicted ones are waiting for you.
- The markers are real text written into your file. Any editor will show them.
- Between <<<<<<< and ======= is your side; between ======= and >>>>>>> is theirs.
- Committing leftover markers is easy and common — git diff --check flags them.
- Resolving means editing the file until it is right, then git add. Git verifies nothing.
- The answer is often neither side: two correct changes need code that satisfies both.
- ours and theirs swap meaning during a rebase, so read the content rather than trusting the label.
- git merge --abort restores everything from before the merge. Attempting a merge risks nothing.
- Prevent conflicts with short branches, frequent merges from main, separate formatting commits, and rerere.
Quick check
Answer these to unlock the next chapter — 3 of 4 to pass. You can retake it anytime.
Answer every question to check.
Make a free account to read on
Every chapter is free — an account is how your progress, XP, and streak follow you from your laptop to your phone, and how you show up on the leaderboard. No payment, no trial.