refactor: split out merge strategies from essentials

This commit is contained in:
Bjarke Sporring
2026-01-07 21:50:59 +01:00
parent 7fb84560f5
commit df9a2bf7c1
7 changed files with 1164 additions and 177 deletions

View File

@@ -1,13 +1,13 @@
# Module 04: Merging
# Module 04: Merging Branches
## Learning Objectives
In this module, you will:
- Understand what merging means in Git
- Perform a fast-forward merge
- Perform a three-way merge
- Understand when merge commits are created
- Merge divergent branches together
- Understand merge commits and their purpose
- Use `git merge` to combine branches
- Visualize merge history with `git log --graph`
## Challenge
@@ -19,72 +19,217 @@ Run the setup script to create your challenge environment:
.\setup.ps1
```
This will create a `challenge/` directory with a Git repository that has a main branch and a feature branch ready to merge.
This will create a `challenge/` directory with a Git repository that has two divergent branches ready to merge.
### Your Task
This challenge has two parts that teach you about the two types of merges in Git:
You have two branches that have diverged - both `main` and `feature-login` have new commits since they split. Your task is to merge them back together.
**Part 1: Fast-Forward Merge**
1. Merge the existing `feature-api` branch into main
2. Observe that this is a "fast-forward" merge (no merge commit created)
**Scenario:**
- You're working on a team project
- You created a `feature-login` branch to add login functionality
- While you were working, your teammate added documentation to `main`
- Now you need to merge your login feature back into main
**Part 2: Three-Way Merge**
3. Create a new branch called `feature-ui`
4. Make commits on the feature-ui branch
5. Switch back to main and make a commit there too (creates divergence)
6. Merge feature-ui into main
7. Observe that this creates a merge commit (three-way merge)
**Suggested Approach:**
**Steps:**
1. Navigate to the challenge directory: `cd challenge`
2. Check current branch: `git branch` (should be on main)
3. View existing branches: `git branch -a`
4. Merge feature-api: `git merge feature-api`
5. View the log: `git log --oneline --graph`
6. Create feature-ui branch: `git switch -c feature-ui`
7. Create a new file `ui.py` and commit it
8. Make another commit on feature-ui (modify ui.py)
9. Switch back to main: `git switch main`
10. Make a change on main (modify api.py) and commit it
11. Merge feature-ui: `git merge feature-ui`
12. View the merge history: `git log --oneline --graph --all`
3. View the branch structure: `git log --oneline --graph --all`
4. Merge the feature-login branch: `git merge feature-login`
5. View the merge result: `git log --oneline --graph --all`
6. Examine the merge commit: `git show HEAD`
> **Important Notes:**
> - A **fast-forward merge** happens when main hasn't changed since the feature branch was created
> - A **three-way merge** creates a merge commit when both branches have diverged
> - You can see merge commits with `git log --merges`
> - The `--graph` option helps visualize the branch history
> - After merging, the feature branch still exists but you can delete it with `git branch -d`
> - When both branches have new commits, Git creates a **merge commit**
> - The merge commit has TWO parent commits (one from each branch)
> - Git will open an editor for you to write a merge commit message
> - You can use the default message or customize it
> - The `--graph` option helps visualize how branches merged
## Key Concepts
- **Merge**: Combining changes from different branches into one branch.
- **Fast-Forward Merge**: When the target branch hasn't changed, Git simply moves the branch pointer forward. No merge commit is created.
- **Three-Way Merge**: When both branches have new commits, Git creates a merge commit that has two parent commits.
- **Merge Commit**: A special commit with two (or more) parent commits, representing the point where branches were merged.
- **Divergent Branches**: Branches that have different commits since they split from a common ancestor.
### What is Merging?
**Merging** combines changes from different branches into one branch. When you merge, you're telling Git: "Take all the changes from branch X and incorporate them into my current branch."
### Divergent Branches
When two branches have different commits since they split, they are **divergent**:
```
main: A---B---C
\
feature: D---E
```
- Both branches split from commit B
- `main` has commit C
- `feature` has commits D and E
- They have **diverged** - each has unique work
### The Merge Commit
When you merge divergent branches, Git creates a special **merge commit**:
```
Before merge:
main: A---B---C
\
feature: D---E
After merge (on main):
main: A---B---C-------M
\ /
feature: D---E---/
```
**Merge commit (M)** is special because:
- It has **two parent commits**: C and E
- It represents the point where branches came back together
- It contains the combined changes from both branches
### How Git Merges
Git uses a **three-way merge** algorithm:
1. **Common ancestor** (B) - Where the branches split
2. **Your branch** (C) - Latest commit on main
3. **Their branch** (E) - Latest commit on feature
Git compares all three to figure out what changed on each branch and combines them intelligently.
## Understanding Merge Output
When you run `git merge feature-login`, you'll see:
```
Merge made by the 'ort' strategy.
login.py | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
create mode 100644 login.py
```
**What this means:**
- **"Merge made by the 'ort' strategy"** - Git used its merge algorithm to combine changes
- The summary shows what files changed in the merge
- A new merge commit was created
### The Merge Commit Message
Git will open your editor with a default message:
```
Merge branch 'feature-login'
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
```
**Tips for merge messages:**
- The default message is usually fine for simple merges
- For important merges, explain WHY you're merging
- Example: "Merge feature-login - adds user authentication system"
## Visualizing Merges
Use `git log --graph` to see the branch structure:
```bash
git log --oneline --graph --all
```
**Example output:**
```
* a1b2c3d (HEAD -> main) Merge branch 'feature-login'
|\
| * e5f6g7h (feature-login) Implement login validation
| * h8i9j0k Add login form
* | k1l2m3n Update README with setup instructions
|/
* n4o5p6q Initial project structure
```
**Reading the graph:**
- `*` = A commit
- `|` = Branch line continuing
- `/` and `\` = Branch splitting or merging
- `(HEAD -> main)` = Current branch position
- The merge commit has two lines coming into it
## Useful Commands
### Merging
```bash
git merge <branch> # Merge branch into current branch
git log --oneline --graph # View merge history visually
git log --graph --all # View all branches and merges
git log --merges # Show only merge commits
git branch -d <branch> # Delete a merged branch (optional)
git merge <branch> # Merge branch into current branch
git merge --abort # Cancel a merge in progress
```
### Viewing Merge History
```bash
git log --oneline --graph # Simple graph view
git log --oneline --graph --all # Show all branches
git log --merges # Show only merge commits
git show <commit> # View details of a merge commit
```
### Branch Management
```bash
git branch # List local branches
git branch -a # List all branches (including remote)
git branch -d <branch> # Delete a merged branch
```
## Common Questions
### "What if the merge fails?"
Sometimes Git can't automatically merge changes (this is called a **merge conflict**). Don't worry! We'll cover this in the next module (Module 05: Merge Conflicts).
If you see a conflict and want to cancel:
```bash
git merge --abort
```
### "Can I undo a merge?"
Before you've pushed to remote:
```bash
git reset --hard HEAD~1 # Undo the merge commit
```
We'll cover more advanced undoing in later modules.
### "Should I delete the feature branch after merging?"
It's common practice to delete feature branches after merging:
```bash
git branch -d feature-login # Safe delete (only works if merged)
```
This cleans up your branch list. The commits are still in the history - the branch pointer is just removed.
### "What does 'ort strategy' mean?"
Git uses different merge algorithms:
- **ort** (default in Git 2.34+) - Newer, faster merge algorithm
- **recursive** (older) - Traditional merge algorithm
You don't need to worry about this - Git picks the best one automatically!
## Verification
Once you've completed both merges, verify your solution:
Once you've completed the merge, verify your solution:
```powershell
.\verify.ps1
```
The verification script will check that you've successfully merged both feature branches and understand the different merge types.
The verification script will check that you've successfully merged the feature branch.
## Need to Start Over?
@@ -95,3 +240,9 @@ If you want to reset the challenge and start fresh:
```
This will remove the challenge directory and run the setup script again, giving you a clean slate.
## What's Next?
Now that you understand basic merging, the next module covers **Merge Conflicts** - what happens when Git can't automatically merge changes and how to resolve them manually.
**Advanced Topic:** In Module 06 (Advanced section), you'll learn about different merge strategies including fast-forward merges and when to use `--no-ff`.

