Merging Branches
Merging is how you integrate changes from one branch into another. In this lesson, you'll learn different merge types, how to resolve conflicts, and best practices for keeping your project history clean and organized.
What is Merging?
Merging combines the changes from one branch (source) into another branch (target):
Common Merge Scenarios:
- Merge feature branch into main (after development)
- Merge main into feature branch (keep feature updated)
- Merge bugfix into release branch
- Merge hotfix into both main and develop
Key Concept: A merge creates a new commit that has two parents - one from each branch being merged. This preserves the complete history of both branches.
Basic Merge Command
The fundamental merge workflow:
# Switch to target branch (where you want changes)
git switch main
# Merge source branch into current branch
git merge feature-branch
# View merge result
git log --oneline --graph
# If successful, the feature branch changes are now in main
Fast-Forward Merge
When the target branch hasn't changed since the source branch was created:
# Scenario: main hasn't changed since feature branch created
main: A---B
\
feature: C---D
# After merge (fast-forward):
main: A---B---C---D
feature: C---D
# Fast-forward merge command:
git switch main
git merge feature-branch
# Output: "Fast-forward"
# No merge commit created - just moves pointer forward
Tip: Fast-forward merges are clean and simple because no actual merging needs to happen - Git just moves the branch pointer forward. The history remains linear.
Three-Way Merge
When both branches have new commits since they diverged:
# Scenario: Both branches have changes
main: A---B---E---F
\
feature: C---D
# After three-way merge:
main: A---B---E---F---M
\ /
feature: C---D---/
# M is the merge commit with two parents
# Three-way merge command:
git switch main
git merge feature-branch
# Git creates a merge commit automatically
Understanding: Three-way merge uses three commits: the common ancestor, the tip of the target branch, and the tip of the source branch. Git intelligently combines changes from both branches.
No Fast-Forward Merge
Force creation of a merge commit even when fast-forward is possible:
# Always create merge commit (preserves branch history)
git merge --no-ff feature-branch
# Why use --no-ff?
✓ Explicitly shows that a feature was merged
✓ Makes it easy to revert entire features
✓ Preserves context about branch existence
✓ Better for team workflows
# Example merge commit message:
Merge branch 'feature-login'
This feature adds user authentication with:
- Login form
- Password validation
- Session management
Merge Conflicts
Conflicts occur when Git can't automatically merge changes:
Conflicts happen when:
- Same line edited in both branches
- File deleted in one branch, modified in another
- Same file added with different content
- Binary files changed in both branches
# When conflict occurs:
git merge feature-branch
# Auto-merging file.txt
# CONFLICT (content): Merge conflict in file.txt
# Automatic merge failed; fix conflicts and then commit
Important: When a merge conflict occurs, Git stops in the middle of the merge. You must resolve all conflicts and complete the merge manually before continuing work.
Understanding Conflict Markers
Git marks conflicts in files with special markers:
# Conflicted file content:
<<<<<<< HEAD
const greeting = "Hello from main branch";
=======
const greeting = "Hello from feature branch";
>>>>>>> feature-branch
Marker Meanings:
<<<<<<< HEAD - Start of your current branch changes
======= - Separator between the two versions
>>>>>>> feature-branch - End marker showing source branch
# Your job: Choose which version to keep (or combine them)
Resolving Merge Conflicts
Step-by-step conflict resolution:
# Step 1: Identify conflicted files
git status
# Both modified: file.txt
# Both modified: app.js
# Step 2: Open conflicted files and edit
# Remove conflict markers and choose correct code:
# Before (conflicted):
<<<<<<< HEAD
const version = "1.0";
=======
const version = "2.0";
>>>>>>> feature-branch
# After resolution (choose one or combine):
const version = "2.0";
# Step 3: Mark as resolved
git add file.txt
git add app.js
# Step 4: Complete the merge
git commit
# Git opens editor with merge commit message
# You can edit or accept default message
# Step 5: Verify merge
git log --oneline --graph
Pro Tip: Use git diff to see conflicts before resolving: git diff --name-only --diff-filter=U shows only unmerged files.
Merge Conflict Resolution Strategies
Strategy 1: Accept Current Branch (Ours)
git checkout --ours file.txt
git add file.txt
Strategy 2: Accept Incoming Branch (Theirs)
git checkout --theirs file.txt
git add file.txt
Strategy 3: Manual Edit
# Open file in editor, manually resolve
vim file.txt
git add file.txt
Strategy 4: Use Merge Tool
git mergetool
# Opens configured visual merge tool
# Popular tools: meld, kdiff3, vimdiff, VS Code
Aborting a Merge
If conflicts are too complex or you need to reconsider:
# Abort merge and return to pre-merge state
git merge --abort
# This is safe - restores everything to before merge
# Use when:
✓ Conflicts are too complex to resolve immediately
✓ You merged the wrong branch
✓ You need to consult with team members
✓ You want to try a different approach (rebase)
Merge Strategies
Git supports different merge strategies for special situations:
# Default recursive strategy (used automatically)
git merge feature-branch
# Ours strategy - keep only current branch changes
git merge -s ours old-branch
# Theirs strategy (via option) - prefer incoming changes
git merge -X theirs feature-branch
# Octopus strategy - merge multiple branches at once
git merge branch1 branch2 branch3
# Resolve strategy - for two branches (rarely used)
git merge -s resolve feature-branch
Common Use: -X theirs and -X ours are useful when you always want to prefer one side in conflicts. Example: git merge -X theirs main updates your feature branch, preferring main's changes in conflicts.
Best Practices for Merging
Before Merging:
✓ Ensure working directory is clean
✓ Make sure you're on correct target branch
✓ Pull latest changes from remote
✓ Review what will be merged (git log, git diff)
✓ Run tests to ensure code works
During Merge:
✓ Read conflict markers carefully
✓ Test after resolving each conflict
✓ Don't blindly accept one side
✓ Communicate with authors of conflicting code
✓ Write clear merge commit messages
After Merge:
✓ Run full test suite
✓ Verify application still works
✓ Push merged changes promptly
✓ Delete merged feature branches
✓ Inform team of merge completion
Practical Merge Workflow
Complete real-world merge example:
# Scenario: Merge feature-login into main
# 1. Prepare main branch
git switch main
git pull origin main
# 2. Preview merge
git log main..feature-login --oneline
git diff main..feature-login --stat
# 3. Perform merge
git merge --no-ff feature-login
# 4a. If no conflicts:
# Edit merge message if needed, save and exit
git push origin main
# 4b. If conflicts occur:
git status # See conflicted files
# Edit conflicted files, resolve conflicts
git add resolved-file1.txt
git add resolved-file2.js
git commit # Complete merge
# 5. Verify and test
git log --oneline --graph -5
npm test # Run tests
# 6. Clean up
git branch -d feature-login
git push origin --delete feature-login
Using Merge Tools
Visual tools make conflict resolution easier:
# Configure merge tool (one-time setup)
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
# Or use built-in tools
git config --global merge.tool vimdiff
# When conflicts occur, launch merge tool
git mergetool
# Merge tool shows three panels:
# - LOCAL: Your current branch changes
# - BASE: Common ancestor version
# - REMOTE: Incoming branch changes
# - MERGED: Result you're creating
# After resolving in tool, save and exit
git commit
Practice Exercise:
Scenario: Practice complete merge workflow with conflict resolution:
- Create two branches from main:
feature-a and feature-b
- In both branches, modify the same line in the same file
- Merge
feature-a into main (should succeed)
- Try merging
feature-b into main (will conflict)
- Resolve the conflict and complete the merge
Commands:
# 1. Create branches
git switch main
git switch -c feature-a
echo "Version A" > file.txt
git add file.txt
git commit -m "Feature A changes"
git switch main
git switch -c feature-b
echo "Version B" > file.txt
git add file.txt
git commit -m "Feature B changes"
# 2. Merge feature-a (no conflict)
git switch main
git merge feature-a
# 3. Try merging feature-b (conflict!)
git merge feature-b
# CONFLICT!
# 4. Resolve conflict
git status # See conflicted file
# Edit file.txt, choose "Version B" or combine
echo "Version B (resolved)" > file.txt
git add file.txt
# 5. Complete merge
git commit
git log --oneline --graph
# Clean up
git branch -d feature-a feature-b
Merge vs Rebase
Quick comparison (we'll explore rebase in detail later):
Use Merge When:
✓ Working on shared/public branches
✓ Want to preserve complete history
✓ Merging long-lived branches
✓ Team prefers explicit merge commits
✓ Working with multiple contributors
Use Rebase When:
✓ Working on private/local branches
✓ Want linear, clean history
✓ Updating feature branch with main
✓ Preparing branch for merge/PR
✓ Solo work or small team with coordination
Golden Rule:
Never rebase public branches that others work on!
Summary
In this lesson, you learned:
- What merging is and why it's essential for collaboration
- Fast-forward vs three-way merge differences
- Creating explicit merge commits with
--no-ff
- Understanding and identifying merge conflicts
- Reading conflict markers and resolving conflicts manually
- Using merge strategies (
--ours, --theirs)
- Aborting merges safely with
git merge --abort
- Using visual merge tools for easier resolution
- Complete merge workflow best practices
- When to use merge vs rebase
Next Up: In the next lesson, we'll explore rebasing - an alternative to merging that creates a linear history by replaying commits!