12 KiB
Module 06: Merge Strategies - Fast-Forward vs Three-Way
Learning Objectives
In this module, you will:
- Understand the difference between fast-forward and three-way merges
- Learn when Git automatically chooses each strategy
- Force specific merge behavior with
--no-ffand--ff-onlyflags - Understand the trade-offs between linear and branched history
- Make informed decisions about merge strategies for different workflows
Prerequisites
Before starting this module, you should have completed:
- Module 03: Branching Basics
- Module 04: Merging Branches
Challenge
Setup
Run the setup script to create your challenge environment:
.\setup.ps1
This will create a challenge/ directory with scenarios for both fast-forward and three-way merges.
Your Task
You'll experiment with both types of merges and learn how to control Git's merge behavior.
Part 1: Fast-Forward Merge
- Merge
feature-fast-forwardinto main - Observe Git's "Fast-forward" message
- Examine the linear history
Part 2: Three-Way Merge
4. Merge feature-divergent into main
5. Observe Git creates a merge commit
6. Examine the branched history
Part 3: Force Merge Commit
7. Merge feature-optional into main using --no-ff
8. Compare with the fast-forward from Part 1
Steps:
- Navigate to the challenge directory:
cd challenge - View all branches:
git branch -a - View the current graph:
git log --oneline --graph --all - Merge feature-fast-forward:
git merge feature-fast-forward - Check the log:
git log --oneline --graph - Merge feature-divergent:
git merge feature-divergent - Check the log:
git log --oneline --graph --all - Merge feature-optional with --no-ff:
git merge --no-ff feature-optional - Compare the results:
git log --oneline --graph --all
Key Questions to Consider:
- Which merges created merge commits?
- Which merge kept a linear history?
- How does
--no-ffchange Git's behavior?- When would you prefer each approach?
Understanding Merge Strategies
Fast-Forward Merge
A fast-forward merge happens when the target branch (e.g., main) hasn't changed since the feature branch was created. Git simply "fast-forwards" the branch pointer to the latest commit on the feature branch.
Before the merge:
main: A---B
\
feature: C---D
In this scenario:
- Commit B is where
featurebranched off frommain - Commits C and D are new commits on the
featurebranch mainhas NO new commits since the branch split- The history is linear (straight line from A to D)
After git merge feature (on main):
main: A---B---C---D
↑
feature
What happened:
- Git moved the
mainpointer forward to commit D - NO merge commit was created
- The history remains linear (a straight line)
- Both
mainandfeaturenow point to the same commit (D)
Command:
git switch main
git merge feature-fast-forward
Output you'll see:
Updating abc123..def456
Fast-forward
new-feature.py | 10 ++++++++++
1 file changed, 10 insertions(+)
Notice the "Fast-forward" message!
Characteristics:
- ✅ Keeps history linear and clean
- ✅ Simpler to read in
git log - ✅ No extra merge commit
- ❌ Loses visibility that work was done on a branch
- ❌ Harder to revert entire features at once
Three-Way Merge
A three-way merge happens when BOTH branches have new commits since they diverged. Git must combine changes from both branches, which creates a special merge commit.
Before the merge:
main: A---B---C---E
\
feature: D---F
In this scenario:
- Commit B is where
featurebranched off frommain - Commits C and E are new commits on
main - Commits D and F are new commits on
feature - The branches have diverged (both have unique commits)
After git merge feature (on main):
main: A---B---C---E---M
\ /
feature: D---F---/
What happened:
- Git created a new merge commit (M)
- Commit M has TWO parent commits: E (from main) and F (from feature)
- The merge commit combines changes from both branches
- The history shows the branches converging
Why it's called "three-way": Git uses THREE commits to perform the merge:
- Commit B - The common ancestor (where branches split)
- Commit E - The latest commit on
main - Commit F - The latest commit on
feature
Git compares all three to figure out what changed on each branch and how to combine them.
Command:
git switch main
git merge feature-divergent
Output you'll see:
Merge made by the 'ort' strategy.
feature-code.py | 5 +++++
1 file changed, 5 insertions(+)
Notice it says "Merge made by the 'ort' strategy" instead of "Fast-forward"!
Characteristics:
- ✅ Preserves feature branch history
- ✅ Shows when features were merged
- ✅ Easier to revert entire features (revert the merge commit)
- ✅ Clear visualization in git log --graph
- ❌ Creates more commits (merge commits)
- ❌ History can become complex with many merges
When Does Each Type Happen?
| Situation | Merge Type | Merge Commit? | Git's Behavior |
|---|---|---|---|
| Target branch (main) has NO new commits | Fast-Forward | ❌ No | Automatic |
| Target branch (main) HAS new commits | Three-Way | ✅ Yes | Automatic |
You use --no-ff flag |
Three-Way | ✅ Yes | Forced |
You use --ff-only flag |
Fast-Forward | ❌ No | Fails if not possible |
Git chooses automatically based on the branch state, but you can override this behavior!
Controlling Merge Behavior
Force a Merge Commit with --no-ff
Even if a fast-forward is possible, you can force Git to create a merge commit:
git merge --no-ff feature-optional
Before (fast-forward would be possible):
main: A---B
\
feature: C---D
After git merge --no-ff feature (on main):
main: A---B-------M
\ /
feature: C---D
Notice that even though main didn't change, a merge commit (M) was created!
When to use --no-ff:
- ✅ When you want to preserve the feature branch in history
- ✅ For important features that might need to be reverted
- ✅ In team workflows where you want to see when features were merged
- ✅ When following a branching model like Git Flow
Example:
# You've finished a major feature on feature-auth
git switch main
git merge --no-ff feature-auth -m "Merge feature-auth: Add user authentication system"
This creates a clear marker in history showing when and what was merged.
Require Fast-Forward with --ff-only
You can make Git fail the merge if a fast-forward isn't possible:
git merge --ff-only feature-branch
What happens:
- ✅ If fast-forward is possible, Git merges
- ❌ If branches have diverged, Git refuses to merge
Output when it fails:
fatal: Not possible to fast-forward, aborting.
When to use --ff-only:
- ✅ When you want to keep history strictly linear
- ✅ To ensure you rebase before merging
- ✅ In workflows that prohibit merge commits
Example workflow:
# Try to merge
git merge --ff-only feature-branch
# If it fails, rebase first
git switch feature-branch
git rebase main
git switch main
git merge --ff-only feature-branch # Now it works!
Visualizing the Difference
Linear History (Fast-Forward)
git log --oneline --graph
* d1e2f3g (HEAD -> main, feature) Add feature C
* a4b5c6d Add feature B
* 7e8f9g0 Add feature A
* 1a2b3c4 Initial commit
Notice the straight line! No branching visible.
Branched History (Three-Way Merge)
git log --oneline --graph --all
* m1e2r3g (HEAD -> main) Merge branch 'feature'
|\
| * f4e5a6t (feature) Add feature implementation
| * u7r8e9s Feature setup
* | a1b2c3d Update documentation on main
|/
* 0i1n2i3t Initial commit
Notice the branching pattern! You can see:
- Where the branch split (
|/) - Commits on each branch (
|) - Where branches merged (
* merge commit)
Decision Guide: Which Strategy to Use?
Use Fast-Forward When:
- ✅ Working on personal projects
- ✅ Want simplest, cleanest history
- ✅ Small changes or bug fixes
- ✅ Don't need to track feature branches
- ✅ History readability is top priority
Use Three-Way Merge (or force with --no-ff) When:
- ✅ Working in teams
- ✅ Want to preserve feature context
- ✅ Need to revert features as units
- ✅ Following Git Flow or similar workflow
- ✅ Important to see when features were integrated
Force Fast-Forward (--ff-only) When:
- ✅ Enforcing rebase workflow
- ✅ Maintaining strictly linear history
- ✅ Integration branch requires clean history
Comparison Table
| Aspect | Fast-Forward | Three-Way |
|---|---|---|
| When it happens | Target branch unchanged | Both branches have new commits |
| Merge commit created? | ❌ No | ✅ Yes |
| History appearance | Linear | Branched |
| Command output | "Fast-forward" | "Merge made by..." |
| Git log --graph | Straight line | Fork and merge pattern |
| Can revert entire feature? | ❌ No (must revert each commit) | ✅ Yes (revert merge commit) |
| Force it | --ff-only |
--no-ff |
| Best for | Solo work, small changes | Team work, features |
Useful Commands
Merging with Strategy Control
git merge <branch> # Let Git decide automatically
git merge --no-ff <branch> # Force a merge commit
git merge --ff-only <branch> # Only merge if fast-forward possible
git merge --abort # Cancel a merge in progress
Viewing Different Merge Types
# See all merges
git log --merges # Only merge commits
git log --no-merges # Hide merge commits
# Visualize history
git log --oneline --graph # See branch structure
git log --oneline --graph --all # Include all branches
git log --first-parent # Follow only main branch line
# Check if merge would be fast-forward
git merge-base main feature # Find common ancestor
git log main..feature # See commits unique to feature
Configuring Default Behavior
# Disable fast-forward by default
git config merge.ff false # Always create merge commits
# Only allow fast-forward
git config merge.ff only # Refuse non-fast-forward merges
# Reset to default
git config --unset merge.ff
Common Patterns in the Wild
GitHub Flow (Simple)
- Use fast-forward when possible
- Short-lived feature branches
- Merge to main frequently
Git Flow (Structured)
- Always use
--no-fffor features - Preserve branch history
- Complex release management
Rebase Workflow
- Always use
--ff-only - Rebase before merging
- Strictly linear history
Verification
Once you've completed all three merges, verify your solution:
.\verify.ps1
The verification script will check that you've experienced both types of merges and used the --no-ff flag.
Need to Start Over?
If you want to reset the challenge and start fresh:
.\reset.ps1
This will remove the challenge directory and run the setup script again, giving you a clean slate.
What's Next?
Now that you understand merge strategies, you can make informed decisions about your workflow. Consider:
- For personal projects: Fast-forward merges keep history simple
- For team projects: Three-way merges preserve context
- For open source: Follow the project's contribution guidelines
The best strategy depends on your team's needs and workflow!