Programming and IT

How to resolve a merge conflict

A conflict is not a breakage, it is a question. Git merges changes automatically as long as they are in different parts of a file; when two branches edit the same lines, only a person can choose the right version. Your job is to read both versions, build working code out of them and tell Git the question is settled.

Updated
In this article

Where a conflict comes from#

A merge compares three states: the common ancestor of the two branches and the tips of both. If only one side changed a line, Git takes that change silently. If both sides changed the same line in different ways, the program has no grounds to prefer one over the other, so it stops.

A conflict can happen even without overlapping lines: one branch edits a file while the other deletes or renames it. Git describes that case separately, with words like deleted by them or added by us in the status output.

Important: a conflict does not roll your work back. The merge simply stays unfinished until you complete it or abort it.

What the stop looks like#

$ git merge feature-vat
Auto-merging billing/rates.py
CONFLICT (content): Merge conflict in billing/rates.py
Automatic merge failed; fix conflicts and then commit the result.

The first line says part of the changes merged on their own. The second names the file with the question. The full picture is in the status:

$ 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:   billing/rates.py

To list only the conflicted files and nothing else:

$ git diff --name-only --diff-filter=U
billing/rates.py

What the file contains#

Open the file and you will see both versions separated by markers:

<<<<<<< HEAD
VAT_RATE = 0.20
def with_vat(sum_):
    return round(sum_ * (1 + VAT_RATE), 2)
=======
VAT_RATE = Decimal("0.22")
def with_vat(sum_):
    return sum_ * (1 + VAT_RATE)
>>>>>>> feature-vat

Between <<<<<<< and ======= is what you had (HEAD, the current branch). Between ======= and >>>>>>> is what comes in from the branch being merged; its name is written on the last marker.

Resolving the conflict means leaving the correct code in the file and deleting all three markers. The correct code may be the first version, the second, a mix of both or something new written from scratch:

VAT_RATE = Decimal("0.22")
def with_vat(sum_):
    return round(sum_ * (1 + VAT_RATE), 2)

A useful setting shows the ancestor's state as well:

git config --global merge.conflictStyle zdiff3

Then a third chunk appears between the markers, ||||||| — the original text before both edits. With it, it is much clearer who changed what: you can see that one side changed the rate and the other changed the rounding, so you need both changes.

The resolution routine#

$ git diff --name-only --diff-filter=U
billing/rates.py
$ nano billing/rates.py          # edit, remove the markers
$ git add billing/rates.py
$ git status -s
M  billing/rates.py
$ git commit
[main 8b2f0d5] Merge branch 'feature-vat'

Here git add does not mean "add a file" but "this question is closed". As long as even one file has not been added, Git will not let you finish the merge. It suggests the commit message itself — usually you keep it as it is.

When you need one version in full, you do not have to edit markers by hand:

git checkout --ours  billing/rates.py    # keep the current branch's version
git checkout --theirs billing/rates.py   # keep the merged branch's version
git add billing/rates.py

There is a trap here. During a merge, --ours is your branch, which makes sense. But during a rebase the sides swap: --ours becomes the base you are replaying onto, and --theirs becomes your own commits. The reason is that a rebase applies your changes on top of someone else's tip. More on the mechanism itself in the article on git rebase.

How to get out if you are lost#

Until the merge is finished, one command rolls everything back:

$ git merge --abort

The branch and the working copy return to the state before the merge. For a rebase, git rebase --abort does the same; for a revert, git revert --abort.

If the merge is already committed and the result is bad, you can move the branch tip back by one commit — provided the merge commit has not been pushed yet; the modes and the danger of --hard are covered in the article on git reset. A merge commit that has been pushed is undone with git revert -m 1 <hash>, where the 1 says which of the two lines to treat as the main one.

Common mistakes#

Markers left in the code. A classic: the file is added and committed along with the <<<<<<< lines. Catch it by reviewing before the commit (git diff --cached) or by searching the project: grep -rn '<<<<<<<' ..

Picking your version without reading theirs. Fast and almost always wrong: the other person's edits vanish without a trace and the tests fail a week later. If you do not understand why a teammate changed a line, git log --merge -p billing/rates.py shows the history of that spot — only the commits that touched the conflicted area remain in the output.

The same conflicts on every rebase. Turn on recorded resolutions and Git will reapply them for you:

git config --global rerere.enabled true

Starting a merge with a dirty working copy. Git refuses to start if uncommitted edits touch the files being merged. Commit or stash first (git stash push), then merge — see the article on git stash.

A huge conflict after a long-lived branch. The cure is a habit, not a trick: bring the main branch into yours often. A branch two days behind almost always merges without questions; one two months behind almost always has them.

Step-by-step plan

  1. List the conflictsgit status or git diff --name-only --diff-filter=U — only the unresolved files.
  2. Read both versionsMarkers <<<<<<<, =======, >>>>>>>; with zdiff3 you also see the common ancestor.
  3. Build the correct codeKeep what is needed, delete every marker; if appropriate use checkout --ours or --theirs.
  4. Mark it resolvedgit add each file; check that no markers remain (grep the project).
  5. Finish and verifygit commit, then build and run the tests: a merge without conflicts is not the same as working code.

Start learning this in your own space

The plan goes into your repository: tick off stages, keep notes — the change history shows how far you have come.

Start the plan

Check yourself

1.In a conflicted file, whose code is the part between <<<<<<< HEAD and =======?

2.You resolved the conflicts in your editor. Which command tells Git the file is ready?

3.A merge has started, there are many conflicts, and you want everything back as it was. What do you run?

4.Which setting makes Git also show the common ancestor's version between the markers?

Sources

  • Pro Git book“Git Branching” covers basic merges and conflicts, “Git Tools” the hard cases
    free
  • git merge referenceThe section HOW CONFLICTS ARE PRESENTED explains the markers
    free
  • git rerere referenceRecording resolved conflicts for repeated merges
    free

Was this helpful?

More articles

Programming and IT git merge Merging brings work from one branch into another. git merge takes the branch you name and joins its history with the current one — sometimes by just moving a pointer, sometimes by creating a separate commit, and sometimes by stopping and asking for help. Programming and IT git checkout Historically `git checkout` does several unrelated things at once: it moves your working copy to another branch, creates a branch and pulls a single file out of a commit. Because it was so overloaded, Git 2.23 added two separate commands — `switch` and `restore`. Here are both sides: the old way and its modern replacement. Programming and IT git rebase git rebase moves the commits of your branch as if you had started work not from an old state but from the current one. History becomes linear, but the commits are created anew — with new hashes. That leads to the main rule: you can only rebase what nobody else is using. Programming and IT git commit A commit is a saved state of your project with an author, a timestamp and a message. git commit takes what you selected beforehand with git add and writes it into history as one indivisible piece. Programming and IT Git branches A Git branch is a movable pointer to a commit, not a copy of your files. That is why creating one costs nothing, and the git branch command is what you use to list, create, rename and delete branches. Programming and IT git clone git clone takes an entire repository from a server — with all its history, branches and tags — and puts a copy into a new folder on your computer. It is how you start working on any existing project, yours or someone else's.

More solutions