Menu for Per-Arne's Git Good!
Courses and project Per-Arne's Git Good!

Conflicts without panic

Module 2: Branching and merging · 14 min

The interactive lesson needs JavaScript. The text below is the complete lesson.

After this lesson you can

  • explain why Git stops with a conflict when the same lines changed differently on both sides understand
  • read git status and the conflict markers during a merge in progress understand
  • resolve a conflict by deciding the intended content, mark it with git add and finish with git commit apply
  • safely abort a merge in progress with git merge --abort apply

Understand: A question only you can answer

Both branches changed the same line in README.md. On main it says Welcome, new students. On feature it says Welcome to the Git course. The base had just Welcome.

Git cannot know which text you want. So the merge stops halfway, and the file is marked as a conflict. The staging area now holds three versions of the file: the base, ours and theirs. No merge commit has been made yet.

Git has written conflict markers into the file. From <<<<<<< HEAD to ======= is your version, from the branch you are on. From ======= to >>>>>>> feature is the version you are merging in.

You resolve the conflict by deciding what the file should actually say. Often the answer is a mix of both. Write the final text, and remove all the markers.

git add tells Git the conflict is resolved. The new text replaces the three versions in the staging area. Git does not check whether the resolution is good, so read the file before you stage it.

git commit completes the merge commit with two parents.

Watch: Stop, read, decide

We are on main, and both branches changed the first line of README.md. We merge. The terminal says CONFLICT, and the command exits with an error code.

$ git merge feature

When something unexpected happens, read git status first. It says we are in the middle of a merge, that README.md was modified on both sides, and how we can finish or abort.

$ git status

Here are the markers. At the top is our version from HEAD. Below the line is the version from feature.

$ cat README.md

First we practise the emergency exit. We started clean, so git merge --abort takes us back to exactly where we were before the merge. Look at the file areas: README.md is back to the version from main.

$ git merge --abort

Now we start the merge again. This time we resolve the conflict.

$ git merge feature

We decide that the greeting should include both parts. We write the final text, without markers. Look at the working tree: the file now has one line.

$ echo "Welcome to the Git course, new students" > README.md

Now we tell Git the conflict is resolved. Look at the staging area: the conflict mark disappears, and the new text is ready to commit.

$ git add README.md

Finally we finish. Git has already suggested the message Merge branch feature, so we save it. Watch the graph: the merge commit has two parents.

$ git commit

Tasks

  1. Start a merge of feature into main.
  2. Ask Git what the state is now.
  3. Abort the merge, and get back to where you were before.
  4. Write the final text in README.md: both greetings in one line, without markers.
  5. Mark the conflict in README.md as resolved.
  6. Finish the merge.

Practise: Transfer: the canteen menu

You are on main in the canteen's repository. The branches lunch and discount changed files that main also changed. No demo now — use what you have seen.

Tasks

  1. Merge lunch into main. The menu should include both salad and soup. Finish the merge.
  2. Merge discount. Management has decided that the student price of 79 applies. Resolve the conflict accordingly, and finish the merge.

Check yourself: Recall it

You edited the conflicted file and ran git add. What has Git checked?

  • That the resolution is correct and the program works
  • Nothing. Git has only recorded the content you chose as the resolution.
  • That both versions are in the file

You are on main and merging feature. What is between <<<<<<< HEAD and =======?

  • The version from feature
  • The version from main, where you are
  • The version from the merge base

You are in the middle of a conflict and would rather start over. What is safest?

  • Delete the folder and clone again
  • git merge --abort
  • git add . and git commit, so the conflict goes away

What does a merge conflict mean?

  • That someone did something wrong
  • That Git cannot combine the changes from both sides by itself, and needs your decision
  • That both branches changed the same file

Review: In the middle of the conflict you spot a typo in another file that has nothing to do with the merge. What is best?

  • Fix it now, so it goes into the merge commit
  • Finish the merge with only the conflict resolution, and fix the typo in a commit of its own afterwards
  • Wait until you have collected more small things, and commit them all together later

Common misconceptions

  • “A conflict means something is broken.” It is a question Git cannot answer alone. The history is untouched.
  • “git add checks that the conflict is resolved correctly.” It only records the content you chose, even if the markers are still there.
  • “I must pick either my version or yours.” The resolution is what you actually want. Often it is a mix.

Read more

What Git stopped on

A conflict happens when both sides changed the same region of a file differently since the merge base. There are other kinds too, and git status --short labels each one:

