Ran Wei/Git Series
中文
Git and Version Control — Ran Wei

Merge conflicts

Understand why merges sometimes stop, read conflict markers with confidence, and resolve, finish or abort a merge.

2 hours2 study sessions3 command sessionsGit 2.50.1 (Apple Git-155)

By the end you can

  • Explain when Git can merge on its own and when it reports a conflict.
  • Read the conflict markers <<<<<<<, ======= and >>>>>>>.
  • Resolve a conflict, mark it with git add and conclude the merge with git commit.
  • Abort a merge with git merge --abort.
  • Keep conflicts small, and find leftover markers with git diff --check.

Before you start

Module 4: branches, switching, and fast-forward and three-way merges.

Contents

Study plan

2 hours

Times are estimates and include typing the commands. Progress is saved in this browser and shared by both language editions.

Session 160 min
Conflicts
Session 260 min
Aborting, preventing and practice
1

Why conflicts happen

In Module 4, Git combined two branches on its own. It can do that because it merges line by line. It compares each branch with the commit where they split, and takes every change from whichever side made it. Changes to different files combine automatically, and so do changes to different parts of the same file.

Sometimes that is not enough. A merge conflict happens when both branches changed the same lines in different ways, or when one branch deleted a file that the other changed. Git cannot know which version is right: perhaps one is, perhaps you need parts of both, perhaps neither. So it does as much of the merge as it can, stops, and asks you to decide the rest.

A conflict is not an error, and it does not mean that anything is broken or lost. Both versions are safe, and Git shows you exactly where they disagree. Conflicts are a normal part of working with other people, and of working on several branches yourself. This module shows you how to read one, how to resolve it, how to back out when you are not ready, and how to keep conflicts rare and small.

Merging is not the only way to meet a conflict. Reverting an old commit (Module 3), pulling other people’s work (Module 6) and rebasing (Module 8) combine changes in the same way, and can stop in the same way. The markers look the same, and you resolve each file the same way: edit it, then git add it. Only the command that finishes or abandons the operation differs, such as git revert --continue instead of git commit, and git status tells you which one to use.

Analogy

Two editors mark up the same manuscript. One rewrites a sentence in chapter 2, the other fixes a typo in chapter 9, and both sets of changes go in. But if both rewrite the same sentence in different ways, someone has to decide which wording the book keeps.

2

When Git merges on its own

First, a merge that succeeds although both branches changed the same file. The recipe starts with 200 g of flour and a 30-minute rest. On a branch called resting-time, the rest is shortened to 20 minutes; meanwhile, on main, the flour goes up to 250 g. The two changes are several lines apart.

To follow along, first give your recipe a - 50 g sugar line and the method line Mix everything and leave the batter to rest for 30 minutes., as in the cat output below, and commit on main. Then create resting-time, change the resting time and commit; switch back to main, change the flour and commit; and merge resting-time.

Try itTwo changes to one file, no conflict
git merge resting-time
Output
Auto-merging pancakes.md
Merge made by the 'ort' strategy.
 pancakes.md | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)
cat pancakes.md
Output
# Pancakes

- 250 g flour
- 2 eggs
- 300 ml milk
- 50 g sugar

Mix everything and leave the batter to rest for 20 minutes.
Output from git version 2.50.1 (Apple Git-155), run while this page was built. Other versions of Git may word some messages differently.

Auto-merging pancakes.md says that Git had to combine changes inside the file, and the next line says that it succeeded and made a merge commit. cat shows the result: 250 g of flour from main, and 20 minutes from resting-time.

Changes on neighbouring lines can still conflict, even when they do not touch the same line. Git needs at least one unchanged line between two changes to tell them apart; without one, it treats them as a single change made differently on each side.

3

Reading and resolving a conflict

This time the two branches change the same line. The recipe starts with 50 g of sugar. On the branch less-sugar, someone changes it to 30 g (“Use less sugar”); meanwhile, on main, someone changes it to 40 g (“Reduce the sugar a little”). Both are reasonable, and they disagree, so Git cannot merge them alone.

In the session, you are on main and merge less-sugar. The merge stops with a conflict, and you resolve it step by step: look at the state with git status, read the file, edit it into the version you want, mark it resolved, and commit. To follow along, create less-sugar, change the sugar to 30 g and commit, switch back to main, change it to 40 g and commit, then merge less-sugar.

Try itResolve a merge conflict
git merge less-sugar
Output
Auto-merging pancakes.md
CONFLICT (content): Merge conflict in pancakes.md
Automatic merge failed; fix conflicts and then commit the result.
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:   pancakes.md

no changes added to commit (use "git add" and/or "git commit -a")
cat pancakes.md
Output
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
<<<<<<< HEAD
- 40 g sugar
=======
- 30 g sugar
>>>>>>> less-sugar

