Resolving merge conflicts
A merge conflict happens when both branches change the same lines. Git writes conflict markers into the file (HEAD vs incoming), leaving the merge paused. You edit to the final content, delete the markers, git add, then commit. git merge --abort backs out the whole merge.
A three-way merge works automatically as long as the two branches changed different regions. The moment both branches edit the same lines differently, git stops and admits it cannot guess which version you want. New engineers see the >>>>>>> markers, panic, and sometimes nuke the whole repo. There is nothing to fear: a conflict is just git pausing mid-merge and handing you the decision, with both versions laid out in the file.
By the end of this lesson you will be able to read conflict markers, resolve a conflict by hand, finish the merge with git add and git commit, and bail out cleanly with git merge --abort.
After this lesson you can explain why a merge conflict occurs, read the three conflict markers git inserts, resolve the file to its intended final content, complete the merge with git add then git commit (or git merge --continue), and abandon a merge with git merge --abort.
A conflict happens when both branches changed the same region differently. Recall the three-way merge: for each chunk, git compares the merge base against each side. If only one side touched a region, git takes that change silently. If both sides changed the same lines and the changes differ, git has no rule to pick a winner — that chunk is a conflict:
git merge feature
# Auto-merging app.js
# CONFLICT (content): Merge conflict in app.js
# Automatic merge failed; fix conflicts and then commit the result.The merge is now paused, not failed-and-rolled-back. Files git could merge cleanly are already staged; the conflicted file is left on disk with both versions marked for you to reconcile.
Git marks the conflict in the file with three lines. Open the conflicted file and you will see a block like this (the markers are exactly seven characters each):
<<<<<<< HEAD
const timeout = 30;
=======
const timeout = 60;
>>>>>>> featureRead it as: between <<<<<<< HEAD and ======= is your current branch’s version (the branch you merged into); between ======= and >>>>>>> feature is the incoming version from the branch you are merging. The label after >>>>>>> names the source. Everything outside the markers merged fine; only the marked block needs a human.
Resolve by editing the file to the final content you want, then deleting all three markers. You are not forced to pick one side — you write whatever the correct result is. It might be theirs, yours, a combination, or something new:
const timeout = 45;The non-negotiable rule: the three marker lines (<<<<<<<, =======, >>>>>>>) must be gone. Leaving a stray marker is the classic beginner bug — it ships broken syntax to production. A quick git diff or searching the tree for <<<<<<< before committing catches any you missed.
Tell git the conflict is settled with git add, then finish with git commit. Staging the file is how you signal “this one is resolved”:
git add app.js # mark the file resolved
git status # confirm nothing is still "Unmerged"
git commit # completes the merge commit
# or, equivalently after staging:
git merge --continueDuring the conflict, git status is your dashboard — it lists files as “Unmerged paths” until you git add each one. Once all are staged, git commit records the merge commit (git pre-fills a merge message). git merge --continue does the same thing once everything is staged.
▸Why this works
Why does git stop instead of guessing? A wrong auto-resolution is worse than no resolution: silently picking one side could drop a security fix or double-apply a change, and you might not notice until production breaks. Git’s design principle is to never lose or silently alter committed work, so when it genuinely cannot tell which intent is correct, it refuses to decide and surfaces both versions. The conflict is git protecting you from a confident wrong answer.
To bail out entirely, git merge --abort restores the pre-merge state. If the conflict is bigger than you expected, or you started the merge from the wrong branch, back out:
git merge --abort # discard the in-progress merge, return to exactly before 'git merge'This is safe and complete: your branch pointer, working tree, and index go back to the moment before you ran git merge. Two notes worth knowing: git mergetool opens a configured visual three-pane tool to resolve conflicts instead of editing markers by hand, and a git rebase produces the same conflict markers — so everything you just learned about resolving applies there too (you finish a rebase with git rebase --continue instead).
One conflict, start to finish.
Both branches changed the same config line. On main, const retries = 3;. On feature, the same line became const retries = 5;. You merge:
git switch main
git merge feature
# CONFLICT (content): Merge conflict in config.jsgit status shows config.js under “Unmerged paths”. Open it:
<<<<<<< HEAD
const retries = 3;
=======
const retries = 5;
>>>>>>> featureYou decide the right value is 5 (the feature deliberately raised it). Edit the block down to a single correct line and remove every marker:
const retries = 5;Stage and finish:
git add config.js
git status # nothing unmerged now
git commit # writes the merge commitDone. The merge commit has two parents, exactly like a clean three-way merge — the only difference was that you, not git, decided the contested line. Had you wanted out instead, git merge --abort at any point before the commit would have returned config.js and main to their pre-merge state.
▸Common mistake
The most damaging conflict mistake is committing with a marker still in the file. A leftover ======= or >>>>>>> feature is valid text to git but broken syntax to your compiler, and it sails through the merge commit unnoticed. Build the habit of grepping the tree for <<<<<<< (or letting a lint/CI check do it) before you commit a resolution. The second-most-common mistake is “resolving” by blindly keeping HEAD and silently discarding the incoming change — that throws away the very work you were merging in.
After editing a conflicted file to its correct final content, what makes git treat that file as resolved?
A merge conflict occurs when both branches change the same region differently and git cannot pick a winner, so it pauses the merge and writes three conflict markers into the file: <<<<<<< HEAD (your side), =======, and >>>>>>> branch (the incoming side). You resolve by editing the block to the intended final content and deleting every marker, then git add the file to mark it resolved and git commit (or git merge --continue) to record the merge commit. git merge --abort cleanly restores the pre-merge state. The same markers appear during a rebase, and git mergetool offers a visual alternative — but the underlying decision is always yours: git pauses precisely because it refuses to guess wrong.
Practice
Start at the top. Tasks go easiest → hardest: recall a fact, apply it to a case, then a senior-level stretch. Open one, attempt it, then reveal.
Something unclear?
Ask a question about this lesson. Questions are anonymous and go straight to the author to make the lesson better.