Files
git-workshop/02_advanced/06-merge-strategies/README.md
2026-01-07 23:46:32 +01:00

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-ff and --ff-only flags
  • 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

  1. Merge feature-fast-forward into main
  2. Observe Git's "Fast-forward" message
  3. 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:

  1. Navigate to the challenge directory: cd challenge
  2. View all branches: git branch -a
  3. View the current graph: git log --oneline --graph --all
  4. Merge feature-fast-forward: git merge feature-fast-forward
  5. Check the log: git log --oneline --graph
  6. Merge feature-divergent: git merge feature-divergent
  7. Check the log: git log --oneline --graph --all
  8. Merge feature-optional with --no-ff: git merge --no-ff feature-optional
  9. 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-ff change 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 feature branched off from main
  • Commits C and D are new commits on the feature branch
  • main has 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 main pointer forward to commit D
  • NO merge commit was created
  • The history remains linear (a straight line)
  • Both main and feature now 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 feature branched off from main
  • 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:

  1. Commit B - The common ancestor (where branches split)
  2. Commit E - The latest commit on main
  3. 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-ff for 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!