Resolving Merge Conflicts

Git · Lesson 12 of 28 8 min read Updated

A merge conflict happens when two branches changed the same part of the same file in different ways, and Git cannot know which version is right. It is not an error and nothing is broken. Git simply stops halfway through the merge, marks the disputed lines, and asks a human to decide.

Conflicts scare beginners mostly because the output looks alarming. In this lesson you will create one on purpose, read every part of it, resolve it, and learn the shortcuts and settings that make conflicts easier.

How a conflict happens

On a branch called new-prices, Priya raised the price of kaju katli to ₹1,200. Meanwhile, on main, she renamed the same menu line to say "(sugar-free)". Both commits changed line 10 of menu.html. Now she merges:

Terminal
git merge new-prices
Output
Auto-merging menu.html
CONFLICT (content): Merge conflict in menu.html
Automatic merge failed; fix conflicts and then commit the result.

Git merged everything it could and stopped on the one file it could not. git status explains the situation and the way out:

Terminal
git status
Output
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:   menu.html

no changes added to commit (use "git add" and/or "git commit -a")

"both modified" means both branches edited this file. In git status -s the same file shows as UU (unmerged on both sides).

Reading conflict markers

Open menu.html in your editor. Git has written both versions into the file, separated by conflict markers:

Text
  <h1>Our Menu</h1>
  <ul>
<<<<<<< HEAD
    <li>Kaju katli (sugar-free): ₹1,100 per kg</li>
=======
    <li>Kaju katli: ₹1,200 per kg</li>
>>>>>>> new-prices
    <li>Gulab jamun: ₹480 per kg</li>
    <li>Rasgulla: ₹420 per kg</li>
  • Between <<<<<<< HEAD and ======= is your side: the branch you are on (main).
  • Between ======= and >>>>>>> new-prices is their side: the branch you are merging in.
  • Everything outside the markers merged cleanly and needs no attention.

Resolving the conflict

Resolving means editing the file so it contains exactly what you want, and deleting all three marker lines. You can keep one side, the other, both, or write something new. Priya talks to the shop owner and learns that the plain kaju katli goes up to ₹1,200 and the sugar-free one is a separate item at ₹1,250. She edits the lines to:

Text
<h1>Our Menu</h1>
<ul>
  <li>Kaju katli: ₹1,200 per kg</li>
  <li>Kaju katli (sugar-free): ₹1,250 per kg</li>
  <li>Gulab jamun: ₹480 per kg</li>
  <li>Rasgulla: ₹420 per kg</li>

Then she tells Git the file is resolved by staging it, and finishes the merge with a commit. --no-edit keeps the default message "Merge branch 'new-prices'":

Terminal
git add menu.html
git status
git commit --no-edit
Output
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
	modified:   menu.html

[main 8bfdef1] Merge branch 'new-prices'

Done. The merge commit records her decision, and the history graph shows the two branches joining just like a normal merge.

Changing your mind: git merge --abort

If the conflict is bigger than you expected, or you merged the wrong branch, you can cancel the whole merge and return to exactly where you were before it started:

Terminal
git merge --abort
git status -s

The empty status shows that the working tree is clean again, as if git merge had never run. You can try again later, perhaps after talking to whoever wrote the other branch.

Seeing the original text with zdiff3

The two sides often do not tell the whole story. Did main add "(sugar-free)", or did new-prices remove it? Git can also show the base, the text before either branch changed it. Turn this on once, globally:

Terminal
git config set --global merge.conflictStyle zdiff3

The same conflict now has a third section, after |||||||, showing the merge base:

Text
<<<<<<< HEAD
    <li>Kaju katli (sugar-free): ₹1,100 per kg</li>
||||||| d89c43a
    <li>Kaju katli: ₹1,100 per kg</li>
=======
    <li>Kaju katli: ₹1,200 per kg</li>
>>>>>>> new-prices

Now the story is clear: the original said ₹1,100; main added "(sugar-free)" and the other branch changed the price. Many experienced developers consider zdiff3 the most useful Git setting there is.

Taking one whole side

For some files, such as a generated file or an image, you do not want to mix lines; you want one complete version. git restore can pick a side for a conflicted file:

Terminal
git restore --theirs package-lock.json
git restore --ours logo.png
git add package-lock.json logo.png

--ours is the branch you are on and --theirs is the branch being merged in. If you change your mind, git restore --merge <file> puts the conflict markers back. (Older guides use git checkout --ours <file>, which does the same.)

Tools that help

You do not have to edit markers by hand. VS Code highlights conflicts with buttons such as "Accept Current Change", "Accept Incoming Change" and "Accept Both Changes", and has a three-pane merge editor. JetBrains IDEs have a similar tool. git mergetool opens whichever merge program you have configured. Whatever tool you use, the rules are the same: decide what the final text should be, remove the markers, stage, commit.

Avoiding painful conflicts

  • Merge or pull main into long-running branches often, so differences stay small.
  • Keep branches short-lived; a branch that lives for a month will conflict with everything.
  • Do not mix big formatting changes (re-indenting a whole file) with real changes in the same branch.
  • Talk to your team when two people need to change the same file.

Common mistakes

  • Committing conflict markers. Search the file for <<<<<<< before staging, or use git add --resolved, which checks for you.
  • Keeping both sides without thinking. Two <li> lines for the same item, or two definitions of the same function, will compile badly or confuse users.
  • Not testing after resolving. A merge can be textually clean and still break the program. Run the site or the tests before committing.
  • Forgetting that the merge is still in progress. Until you commit (or abort), Git is mid-merge; git status will remind you.

Key points

  • A conflict means both branches changed the same lines; Git stops and marks them for you.
  • <<<<<<< HEAD to ======= is your side; ======= to >>>>>>> branch is theirs.
  • Resolve by editing the file and removing all markers, then git add and git commit.
  • git merge --abort cancels the merge; git restore --ours/--theirs takes one whole side.
  • merge.conflictStyle zdiff3 shows the original base text, and Git 2.56's git add --resolved refuses files that still contain markers.

Exercise

Create a conflict on purpose. In a practice repository, make a file team.txt with the line Captain: Rohan and commit it. On a new branch, change it to Captain: Meera; on main, change it to Captain: Farhan. Merge the branch into main, read the markers, resolve it so the file says Captain: Meera and Vice-captain: Farhan on two lines, and finish the merge.

Show solution
Terminal
git switch -c new-captain
git commit -am "Make Meera captain"
git switch main
git commit -am "Make Farhan captain"
git merge new-captain
git add --resolved
git commit --no-edit

Before git add --resolved, edit team.txt so it contains only Captain: Meera and Vice-captain: Farhan, with the <<<<<<<, ======= and >>>>>>> lines deleted. If you are on a Git older than 2.56, use git add team.txt instead.

Quick Quiz

1. In a conflict, which text is between <<<<<<< HEAD and =======?

2. How do you tell Git that you have resolved a conflicted file?

3. What does git merge --abort do?

4. What extra information does the zdiff3 conflict style show?

Learn Git with a mentor

Free lessons get you started. Our Full Stack Web Development training adds real projects and code reviews from working developers.

  • Project-based learning
  • Mentor code reviews
  • Free counselling call