How to Fix Git Merge Conflict Step by Step: The Complete 2026 Guide

How to Fix Git Merge Conflict Step by Step: The Complete 2026 Guide

Every developer has been there. You type git merge feature-branch, press Enter, and instead of the satisfying “Merge made by the ‘ort’ strategy” message, you see something like:

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

Your terminal just told you that Git got confused — and now it’s your job to sort things out. In this guide, I’ll walk you through exactly how to fix git merge conflict step by step, covering everything from basic line-level conflicts to the gnarly edge cases that stump even experienced developers.


What Is a Git Merge Conflict?

A merge conflict happens when two branches modify the same lines of the same file (or when one deletes a file that the other modifies), and Git can’t automatically reconcile the differences. Instead of guessing which change is correct, Git pauses the merge and asks you to decide.

This is actually a safety feature — not a bug. Git is protecting you from silently overwriting someone’s work.


Root Cause Analysis: Why Do Merge Conflicts Happen?

The Three Sources of Conflicts

Understanding the root cause helps you resolve conflicts faster and prevent them in the future. Merge conflicts typically arise from three scenarios:

  1. Same-line edits: Two branches change the exact same line(s) in a file differently. This is the most common type.
  2. Overlapping changes: One branch modifies a block of lines that another branch also modifies, even if the specific edits are on different lines within that block.
  3. File-level conflicts: One branch deletes a file while the other modifies it, or both branches rename the same file differently.

A Real-World Example

Let’s say you and a teammate are working on the same React component. Your teammate changes the button text from “Submit” to “Send” on the main branch, while your feature branch changes it to “Submit Order.” When you merge, Git sees two conflicting intentions for line 42 and throws a conflict.

The key insight: Git conflicts are about intent, not just text. You need to understand what both sides were trying to accomplish.


Step 1: Identify Which Files Have Conflicts

Before you can fix anything, you need to know what’s broken. After a failed merge, run:

git status

You’ll see output like this:

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:   src/components/Header.jsx
        both modified:   src/utils/helpers.ts
        deleted by them: src/legacy/old-api.js

The Unmerged paths section lists every file with a conflict. Pay attention to the status labels:

Status Meaning
both modified Both branches changed the same lines
deleted by them Your branch has the file; the other branch deleted it
deleted by us Your branch deleted the file; the other branch modified it
both added Both branches created the same file independently

To get a quick count of how many files have conflicts:

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

Step 2: Understand Conflict Markers in Your Files

When you open a conflicted file, you’ll see conflict markers that Git inserts directly into the code. Here’s what they look like:

function getUserProfile(id) {
<<<<<<< HEAD
  const user = await fetchUser(id);
  return { ...user, status: 'active' };
=======
  const user = await fetchUserById(id);
  return { ...user, lastSeen: new Date() };
>>>>>>> feature-branch
}

Let’s break down the markers:

  • <<<<<<< HEAD — The start of the change that exists in your current branch (where you’re merging into)
  • ======= — The separator between the two versions
  • >>>>>>> feature-branch — The start of the change from the branch you’re merging in

Everything between <<<<<<< HEAD and ======= is your version. Everything between ======= and >>>>>>> is the incoming version.

Pro Tip: Use git log --merge

To see the commit history of just the conflicted files during a merge:

git log --merge --oneline

This shows you which commits on both branches touched the conflicting files, which helps you understand the intent behind each change.


Step 3: Resolve the Conflict (The Core Process)

Resolving a Simple Line-Level Conflict

Let’s work through the example above. The HEAD version uses fetchUser(id) and adds a status field. The feature-branch version uses fetchUserById(id) and adds a lastSeen field.

Your resolution might combine both changes:

function getUserProfile(id) {
  const user = await fetchUserById(id);
  return { ...user, status: 'active', lastSeen: new Date() };
}

The resolution process:

  1. Remove the conflict markers (<<<<<<<, =======, >>>>>>>)
  2. Decide what the final code should be — you can keep one version, combine both, or write something entirely new
  3. Ensure the result is valid code — check for syntax errors, missing braces, etc.

Once you’ve resolved a file, stage it:

git add src/components/Header.jsx

Choosing One Side Entirely

If you know you want to keep one side and discard the other, you don’t need to manually edit the file. Use these shortcuts:

# Keep your version (HEAD), discard incoming changes
git checkout --ours src/utils/config.ts

# Keep the incoming version, discard your changes
git checkout --theirs src/utils/config.ts

Then stage the file:

git add src/utils/config.ts

Note: --ours and --theirs can be confusing during a rebase (they’re swapped). During a rebase, --ours is actually the branch you’re rebasing onto, and --theirs is your current branch. Keep this in mind.


