feat: add module 9 for cherry picking

This commit is contained in:
Bjarke Sporring
2026-01-04 17:29:52 +01:00
parent 23f0ff511c
commit 8f488c2e88
4 changed files with 596 additions and 0 deletions

View File

@@ -0,0 +1,160 @@
# Module 09: Cherry-Pick
## Learning Objectives
By the end of this module, you will:
- Understand what cherry-picking is and how it works
- Know when to use cherry-pick vs merge or rebase
- Apply specific commits from one branch to another
- Handle cherry-pick conflicts if they occur
- Understand common use cases for cherry-picking
## Challenge Description
You have a `development` branch with several commits. Some of these commits are bug fixes that need to be applied to the `main` branch immediately, but other commits are experimental features that shouldn't be merged yet.
Your task is to:
1. Review the commits on the development branch
2. Identify which commits are bug fixes
3. Cherry-pick only the bug fix commits to the main branch
4. Verify that main has the bug fixes but not the experimental features
## Key Concepts
### What is Cherry-Pick?
Cherry-pick allows you to apply a specific commit from one branch to another. Instead of merging an entire branch, you can selectively choose individual commits.
```
A---B---C---D development
/
E---F main
```
After cherry-picking commit C:
```
A---B---C---D development
/
E---F---C' main
```
Note that C' is a new commit with the same changes as C but a different commit hash.
### Cherry-Pick vs Merge vs Rebase
- **Merge**: Brings all commits from another branch and creates a merge commit
- **Rebase**: Replays all commits from your branch on top of another branch
- **Cherry-Pick**: Applies one or more specific commits to your current branch
### When to Use Cherry-Pick
Cherry-pick is useful when you:
- Need a bug fix from a feature branch but can't merge the whole branch yet
- Want to apply a specific commit to a release branch
- Need to backport a fix to an older version
- Made a commit on the wrong branch and need to move it
- Want to duplicate a commit across multiple branches
### Cherry-Pick Creates New Commits
Important: Cherry-picked commits are new commits with different hashes. The original commit remains on the source branch, and a copy is created on the target branch with the same changes but a different commit ID.
## Useful Commands
```bash
# View commits on another branch
git log <branch-name> --oneline
# View a specific commit's details
git show <commit-hash>
# Cherry-pick a single commit
git cherry-pick <commit-hash>
# Cherry-pick multiple commits
git cherry-pick <commit-hash1> <commit-hash2>
# Cherry-pick a range of commits
git cherry-pick <start-hash>..<end-hash>
# If conflicts occur during cherry-pick:
# 1. Resolve conflicts in files
# 2. Stage the resolved files
git add <file>
# 3. Continue the cherry-pick
git cherry-pick --continue
# Abort a cherry-pick if something goes wrong
git cherry-pick --abort
# View commit history graph
git log --oneline --graph --all
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- You're on the main branch
- The security bug fix commit has been applied to main
- The performance bug fix commit has been applied to main
- The experimental features are NOT on main
- The commits were cherry-picked (not merged)
## Challenge Steps
1. Navigate to the challenge directory
2. You're currently on the development branch
3. View the commits: `git log --oneline`
4. You'll see several commits - identify the bug fixes
5. Switch to main branch: `git checkout main`
6. Cherry-pick the bug fix commits (you'll need their commit hashes)
7. Verify the result with `git log --oneline`
8. Run the verification script
## Tips
- Use `git log development --oneline` to see commits on the development branch
- Use `git show <hash>` to view details of a specific commit
- You can cherry-pick by commit hash - you only need the first 7 characters
- Cherry-pick commits in chronological order (oldest first) to avoid conflicts
- If you make a mistake, use `.\reset.ps1` to start over
- The commit message will be preserved when cherry-picking
## Common Cherry-Pick Scenarios
### Hotfix to Production
You have a critical bug fix on a development branch that needs to go to production immediately:
```bash
git checkout production
git cherry-pick <bugfix-commit-hash>
```
### Wrong Branch
You accidentally committed on the wrong branch:
```bash
# On wrong branch, note the commit hash
git log --oneline
# Switch to correct branch
git checkout correct-branch
git cherry-pick <commit-hash>
# Go back and remove from wrong branch
git checkout wrong-branch
git reset --hard HEAD~1
```
### Backporting
You need to apply a fix to an older release branch:
```bash
git checkout release-2.0
git cherry-pick <fix-from-main>
```
## What You'll Learn
Cherry-pick is a surgical tool in your Git toolbox. While merge and rebase work with entire branches, cherry-pick lets you be selective about which changes to apply. This is invaluable for managing hotfixes, maintaining multiple release branches, and handling situations where you need specific changes without bringing along everything else. Understanding when to use cherry-pick versus other Git operations is a mark of Git expertise.

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the cherry-pick challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
}
# Run setup script
Write-Host "Running setup script...`n" -ForegroundColor Cyan
& ".\setup.ps1"

