# 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: ```powershell .\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:** ```bash 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:** ```bash 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: ```bash 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:** ```bash # 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: ```bash 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:** ```bash # 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) ```bash 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) ```bash 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 ```bash git merge # Let Git decide automatically git merge --no-ff # Force a merge commit git merge --ff-only # Only merge if fast-forward possible git merge --abort # Cancel a merge in progress ``` ### Viewing Different Merge Types ```bash # 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 ```bash # 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: ```powershell .\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: ```powershell .\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!