Step 06 of 10
Resolving conflicts
Understand why merge conflicts happen, read conflict markers, and resolve, verify or abort a conflicted merge without panic.
A merge conflict looks alarming the first time: capital letters, angle brackets in your code, and a repository that seems stuck. In reality a conflict is Git being honest. Two people changed the same thing in different ways, and Git refuses to guess which one is right. This lesson explains exactly when conflicts happen, how to read what Git is showing you, and a calm, repeatable procedure for resolving them — plus how to back out safely when you are not ready.
Why conflicts happen
Recall that a three-way merge compares each branch tip with the merge base, the last commit both branches share. For every file, Git asks: did one side change this region, or both?
| Merge base | Your branch (ours) | Other branch (theirs) | Result |
|---|---|---|---|
USD | USD | EUR | Clean: take EUR |
USD | GBP | USD | Clean: take GBP |
USD | GBP | GBP | Clean: both agree |
USD | GBP | EUR | Conflict: both changed it differently |
Only the last row needs a human. Git works line by line, so changes to different parts of the same file merge cleanly; a conflict needs both sides to touch the same or adjacent lines. The other common causes are structural: one side edits a file the other deletes (modify/delete), or both sides create a file at the same path (add/add).
The opposite also matters: a clean merge is not a guarantee of correct code. If you rename a function on one branch and a teammate adds a new call to the old name on another, Git merges both happily and the build breaks. Always run your tests after a merge, conflicted or not.
Anatomy of a conflict
Here is a real example. Both branches started from this config.js:
export const config = {
title: "My Shop",
currency: "USD",
pageSize: 20,
};On main you changed the currency to GBP and the page size to 50. On feature/euro a teammate changed the currency to EUR. Now merge:
git switch main
git merge feature/euroAuto-merging config.js
CONFLICT (content): Merge conflict in config.js
Automatic merge failed; fix conflicts and then commit the result.The merge is paused, not failed. Git merged everything it could, and left you in a special state. git status shows what is still open:
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: config.jsOpen the file and you will find conflict markers around the disputed region:
export const config = {
title: "My Shop",
<<<<<<< HEAD
currency: "GBP",
pageSize: 50,
=======
currency: "EUR",
pageSize: 20,
>>>>>>> feature/euro
};- Between
<<<<<<< HEADand=======is ours: the version on the branch you are on. - Between
=======and>>>>>>> feature/eurois theirs: the version from the branch being merged in. - Everything outside the markers merged cleanly and needs no attention.
Why is pageSize inside the conflict, when only you changed it? Because your two changed lines are adjacent and Git treats them as one chunk. Lines you see inside a conflict are not necessarily disputed; they are just in the same hunk.
Resolving step by step
Resolution is always the same four steps.
1. Understand both intentions. Do not just pick a side. Ask: what was each change trying to achieve? Here, you wanted bigger pages and a currency switch; your teammate wanted euros. Perhaps after talking to them you agree the shop is moving to euros, but bigger pages should stay.
2. Edit the file into its final form and delete every marker line:
export const config = {
title: "My Shop",
currency: "EUR",
pageSize: 50,
};The correct result is often neither side exactly but a combination, which is why this needs a person.
3. Mark the file resolved by staging it. For Git, git add on a conflicted file means "I have fixed this":
git add config.js
git statusOn branch main
All conflicts fixed but you are still merging.
(use "git commit" to conclude merge)
Changes to be committed:
modified: config.js4. Verify, then conclude the merge. Make sure no markers slipped through, run the tests, then commit. Git pre-fills the message (Merge branch 'feature/euro'); --no-edit accepts it.
git diff --cached --check # reports any leftover conflict markers
npm test # or whatever your project uses
git commit --no-edit* da2ed46 Merge branch 'feature/euro'
|\
| * 30f3a16 Switch currency to EUR
* | 3b53ed7 Use GBP and larger pages
|/
* c154657 Add configIf several files conflict, repeat steps 1–3 for each; git status lists the ones still unmerged. If you run git diff --check before staging, it flags markers too:
config.js:3: leftover conflict marker
config.js:6: leftover conflict marker
config.js:9: leftover conflict markerBacking out
If the conflict is bigger than you expected, or you need to ask someone, stop cleanly:
git merge --abortThis returns the working tree, index and branch to exactly where they were before git merge. Nothing you had committed is at risk, which is why a conflict is never an emergency.
Getting more context
Show the base with zdiff3
Two-way markers hide crucial information: what the code looked like before either change. Turn on the zdiff3 conflict style once and every conflict shows the base as a third section:
git config --global merge.conflictStyle zdiff3<<<<<<< HEAD
currency: "GBP",
pageSize: 50,
||||||| c154657
currency: "USD",
pageSize: 20,
=======
currency: "EUR",
pageSize: 20,
>>>>>>> feature/euroNow it is obvious: the base had USD and 20; you changed both; they changed only the currency. So "their" pageSize: 20 is not a decision, merely unchanged, and keeping 50 is clearly right. If you are already mid-conflict, git restore --conflict=zdiff3 config.js re-creates the markers in this style.
Inspect the three versions
During a conflict Git keeps all three versions of the file in the index, as numbered stages: 1 is the base, 2 is ours, 3 is theirs.
git show :1:config.js # merge base version
git show :2:config.js # ours (HEAD)
git show :3:config.js # theirs (feature/euro)
git log --merge --oneline # commits on either side that touched conflicted files3b53ed7 Use GBP and larger pages
30f3a16 Switch currency to EURReading those commits' messages (git show 30f3a16) usually tells you why each change was made, which is what you need to resolve correctly.
Shortcuts and tools
When you know one side should win for an entire file — a generated lockfile, say — take it wholesale:
git restore --theirs package-lock.json # or --ours
git add package-lock.json(The older spelling is git checkout --theirs <file>.) Be careful: this discards the other side's changes to that file completely, including non-conflicting ones. For lockfiles specifically, it is often better to take one side and then regenerate the file by running your package manager.
Most editors have a merge view. VS Code, for example, shows "Accept Current / Accept Incoming / Accept Both" buttons above each conflict and a three-pane merge editor. git mergetool launches whichever external tool you configure. These are conveniences over the same markers; the reasoning in step 1 is still yours.
Other kinds of conflict
Structural conflicts have no markers to edit. A modify/delete looks like this:
CONFLICT (modify/delete): config.js deleted in feature/rm and modified in HEAD. Version HEAD of config.js left in tree.git status shows deleted by them: config.js. Resolve it by choosing: keep the file (git add config.js) or accept the deletion (git rm config.js), then commit.
You will also meet conflicts outside git merge — when pulling, rebasing, cherry-picking or reverting. The markers and the resolve–stage–continue procedure are identical; only the command to finish changes (git rebase --continue, git revert --continue, and so on), and git status always tells you which one.
Practice
Create a conflict on purpose and resolve it properly:
- In a new repository, commit
greeting.txtcontaining three lines:Hello,Welcome to the site,Goodbye. - On a branch
formal, change line 2 toWelcome to our website. Commit. - On
main, change line 2 toWelcome to the shopand line 3 toSee you soon. Commit. - Merge
formalintomain. Before resolving, turn onzdiff3and regenerate the markers, then use them to work out which side actually changed line 3. - Resolve to
Welcome to our shop/See you soon, verify there are no markers, and complete the merge.
Show solution
git init conflict-lab && cd conflict-lab
printf 'Hello\nWelcome to the site\nGoodbye\n' > greeting.txt
git add greeting.txt && git commit -m "Add greeting"
git switch -c formal
sed -i 's/Welcome to the site/Welcome to our website/' greeting.txt
git commit -am "Make greeting formal"
git switch main
printf 'Hello\nWelcome to the shop\nSee you soon\n' > greeting.txt
git commit -am "Shop wording and friendlier goodbye"
git merge formal # CONFLICT (content) in greeting.txt
git config merge.conflictStyle zdiff3
git restore --conflict=zdiff3 greeting.txt
cat greeting.txtHello
<<<<<<< ours
Welcome to the shop
See you soon
||||||| base
Welcome to the site
Goodbye
=======
Welcome to our website
Goodbye
>>>>>>> theirsThe base section shows Goodbye, and "theirs" still has Goodbye, so only main changed line 3 — keep See you soon. Line 2 was changed by both sides; combine them. (When regenerated with git restore --conflict, the labels read ours, base and theirs.)
printf 'Hello\nWelcome to our shop\nSee you soon\n' > greeting.txt
git add greeting.txt
git diff --cached --check # no output: no markers left
git commit --no-edit
git log --oneline --graphKey takeaways
- Conflicts arise only when both sides change the same or adjacent lines, or make clashing structural changes such as modify/delete.
- In markers, the
HEADsection is ours (current branch) and the bottom section is theirs (branch being merged). - Resolve by understanding both intentions, editing to the final form,
git addto mark resolved, then committing. merge.conflictStyle zdiff3shows the merge base, which tells you who actually changed what.git merge --abortalways gets you back to safety;git diff --checkcatches leftover markers.- A clean merge can still be broken code, so run tests after every merge.
Check yourself
Quiz
0/4 correct · 4 left
1.During git merge feature while on main, which section of a conflict hunk contains the version from main?
2.You edited a conflicted file so that it is correct. How do you tell Git the conflict is resolved?
3.Branch A changed line 10 of utils.js and branch B changed line 80 of the same file. What happens when you merge them?
4.Halfway through a conflicted merge you realise you need to ask a teammate about their change. What is the safest move?
Concepts in this lesson
One-page notes on each idea, for review or a quick lookup later.
Finished reading?
Mark it complete to fill in your roadmap.