Skip to content

Fix: CONFLICT (content): Merge conflict in file — fix conflicts and then commit the result

FixDevs · (Updated: )

Part of:  Docker, DevOps & Infrastructure

Quick Answer

How to fix Git merge conflicts during merge, rebase, cherry-pick, and pull — resolve conflict markers, use merge tools, accept theirs or ours, abort, and prevent future conflicts.

The Error

You run a Git operation and see one of these messages:

During a merge:

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

During a rebase:

Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
error: could not apply 3a1f29c... Add new feature
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run "git rebase --continue".
hint: You can instead skip this commit: "git rebase --skip".
hint: To abort and get back to the state before "git rebase", run "git rebase --abort".

During a cherry-pick:

Auto-merging src/app.js
CONFLICT (content): Merge conflict in src/app.js
error: could not apply 7b2e4d1... Fix login bug
hint: After resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'

During a pull:

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

The wording varies slightly, but the cause is the same: Git found changes on both sides that touch the same lines, and it cannot decide which version to keep.

Why This Happens

Git tracks changes as diffs — additions, deletions, and modifications to lines. When you merge two branches (or rebase, cherry-pick, or pull), Git attempts a three-way merge. It compares each side’s changes against the common ancestor commit and applies both sets of changes automatically.

A conflict occurs when both sides modify the same lines in the same file. Git has no way to know which change is correct. It could be that one side renamed a variable while the other side changed the logic on that same line. Applying both would produce broken code, so Git stops and asks you to decide.

Conflicts can also happen with file-level operations: one side deletes a file while the other side modifies it, or both sides add a file with the same name but different contents. These produce CONFLICT (modify/delete) or CONFLICT (add/add) messages.

The error is not a sign that something is wrong with your repository. It is a normal part of collaborative development. If you have worked with a team or maintained long-lived branches, you will encounter this regularly. The key is knowing how to resolve conflicts efficiently. If your repository itself seems broken, check Fix: fatal: not a git repository first to rule out structural issues.

Fix 1: Understand Conflict Markers

Before resolving anything, you need to understand what Git puts in your files. When a conflict occurs, Git inserts markers that look like this:

<<<<<<< HEAD
const API_URL = "https://api.example.com/v2";
const TIMEOUT = 5000;
=======
const API_URL = "https://staging.example.com/v1";
const TIMEOUT = 10000;
>>>>>>> feature-branch

Here is what each section means:

  • <<<<<<< HEAD marks the start of your current branch’s version (the branch you are merging into).
  • ======= separates the two versions.
  • >>>>>>> feature-branch marks the end of the incoming branch’s version (the branch you are merging from).

During a rebase, the labels are swapped because rebase replays your commits on top of the target branch. HEAD refers to the target branch (not your branch), and the bottom section shows the commit being replayed.

Your job is to edit the file so it contains exactly the code you want, and then remove all three marker lines. You can keep one side, keep the other, keep both, or write something entirely new.

Fix 2: Manually Resolve Conflicts

This is the most common fix and works in every situation.

Step 1: Identify conflicting files:

git status

Files with conflicts appear under “Unmerged paths” and are marked as both modified, both added, or deleted by us/deleted by them.

Step 2: Open each conflicting file in your editor. Search for <<<<<<< to find every conflict block. Edit the file to contain the correct final content. Remove all <<<<<<<, =======, and >>>>>>> lines.

For example, if you want to keep the production URL but use the longer timeout:

const API_URL = "https://api.example.com/v2";
const TIMEOUT = 10000;

Step 3: Stage the resolved files:

git add src/app.js

Step 4: Complete the merge with a commit:

git commit

Git pre-fills a merge commit message. You can keep it or edit it. If you want to add context about how you resolved the conflict, add it to this message.

Fix 3: Use VS Code Merge Editor

VS Code detects conflict markers automatically and provides an inline interface. When you open a file with conflicts, VS Code shows buttons above each conflict block: Accept Current Change, Accept Incoming Change, Accept Both Changes, and Compare Changes.

For more complex conflicts, use the dedicated merge editor:

  1. Open the file with conflicts.
  2. Click Resolve in Merge Editor at the bottom of the conflict notification.
  3. The three-pane editor shows: the current branch (left), the incoming branch (right), and the result (bottom).
  4. Check the boxes next to the changes you want to accept, or edit the result pane directly.
  5. Click Complete Merge when finished.
  6. Stage and commit as usual.

This visual approach makes it much easier to handle files with multiple conflict blocks or conflicts in code you are not familiar with.

Pro Tip: In VS Code, you can search across all files for <<<<<<< using Ctrl+Shift+F to make sure you haven’t missed any conflict markers before committing. A single leftover marker will break your code at runtime.