Mix everything and leave the batter to rest for 30 minutes.
Create or edit pancakes.md so that it contains:
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
- 35 g sugar

Mix everything and leave the batter to rest for 30 minutes.
git add pancakes.md
git status
Output
On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Changes to be committed:
	modified:   pancakes.md
git commit
Output
[main a4d11e9] Merge branch 'less-sugar'
*   a4d11e9 (HEAD -> main) Merge branch 'less-sugar'
|\
| * 7d5360a (less-sugar) Use less sugar
* | 3c34e74 Reduce the sugar a little
|/
* f49b1d8 Add a pancake recipe
git log --oneline --graph
Output
*   a4d11e9 Merge branch 'less-sugar'
|\
| * 7d5360a Use less sugar
* | 3c34e74 Reduce the sugar a little
|/
* f49b1d8 Add a pancake recipe
Output from git version 2.50.1 (Apple Git-155), run while this page was built. Other versions of Git may word some messages differently.

The merge reports CONFLICT (content): Merge conflict in pancakes.md and stops, as its last line says: Automatic merge failed; fix conflicts and then commit the result. The merge is now in progress, and it stays that way until you finish it or abort it. git status confirms this with You have unmerged paths, lists pancakes.md as both modified, and even suggests the next commands.

cat shows where the branches disagree. The conflicting part sits between three conflict markers:

  • From <<<<<<< HEAD to ======= is the version on your current branch, main: 40 g.
  • From ======= to >>>>>>> less-sugar is the version from the branch you are merging: 30 g.

Everything outside the markers merged cleanly and needs nothing from you.

When several files conflict, git status lists each of them under Unmerged paths. Resolve and git add them one at a time, in any order; git status shows which ones are left.

To resolve the conflict, edit the file into the version you want, and delete all three marker lines. You can keep one side, keep both, or write something new; here the cooks settle on 35 g. Then git add pancakes.md marks the file as resolved, and git status says All conflicts fixed but you are still merging. Finally, git commit concludes the merge. Git opens your editor with the message Merge branch 'less-sugar'; save and close it to accept, as the session does. The graph shows the finished merge: a merge commit with both sugar commits as its parents.

A resolution is your own edit, and Git trusts it completely: whatever the file contains when you run git add goes into the merge commit. Read the whole file once more before you add it, and make sure the result still makes sense, not just that the markers are gone.

Tip

Editors such as Visual Studio Code highlight conflicts in colour and offer buttons above each one: Accept Current Change (your branch), Accept Incoming Change (the branch you are merging) and Accept Both Changes. They only edit the file for you. You still finish with git add and git commit.

4

Backing out of a merge

Sometimes you start a merge and realise that you cannot resolve it now: you need to ask a colleague which version is right, or the conflict is bigger than you expected. git merge --abort puts everything back as it was before the merge started, as if you had never run it. In the session, the sugar conflict happens again, and this time you back out. In your own repository less-sugar is already merged, so to try this, make a fresh conflict: for example, change the eggs line one way on a new branch and another way on main, then merge that branch.

Try itBack out of a merge
git merge less-sugar
Output
Auto-merging pancakes.md
CONFLICT (content): Merge conflict in pancakes.md
Automatic merge failed; fix conflicts and then commit the result.
git merge --abort
git status
Output
On branch main
nothing to commit, working tree clean
cat pancakes.md
Output
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
- 40 g sugar

Mix everything and leave the batter to rest for 30 minutes.
Output from git version 2.50.1 (Apple Git-155), run while this page was built. Other versions of Git may word some messages differently.

After the abort, git status reports a clean working tree, and cat shows that pancakes.md still has main’s 40 g, with no markers. You can merge again whenever you are ready.

Aborting works only while the merge is in progress. Once you have committed the merge, there is nothing left to abort: the merge commit is an ordinary part of your history.

If you had uncommitted changes when the merge started, --abort may not be able to restore them. So commit your own changes before you merge.

5

Keeping conflicts small

You cannot avoid conflicts entirely, but a few habits keep them rare, and small when they do happen:

  • Merge main into your branch often. You then meet other people’s changes early, while they are small and fresh in everyone’s mind, instead of all at once at the end. Module 8 shows rebasing as an alternative.
  • Keep branches short-lived and commits focused. A branch that lives for a day collects fewer surprises than one that lives for a month, and a commit that does one thing is easier to merge, and to understand when it conflicts.
  • Talk to each other. Agree who works on which files, and speak up before two people rework the same part.
  • Do not reformat whole files on a feature branch. Re-indenting a file, or changing its line endings, touches every line, so it conflicts with every other change to that file. If a file needs reformatting, do it in a commit of its own, agreed with everyone, and merge it quickly.
  • Check before committing a resolution. A forgotten marker line is easy to miss in a long file. Run git diff --check after editing and before git add: it reports leftover conflict markers. If you have already staged the file, use git diff --staged --check. Exercise 6 shows the check at work.