Step 4: Complete the Merge

Once all conflicts are resolved and staged, verify everything looks clean:

git status

You should see:

On branch main
All conflicts fixed but you are still merging.
  (use "git commit" to conclude merge)

Now, complete the merge:

git commit -m "Merge feature-branch: resolve conflicts in Header.jsx and helpers.ts"

If you’re using Git 2.14+ (and you should be in 2026), you can enable auto-squash and auto-commit with a config:

git config --global merge.conflictstyle zdiff3

This gives you three-way merge markers that also show the original (base) version:

<<<<<<< HEAD
  const user = await fetchUser(id);
||||||| merged common ancestor
  const user = await getUser(id);
=======
  const user = await fetchUserById(id);
>>>>>>> feature-branch

The ||||||| section shows what the code looked like before either branch changed it. This context makes it much easier to understand what each side was modifying. I highly recommend enabling this globally.


Step 5: Abort the Merge When Things Go Wrong

Sometimes a merge is too tangled to fix, or you realize you merged the wrong branch. No problem — back out cleanly:

git merge --abort

This resets your working directory to the state before the merge started. All conflict markers disappear, and staged changes from the merge are undone.

If you’ve already committed the merge (but haven’t pushed yet) and realize something is wrong:

git reset --hard HEAD~1

This removes the merge commit entirely. Be careful — any uncommitted changes will be lost.


Advanced Scenario: Resolving Conflicts During a Rebase

Rebasing is where conflicts get tricky because they can happen multiple times — once for each commit being replayed.

git rebase main

If a conflict occurs:

CONFLICT (content): Merge conflict in src/api/endpoints.ts

Resolve the conflict in the file, then:

git add src/api/endpoints.ts
git rebase --continue

Git will move to the next commit in the rebase, which might trigger another conflict. Repeat the process.

To skip a commit entirely during a rebase:

git rebase --skip

To abort the rebase and return to your original branch:

git rebase --abort

Using rerere to Remember Resolutions

Git has a feature called rerere (reuse recorded resolution) that remembers how you resolved a conflict and automatically applies the same fix if you encounter the same conflict again:

git config --global rerere.enabled true

This is incredibly useful when rebasing a long-lived feature branch multiple times.


Advanced Scenario: Binary File Conflicts

Git can’t show conflict markers in binary files (images, compiled assets, PDFs). Instead, you’ll see:

CONFLICT (content): Merge conflict in public/logo.png

When you open the file, Git won’t show inline conflicts. You must choose a version:

# Keep your version of the binary file
git checkout --ours public/logo.png

# Keep the incoming version
git checkout --theirs public/logo.png

Then stage it:

git add public/logo.png

For large binary files, consider using Git LFS (Large File Storage) to avoid these conflicts entirely:

git lfs install
git lfs track "*.psd"
git lfs track "*.mp4"

Advanced Scenario: Handling “Deleted by Them/Us” Conflicts

This is one of the trickiest scenarios. Git reports:

deleted by them:   src/legacy/auth.js

This means your branch still has the file, but the other branch deleted it. You have two choices:

Option 1: Accept the deletion (the file is no longer needed)

git rm src/legacy/auth.js

Option 2: Keep the file (you still need it)

git add src/legacy/auth.js

If the incoming branch modified the file before deleting it and you want to see what those modifications were:

git log --oneline MERGE_HEAD -- src/legacy/auth.js

Using Visual Merge Tools

Manual conflict resolution in a text editor works fine for simple conflicts, but for complex merges, a visual diff tool can save significant time and mental energy.

VS Code (Built-in)

VS Code has an excellent built-in merge conflict resolver. When you open a conflicted file, it displays clickable buttons above each conflict:

  • Accept Current Change
  • Accept Incoming Change
  • Accept Both Changes
  • Compare Changes

This is the fastest way to resolve simple conflicts in 2026.

Configuring a Custom Merge Tool

Set up popular merge tools like Meld, Beyond Compare, or KDiff3:

# Configure VS Code as your default merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'

# Configure Beyond Compare
git config --global merge.tool bc
git config --global mergetool.bc.cmd '"bcomp" "$LOCAL" "$REMOTE" "$BASE" "$MERGED"'

# Launch the tool during a conflict
git mergetool

After using a merge tool, Git automatically stages the file. If your tool creates backup files (.orig), clean them up:

git config --global mergetool.keepBackup false

Prevention Tips: How to Minimize Merge Conflicts

1. Keep Branches Short-Lived

