449 lines
12 KiB
Markdown
449 lines
12 KiB
Markdown
# 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 <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
|
|
|
|
```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!
|