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:
- Same-line edits: Two branches change the exact same line(s) in a file differently. This is the most common type.
- 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.
- 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:
- Remove the conflict markers (
<<<<<<<,=======,>>>>>>>) - Decide what the final code should be — you can keep one version, combine both, or write something entirely new
- 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 statusto 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
zdiff3conflict style globally for three-way merge context that makes resolution faster and more accurate. - Use
git merge --abortto 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