Code Meaning
UU both modified the same lines (a content conflict)
AA both added a file with the same name but different content
DU / UD one side deleted a file the other side modified

The merge exits with status 1 and tells you which files need you:

$ git merge feature
Auto-merging README.md
CONFLICT (content): Merge conflict in README.md
Automatic merge failed; fix conflicts and then commit the result.

Every file without a conflict has already been merged and staged. Only the listed files wait for you.

Read the state before you touch anything

$ git status
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:   README.md

git status always names the operation in progress and the commands that finish or abort it. That matters, because rebase and cherry-pick conflicts look the same but are finished differently.

Three versions in the staging area

During a conflict the staging area (the index) holds up to three versions of the file, called stages:

$ git ls-files -u
100644 ba4c5142cc… 1    README.md
100644 b489365621… 2    README.md
100644 4b854ebe86… 3    README.md

Stage 1 is the base, 2 is ours (HEAD) and 3 is theirs (the branch being merged). You can print each one, for example git show :1:README.md for the base. git add README.md replaces the three stages with one ordinary entry. That is all "marking as resolved" means: Git does not look at what you wrote.

Seeing the base in the markers

By default the markers show only ours and theirs. Turn on the base too:

git config --global merge.conflictStyle diff3

The next conflict shows what the line looked like before either side changed it:

<<<<<<< HEAD
Welcome, new students
||||||| 0483acd
Welcome
=======
Welcome to the Git course
>>>>>>> feature

Now the intent is clear: both sides extended "Welcome", so the answer is probably to keep both additions. zdiff3 is a more compact variant of the same style.

Taking one whole side

When you really want one version of a whole file, you do not have to edit by hand:

  • git checkout --ours README.md or git checkout --theirs README.md writes that side into the working tree. git restore --ours and git restore --theirs do the same.
  • git checkout -m README.md brings the conflict markers back if you made a mess and want to start the file over.

Then stage the file as usual. For -X ours and -X theirs, which decide every conflict in advance, see the three-way merge lesson.

Check before you commit

git add happily stages a file that still contains markers. Two safety nets:

$ git diff --cached --check
README.md:1: leftover conflict marker
README.md:3: leftover conflict marker
README.md:5: leftover conflict marker

And git commit refuses while any file is still unmerged:

error: Committing is not possible because you have unmerged files.

Neither checks that the result is right. Read the file, build, and run the tests.

Finish or go back

  • git commit or git merge --continue completes the merge commit with two parents. They do the same thing.
  • git merge --abort returns to the state before the merge. It works reliably only if you started clean. If you had uncommitted changes, Git may not be able to restore them. That is why Git refuses to start a merge that would overwrite local changes: "Please commit your changes or stash them before you merge."

Merge tools

git mergetool opens a three-way merge tool, graphical or in the terminal, such as Meld, KDiff3, vimdiff or VS Code, for each conflicted file, with base, ours and theirs side by side. git mergetool --tool-help lists the tools Git finds on your machine. Some tools leave *.orig backup files behind; git config --global mergetool.keepBackup false turns that off. The browser sandbox has no merge tools, but on your own machine they help with large conflicts.

Conflicts in rebase and cherry-pick

The markers and stages are the same, but you finish with the command that started the operation:

$ git rebase main
CONFLICT (content): Merge conflict in README.md
error: could not apply 01d2650... Name the course in the greeting
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".

Use git rebase --continue, git rebase --skip or git rebase --abort, and git cherry-pick --continue or git cherry-pick --abort. Beware that during a rebase the sides swap: "ours" is the branch you are rebasing onto, and "theirs" is your own commit being replayed.

Resolving the same conflict again: rerere

If you meet the same conflict repeatedly, for example when rebasing a long-lived branch many times, turn on "reuse recorded resolution":

git config --global rerere.enabled true

Git records how you resolved each conflict and applies the same resolution next time:

Recorded resolution for 'README.md'.
...
Resolved 'README.md' using previous resolution.

The file is updated but not staged. Check it, then git add as usual.

Fewer and smaller conflicts

  • Merge often. A day of divergence gives small conflicts; a month gives big ones.
  • Keep commits focused. Do not mix reformatting of a whole file with real changes.
  • Agree on a formatter, so whitespace and style do not collide.
  • Talk to the person whose change you are combining with. The right resolution is a question about intent, not about text.

Official documentation