Fix 4: Use git mergetool

Git can launch an external merge tool to help resolve conflicts. If you have a diff tool configured (such as meld, kdiff3, vimdiff, or p4merge), run:

git mergetool

Git opens each conflicting file in the configured tool one at a time. The tool typically shows a three-way diff: the base version (common ancestor), the local version, and the remote version. You resolve the conflict in the tool, save, and close it. Git marks the file as resolved.

To configure a default merge tool:

git config --global merge.tool meld

After the tool finishes, Git creates .orig backup files. To disable these:

git config --global mergetool.keepBackup false

Then commit the result:

git commit

Fix 5: Accept Theirs or Ours (Bulk Resolution)

Sometimes you know that one side is entirely correct and you want to discard the other side’s changes for specific files. Git provides shortcuts for this.

Accept the incoming branch’s version (theirs):

git checkout --theirs src/app.js
git add src/app.js

Accept your current branch’s version (ours):

git checkout --ours src/app.js
git add src/app.js

You can apply this to multiple files at once:

git checkout --theirs src/config.js src/utils.js src/routes.js
git add src/config.js src/utils.js src/routes.js

Or accept one side for all conflicting files:

git checkout --theirs .
git add .

Warning: This discards all changes from one side across every conflicting file. Only use it when you are certain that one branch’s version is completely correct. After resolving, commit the result with git commit.

Important note on rebase: During a rebase, --ours and --theirs are swapped compared to merge. In a rebase, --theirs refers to your branch (the commits being replayed), and --ours refers to the target branch. This is a common source of confusion.

Fix 6: Abort the Merge

If the conflicts are too complex or you started the merge by mistake, you can abort and return to the state before the merge began:

git merge --abort

This resets your working tree and index to the pre-merge state. No changes are lost from either branch — the merge simply did not happen.

For a cherry-pick:

git cherry-pick --abort

For a pull that resulted in conflicts (since git pull is essentially git fetch + git merge):

git merge --abort

Aborting gives you time to review the branches, coordinate with teammates, or restructure your approach before trying again. This is particularly useful if you realize the merge needs more planning. If you are working in a CI/CD pipeline and conflicts cause a build failure, aborting and fixing locally before pushing is the right approach.

Fix 7: Rebase Conflicts (git rebase --continue)

Rebase conflicts are different from merge conflicts because rebase replays commits one at a time. You may need to resolve conflicts multiple times — once for each commit that conflicts.

Step 1: Resolve the conflict in the current file (see Fix 2).

Step 2: Stage the resolved files:

git add src/app.js

Step 3: Continue the rebase:

git rebase --continue

Git applies the next commit. If that commit also conflicts, you repeat the process. This continues until all commits have been replayed.

Skip a commit entirely:

If a commit’s changes are no longer relevant (they were already incorporated into the target branch), skip it:

git rebase --skip

Abort the entire rebase:

git rebase --abort

This returns everything to the state before you started the rebase. No commits are lost.

Tip: If you are rebasing a long branch with many conflicts, consider using git merge instead. A merge produces a single merge commit and requires resolving conflicts only once, while a rebase may require conflict resolution for each individual commit.

Fix 8: Pull Conflicts (git pull --rebase)

When git pull causes conflicts, it is because the remote branch and your local branch both have new commits that touch the same lines.

Option A: Pull with merge (default behavior).

Resolve the conflicts as described in Fix 2, then commit. This creates a merge commit.

Option B: Pull with rebase instead.

If you prefer a linear history without merge commits:

git pull --rebase

If conflicts occur during the rebase, resolve them and continue:

git add src/app.js
git rebase --continue

Option C: Fetch first, review, then merge.

This is the safest approach and helps prevent surprises:

git fetch origin
git log HEAD..origin/main --oneline
git diff HEAD...origin/main

This lets you see exactly what changed on the remote before you integrate. If you see potential conflicts, you can plan your resolution strategy. Then merge or rebase when ready:

git merge origin/main

If you encounter permission errors while pushing or pulling, resolve those authentication issues before attempting the merge again.

Fix 9: Prevent Conflicts (Fetch and Review First)

The best conflict is one that never happens. While you cannot always avoid conflicts, you can minimize them:

Fetch and review before merging:

git fetch origin
git log --oneline HEAD..origin/main
git diff HEAD...origin/main -- src/

Review what changed on the remote. If you see modifications to files you also changed, you know a conflict is coming and can prepare.

Pull frequently. The longer your branch lives without integrating upstream changes, the more conflicts you accumulate. Pull or rebase daily on active projects.

