Git Merge Conflicts: The Complete Guide to Resolving Them in 2026
Merge conflicts are the most common source of frustration for Git users, yet they follow a simple, predictable pattern. Every conflict is Git telling you: "Two people changed the same thing and I do not know which version to keep." Once you understand how conflicts arise, how to read the conflict markers, and the handful of commands that resolve them, they stop being scary and become routine.
This guide covers every type of merge conflict you will encounter: during merges, rebases, cherry-picks, and stash pops. You will learn how to use merge tools, how git rerere can automate repeated resolutions, how to handle conflicts in binary and auto-generated files, and how to structure your team workflow to minimize conflicts in the first place.
Table of Contents
- What Causes Merge Conflicts?
- Understanding Conflict Markers
- Step-by-Step Conflict Resolution
- Resolving Conflicts During Merge
- Resolving Conflicts During Rebase
- Resolving Conflicts During Cherry-Pick
- Using Merge Tools
- The git mergetool Command
- Aborting a Merge
- Preventing Merge Conflicts
- Advanced: git rerere
- Conflicts in Special File Types
- Team Workflows to Minimize Conflicts
- Common Scenarios and Solutions
- FAQ
What Causes Merge Conflicts?
Git can automatically merge most changes. Conflicts only occur when Git cannot determine the correct result on its own. There are three situations that trigger a conflict:
1. Same Line, Different Changes
Two branches modify the same line in the same file. This is the most common type of conflict.
# On main branch, line 15 of config.js:
const MAX_RETRIES = 3;
# On feature branch, line 15 of config.js:
const MAX_RETRIES = 5;
# Git cannot decide: should MAX_RETRIES be 3 or 5?
# Result: CONFLICT
2. Edit vs Delete
One branch modifies a file while another branch deletes it entirely:
# On main: deleted utils/legacy-parser.js
# On feature: modified utils/legacy-parser.js (added new function)
$ git merge feature
CONFLICT (modify/delete): utils/legacy-parser.js deleted in HEAD
and modified in feature. Version feature of utils/legacy-parser.js
left in tree.
3. Competing Additions
Two branches add different content at the same location, or both create a file with the same name but different contents:
# Both branches added a new file: src/helpers/format.js
# but with completely different implementations
$ git merge feature
CONFLICT (add/add): Merge conflict in src/helpers/format.js
What Does NOT Cause Conflicts
Git is smart about merging. These situations resolve automatically:
- Different files — Two branches edit completely different files
- Different parts of the same file — One branch edits line 10, another edits line 200
- One branch adds lines, the other does not touch them — Non-overlapping additions merge cleanly
- Identical changes — Both branches make the exact same edit to the same line
Understanding Conflict Markers
When Git encounters a conflict, it writes special markers into the file to show both versions. Learning to read these markers is essential.
The Three-Part Marker
<<<<<<< HEAD
const apiUrl = "https://api.example.com/v2";
const timeout = 5000;
=======
const apiUrl = "https://api.example.com/v3";
const timeout = 10000;
>>>>>>> feature/upgrade-api
Here is what each section means:
<<<<<<< HEAD— Start of your current branch's version (the branch you are merging INTO)- Content above
=======— Your current branch's code (HEAD, also called "ours") =======— Divider between the two versions- Content below
=======— The incoming branch's code (also called "theirs") >>>>>>> feature/upgrade-api— End marker showing the incoming branch name
The diff3 Format (Recommended)
The default two-way marker does not show what the code looked like before either branch changed it. The diff3 style adds a third section showing the common ancestor:
# Enable diff3 conflict style (highly recommended)
git config --global merge.conflictStyle diff3
Now conflicts look like this:
<<<<<<< HEAD
const apiUrl = "https://api.example.com/v2";
const timeout = 5000;
||||||| parent of abc1234
const apiUrl = "https://api.example.com/v1";
const timeout = 3000;
=======
const apiUrl = "https://api.example.com/v3";
const timeout = 10000;
>>>>>>> feature/upgrade-api
The ||||||| section shows the original code both branches started from. With this context, you can see that HEAD bumped the API to v2 and timeout to 5000, while the feature branch bumped to v3 and 10000. The resolution becomes clear: take v3 and decide on the timeout.
Multiple Conflicts in One File
A single file can have multiple conflict regions. Each one is independent and marked with its own set of markers:
import { useState } from 'react';
<<<<<<< HEAD
import { validateEmail } from './validators';
=======
import { validateEmail, validatePhone } from './validators';
>>>>>>> feature/phone-auth
// ... many lines of non-conflicting code ...
<<<<<<< HEAD
function handleSubmit(email) {
=======
function handleSubmit(email, phone) {
>>>>>>> feature/phone-auth
You must resolve every conflict region in the file before staging it.
Step-by-Step Conflict Resolution
Here is the exact process to follow every time you hit a conflict, regardless of whether it happened during a merge, rebase, or cherry-pick.
Step 1: Identify conflicted files
$ git status
On branch main
You have unmerged paths.
(fix conflicts and run "git commit")
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/config.js
both modified: src/api/client.js
deleted by us: src/legacy/parser.js
Step 2: Open each conflicted file and resolve
For each file, find the conflict markers, decide what the final code should be, and remove all markers. Your resolution can be any of these:
- Keep ours — Delete their changes and the markers, keep only HEAD's version
- Keep theirs — Delete our changes and the markers, keep only the incoming version
- Combine both — Merge the relevant parts from each version into new code
- Write something new — Sometimes neither version is correct on its own
# Before resolution (conflict markers present):
<<<<<<< HEAD
const MAX_CONNECTIONS = 10;
=======
const MAX_CONNECTIONS = 25;
>>>>>>> feature/scaling
# After resolution (markers removed, final code):
const MAX_CONNECTIONS = 25;
Step 3: Stage the resolved files
# Stage each file after resolving its conflicts
git add src/config.js
git add src/api/client.js
# For delete conflicts, choose to keep or remove
git add src/legacy/parser.js # keep the file
git rm src/legacy/parser.js # accept the deletion
Step 4: Complete the operation
# For merges: commit
git commit
# Git auto-generates a merge commit message
# For rebases: continue
git rebase --continue
# For cherry-picks: continue
git cherry-pick --continue
Step 5: Verify the result
# Check that no conflict markers remain
git diff --check
# Review what the merge produced
git log --oneline -5
git diff HEAD~1
Resolving Conflicts During Merge
A merge conflict is the simplest case. You are combining two branches and Git stops at the conflicting files.
# Start the merge
$ git merge feature/user-dashboard
Auto-merging src/components/Header.jsx
CONFLICT (content): Merge conflict in src/components/Header.jsx
Auto-merging src/styles/main.css
CONFLICT (content): Merge conflict in src/styles/main.css
Automatic merge failed; fix conflicts and then commit the result.
A realistic conflict in a React component:
# src/components/Header.jsx
import React from 'react';
<<<<<<< HEAD
import { Logo, NavMenu, SearchBar } from './ui';
export function Header({ user }) {
return (
<header className="header-main">
<Logo />
<NavMenu />
<SearchBar />
<span>{user.name}</span>
=======
import { Logo, NavMenu, UserAvatar } from './ui';
export function Header({ user, notifications }) {
return (
<header className="header-redesign">
<Logo size="large" />
<NavMenu />
<UserAvatar user={user} />
<span className="badge">{notifications.length}</span>
>>>>>>> feature/user-dashboard
To resolve this, you combine both changes: keep the SearchBar from HEAD, the UserAvatar and notifications from the feature branch, and use the redesigned class name:
# Resolved version:
import React from 'react';
import { Logo, NavMenu, SearchBar, UserAvatar } from './ui';
export function Header({ user, notifications }) {
return (
<header className="header-redesign">
<Logo size="large" />
<NavMenu />
<SearchBar />
<UserAvatar user={user} />
<span className="badge">{notifications.length}</span>
Then stage and commit:
git add src/components/Header.jsx src/styles/main.css
git commit
# Editor opens with: "Merge branch 'feature/user-dashboard'"
# Save and close to complete the merge
Shortcut: Accept One Side Entirely
When you know you want to accept one side for an entire file, use checkout:
# Keep our version (discard their changes)
git checkout --ours src/styles/main.css
git add src/styles/main.css
# Keep their version (discard our changes)
git checkout --theirs src/config/database.yml
git add src/config/database.yml
For an entire merge where you want one side to win on all conflicts:
# Accept all of their changes on conflicts
git merge feature/branch -X theirs
# Accept all of our changes on conflicts
git merge feature/branch -X ours
Note: -X theirs still auto-merges non-conflicting changes from both sides. It only uses "theirs" as a tiebreaker for actual conflicts.
Resolving Conflicts During Rebase
Rebase conflicts work differently because rebase replays commits one at a time. You might hit a conflict at commit 3 of 8, resolve it, continue, and hit another at commit 5. This means the --ours and --theirs labels are swapped compared to merge.
During rebase, "ours" is the branch you are rebasing onto (main), and "theirs" is your feature branch commit being replayed. This is the opposite of merge and catches many developers off guard.
# Rebase your feature branch onto main
$ git rebase main
Applying: Add payment processing
Applying: Add order validation
CONFLICT (content): Merge conflict in src/checkout.js
error: could not apply c4d5e6f... Add order validation
hint: Resolve all conflicts manually, mark them as resolved with
hint: "git add/rm <conflicted_files>", then run
hint: "git rebase --continue".
Resolve the conflict just like a merge, but finish with --continue instead of commit:
# 1. Fix the conflict in src/checkout.js
# 2. Stage it
git add src/checkout.js
# 3. Continue the rebase (applies remaining commits)
git rebase --continue
# Git may stop again at the next conflict. Repeat until done.
Rebase --ours vs --theirs (Swapped!)
# During rebase: accept the main branch version (drop your change)
git checkout --ours src/checkout.js
# During rebase: accept your feature branch version
git checkout --theirs src/checkout.js
For a deep dive into rebase workflows, see our Git Rebase Complete Guide.
Resolving Conflicts During Cherry-Pick
Cherry-pick applies a single commit from another branch. Conflicts arise when that commit touches code that has diverged on your current branch.
# Cherry-pick a specific bugfix commit
$ git cherry-pick a1b2c3d
error: could not apply a1b2c3d... Fix null pointer in user lookup
hint: After resolving the conflicts, mark them with "git add"
hint: and run "git cherry-pick --continue"
The resolution process is identical to merge:
# 1. Resolve conflicts in the affected files
# 2. Stage resolved files
git add src/services/user-service.js
# 3. Continue the cherry-pick
git cherry-pick --continue
# Or abort if the cherry-pick is not worth the trouble
git cherry-pick --abort
Cherry-Picking Multiple Commits
# Cherry-pick a range (may conflict at each commit)
git cherry-pick a1b2c3d..f6g7h8i
# Cherry-pick specific commits
git cherry-pick a1b2c3d e4f5g6h
# If a conflict occurs, resolve it and continue
git cherry-pick --continue
Using Merge Tools
While you can resolve conflicts in any text editor, dedicated merge tools show both versions side by side (or in a three-way view) and let you click to accept changes. This is faster and less error-prone for complex conflicts.
VS Code
VS Code detects conflict markers automatically and adds inline buttons:
- Accept Current Change — Keep HEAD's version
- Accept Incoming Change — Keep the other branch's version
- Accept Both Changes — Keep both, one after the other
- Compare Changes — Open a diff view
# Set VS Code as your merge tool
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
IntelliJ / WebStorm / JetBrains IDEs
JetBrains IDEs have a built-in three-way merge tool that shows the base version, your version, and their version in three panes with a result pane at the bottom:
# Set IntelliJ as your merge tool
git config --global merge.tool intellij
git config --global mergetool.intellij.cmd 'idea merge $LOCAL $REMOTE $BASE $MERGED'
git config --global mergetool.intellij.trustExitCode true
vimdiff
For terminal users, vimdiff shows conflicts in split panes:
# Set vimdiff as your merge tool
git config --global merge.tool vimdiff
git config --global mergetool.vimdiff.cmd 'vim -d $LOCAL $REMOTE $MERGED'
# vimdiff keyboard shortcuts:
# ]c - jump to next conflict
# [c - jump to previous conflict
# :diffget LOCAL - accept left (local) version
# :diffget REMOTE - accept right (remote) version
# :wqa - save all and quit
Meld
Meld is a popular graphical diff and merge tool for Linux:
# Install meld (Debian/Ubuntu)
sudo apt install meld
# Set as merge tool
git config --global merge.tool meld
git config --global mergetool.meld.cmd 'meld $LOCAL $BASE $REMOTE --output $MERGED'
The git mergetool Command
Once you have configured a merge tool, git mergetool walks you through each conflicted file one at a time, launching your tool for each one:
$ git mergetool
Merging:
src/config.js
src/api/client.js
Normal merge conflict for 'src/config.js':
{local}: modified file
{remote}: modified file
Hit return to start merge resolution tool (vscode):
After you resolve and save in the tool, Git asks if the merge was successful, then moves to the next file. When all files are done:
# mergetool creates .orig backup files. Clean them up:
git clean -f *.orig
# Or prevent .orig files from being created:
git config --global mergetool.keepBackup false
# Stage and commit
git add .
git commit
Useful mergetool Options
# Only resolve specific files
git mergetool src/config.js
# Use a different tool for one invocation
git mergetool --tool=meld
# List available tools
git mergetool --tool-help
Aborting a Merge
Sometimes the best move is to stop, think, and try again later. Every Git operation with conflicts has a --abort flag that cleanly restores the pre-conflict state:
# Abort a merge
git merge --abort
# Abort a rebase
git rebase --abort
# Abort a cherry-pick
git cherry-pick --abort
# Abort a stash pop (revert to pre-stash state)
git checkout -- .
git clean -fd
Aborting is always safe. It does not lose any previous commits or changes. It simply restores your working directory and index to the state before the operation started.
When to Abort
- You realize you are merging the wrong branch
- The conflicts are too complex and you need to discuss with a teammate first
- You want to try a different merge strategy (like
-X theirs) - You want to do prep work (refactoring, splitting files) before attempting the merge again
Reset After a Completed Merge
If you already committed the merge but realize it was wrong:
# Undo the last commit (the merge commit), keep changes staged
git reset --soft HEAD~1
# Or undo completely (discard merge changes)
git reset --hard HEAD~1
# Or revert the merge (creates a new commit that undoes it)
git revert -m 1 HEAD
Preventing Merge Conflicts
The best conflict is one that never happens. These strategies drastically reduce conflict frequency:
1. Keep Branches Short-Lived
The longer a branch lives, the more it diverges from main, and the more likely conflicts become. Aim for branches that last 1-3 days, not weeks.
2. Pull and Rebase Frequently
# Do this daily (or more often) on your feature branch
git fetch origin
git rebase origin/main
# Or set pull to rebase by default
git config --global pull.rebase true
3. Use Consistent Code Formatting
Many conflicts come from formatting differences: tabs vs spaces, trailing whitespace, line endings. Use automated formatters:
# JavaScript/TypeScript: Prettier
npx prettier --write .
# Python: Black
black .
# Go: gofmt (built in)
gofmt -w .
# Set up pre-commit hooks to format automatically
# See our Git Hooks Complete Guide
4. Break Up Large Files
A 2000-line file is a conflict magnet. If multiple developers need to touch it regularly, refactor it into smaller, focused modules.
5. Communicate About Shared Code
If you know a teammate is working on the same file, coordinate. A quick message saying "I am refactoring the auth module today" prevents duplicate work and conflicts.
6. Use .gitattributes for Merge Strategies
# .gitattributes
# Always accept ours for lock files
package-lock.json merge=ours
yarn.lock merge=ours
# Use union merge for changelog (append both changes)
CHANGELOG.md merge=union
Advanced: git rerere (Reuse Recorded Resolution)
rerere stands for "reuse recorded resolution." When enabled, Git records how you resolve each conflict. If the same conflict appears again (common during repeated rebases), Git applies your previous resolution automatically.
# Enable rerere globally
git config --global rerere.enabled true
How It Works
# First time: you resolve a conflict manually
$ git merge feature/api-v2
CONFLICT: src/api/client.js
# You resolve it and commit
# Git records the resolution in .git/rr-cache/
# Later: you abort the merge, update main, and merge again
$ git merge feature/api-v2
Resolved 'src/api/client.js' using previous resolution.
# The same conflict is auto-resolved!
rerere Commands
# See what rerere has recorded
git rerere status
# Show the diff rerere would apply
git rerere diff
# Forget a specific resolution (if it was wrong)
git rerere forget src/api/client.js
# Clear all recorded resolutions
git rerere gc
When rerere Saves You
- Repeated rebases — You rebase onto main, hit conflicts, resolve them. Tomorrow you rebase again and hit the same conflicts. With rerere, they are auto-resolved.
- Testing a merge before committing — Merge, test, abort, then merge again for real. The resolutions carry over.
- Long-running feature branches — You periodically merge main into your feature branch. The same conflicts recur each time.
Conflicts in Special File Types
Binary Files
Git cannot merge binary files (images, compiled assets, PDFs). It simply reports the conflict and you must choose one version:
$ git merge feature/new-logo
CONFLICT (content): Merge conflict in assets/logo.png
# You must choose one version:
git checkout --ours assets/logo.png # keep current
git checkout --theirs assets/logo.png # take incoming
git add assets/logo.png
For binary assets, use Git LFS (Large File Storage) and establish a process where one person owns each asset at a time.
Lock Files (package-lock.json, yarn.lock)
Lock files are auto-generated and should not be hand-merged. Instead, accept one version and regenerate:
# For npm (package-lock.json)
git checkout --theirs package-lock.json
npm install
git add package-lock.json
# For yarn (yarn.lock)
git checkout --theirs yarn.lock
yarn install
git add yarn.lock
# For pip (requirements.txt generated by pip-compile)
git checkout --theirs requirements.txt
pip-compile requirements.in
git add requirements.txt
Auto-Generated Files
Files generated by build tools, schema generators, or ORMs should be regenerated rather than hand-merged:
# Accept either version, then regenerate
git checkout --theirs prisma/schema.prisma
npx prisma generate
git add prisma/
Preventing Lock File Conflicts with .gitattributes
# .gitattributes
# Mark lock files as binary so Git does not attempt line-by-line merge
package-lock.json binary
yarn.lock binary
# Or use a custom merge driver that always regenerates
package-lock.json merge=npm-lock
Team Workflows to Minimize Conflicts
CODEOWNERS for Coordination
GitHub and GitLab support CODEOWNERS files that assign reviewers based on file paths. This naturally reduces conflicts by making ownership explicit:
# .github/CODEOWNERS
/src/auth/ @auth-team
/src/payments/ @payments-team
/src/components/ui/ @design-system-team
*.sql @dba-team
Feature Flags Over Long Branches
Instead of keeping a large feature on a branch for weeks, merge small incremental changes behind a feature flag:
# Merge to main daily, hidden behind a flag
if (featureFlags.newCheckout) {
renderNewCheckout();
} else {
renderLegacyCheckout();
}
// Small, frequent merges = fewer conflicts
Trunk-Based Development
The ultimate conflict-prevention strategy: everyone commits to main (or very short-lived branches). Conflicts are rare because branches diverge minimally. See our Git Branching Strategies Guide for implementation details.
Recommended Team Git Configuration
# Every team member should run:
git config --global merge.conflictStyle diff3
git config --global rerere.enabled true
git config --global pull.rebase true
git config --global mergetool.keepBackup false
Common Scenarios and Solutions
Scenario 1: Conflicting Package Versions
# package.json conflict:
<<<<<<< HEAD
"lodash": "^4.17.21",
"axios": "^1.6.0",
=======
"lodash": "^4.17.21",
"axios": "^1.7.2",
>>>>>>> feature/upgrade-deps
# Resolution: take the higher version
"lodash": "^4.17.21",
"axios": "^1.7.2",
# Then regenerate the lock file:
npm install
Scenario 2: Both Branches Add to the Same List
# routes.js conflict:
<<<<<<< HEAD
app.get('/api/users', usersController.list);
app.get('/api/users/:id', usersController.get);
app.post('/api/users', usersController.create);
=======
app.get('/api/users', usersController.list);
app.get('/api/users/:id', usersController.get);
app.get('/api/products', productsController.list);
>>>>>>> feature/products
# Resolution: keep all routes
app.get('/api/users', usersController.list);
app.get('/api/users/:id', usersController.get);
app.post('/api/users', usersController.create);
app.get('/api/products', productsController.list);
Scenario 3: Refactored File vs Modified File
# You refactored utils.js into utils/string.js and utils/number.js
# A teammate added a new function to the old utils.js
$ git merge feature/new-helpers
CONFLICT (modify/delete): utils.js deleted in HEAD and modified
in feature/new-helpers.
# Solution: take their new function and put it in the right module
git show feature/new-helpers:utils.js # view their version
# Copy the new function into utils/string.js or utils/number.js
git rm utils.js
git add utils/
git commit
Scenario 4: Conflicting Database Migrations
# Two branches created migration files with the same sequence number
# migrations/0042_add_email_field.sql (yours)
# migrations/0042_add_phone_field.sql (theirs)
# Solution: renumber one migration
mv migrations/0042_add_phone_field.sql migrations/0043_add_phone_field.sql
# Update any migration dependency references
git add migrations/
Scenario 5: Stash Pop Conflict
$ git stash pop
CONFLICT (content): Merge conflict in src/app.js
# The stash is NOT dropped when there is a conflict
# Resolve the conflict, then manually drop the stash
git add src/app.js
git stash drop
Frequently Asked Questions
What causes a git merge conflict?
A git merge conflict occurs when two branches modify the same line in the same file, or when one branch deletes a file that another branch has modified. Git cannot automatically determine which change to keep, so it pauses the merge and asks you to resolve the conflict manually by editing the file and choosing the correct content.
How do I resolve a merge conflict in Git?
Open the conflicted file and look for conflict markers (<<<<<<< HEAD, =======, >>>>>>>). The content between <<<<<<< HEAD and ======= is your current branch's version. The content between ======= and >>>>>>> is the incoming branch's version. Edit the file to keep the code you want, remove all conflict markers, then run git add on the file and git commit to complete the merge.
How do I abort a merge conflict in Git?
Run git merge --abort to cancel the merge and return your working directory to the state before the merge started. For rebase conflicts, use git rebase --abort. For cherry-pick conflicts, use git cherry-pick --abort. These commands are safe and will not lose any of your previous work.
What is git rerere and how does it help with merge conflicts?
git rerere (reuse recorded resolution) is a feature that records how you resolve a conflict and automatically applies the same resolution if the same conflict occurs again. Enable it with git config --global rerere.enabled true. It is especially useful when rebasing frequently, as you may encounter the same conflict multiple times across different commits.
How do I prevent merge conflicts in a team?
Keep feature branches short-lived (1-3 days). Pull and rebase from main frequently. Break large files into smaller modules so team members edit different files. Use code ownership and CODEOWNERS files. Coordinate with teammates when working on the same area. Use consistent code formatting with tools like Prettier or Black to avoid whitespace-only conflicts.
Continue Learning
This guide is part of our Git deep-dive series. Explore the related guides to master every aspect of Git:
- The Complete Guide to Git — Fundamentals through advanced workflows
- Git Rebase: The Complete Guide — Interactive rebase, squash, and conflict resolution
- Git Branching Strategies Guide — Git Flow, GitHub Flow, and Trunk-Based Development
- Git Commands Every Developer Should Know — Essential command reference
- Git Hooks: The Complete Guide — Automate your workflow with hooks