View File

@@ -5,8 +5,7 @@
.DESCRIPTION
This script creates a challenge directory with a Git repository that
contains a main branch and a feature branch ready to merge. Students
will practice both fast-forward and three-way merges.
contains two divergent branches ready to merge (three-way merge scenario).
#>
Write-Host "`n=== Setting up Module 04 Challenge ===" -ForegroundColor Cyan
@@ -30,99 +29,121 @@ git init | Out-Null
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Commit 1: Initial API file on main
Write-Host "Creating initial API structure on main..." -ForegroundColor Green
$apiContent = @"
# api.py - API module
# Commit 1: Initial project structure on main
Write-Host "Creating initial project structure..." -ForegroundColor Green
$readmeContent = @"
# My Project
def api_handler():
print("API Handler initialized")
return True
A simple web application project.
## Setup
Coming soon...
"@
Set-Content -Path "api.py" -Value $apiContent
Set-Content -Path "README.md" -Value $readmeContent
$appContent = @"
# app.py - Main application file
def main():
print("Welcome to My App!")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Initial API setup" | Out-Null
git commit -m "Initial project structure" | Out-Null
# Commit 2: Add more API functionality on main
$apiContent = @"
# api.py - API module
# Create feature-login branch
Write-Host "Creating feature-login branch..." -ForegroundColor Green
git switch -c feature-login | Out-Null
def api_handler():
print("API Handler initialized")
# Commit on feature-login: Add login module
Write-Host "Adding login functionality on feature-login..." -ForegroundColor Green
$loginContent = @"
# login.py - User login module
def login(username, password):
"""Authenticate a user."""
print(f"Authenticating user: {username}")
# TODO: Add actual authentication logic
return True
def get_data():
print("Fetching data from API...")
return {"status": "ok"}
def logout(username):
"""Log out a user."""
print(f"Logging out user: {username}")
return True
"@
Set-Content -Path "api.py" -Value $apiContent
Set-Content -Path "login.py" -Value $loginContent
git add .
git commit -m "Add get_data function" | Out-Null
git commit -m "Add login module" | Out-Null
# Create feature-api branch and add commits
Write-Host "Creating feature-api branch..." -ForegroundColor Green
git checkout -b feature-api 2>$null | Out-Null
# Second commit on feature-login: Integrate login with app
$appContent = @"
# app.py - Main application file
from login import login, logout
# Commit on feature-api: Add API routes
$routesContent = @"
# api-routes.py - API Routes module
def main():
print("Welcome to My App!")
# Add login integration
if login("testuser", "password"):
print("Login successful!")
pass
def setup_routes():
print("Setting up API routes...")
routes = {
"/api/data": "get_data",
"/api/status": "get_status"
}
return routes
if __name__ == "__main__":
main()
"@
Set-Content -Path "api-routes.py" -Value $routesContent
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add API routes" | Out-Null
# Second commit on feature-api: Enhance routes
$routesContent = @"
# api-routes.py - API Routes module
def setup_routes():
print("Setting up API routes...")
routes = {
"/api/data": "get_data",
"/api/status": "get_status",
"/api/health": "health_check"
}
return routes
def health_check():
return {"healthy": True}
"@
Set-Content -Path "api-routes.py" -Value $routesContent
git add .
git commit -m "Add health check route" | Out-Null
git commit -m "Integrate login with main app" | Out-Null
# Switch back to main branch
Write-Host "Switching back to main branch..." -ForegroundColor Green
git checkout main 2>$null | Out-Null
git switch main | Out-Null
# Make a commit on main (creates divergence)
Write-Host "Adding documentation on main (creates divergence)..." -ForegroundColor Green
$readmeContent = @"
# My Project
A simple web application project.
## Setup
1. Install Python 3.8 or higher
2. Run: python app.py
## Features
- User authentication (coming soon)
- Data management (coming soon)
## Contributing
Please follow our coding standards when contributing.
"@
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Update README with setup instructions" | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
Write-Host "`nYou have:" -ForegroundColor Cyan
Write-Host " - A main branch with API code" -ForegroundColor White
Write-Host " - A feature-api branch with API routes ready to merge" -ForegroundColor White
Write-Host "`nScenario: You have two divergent branches!" -ForegroundColor Yellow
Write-Host " - main: Has updated documentation" -ForegroundColor White
Write-Host " - feature-login: Has new login functionality" -ForegroundColor White
Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. View branches: git branch -a" -ForegroundColor White
Write-Host " 3. Merge feature-api: git merge feature-api (fast-forward merge)" -ForegroundColor White
Write-Host " 4. Create feature-ui branch: git checkout -b feature-ui" -ForegroundColor White
Write-Host " 5. Make commits on feature-ui" -ForegroundColor White
Write-Host " 6. Switch back to main and make a commit there" -ForegroundColor White
Write-Host " 7. Merge feature-ui: git merge feature-ui (three-way merge)" -ForegroundColor White
Write-Host " 8. View merge history: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 9. Run '..\verify.ps1' to check your solution" -ForegroundColor White
Write-Host " 2. View the branch structure: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 3. Merge feature-login into main: git merge feature-login" -ForegroundColor White
Write-Host " 4. View the merge result: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 5. Run '..\verify.ps1' to check your solution" -ForegroundColor White
Write-Host ""