Communicate with your team. If two people are working on the same file, coordinate. Code ownership conventions and smaller, more focused pull requests reduce conflict frequency.

Keep branches short-lived. Feature branches that live for weeks accumulate massive diffs. Break large features into smaller, incremental pull requests.

Use .gitattributes for merge strategies. For files that always conflict unnecessarily (like lock files or generated files), you can tell Git how to handle them:

# Always take ours for the lock file
package-lock.json merge=ours

If you run into issues where your shell cannot execute Git commands at all, fix the environment before attempting another merge. A failed merge attempt against a broken shell or PATH will leave the working tree half-resolved and harder to recover.

Fix 10: Resolve Binary File Conflicts

Binary files (images, PDFs, compiled assets) cannot be merged line-by-line. When Git detects a conflict in a binary file, it does not insert conflict markers. Instead, it tells you there is a conflict and leaves both versions available.

warning: Cannot merge binary files: assets/logo.png (HEAD vs. feature-branch)
CONFLICT (content): Merge conflict in assets/logo.png

You must choose one version or the other:

# Keep the version from the current branch
git checkout --ours assets/logo.png
git add assets/logo.png

# Keep the version from the incoming branch
git checkout --theirs assets/logo.png
git add assets/logo.png

If you need to combine changes from both versions (for example, two different edits to an image), you must do this manually outside of Git using the appropriate editor for that file type. Extract both versions, combine them in your image editor, save the result, and stage it.

For projects with many binary assets, consider using Git LFS (Large File Storage), which handles binary files more efficiently and provides better conflict workflows.

Fix 11: Large-Scale Conflicts with git rerere

If you frequently resolve the same conflicts — for example, when maintaining multiple long-lived branches or doing repeated merges during integration testing — Git’s rerere (“reuse recorded resolution”) feature can help.

Enable rerere:

git config --global rerere.enabled true

Once enabled, Git records how you resolve each conflict. The next time it encounters the same conflict (the same two conflicting hunks), it applies your previous resolution automatically.

How it works:

  1. You resolve a conflict and commit.
  2. Git records the resolution in .git/rr-cache/.
  3. Later, the same conflict appears again (perhaps during a re-merge or when another branch hits the same conflict).
  4. Git applies the recorded resolution automatically and tells you: Resolved 'src/app.js' using previous resolution.
  5. You still need to verify and stage the file, but the editing is done.

View recorded resolutions:

git rerere status
git rerere diff

Clear a bad recorded resolution:

git rerere forget src/app.js

This is especially useful in workflows where you rebase feature branches onto a frequently updated main branch. The first rebase requires manual resolution; subsequent rebases reuse your decisions.

Common Mistake: After resolving conflicts and running git add, some developers forget to run git commit (for merges) or git rebase --continue (for rebases). The resolved files sit in the staging area indefinitely, and the merge or rebase never completes.

If you are dealing with merge conflicts inside a stash pop operation, the resolution process is similar but has some additional considerations around stash management.

How other tools handle this

Git’s conflict-marker UX is the default mental model for most developers, but several modern VCS designs handle conflicts very differently. Knowing how they work changes which workflow you reach for next time conflicts pile up.

Jujutsu (jj) treats conflicts as first-class data. When jj rebase or jj merge hits a conflict, the conflicting commit is materialized with the conflict embedded in the working copy as a real commit — not a transient unmerged state. You can keep working, run other commands, swap branches, and come back later. The conflict doesn’t “block” the operation. You resolve it by amending the commit, and the conflict markers disappear automatically. This is the biggest UX win over Git: there is no “in the middle of a merge” mode that traps you.

Sapling (sl) comes from Meta’s Mercurial fork. Its smartlog view shows the entire pending change graph, and conflict resolution happens with sl resolve --tool against an explicit tool name. Sapling tracks unfinished operations more aggressively than Git, so sl status always tells you whether you’re mid-rebase and what’s blocking you. The conflict markers look the same as Git’s, but the recovery commands (sl unamend, sl uncommit, sl absorb) make it much easier to back out of a bad resolution without reflog archaeology.

Mercurial (hg) uses the same <<<<<<< / ======= / >>>>>>> markers but exposes additional commands: hg graft (the cherry-pick equivalent), hg rebase (with the rebase extension), and hg evolve (changeset evolution). Mercurial’s “phases” make it explicit which commits are public (immutable) versus draft (still mutable), which prevents one of Git’s most common conflict-after-rebase footguns: rewriting commits that other people already pulled.

Pijul is built on a theory-of-patches model rather than snapshots. Conflicts in Pijul are between patches, not file states. Two patches conflict when their changes overlap, and the conflict is associative — resolving the same conflict in two different branches produces the same merged result. In practice this means you don’t get the “resolve the same conflict on every rebase” experience that Git’s rerere was invented to fix. Pijul records the resolution at the patch level, not the line level.