View File

@@ -0,0 +1,208 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the cherry-pick challenge environment.
.DESCRIPTION
Creates a Git repository with a development branch containing both
bug fixes and experimental features. Students must cherry-pick only
the bug fixes to the main branch.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Create initial commits on main branch
$app = @"
class App {
constructor() {
this.version = '1.0.0';
}
start() {
console.log('App started');
}
}
module.exports = App;
"@
Set-Content -Path "app.js" -Value $app
git add app.js
git commit -m "Initial app implementation" | Out-Null
$readme = @"
# Application
Version 1.0.0
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Add README" | Out-Null
# Create development branch
git checkout -b development | Out-Null
# Commit 1: Experimental feature (should NOT be cherry-picked)
$appWithExperimental = @"
class App {
constructor() {
this.version = '1.0.0';
this.experimentalMode = false;
}
start() {
console.log('App started');
if (this.experimentalMode) {
this.enableExperimentalFeatures();
}
}
enableExperimentalFeatures() {
console.log('Experimental features enabled');
}
}
module.exports = App;
"@
Set-Content -Path "app.js" -Value $appWithExperimental
git add app.js
git commit -m "Add experimental AI features" | Out-Null
# Commit 2: Security bug fix (SHOULD be cherry-picked)
$security = @"
class Security {
static sanitizeInput(input) {
// Remove potential XSS attacks
return input.replace(/[<>]/g, '');
}
static validateToken(token) {
if (!token || token.length < 32) {
throw new Error('Invalid security token');
}
return true;
}
}
module.exports = Security;
"@
Set-Content -Path "security.js" -Value $security
git add security.js
git commit -m "Fix security vulnerability in input validation" | Out-Null
# Commit 3: Another experimental feature (should NOT be cherry-picked)
$appWithMoreExperimental = @"
class App {
constructor() {
this.version = '1.0.0';
this.experimentalMode = false;
this.betaFeatures = [];
}
start() {
console.log('App started');
if (this.experimentalMode) {
this.enableExperimentalFeatures();
}
}
enableExperimentalFeatures() {
console.log('Experimental features enabled');
}
addBetaFeature(feature) {
this.betaFeatures.push(feature);
}
}
module.exports = App;
"@
Set-Content -Path "app.js" -Value $appWithMoreExperimental
git add app.js
git commit -m "Add beta features framework" | Out-Null
# Commit 4: Performance bug fix (SHOULD be cherry-picked)
$appWithPerformance = @"
class App {
constructor() {
this.version = '1.0.0';
this.experimentalMode = false;
this.betaFeatures = [];
this.cache = new Map();
}
start() {
console.log('App started');
if (this.experimentalMode) {
this.enableExperimentalFeatures();
}
}
enableExperimentalFeatures() {
console.log('Experimental features enabled');
}
addBetaFeature(feature) {
this.betaFeatures.push(feature);
}
getData(key) {
// Use cache to improve performance
if (this.cache.has(key)) {
return this.cache.get(key);
}
const data = this.fetchData(key);
this.cache.set(key, data);
return data;
}
fetchData(key) {
// Simulate data fetching
return { key: key, value: 'data' };
}
}
module.exports = App;
"@
Set-Content -Path "app.js" -Value $appWithPerformance
git add app.js
git commit -m "Fix performance issue with data caching" | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan
Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow
Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White
Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White
Write-Host "4. Switch to main branch: git checkout main" -ForegroundColor White
Write-Host "5. Cherry-pick ONLY the bug fix commits to main" -ForegroundColor White
Write-Host "6. Do NOT bring the experimental features to main" -ForegroundColor White
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the cherry-pick challenge solution.
.DESCRIPTION
Checks that the user successfully cherry-picked only the bug fix commits
to the main branch without merging the experimental features.
#>
Set-Location "challenge" -ErrorAction SilentlyContinue
# Check if challenge directory exists
if (-not (Test-Path "../verify.ps1")) {
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
exit 1
}
if (-not (Test-Path ".")) {
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
Set-Location ..
exit 1
}
Write-Host "Verifying your solution..." -ForegroundColor Cyan
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check current branch
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -ne "main") {
Write-Host "[FAIL] You should be on the 'main' branch." -ForegroundColor Red
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
Write-Host "Hint: Use 'git checkout main' to switch to main branch" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check if there's an ongoing cherry-pick
if (Test-Path ".git/CHERRY_PICK_HEAD") {
Write-Host "[FAIL] Cherry-pick is not complete. There may be unresolved conflicts." -ForegroundColor Red
Write-Host "Hint: Resolve any conflicts, then use:" -ForegroundColor Yellow
Write-Host " git add <file>" -ForegroundColor White
Write-Host " git cherry-pick --continue" -ForegroundColor White
Write-Host "Or abort with: git cherry-pick --abort" -ForegroundColor White
Set-Location ..
exit 1
}
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
$mainCommitCount = (git rev-list --count main 2>$null)
if ($mainCommitCount -ne 4) {
Write-Host "[FAIL] Expected 4 commits on main branch, found $mainCommitCount" -ForegroundColor Red
if ($mainCommitCount -lt 4) {
Write-Host "Hint: You should cherry-pick 2 bug fix commits to main" -ForegroundColor Yellow
} else {
Write-Host "Hint: You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
}
Write-Host "`nExpected commits on main:" -ForegroundColor Yellow
Write-Host " 1. Initial app implementation" -ForegroundColor White
Write-Host " 2. Add README" -ForegroundColor White
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
Write-Host " 4. Fix performance issue with data caching (cherry-picked)" -ForegroundColor White
Set-Location ..
exit 1
}
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
$mergeCommits = git log --merges --oneline main 2>$null
if ($mergeCommits) {
Write-Host "[FAIL] Found merge commits on main. You should use cherry-pick, not merge." -ForegroundColor Red
Write-Host "Hint: Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that security.js exists (from the security fix commit)
if (-not (Test-Path "security.js")) {
Write-Host "[FAIL] security.js not found on main branch." -ForegroundColor Red
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that security.js has the security fix
$securityContent = Get-Content "security.js" -Raw
if ($securityContent -notmatch "sanitizeInput") {
Write-Host "[FAIL] security.js is missing the sanitizeInput function." -ForegroundColor Red
Set-Location ..
exit 1
}
if ($securityContent -notmatch "validateToken") {
Write-Host "[FAIL] security.js is missing the validateToken function." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that app.js exists
if (-not (Test-Path "app.js")) {
Write-Host "[FAIL] app.js not found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that app.js has the performance fix (cache) but NOT experimental features
$appContent = Get-Content "app.js" -Raw
# Should have cache (from performance fix)
if ($appContent -notmatch "cache") {
Write-Host "[FAIL] app.js is missing the performance fix (cache)." -ForegroundColor Red
Write-Host "Hint: You need to cherry-pick the 'Fix performance issue' commit" -ForegroundColor Yellow
Set-Location ..
exit 1
}
if ($appContent -notmatch "getData") {
Write-Host "[FAIL] app.js is missing the getData method from performance fix." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should NOT have experimental features
if ($appContent -match "experimentalMode") {
Write-Host "[FAIL] app.js contains experimental features (experimentalMode)." -ForegroundColor Red
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
Write-Host " The experimental feature commits should stay on development branch only" -ForegroundColor Yellow
Set-Location ..
exit 1
}
if ($appContent -match "betaFeatures") {
Write-Host "[FAIL] app.js contains experimental features (betaFeatures)." -ForegroundColor Red
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
Set-Location ..
exit 1
}
if ($appContent -match "enableExperimentalFeatures") {
Write-Host "[FAIL] app.js contains experimental features (enableExperimentalFeatures)." -ForegroundColor Red
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check commit messages to verify cherry-picks
$commits = git log --pretty=format:"%s" main 2>$null
$commitArray = $commits -split "`n"
$hasSecurityFix = $false
$hasPerformanceFix = $false
foreach ($commit in $commitArray) {
if ($commit -match "security vulnerability") {
$hasSecurityFix = $true
}
if ($commit -match "performance issue") {
$hasPerformanceFix = $true
}
}
if (-not $hasSecurityFix) {
Write-Host "[FAIL] Security fix commit not found on main branch." -ForegroundColor Red
Write-Host "Hint: Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
Set-Location ..
exit 1
}
if (-not $hasPerformanceFix) {
Write-Host "[FAIL] Performance fix commit not found on main branch." -ForegroundColor Red
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Verify development branch still has all commits
$devCommitCount = (git rev-list --count development 2>$null)
if ($devCommitCount -ne 6) {
Write-Host "[FAIL] Development branch should still have 6 commits." -ForegroundColor Red
Write-Host "Found: $devCommitCount commits" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Success!
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
Write-Host "- Cherry-picked the security vulnerability fix to main" -ForegroundColor White
Write-Host "- Cherry-picked the performance issue fix to main" -ForegroundColor White
Write-Host "- Left experimental features on development branch only" -ForegroundColor White
Write-Host "- Kept development branch intact with all commits" -ForegroundColor White
Write-Host "`nPerfect use of cherry-pick!" -ForegroundColor Green
Write-Host "You selectively applied critical fixes without merging unfinished features.`n" -ForegroundColor Green
Write-Host "Try 'git log --oneline --graph --all' to see both branches." -ForegroundColor Cyan
Set-Location ..
exit 0