How to Fix Git Merge Conflicts: Step-by-Step Guide With Examples
On this page
How to Fix Git Merge Conflicts: Step-by-Step Guide With Examples
The first time I hit a merge conflict, I panicked. Those <<<<<<< markers scattered through my code looked like something had gone terribly wrong. I vaguely remember closing the terminal and re-cloning the repo because I didn't know what else to do. Don't be like me.
Merge conflicts are one of the most common things you'll deal with in Git, and once you understand what's happening, they're completely routine. Here's everything I wish someone had explained to me early on.
What Is a Merge Conflict, Actually?
A merge conflict happens when Git tries to combine changes from two branches and both have modified the same lines in the same file. Git can automatically merge changes that don't overlap — if you edited line 10 and your colleague edited line 50, Git handles that silently. But when you both changed line 10 differently, Git has no way to decide which version is right. It stops and asks you.
Conflicts also happen when one branch deletes a file another branch modified, or both branches rename the same file differently.
Prevention Is Cheaper Than Resolution
A few habits that prevent most painful conflicts:
Keep branches short-lived. The longer a feature branch lives without merging, the more it diverges. Conflicts compound over time. Merge or rebase frequently.
Pull before you push:
git fetch origin
git merge origin/main
Talk to your team. If you know someone is working in the same files, coordinate. Simple communication prevents a lot of conflicts before they start.
Step 1: Start the Merge
git checkout main
git merge feature/user-authentication
If there are no conflicts, Git completes it automatically. If there are, you'll see:
Auto-merging src/auth/login.js
CONFLICT (content): Merge conflict in src/auth/login.js
Auto-merging src/utils/helpers.js
Automatic merge failed; fix conflicts and then commit the result.
The merge is paused. Nothing's been committed. Don't panic.
Step 2: See What Needs Fixing
git status
Unmerged paths:
(use "git add <file>..." to mark resolution)
both modified: src/auth/login.js
both modified: src/utils/helpers.js
Work through each file.
Step 3: Read the Conflict Markers
Open a conflicted file and you'll see markers:
function loginUser(username, password) {
<<<<<<< HEAD
const hashedPassword = bcrypt.hashSync(password, 10);
return db.users.findOne({ username, password: hashedPassword });
=======
const hashedPassword = bcrypt.hash(password, 10);
return db.users.findOne({ username }).then(user => {
return bcrypt.compare(password, user.password);
});
>>>>>>> feature/user-authentication
}
<<<<<<< HEAD— your current branch's version=======— the divider>>>>>>> feature/...— the incoming branch's version
Everything above the divider is yours. Everything below is theirs. Your job: edit this into the correct final version, removing all three markers.
Step 4: Resolve It
Three options:
Keep yours: Delete from ======= through >>>>>>> and remove the <<<<<<< HEAD marker.
Keep theirs: Delete from <<<<<<< HEAD through ======= and remove the >>>>>>> marker.
Combine both (most common): Look at both versions, understand the intent, and write correct code that incorporates both. In our example, the incoming branch switched to async bcrypt with proper comparison — that's the better approach:
function loginUser(username, password) {
return db.users.findOne({ username }).then(user => {
if (!user) return null;
return bcrypt.compare(password, user.password).then(match => {
return match ? user : null;
});
});
}
Markers gone, function clean, conflict resolved.
Step 5: Stage the Resolution
git add src/auth/login.js
Repeat for every conflicted file. Verify with:
git diff --check
If this returns nothing, you're clean.
Step 6: Complete the Merge
git commit
Git pre-fills a merge commit message. Accept or customize. Done.
Visual Merge Tools
For complex conflicts, a visual tool makes life easier:
git mergetool
Popular options:
- VS Code — excellent built-in merge editor with "Accept Current/Incoming/Both" buttons
- IntelliJ/WebStorm — three-panel diff with clear controls
- vimdiff — powerful for terminal-only environments
- Meld — standalone GUI, works well on Linux/macOS
To set VS Code as your merge tool:
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait $MERGED'
VS Code's merge editor shows your version, incoming version, and a live result preview in three panes. It's genuinely excellent.
Aborting When Things Go Sideways
If you're in over your head and want to start over:
git merge --abort
This returns your branch to exactly where it was before the merge. Nothing lost. Regroup and try again.
Rebase as an Alternative
Some teams prefer git rebase over git merge. Rebase replays your commits on top of the target branch instead of creating a merge commit:
git checkout feature/user-authentication
git rebase main
Conflicts get resolved one commit at a time, which can make them easier to reason about. After each:
git add <resolved-file>
git rebase --continue
To bail:
git rebase --abort
Important: Rebase rewrites history. Never rebase commits that have already been pushed to a shared branch without coordinating with your team.
Practical Tips
Read both versions before editing. Understand what each developer was trying to do. I've seen conflicts resolved incorrectly because someone just picked one version without understanding the other's intent.
Test after resolving. Always run your tests. A syntactically correct resolution can still introduce logical bugs if you combined changes wrong.
Use git log for context:
git log -p src/auth/login.js
This shows the commit history for conflicting lines, helping you understand why each change was made.
Never commit conflict markers. Set up a pre-commit hook or CI check that searches for <<<<<<<. Committed markers cause runtime errors and signal a broken process.
FAQ
Q: Can I prevent conflicts entirely?
Not entirely, but you can minimize them dramatically. Short-lived branches, frequent merges, smaller files, and communicating with teammates about overlapping work. Smaller, more frequent integrations = smaller, more manageable conflicts.
Q: What if I accidentally commit conflict markers?
Fix immediately. Edit the file, remove markers, write correct code, commit the fix. If it's on a shared branch, let your team know.
Q: Merge vs rebase — which should I use?
Merge preserves true history and is safer for shared branches. Rebase gives cleaner linear history but rewrites commits. Many teams rebase locally before merging ("merge after rebase") to get the best of both.
Q: How do I handle delete-vs-modify conflicts?
Git reports these as "deleted by us/them." Run git status to see which. To keep the deletion: git rm <file>. To keep the modification: git add <file>.
Q: Why does my merge tool show a "base" version?
That's the common ancestor — the last state before the branches diverged. It shows what each side changed relative to the original, making both sets of changes much easier to understand.
Merge conflicts feel intimidating at first and routine within a few months. The key insight is that a conflict isn't Git failing — it's Git correctly identifying a decision only a human can make. Once you internalize that, the markers stop looking like errors and start looking like what they are: a request for your judgment.