Fossil and Bazaar (Breezy) behave more like Git in terms of conflict markers, but Fossil bundles everything (tickets, wiki, builds) into a single SQLite file, so the entire conflict and history workflow happens inside one local artifact rather than against a remote.

The takeaway: if Git’s “you are now in detached merge mode, resolve and commit before doing anything else” trap keeps biting you, Jujutsu’s materialized conflicts solve exactly that. If you find yourself running rerere constantly, Pijul’s patch theory is the principled answer. For most teams, the conflict UX is good enough — the bigger wins come from smaller PRs and more frequent integration, not from changing version control systems.

Still Not Working?

Conflict markers left in committed files

If you accidentally committed a file that still contains <<<<<<< markers, the conflict is “resolved” from Git’s perspective but your code is broken. Fix the file, stage it, and create a new commit:

git add src/app.js
git commit -m "Fix unresolved conflict markers in app.js"

To prevent this, add a pre-commit hook that checks for conflict markers:

git diff --cached --check

This command detects leftover conflict markers in staged files and exits with an error if any are found.

CONFLICT (modify/delete)

This means one side modified a file while the other side deleted it. Git does not know whether to keep the modified version or honor the deletion.

To keep the file:

git add src/app.js

To accept the deletion:

git rm src/app.js

Then continue with git commit (for merge) or git rebase --continue (for rebase).

CONFLICT (rename/delete)

One side renamed a file while the other deleted the original. Similar to modify/delete, decide whether the renamed file should exist:

# Keep the renamed file
git add src/newname.js
git rm src/oldname.js

# Or accept the deletion
git rm src/newname.js

Merge says “Already up to date” but branches differ

If git merge feature-branch says “Already up to date” but you know the branches have different content, it means the current branch already contains all commits from the feature branch (the feature branch is an ancestor). This is not a conflict — it means the merge was already completed at some point, or the feature branch was branched from a point that is behind your current HEAD.

Check the commit history:

git log --oneline --graph --all

Conflicts reappear after resolution

If you resolve conflicts, commit, and then see the same conflicts again on the next merge, it usually means the branches continue to diverge. Each new merge only resolves conflicts for the commits involved at that point. New commits on either branch can introduce new conflicts. Enable git rerere (Fix 11) to automate re-resolution of identical conflicts.

Repository seems corrupted

If Git itself is throwing unexpected errors beyond merge conflicts — such as complaints about missing objects or corrupted refs — you may have a different problem entirely. Diagnose the repository structure first; a .git directory that has lost track of HEAD will make every merge attempt fail in confusing ways.

Line-ending conflicts (CRLF vs LF)

If every line in a file appears as a conflict even though the visible content is identical, the cause is almost always line endings. Windows checkouts default to CRLF, Linux and macOS to LF, and Git’s core.autocrlf setting decides whether to convert on commit. When a teammate on a different platform commits the file, every line looks like a change. Fix it once by adding .gitattributes:

*.js text eol=lf
*.md text eol=lf
*.png binary

Then run git add --renormalize . and commit. After that, every checkout normalizes the same way and the phantom line-ending conflicts stop. This is the single most common “every file conflicts at once” cause on cross-platform teams.

Whitespace-only conflicts

If the conflict is purely whitespace (different indentation styles, tabs vs spaces, trailing whitespace), use git merge -X ignore-all-space or git merge -X ignore-space-change to make Git ignore those when computing the merge. For rebases, use git rebase -Xignore-all-space. This won’t help if both sides actually changed code, but it makes “the linter reformatted the file” conflicts evaporate.

Submodule conflicts

If git status shows both modified on a submodule entry, the conflict is over which commit the parent repository should point at — not over any file contents. Resolve it by cd-ing into the submodule, checking out the commit you actually want, then cd .. back and running git add <submodule>. Editing files inside the submodule from the parent’s merge state will not work.

LFS pointer conflicts

For repositories using Git LFS, the conflict happens in the pointer file, not the binary itself. Resolving with --ours or --theirs picks one LFS object hash over the other. You cannot “merge” two LFS-tracked binaries from the command line — the pointer file is the only thing Git sees, and the actual binary lives on the LFS server. Use the binary editor route described in Fix 10, then re-add the file so LFS uploads a new object.


Related: Conflict resolution is one piece of a larger Git workflow. Push errors, stash conflicts, repository corruption, and CI failures all interact — a clean merge process makes every other Git operation easier.

F

FixDevs

Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.

Was this article helpful?

Related Articles