View File

@@ -8,9 +8,9 @@
- The challenge directory exists
- A Git repository exists
- Currently on main branch
- feature-api has been merged into main
- feature-ui branch exists and has been merged
- A merge commit exists (from three-way merge)
- feature-login has been merged into main
- A merge commit exists (three-way merge)
- Login functionality is present on main
#>
Write-Host "`n=== Verifying Module 04 Solution ===" -ForegroundColor Cyan
@@ -38,82 +38,63 @@ if ($currentBranch -eq "main") {
Write-Host "[PASS] Currently on main branch" -ForegroundColor Green
} else {
Write-Host "[FAIL] Not on main branch (currently on: $currentBranch)" -ForegroundColor Red
Write-Host "[HINT] Switch to main with: git checkout main" -ForegroundColor Yellow
Write-Host "[HINT] Switch to main with: git switch main" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check if feature-api branch exists
$featureApiBranch = git branch --list "feature-api" 2>$null
if ($featureApiBranch) {
Write-Host "[PASS] Branch 'feature-api' exists" -ForegroundColor Green
# Check if feature-login branch exists
$featureLoginBranch = git branch --list "feature-login" 2>$null
if ($featureLoginBranch) {
Write-Host "[PASS] Branch 'feature-login' exists" -ForegroundColor Green
} else {
Write-Host "[INFO] Branch 'feature-api' not found (may have been deleted after merge)" -ForegroundColor Cyan
Write-Host "[INFO] Branch 'feature-login' not found (may have been deleted after merge)" -ForegroundColor Cyan
}
# Check if commits from feature-api are in main
$apiRoutesCommit = git log --all --grep="Add API routes" --oneline 2>$null
$healthCheckCommit = git log --all --grep="Add health check route" --oneline 2>$null
# Check if login.py exists (should be on main after merge)
if (Test-Path "login.py") {
Write-Host "[PASS] File 'login.py' exists on main (from feature-login merge)" -ForegroundColor Green
} else {
Write-Host "[FAIL] File 'login.py' not found on main" -ForegroundColor Red
Write-Host "[HINT] This file should appear after merging feature-login into main" -ForegroundColor Yellow
$allChecksPassed = $false
}
if ($apiRoutesCommit -and $healthCheckCommit) {
# Check if these commits are in main's history
$apiRoutesInMain = git log --grep="Add API routes" --oneline 2>$null
$healthCheckInMain = git log --grep="Add health check route" --oneline 2>$null
if ($apiRoutesInMain -and $healthCheckInMain) {
Write-Host "[PASS] Commits from feature-api are merged into main" -ForegroundColor Green
# Check if app.py contains login integration
if (Test-Path "app.py") {
$appContent = Get-Content "app.py" -Raw
if ($appContent -match "login") {
Write-Host "[PASS] app.py contains login integration" -ForegroundColor Green
} else {
Write-Host "[FAIL] Commits from feature-api not found in main" -ForegroundColor Red
Write-Host "[HINT] Merge feature-api with: git merge feature-api" -ForegroundColor Yellow
Write-Host "[FAIL] app.py doesn't contain login integration" -ForegroundColor Red
Write-Host "[HINT] After merging, app.py should import and use the login module" -ForegroundColor Yellow
$allChecksPassed = $false
}
} else {
Write-Host "[FAIL] Expected commits from feature-api not found" -ForegroundColor Red
Write-Host "[HINT] Did you run setup.ps1?" -ForegroundColor Yellow
Write-Host "[FAIL] app.py not found" -ForegroundColor Red
$allChecksPassed = $false
}
# Check if api-routes.py exists (should be on main after merge)
if (Test-Path "api-routes.py") {
Write-Host "[PASS] File 'api-routes.py' exists on main (from feature-api merge)" -ForegroundColor Green
} else {
Write-Host "[FAIL] File 'api-routes.py' not found on main" -ForegroundColor Red
Write-Host "[HINT] This file should appear after merging feature-api" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check if feature-ui branch exists
$featureUiBranch = git branch --list "feature-ui" 2>$null
if ($featureUiBranch) {
Write-Host "[PASS] Branch 'feature-ui' exists" -ForegroundColor Green
# Check if feature-ui has commits
$uiCommitCount = git rev-list main..feature-ui --count 2>$null
if ($uiCommitCount -gt 0) {
Write-Host "[INFO] Branch 'feature-ui' has $uiCommitCount commit(s)" -ForegroundColor Cyan
}
} else {
Write-Host "[FAIL] Branch 'feature-ui' not found" -ForegroundColor Red
Write-Host "[HINT] Create feature-ui with: git checkout -b feature-ui" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check for merge commits (indicates three-way merge happened)
# Check for merge commit (indicates three-way merge happened)
$mergeCommits = git log --merges --oneline 2>$null
if ($mergeCommits) {
Write-Host "[PASS] Merge commit(s) found (three-way merge completed)" -ForegroundColor Green
Write-Host "[PASS] Merge commit found (three-way merge completed)" -ForegroundColor Green
# Get the merge commit message
$mergeMessage = git log --merges --format=%s -1 2>$null
Write-Host "[INFO] Merge commit message: '$mergeMessage'" -ForegroundColor Cyan
} else {
Write-Host "[FAIL] No merge commits found" -ForegroundColor Red
Write-Host "[HINT] Create divergence: make commits on both main and feature-ui, then merge" -ForegroundColor Yellow
Write-Host "[FAIL] No merge commit found" -ForegroundColor Red
Write-Host "[HINT] Merge feature-login into main with: git merge feature-login" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check if ui.py exists (should be on main after merge)
if (Test-Path "ui.py") {
Write-Host "[PASS] File 'ui.py' exists on main (from feature-ui merge)" -ForegroundColor Green
# Check if both branches contributed commits (true three-way merge)
$totalCommits = git rev-list --count HEAD 2>$null
if ($totalCommits -ge 4) {
Write-Host "[PASS] Repository has $totalCommits commits (branches diverged properly)" -ForegroundColor Green
} else {
Write-Host "[FAIL] File 'ui.py' not found on main" -ForegroundColor Red
Write-Host "[HINT] Create ui.py on feature-ui branch and merge it into main" -ForegroundColor Yellow
$allChecksPassed = $false
Write-Host "[WARN] Repository has only $totalCommits commits (expected at least 4)" -ForegroundColor Yellow
Write-Host "[INFO] This might still be correct if you deleted commits" -ForegroundColor Cyan
}
Set-Location ..
@@ -124,13 +105,14 @@ if ($allChecksPassed) {
Write-Host "=====================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Green
Write-Host "`nYou've successfully learned about Git merging!" -ForegroundColor Cyan
Write-Host "`nYou've successfully completed your first branch merge!" -ForegroundColor Cyan
Write-Host "You now understand:" -ForegroundColor Cyan
Write-Host " - Fast-forward merges (when main hasn't changed)" -ForegroundColor White
Write-Host " - Three-way merges (when branches have diverged)" -ForegroundColor White
Write-Host " - How to use git merge to combine branches" -ForegroundColor White
Write-Host " - Merge commits and how to view them" -ForegroundColor White
Write-Host "`nReady for the next module!" -ForegroundColor Green
Write-Host " - How to merge branches with git merge" -ForegroundColor White
Write-Host " - What a merge commit is and why it's created" -ForegroundColor White
Write-Host " - How divergent branches are combined" -ForegroundColor White
Write-Host " - How to visualize merges with git log --graph" -ForegroundColor White
Write-Host "`nNext up: Module 05 - Merge Conflicts!" -ForegroundColor Yellow
Write-Host "You'll learn what happens when Git can't automatically merge." -ForegroundColor Cyan
Write-Host ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red