Key idea

A conflict is Git asking a question, not reporting a failure. Read the markers, decide what the file should say, then git add and git commit. When you are not ready to answer, git merge --abort takes you back to where you started.

6

Exercises

Exercise 1★★★conceptual5 min

Will it conflict? Say whether each merge stops with a conflict.

  1. Branch A edits soup.md; branch B edits bread.md.
  2. Both edit pancakes.md: A the first ingredient, B the last line of the method.
  3. Both change the sugar line of pancakes.md, to different amounts.
  4. Branch A deletes cake.md; branch B edits it.
Show solution
  1. No. Different files merge automatically.
  2. No, as long as unchanged lines separate the two changes, as in Section 2.
  3. Yes. Both changed the same line differently.
  4. Yes. Git cannot both delete the file and keep the edit, so it asks you to choose.
Exercise 2★★★conceptual5 min

Read the markers. A conflicted file contains:

<<<<<<< HEAD
- 40 g sugar
=======
- 30 g sugar
>>>>>>> less-sugar

Which version is from the branch you are on, and which from the branch you are merging? What must the file contain when you have resolved the conflict?

Show solution

- 40 g sugar, between <<<<<<< HEAD and =======, is from the branch you are on. - 30 g sugar, between ======= and >>>>>>> less-sugar, is from less-sugar. When resolved, the file contains only the line you choose (one of them, both, or something new), and none of the three marker lines.

Exercise 3★★★coding8 min

Keep both changes. On one branch you added chocolate chips to the pancakes; on main someone added a banana, in the same place. Merge the branch and keep both ingredients.

Show solution

The merge stops, because both changes are on the same line of the list. Keep both lines and remove the markers:

SolutionKeep both changes
cat pancakes.md
Output
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
- 50 g sugar
<<<<<<< HEAD
- 1 banana, mashed
=======
- 50 g chocolate chips
>>>>>>> chocolate

Mix everything and leave the batter to rest for 30 minutes.
Create or edit pancakes.md so that it contains:
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
- 50 g sugar
- 1 banana, mashed
- 50 g chocolate chips

Mix everything and leave the batter to rest for 30 minutes.
git add pancakes.md
git commit
Output
[main a653ada] Merge branch 'chocolate'
git log --oneline --graph
Output
*   a653ada Merge branch 'chocolate'
|\
| * 2fb1669 Add chocolate chips
* | 6dc884d Add a banana
|/
* f49b1d8 Add a pancake recipe
Output from git version 2.50.1 (Apple Git-155), run while this page was built. Other versions of Git may word some messages differently.

A resolution does not have to pick a side: here the right answer is both.

Exercise 4★★★conceptual4 min

Committing too early. You removed the markers from pancakes.md and ran git commit, but Git refused, saying that committing is not possible because you have unmerged files. Why?

Show solution

You skipped git add pancakes.md. Until you stage the file, Git still counts it as unmerged, whatever its content. Run git add pancakes.md, then git commit.

Exercise 5★★★conceptual4 min

Prevent it. Two cooks changed the sugar line at the same time, on different branches. What could they have done to avoid the conflict?

Show solution

Any of these: talk first and let one person make the change; keep the branches short, so that one change is merged before the other begins; merge main into each branch often, so that the second cook sees the first change before editing the same line.

Exercise 6★★★coding4 min

A leftover marker. Someone resolved the sugar conflict in a hurry and committed the file with the markers still in it. Find them and fix the file. To try it, make a conflict as in Section 3, then git add and commit the file without removing the markers.

Show solution

git diff --check reports leftover conflict markers, and fails when it finds any; comparing with HEAD~1 checks what the last commit changed:

SolutionFind a leftover conflict marker
git diff --check HEAD~1
Output
pancakes.md:6: leftover conflict marker
pancakes.md:8: leftover conflict marker
pancakes.md:10: leftover conflict marker
Create or edit pancakes.md so that it contains:
# Pancakes

- 200 g flour
- 2 eggs
- 300 ml milk
- 35 g sugar

Mix everything and leave the batter to rest for 30 minutes.
git add pancakes.md
git commit -m "Remove leftover conflict markers"
Output
[main 79d79ee] Remove leftover conflict markers
 1 file changed, 1 insertion(+), 5 deletions(-)
Output from git version 2.50.1 (Apple Git-155), run while this page was built. Other versions of Git may word some messages differently.

Run git diff --check before you git add a resolution (or git diff --staged --check after it), and you will not need this repair.

7

Self-check quiz

1
When does Git report a merge conflict?
2
In a conflicted file, what lies between <<<<<<< HEAD and =======?
3
You have edited a conflicted file into its final form. What do you run next?
4
Which command puts everything back as it was before the merge started?
5
Which habit keeps conflicts small?
6
What does git diff --check warn about?
8

Further reading