The longer a branch lives, the more it diverges from main, and the more likely conflicts become. Aim to merge branches within 2-3 days. For larger features, break them into smaller PRs.

2. Pull Frequently from the Base Branch

Keep your feature branch up to date:

# While on your feature branch
git fetch origin
git rebase origin/main

Doing this daily catches conflicts early, when they’re small and easy to fix.

3. Communicate with Your Team

If two people are working on the same file, coordinate. A quick message in your team chat can prevent both of you from rewriting the same function simultaneously.

4. Structure Code to Reduce Conflicts

  • Break large files into smaller, focused modules
  • Avoid trailing commas in the last array/object element (rebase conflicts love these)
  • Use tools like Prettier to enforce consistent formatting (so formatting changes don’t trigger conflicts)
  • Put each import on its own line rather than grouping imports

5. Use Feature Flags Instead of Long Branches

Instead of building a feature on a long-lived branch, merge incomplete features behind a feature flag:

if (featureFlags.newCheckoutEnabled) {
  return <NewCheckout />;
}
return <OldCheckout />;

This lets you merge to main frequently without worrying about conflicting with other work.

6. Leverage .gitattributes for Line Ending and Whitespace Issues

# Normalize line endings
* text=auto eol=lf

# Or for specific file types
*.js text eol=lf
*.cs text eol=crlf

This prevents phantom conflicts caused by Windows vs. Unix line endings.


Automation: Scripts to Speed Up Conflict Resolution

Script to Open All Conflicted Files at Once

Save this as git-open-conflicts:

#!/bin/bash
# Opens all conflicted files in VS Code
CONFLICTED_FILES=$(git diff --name-only --diff-filter=U | tr '\n' ' ')
if [ -z "$CONFLICTED_FILES" ]; then
  echo "No conflicts found."
else
  code $CONFLICTED_FILES
fi

Make it executable and add it to your PATH:

chmod +x git-open-conflicts
sudo mv git-open-conflicts /usr/local/bin/

Now run git-open-conflicts whenever you have conflicts to resolve.

Script to Count Remaining Conflicts

Save this as git-conflict-count:

#!/bin/bash
# Counts the number of conflict markers remaining in all conflicted files
TOTAL=0
for file in $(git diff --name-only --diff-filter=U); do
  COUNT=$(grep -c "^<<<<<<<" "$file" 2>/dev/null || echo 0)
  if [ "$COUNT" -gt 0 ]; then
    echo "$file: $COUNT conflicts"
  fi
  TOTAL=$((TOTAL + COUNT))
done
echo "Total remaining conflicts: $TOTAL"

Troubleshooting Common Errors During Conflict Resolution

Error: “fatal: cannot do a partial commit during a merge”

This happens when you try to commit specific files during a merge without resolving all conflicts:

git commit -m "fix" src/components/Header.jsx
# fatal: cannot do a partial commit during a merge.

Fix: You must stage all resolved files and commit the merge as a whole:

git add .
git commit -m "Merge feature-branch with conflict resolutions"

Error: “error: commit is not possible because you have unmerged files”

You tried to commit, but some files still have conflict markers:

git commit -m "done"
# error: commit is not possible because you have unmerged files
# hint: Fix them up in the work tree, and then use 'git add <file>'

Fix: Check which files still need attention:

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

Open each file, remove all conflict markers, then git add and commit.

Error: Stuck in a Rebase Loop

If you keep getting conflicts during a rebase and can’t get out:

git rebase --abort

This takes you back to your pre-rebase state. Consider a regular merge instead, or squash your commits before rebasing to reduce the number of replayed commits.


Key Takeaways

  • Merge conflicts are normal — they’re Git’s way of protecting your code from silent data loss. Don’t panic when you see them.
  • Always start with git status to see which files have conflicts and what type of conflicts they are.
  • Read the conflict markers carefully — understanding both sides’ intent is more important than choosing one mechanically.
  • Enable zdiff3 conflict style globally for three-way merge context that makes resolution faster and more accurate.
  • Use git merge --abort to bail out of a messy merge and start over with a clear head.
  • Prevention beats cure — short-lived branches, frequent rebase/pull cycles, and good code structure dramatically reduce conflicts.
  • Leverage rerere if you frequently rebase the same branch — Git will auto-apply your previous conflict resolutions.

FAQ

What happens if I accidentally commit a file with conflict markers still in it?

Git allows you to commit files with unresolved conflict markers (it’s just text). If you catch this before pushing, amend the commit:

# Remove the markers, save the file
git add <fixed-file>
git commit --amend --no-edit

If you’ve already pushed, create a follow-up commit that removes the

Leave a Reply

Your email address will not be published. Required fields are marked *