GitGit

CONFLICT (content): Merge conflict in file

Git could not combine two sets of changes to the same lines. How to read the markers, resolve with the tools Git gives you, and abort safely if you need to start again.

medium fix6 min read

the git error
Auto-merging src/config.ts
CONFLICT (content): Merge conflict in src/config.ts
Automatic merge failed; fix conflicts and then commit the result.

CONFLICT (modify/delete): src/old.ts deleted in HEAD and modified in feature/new. Version feature/new of src/old.ts left in tree.

error: could not apply a1b2c3d... Add retry logic

Do this first3 steps

Run these in order. Each one tells you what its output means before you change anything.

  1. 1

    List exactly which files are conflicted

    git diff --name-only --diff-filter=U

    This is the authoritative list, and it is shorter than git status. Anything not listed merged cleanly and does not need your attention.

  2. 2

    See both sides plus the common ancestor

    git checkout --conflict=diff3 -- src/config.ts

    diff3 style adds the original version between the two sides, which usually makes it obvious which side changed what. Without it you see two versions and have to guess what the base was.

  3. 3

    If the resolution is going badly, get back to where you started

    git merge --abort

    This restores the pre-merge state completely. Use git rebase --abort during a rebase, and git cherry-pick --abort during a cherry-pick. Nothing is lost.

All 9 sections

Both branches changed the same lines in the same file, so Git cannot decide which version is correct. It marks the file, stops, and waits for you.

This is normal. It is not a failure state.

Find the conflicted files

git diff --name-only --diff-filter=U
src/config.ts
src/api/client.ts

--diff-filter=U means unmerged, and this is the authoritative list. git status shows it too, buried among other information.

Read the markers

<<<<<<< HEAD
const TIMEOUT = 5000
=======
const TIMEOUT = 30000
>>>>>>> feature/retry

Between <<<<<<< and ======= is your current branch. Between ======= and >>>>>>> is what you are merging in. The labels tell you which is which, and they matter during a rebase where the sides are swapped from what you might expect.

Add the common ancestor, which usually makes the decision obvious:

git checkout --conflict=diff3 -- src/config.ts
<<<<<<< HEAD
const TIMEOUT = 5000
||||||| merged common ancestor
const TIMEOUT = 10000
=======
const TIMEOUT = 30000
>>>>>>> feature/retry

Now you can see the base was 10000: one side lowered it, the other raised it. Without the ancestor you are guessing which side actually changed.

Make it the default:

git config --global merge.conflictStyle zdiff3

zdiff3 is the improved version, which also removes lines common to both sides from the conflict region, leaving a smaller thing to read.

Resolve

Edit the file, remove all three marker lines, keep what is correct. It is frequently neither side verbatim.

Then stage it:

git add src/config.ts

Staging a conflicted file is how you tell Git it is resolved. Repeat for each file, then:

git commit          # merge: the message is pre-filled
git rebase --continue   # rebase

Before committing, check you did not leave a marker behind:

git diff --check
grep -rn '^<<<<<<<\|^>>>>>>>' src/

Committed conflict markers are a genuinely common and embarrassing mistake, and a CI grep for them is worth adding.

Taking one side wholesale

When one side is simply correct, for a lockfile or generated output:

git checkout --ours package-lock.json      # current branch
git checkout --theirs package-lock.json    # incoming branch
git add package-lock.json

During a rebase, --ours and --theirs are reversed from what you expect. A rebase replays your commits on top of the upstream branch, so the upstream is checked out and becomes "ours", while your commit being replayed is "theirs". This catches almost everyone once.

For a lockfile the better answer is usually to regenerate it rather than pick a side:

git checkout --theirs package-lock.json
npm install
git add package-lock.json

Other conflict types

modify/delete

CONFLICT (modify/delete): src/old.ts deleted in HEAD and modified in feature/new.

There are no markers because there is no content to interleave. Decide:

git rm src/old.ts          # accept the deletion
git add src/old.ts         # keep the modified version

add/add. Both branches created the same path independently. Resolved like a content conflict.

rename/rename. Both renamed the same file differently. Git leaves all the variants in the tree; pick one and git rm the rest.

Abort

At any point:

git merge --abort
git rebase --abort
git cherry-pick --abort

Each restores the state before the operation started. Nothing is lost, and there is no reason to hesitate: a half-finished merge you no longer understand is worth abandoning and restarting with a clearer head.

git merge --abort fails if you had uncommitted changes when you started the merge, which is the argument for a clean working tree before merging.

Reducing how often this happens

Merge the target branch into yours regularly. A feature branch that tracks main weekly resolves conflicts in small pieces rather than one enormous one at the end.

Turn on rerere. Git records how you resolved a conflict and replays it automatically next time it sees the same one:

git config --global rerere.enabled true

This pays for itself during a long rebase where the same conflict reappears in commit after commit.

Use a merge tool for large conflicts:

git mergetool --tool=vimdiff

VS Code, IntelliJ and the rest all have three-way merge editors, and for a conflict spanning many hunks they are considerably faster than editing markers by hand.

A checklist

  1. git diff --name-only --diff-filter=U for the real list.
  2. Set merge.conflictStyle = zdiff3 so you can see the common ancestor.
  3. Edit, remove all markers, keep what is correct. Often a blend of both.
  4. git add <file> to mark each one resolved.
  5. git diff --check before committing, to catch leftover markers.
  6. --ours and --theirs are reversed during a rebase.
  7. Lockfiles: regenerate rather than choosing a side.
  8. Lost? git merge --abort or git rebase --abort. Nothing is lost.

Frequently Asked Questions

What do the conflict markers mean?

<<<<<<< to ======= is the version on your current branch, and ======= to >>>>>>> is the incoming version, with the branch names on the marker lines. With merge.conflictStyle set to diff3 or zdiff3 you also get the common ancestor between ||||||| and =======, which is much more useful because it shows what both sides started from. Seeing the base usually makes it immediately clear which side made the meaningful change, rather than leaving you to guess from two similar-looking versions.

Why are --ours and --theirs backwards during a rebase?

Because a rebase works by checking out the upstream branch and replaying your commits on top of it, one at a time. From Git's point of view the checked-out upstream is "ours" and the commit being applied, which is yours, is "theirs". It is the opposite of a merge, where your branch is checked out and the incoming one is theirs. The reliable habit is to read the branch names on the conflict marker lines rather than relying on which flag feels right.

How do I undo a merge that is going badly?

git merge --abort restores the state exactly as it was before the merge began, and the equivalents are git rebase --abort and git cherry-pick --abort. Nothing is lost and there is no penalty for using them, so abandoning a confusing resolution and starting again is often the fastest path. The one limitation is that git merge --abort can fail if you had uncommitted changes when you started, which is a good reason to begin any merge with a clean working tree.

What should I do with a conflicted lockfile?

Regenerate it rather than resolving it by hand. A package-lock.json or yarn.lock is generated output, and hand-merging two versions reliably produces something that does not correspond to any real dependency resolution. Take either side with git checkout --theirs package-lock.json, then run npm install to regenerate it from the merged package.json, and stage the result. The same logic applies to any generated file: resolve the source, then regenerate.

How can I avoid resolving the same conflict repeatedly?

Enable rerere with git config --global rerere.enabled true. Git records your resolution of each conflict and replays it automatically whenever it encounters an identical one, which is exactly what happens during a long rebase where the same hunk conflicts in commit after commit. The broader habit that helps more is merging the target branch into your feature branch regularly, so conflicts arrive in small, comprehensible pieces instead of as one large one at the end.

Reference and practice

Learn the underlying concept

Other Git errors