How to Resolve Git Merge Conflicts
Merge conflicts occur when Git cannot automatically combine changes from different branches because they modify the same lines of code in incompatible ways. While conflicts can seem intimidating, they're a normal part of collaborative development and can be resolved systematically.
In this guide, you'll learn how to identify, understand, and resolve merge conflicts using various tools and strategies to maintain smooth development workflows.
Prerequisites
You need Git installed on your system and basic knowledge of Git branches and merging. You should understand how collaborative development works and have experience with text editors. Working in a repository with multiple contributors will give you practical experience with conflicts.
Understanding Merge Conflicts
Merge conflicts happen when:
- Two branches modify the same lines in a file
- One branch deletes a file while another modifies it
- Both branches add files with the same name but different content
- Binary files are modified differently on both branches
Git marks conflicted areas in your files with special conflict markers, allowing you to manually decide how to combine the changes.
Identifying Merge Conflicts
Recognizing When Conflicts Occur
When you attempt a merge that results in conflicts:
# Attempting to merge a branch
git merge feature-branch
# Git output when conflicts occur:
# Auto-merging src/utils.js
# CONFLICT (content): Merge conflict in src/utils.js
# Automatic merge failed; fix conflicts and then commit the result.
Check your repository status after a failed merge:
# See which files have conflicts
git status
# Output shows:
# You have unmerged paths.
# Unmerged paths:
# (use "git add <file>..." to mark resolution)
# both modified: src/utils.js
Understanding Conflict Markers
Git inserts conflict markers into affected files:
function calculateTotal(items) {
<<<<<<< HEAD
// Current branch version
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
=======
// Incoming branch version
return items.reduce((total, item) => total + (item.price * item.qty), 0);
>>>>>>> feature-branch
}
The conflict markers indicate:
<<<<<<< HEAD
: Start of your current branch's version=======
: Separator between the two versions>>>>>>> feature-branch
: End of the incoming branch's version
Manual Conflict Resolution
Basic Resolution Process
To resolve conflicts manually:
# 1. Open the conflicted file in your editor
# 2. Locate conflict markers (<<<<<<< ======= >>>>>>>)
# 3. Decide which version to keep or combine both
# 4. Remove conflict markers
# 5. Save the file
# 6. Stage the resolved file
git add src/utils.js
# 7. Continue the merge
git commit
Resolution Strategies
Keep Your Version (Current Branch)
// Original conflict:
<<<<<<< HEAD
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
=======
return items.reduce((total, item) => total + (item.price * item.qty), 0);
>>>>>>> feature-branch
// Resolution - keep current branch version:
return items.reduce((sum, item) => sum + item.price * item.quantity, 0);
Keep Their Version (Incoming Branch)
// Resolution - keep incoming branch version:
return items.reduce((total, item) => total + item.price * item.qty, 0);
Combine Both Versions
// Resolution - combine the best of both:
return items.reduce((sum, item) => {
// Use 'sum' variable name from current branch
// Use 'qty' property name from incoming branch
return sum + item.price * item.qty;
}, 0);
Resolving Different Conflict Types
Multiple Conflicts in One File
When a file has multiple conflict sections:
class UserService {
<<<<<<< HEAD
constructor(apiUrl) {
this.apiUrl = apiUrl;
=======
constructor(baseUrl, timeout = 5000) {
this.baseUrl = baseUrl;
this.timeout = timeout;
>>>>>>> feature-branch
}
async getUser(id) {
<<<<<<< HEAD
const response = await fetch(`${this.apiUrl}/users/${id}`);
=======
const response = await fetch(`${this.baseUrl}/api/users/${id}`, {
timeout: this.timeout
});
>>>>>>> feature-branch
return response.json();
}
}
Resolve each conflict section individually, ensuring the final code is consistent and functional.
Added vs Modified Conflicts
When one branch adds content and another modifies nearby code:
// Current branch added new function
function validateUser(user) {
return user && user.email && user.name;
}
<<<<<<< HEAD
function createUser(userData) {
if (validateUser(userData)) {
return new User(userData);
}
throw new Error('Invalid user data');
=======
function createUser(data) {
// Enhanced validation from feature branch
if (!data || !data.email || !data.name) {
throw new ValidationError('Missing required fields');
}
return new User(data);
>>>>>>> feature-branch
}
Using Git Tools for Conflict Resolution
Using Git Mergetool
Git provides built-in support for merge tools:
# Configure your preferred merge tool
git config --global merge.tool vimdiff
# Or use other popular tools
git config --global merge.tool vscode
git config --global merge.tool kdiff3
# Launch the merge tool for conflicts
git mergetool
# This opens a visual diff tool to help resolve conflicts
Command Line Resolution Helpers
Use Git commands to understand conflicts better:
# See the differences for conflicted files
git diff
# Show differences with more context
git diff --context=10
# See the conflict state for specific file
git show :1:filename.js # Common ancestor version
git show :2:filename.js # Your version (HEAD)
git show :3:filename.js # Their version (incoming branch)
VS Code Integration
If you use VS Code, it provides excellent conflict resolution support:
# Open VS Code at the conflicted file
code src/utils.js
# VS Code highlights conflicts and provides:
# - "Accept Current Change" button
# - "Accept Incoming Change" button
# - "Accept Both Changes" button
# - "Compare Changes" option
Advanced Conflict Resolution
Aborting and Restarting
If conflicts become too complex:
# Abort the current merge
git merge --abort
# This returns you to the state before attempting the merge
# Try alternative approaches:
# 1. Rebase instead of merge
git rebase feature-branch
# 2. Merge with strategy options
git merge -X ours feature-branch # Favor your changes
git merge -X theirs feature-branch # Favor their changes
Resolving Binary File Conflicts
For binary files (images, documents), you must choose one version:
# Keep your version of the binary file
git checkout --ours binary-file.png
git add binary-file.png
# Keep their version of the binary file
git checkout --theirs binary-file.png
git add binary-file.png
# Continue the merge
git commit
Handling Rename Conflicts
When files are renamed differently on both branches:
# Git shows rename conflicts like:
# CONFLICT (rename/rename): file.js renamed to newfile.js in HEAD and to another.js in feature-branch
# Choose the name you want
git mv file.js chosen-name.js
git add chosen-name.js
# Remove the unwanted version
git rm another.js
Prevention Strategies
Frequent Integration
Reduce conflict likelihood through regular merging:
# Regularly update your feature branch with main
git checkout feature-branch
git merge main
# Or use rebase for cleaner history
git rebase main
# This keeps your branch current and reduces conflicts
Communication and Coordination
Coordinate with your team to avoid conflicts:
# Before starting work, check what others are doing
git fetch origin
git log --oneline origin/main..origin/other-feature
# Discuss with team members working on similar areas
# Consider pair programming for complex changes
Code Organization
Structure your code to minimize conflicts:
- Keep functions small and focused
- Separate concerns into different files
- Use consistent formatting (automated with tools like Prettier)
- Avoid large refactoring commits when possible
Complex Conflict Scenarios
Three-Way Merge Conflicts
When multiple branches are involved:
# Start with the main branch
git checkout main
# Merge the first feature branch
git merge feature-a
# Merge the second feature branch (conflicts may occur)
git merge feature-b
# Resolve conflicts considering all three states:
# - Original main branch
# - Changes from feature-a
# - Changes from feature-b
Rebase Conflicts
Conflicts during interactive rebase:
# During rebase, conflicts are resolved one commit at a time
git rebase -i main
# When conflicts occur:
# 1. Resolve conflicts in affected files
# 2. Stage resolved files
git add resolved-file.js
# 3. Continue the rebase
git rebase --continue
# Repeat until rebase is complete
Merge vs Rebase Conflict Resolution
The approach differs slightly:
# For merge conflicts:
git add resolved-file.js
git commit # Completes the merge
# For rebase conflicts:
git add resolved-file.js
git rebase --continue # Continues to next commit
Best Practices for Conflict Resolution
Testing After Resolution
Always test your resolution:
# After resolving conflicts and committing
git merge feature-branch
# Run your test suite
npm test
# Check that the application still works
npm start
# If tests fail, the resolution may need adjustment
Documentation and Communication
Document complex resolutions:
# Write descriptive commit messages for merge commits
git commit -m "Merge feature-branch: resolve conflicts in user validation
- Combined validation logic from both branches
- Kept enhanced error handling from feature-branch
- Maintained backward compatibility from main branch"
Learning from Conflicts
Analyze patterns in your conflicts:
# Review recent merge commits
git log --merges --oneline -10
# Look for frequently conflicted files
git log --name-only --pretty=format: --merges | sort | uniq -c | sort -nr
Now you have comprehensive knowledge for handling Git merge conflicts. Remember that conflicts are opportunities to understand your codebase better and improve team coordination. With practice, conflict resolution becomes a routine part of collaborative development.
Found an issue?