refactor: move modules into levels

This commit is contained in:
Bjarke Sporring
2026-01-07 17:59:02 +01:00
parent d7c146975d
commit cf073d569e
52 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,111 @@
# Module 01: Git Basics
## Learning Objectives
- Understand what a git repository is
- Learn the basic git workflow: modify → stage → commit
- Use `git status` to check repository state
- Use `git add` to stage changes
- Use `git commit` to save changes
## Challenge
In this challenge, you'll learn the fundamental git workflow.
### Setup
Run the setup script to prepare the challenge:
```powershell
.\setup.ps1
```
This will create a directory called `challenge` with some files that need to be committed.
### Your Task
Your goal is to commit both `welcome.txt` and `instructions.txt` to a git repository. Here's a suggested approach:
1. Navigate into the `challenge` directory: `cd challenge`
2. **Initialize a new git repository**: `git init` (this is your first step!)
3. Check the status of your repository: `git status`
4. Stage the files you want to commit: `git add welcome.txt` (or `git add .` to stage all files)
5. Create a commit: `git commit -m "Your commit message"`
6. Verify both files are committed: `git ls-tree -r HEAD --name-only`
**Important Notes**:
- The challenge directory is NOT a git repository until you run `git init`. This is intentional - you're learning to start from scratch!
- You can commit both files together in one commit, or separately in multiple commits - it's up to you!
- The verification script checks that both files are committed, not the specific commit messages or order
### Key Concepts
- **Repository**: A directory tracked by git, containing your project files and their history
- **Working Directory**: The files you see and edit
- **Staging Area (Index)**: A preparation area for your next commit
- **Commit**: A snapshot of your staged changes
### Useful Commands
```bash
git init # Initialize a new git repository
git status # Show the working tree status
git add <file> # Stage a specific file for commit
git add . # Stage all files in current directory
git commit -m "<message>" # Create a commit with a message
git ls-tree -r HEAD --name-only # List all files in the latest commit
git log # View commit history
```
### Verification
Once you think you've completed the challenge, run the verification script.
**Important:** Run this from the **module directory**, not the challenge directory.
```powershell
# If you're in the challenge directory, go back up:
cd ..
# Then verify:
.\verify.ps1
```
This will check if you've successfully completed all the steps.
### Troubleshooting
**Error: "fatal: unable to auto-detect email address"**
This means Git doesn't know who you are yet. You need to configure your name and email:
```powershell
git config user.name "Your Name"
git config user.email "your.email@example.com"
```
Then try your commit again. For more details, see the "Requirements" section in the main README.md.
**Error: "Not a git repository"**
Make sure you ran `git init` in the challenge directory. This creates a hidden `.git` folder that tracks your project.
**Can't find the challenge directory?**
Make sure you ran `.\setup.ps1` first from the module directory. This creates the `challenge/` folder.
**Where am I?**
Use `pwd` (Print Working Directory) to see your current location:
- If you're in something like `.../module-01-basics/challenge`, you're in the challenge directory
- If you're in something like `.../module-01-basics`, you're in the module directory
### Need to Start Over?
If you want to reset the challenge and start fresh, run:
```powershell
.\reset.ps1
```
This will remove your challenge directory and set up a new one.

View File

@@ -0,0 +1,29 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 01 challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1 to create a fresh challenge.
This is useful if you want to start over from scratch.
#>
Write-Host "Resetting Module 01: Git Basics Challenge..." -ForegroundColor Cyan
Write-Host ""
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "[SUCCESS] Challenge directory removed" -ForegroundColor Green
} else {
Write-Host "[INFO] No existing challenge directory found" -ForegroundColor Gray
}
Write-Host ""
Write-Host "Running setup script..." -ForegroundColor Cyan
Write-Host ""
# Run the setup script
& "$PSScriptRoot/setup.ps1"

View File

@@ -0,0 +1,64 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 01 challenge environment.
.DESCRIPTION
Creates a challenge directory with files that need to be committed to git.
The student will need to initialize the repository and make commits.
#>
Write-Host "Setting up Module 01: Git Basics Challenge..." -ForegroundColor Cyan
Write-Host ""
# Remove existing challenge directory if it exists
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 directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
# Create welcome.txt
$welcomeContent = @"
Welcome to Git Workshop!
This is your first challenge. You'll learn the basics of git:
- Initializing a repository
- Staging changes
- Creating commits
Good luck!
"@
Set-Content -Path "challenge/welcome.txt" -Value $welcomeContent
# Create instructions.txt
$instructionsContent = @"
Git Basics Instructions
========================
The basic git workflow follows these steps:
1. Make changes to files in your working directory
2. Stage the changes you want to commit (git add)
3. Commit the staged changes with a message (git commit)
This workflow allows you to carefully select which changes
to include in each commit, making your history clean and meaningful.
"@
Set-Content -Path "challenge/instructions.txt" -Value $instructionsContent
Write-Host ""
Write-Host "Setup complete!" -ForegroundColor Green
Write-Host ""
Write-Host "Next steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge"
Write-Host " 2. Read the README.md in the parent directory"
Write-Host " 3. Complete the challenge"
Write-Host " 4. Run ../verify.ps1 to check your solution"
Write-Host ""

View File

@@ -0,0 +1,90 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 01 challenge solution.
.DESCRIPTION
Checks if the student has correctly:
- Initialized a git repository
- Created at least one commit
- Committed both required files (welcome.txt and instructions.txt)
#>
Write-Host "Verifying Module 01: Git Basics Challenge..." -ForegroundColor Cyan
Write-Host ""
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] No git repository found. Did you run 'git init'?" -ForegroundColor Red
Set-Location ".."
exit 1
}
Write-Host "[PASS] Git repository initialized" -ForegroundColor Green
# Check if there are any commits
$commitCount = (git rev-list --all --count 2>$null)
if ($null -eq $commitCount -or $commitCount -eq 0) {
Write-Host "[FAIL] No commits found. Have you committed your changes?" -ForegroundColor Red
$allChecksPassed = $false
} else {
Write-Host "[PASS] Found $commitCount commit(s)" -ForegroundColor Green
}
# Check if both files are in the git history using git ls-tree
if ($commitCount -gt 0) {
$trackedFiles = git ls-tree -r HEAD --name-only 2>$null
if ($trackedFiles -match "welcome.txt") {
Write-Host "[PASS] welcome.txt is committed" -ForegroundColor Green
} else {
Write-Host "[FAIL] welcome.txt is not in the commit history" -ForegroundColor Red
$allChecksPassed = $false
}
if ($trackedFiles -match "instructions.txt") {
Write-Host "[PASS] instructions.txt is committed" -ForegroundColor Green
} else {
Write-Host "[FAIL] instructions.txt is not in the commit history" -ForegroundColor Red
$allChecksPassed = $false
}
}
# Check for uncommitted changes
$statusOutput = git status --porcelain 2>$null
if ($statusOutput) {
Write-Host "[INFO] You have uncommitted changes. This is OK, but make sure all required files are committed." -ForegroundColor Yellow
}
Set-Location ".."
Write-Host ""
if ($allChecksPassed) {
Write-Host "========================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! Challenge Complete! " -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host ""
Write-Host "You've mastered the basics of git!" -ForegroundColor Cyan
Write-Host "You can now move on to Module 02." -ForegroundColor Cyan
Write-Host ""
} else {
Write-Host "========================================" -ForegroundColor Red
Write-Host " Challenge Not Complete - Try Again! " -ForegroundColor Red
Write-Host "========================================" -ForegroundColor Red
Write-Host ""
Write-Host "Review the errors above and try again." -ForegroundColor Yellow
Write-Host "Hint: Check 'git log' and 'git status' to see what you've done." -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

@@ -0,0 +1,186 @@
# Module 02: Viewing History
## Learning Objectives
In this module, you will:
- Understand commit history and how to navigate it
- Use `git log` to view commit history with various formats
- Use `git show` to view specific commit details
- Use `git diff` to compare changes between commits
- Understand commit hashes and references
## Challenge
### Setup
Run the setup script to create your challenge environment:
```powershell
.\setup.ps1
```
This will create a `challenge/` directory with a Git repository that already has some commit history.
### Your Task
You'll explore an existing Git repository that contains multiple commits. Your goal is to use Git commands to discover information about the repository's history.
The setup script will create an `answers.md` file in the challenge directory with questions for you to answer. Fill in your answers directly in that file.
**Suggested Approach:**
1. Navigate to the challenge directory: `cd challenge`
2. Open `answers.md` to see the questions
3. View the commit history: `git log`
4. Try different log formats: `git log --oneline`, `git log --stat`
5. View specific commits: `git show <commit-hash>`
6. Compare commits: `git diff <commit1> <commit2>`
7. Fill in your answers in `answers.md`
> **Important Notes:**
> - You can use any Git commands you like to explore the repository
> - Fill in your answers directly in the `answers.md` file (there are placeholder sections for each answer)
> - Try different `git log` options to see which format you prefer
> - Commit hashes can be referenced by their full hash or just the first 7 characters
## Key Concepts
- **Commit Hash**: A unique identifier (SHA-1 hash) for each commit. You can use the full hash or just the first few characters.
- **Commit Message**: A description of what changed in that commit, written by the author.
- **Commit History**: The chronological record of all changes made to a repository.
- **HEAD**: A pointer to the current commit you're working from.
- **Diff**: A view showing the differences between two versions of files.
## Understanding Diff Output
When you run `git diff` between commits, the output can look confusing at first. Here's how to read it:
### Example Diff Output
```diff
diff --git a/app.py b/app.py
index 1a2b3c4..5d6e7f8 100644
--- a/app.py
+++ b/app.py
@@ -1,5 +1,7 @@
# app.py - Main application file
+from auth import login, logout
def main():
print("Welcome to My App!")
- # Application initialization code here
+ login("user", "password")
pass
```
### Breaking It Down
**1. File Header**
```diff
diff --git a/app.py b/app.py
```
- Shows which file is being compared
- `a/app.py` = old version (before)
- `b/app.py` = new version (after)
**2. Metadata**
```diff
index 1a2b3c4..5d6e7f8 100644
--- a/app.py
+++ b/app.py
```
- `---` indicates the old version
- `+++` indicates the new version
- The hashes (1a2b3c4, 5d6e7f8) are internal Git identifiers
**3. Change Location (Hunk Header)**
```diff
@@ -1,5 +1,7 @@
```
- `@@ -1,5 +1,7 @@` tells you where changes occurred
- `-1,5` = in the old file, starting at line 1, showing 5 lines
- `+1,7` = in the new file, starting at line 1, showing 7 lines
- The file grew by 2 lines (from 5 to 7)
**4. The Actual Changes**
Lines are prefixed with symbols:
- ` ` (space) = unchanged line (context)
- `-` (minus) = line removed from old version (shown in red in terminal)
- `+` (plus) = line added in new version (shown in green in terminal)
In our example:
```diff
# app.py - Main application file ← unchanged
+from auth import login, logout ← added (new)
def main(): ← unchanged
print("Welcome to My App!") ← unchanged
- # Application initialization code here ← removed (old)
+ login("user", "password") ← added (new)
pass ← unchanged
```
### Reading Multiple Files
If multiple files changed, you'll see multiple diff sections:
```diff
diff --git a/app.py b/app.py
[changes to app.py]
diff --git a/auth.py b/auth.py
[changes to auth.py]
```
### Pro Tips
- **Context lines**: Unchanged lines around changes help you understand where the change happened
- **Color coding**: In your terminal, deletions are usually red, additions are green
- **No newline warning**: If you see `\ No newline at end of file`, it means the file doesn't end with a newline character (usually not important for beginners)
- **Binary files**: For images or other binary files, Git just says "Binary files differ"
### Try It Yourself
In this module's challenge, you'll use:
```bash
git diff <commit1> <commit2> app.py
```
Pay attention to:
- Which lines were added (green, with `+`)
- Which lines were removed (red, with `-`)
- The surrounding context (white, with space)
## Useful Commands
```bash
git log # View commit history
git log --oneline # Compact one-line format
git log --stat # Show files changed in each commit
git log --graph # Show branch graph (more useful with branches)
git show <commit> # View specific commit details
git show <commit>:<file> # View a file from a specific commit
git diff <commit1> <commit2> # Compare two commits
git diff <commit> # Compare commit with current working directory
```
## Verification
Once you've filled in your answers in `answers.md`, verify your solution:
```powershell
.\verify.ps1
```
The verification script will check that your answers contain the expected information.
## 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.

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 02 challenge environment.
.DESCRIPTION
This script removes the existing challenge directory and runs
the setup script again to create a fresh challenge environment.
#>
Write-Host "`n=== Resetting Module 02 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
} else {
Write-Host "No existing challenge directory found." -ForegroundColor Cyan
}
Write-Host ""
Write-Host "----------------------------------------" -ForegroundColor Cyan
Write-Host ""
# Run setup script
& "$PSScriptRoot\setup.ps1"

View File

@@ -0,0 +1,247 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 02 challenge environment with commit history.
.DESCRIPTION
This script creates a challenge directory with a Git repository that
contains multiple commits for students to explore using git log and git diff.
#>
Write-Host "`n=== Setting up Module 02 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Configure git for this repository
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Commit 1: Initial project structure
Write-Host "Creating initial project structure..." -ForegroundColor Green
$appContent = @"
# app.py - Main application file
def main():
print("Welcome to My App!")
# Application initialization code here
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Initial project structure" | Out-Null
# Commit 2: Add user authentication
Write-Host "Adding user authentication..." -ForegroundColor Green
$authContent = @"
# auth.py - Authentication module
def login(username, password):
# Authenticate user
print(f"Logging in user: {username}")
return True
def logout(username):
# Log out user
print(f"Logging out user: {username}")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
$appContent = @"
# app.py - Main application file
from auth import login, logout
def main():
print("Welcome to My App!")
# Application initialization code here
login("user", "password")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add user authentication" | Out-Null
# Commit 3: Add database connection
Write-Host "Adding database connection..." -ForegroundColor Green
$databaseContent = @"
# database.py - Database connection module
def connect():
# Connect to database
print("Connecting to database...")
return True
def disconnect():
# Disconnect from database
print("Disconnecting from database...")
return True
"@
Set-Content -Path "database.py" -Value $databaseContent
$appContent = @"
# app.py - Main application file
from auth import login, logout
from database import connect, disconnect
def main():
print("Welcome to My App!")
connect()
# Application initialization code here
login("user", "password")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add database connection" | Out-Null
# Commit 4: Fix authentication bug
Write-Host "Fixing authentication bug..." -ForegroundColor Green
$authContent = @"
# auth.py - Authentication module
def login(username, password):
# Authenticate user
if not username or not password:
print("Error: Username and password required")
return False
print(f"Logging in user: {username}")
return True
def logout(username):
# Log out user
print(f"Logging out user: {username}")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Fix authentication bug" | Out-Null
# Commit 5: Add user profile feature
Write-Host "Adding user profile feature..." -ForegroundColor Green
$profileContent = @"
# profile.py - User profile module
def get_profile(username):
# Get user profile
print(f"Fetching profile for: {username}")
return {"username": username, "email": f"{username}@example.com"}
def update_profile(username, data):
# Update user profile
print(f"Updating profile for: {username}")
return True
"@
Set-Content -Path "profile.py" -Value $profileContent
$appContent = @"
# app.py - Main application file
from auth import login, logout
from database import connect, disconnect
from profile import get_profile
def main():
print("Welcome to My App!")
connect()
# Application initialization code here
if login("user", "password"):
profile = get_profile("user")
print(f"User profile: {profile}")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add user profile feature" | Out-Null
# Create answers.md template
Write-Host "Creating answers.md template..." -ForegroundColor Green
$answersTemplate = @"
# Git History Exploration - Answers
Answer the following questions by exploring the Git repository history.
## Question 1: How many commits are in the repository?
**Your Answer:**
<!-- Write your answer here -->
## Question 2: What was the commit message for the third commit?
(Counting from the first/oldest commit)
**Your Answer:**
<!-- Write your answer here -->
## Question 3: Which file was modified in the "Fix authentication bug" commit?
**Your Answer:**
<!-- Write your answer here -->
## Question 4: What changes were made to app.py between the first and last commits?
Briefly describe the main changes you observe.
**Your Answer:**
<!-- Write your answer here -->
---
**Hints:**
- Use `git log` or `git log --oneline` to view commit history
- Use `git log --stat` to see which files were changed in each commit
- Use `git show <commit-hash>` to view details of a specific commit
- Use `git diff <commit1> <commit2> <file>` to compare changes between commits
"@
Set-Content -Path "answers.md" -Value $answersTemplate
# 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 "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. Open 'answers.md' to see the questions" -ForegroundColor White
Write-Host " 3. Explore the commit history using 'git log'" -ForegroundColor White
Write-Host " 4. Fill in your answers in 'answers.md'" -ForegroundColor White
Write-Host " 5. Run '..\verify.ps1' to check your answers" -ForegroundColor White
Write-Host ""

View File

@@ -0,0 +1,102 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 02 challenge solution.
.DESCRIPTION
This script checks that:
- The challenge directory exists
- A Git repository exists
- answers.txt exists with correct information about commit history
#>
Write-Host "`n=== Verifying Module 02 Solution ===" -ForegroundColor Cyan
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
Set-Location ..
exit 1
}
# Check if answers.md exists
if (-not (Test-Path "answers.md")) {
Write-Host "[FAIL] answers.md not found. Did you run setup.ps1?" -ForegroundColor Red
Write-Host "[HINT] The setup script should have created answers.md for you" -ForegroundColor Yellow
$allChecksPassed = $false
} else {
Write-Host "[PASS] answers.md exists" -ForegroundColor Green
# Read the answers file
$answers = Get-Content "answers.md" -Raw
$answersLower = $answers.ToLower()
# Check 1: Contains "5" or "five" for commit count
if ($answersLower -match "5|five") {
Write-Host "[PASS] Correct commit count found" -ForegroundColor Green
} else {
Write-Host "[FAIL] Commit count not found or incorrect" -ForegroundColor Red
Write-Host "[HINT] Use 'git log --oneline' to count commits easily" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 2: Contains "database" keyword for third commit
if ($answersLower -match "database") {
Write-Host "[PASS] Third commit message identified" -ForegroundColor Green
} else {
Write-Host "[FAIL] Third commit message not found" -ForegroundColor Red
Write-Host "[HINT] Use 'git log' to see commit messages in order" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 3: Contains "auth" keyword for file modified in bug fix
if ($answersLower -match "auth") {
Write-Host "[PASS] Correct file identified for bug fix commit" -ForegroundColor Green
} else {
Write-Host "[FAIL] File modified in bug fix commit not found" -ForegroundColor Red
Write-Host "[HINT] Use 'git log --stat' to see which files were changed in each commit" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 4: Some mention of changes (flexible check)
if ($answersLower -match "import|profile|function|added|login|connect") {
Write-Host "[PASS] Changes to app.py described" -ForegroundColor Green
} else {
Write-Host "[FAIL] Changes to app.py not described" -ForegroundColor Red
Write-Host "[HINT] Use 'git diff <first-commit> <last-commit> app.py' to see changes" -ForegroundColor Yellow
$allChecksPassed = $false
}
}
Set-Location ..
# Final summary
if ($allChecksPassed) {
Write-Host "`n" -NoNewline
Write-Host "=====================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Green
Write-Host "`nYou've successfully explored Git commit history!" -ForegroundColor Cyan
Write-Host "You now know how to:" -ForegroundColor Cyan
Write-Host " - View commit history with git log" -ForegroundColor White
Write-Host " - Find specific commits and their messages" -ForegroundColor White
Write-Host " - See which files changed in commits" -ForegroundColor White
Write-Host " - Compare changes between commits with git diff" -ForegroundColor White
Write-Host "`nReady for the next module!" -ForegroundColor Green
Write-Host ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

@@ -0,0 +1,92 @@
# Module 03: Branching Basics
## Learning Objectives
In this module, you will:
- Understand what a branch is in Git
- Create new branches using `git branch` or `git switch -c`
- Switch between branches using `git switch`
- View all branches with `git branch`
- Understand that branches allow parallel development
## Challenge
### Setup
Run the setup script to create your challenge environment:
```powershell
.\setup.ps1
```
This will create a `challenge/` directory with a Git repository that has some initial commits on the main branch.
### Your Task
Your goal is to create a feature branch, make commits on it, and understand how branches work independently.
**Steps:**
1. Create a new branch called `feature-login`
2. Switch to the new branch
3. Create a new file `login.py` with some login functionality
4. Commit the new file to your feature branch
5. Make another change to `login.py` and commit it
6. Switch back to the main branch and observe that `login.py` doesn't exist there
**Suggested Approach:**
1. Navigate to the challenge directory: `cd challenge`
2. View existing branches: `git branch`
3. Create and switch to new branch: `git switch -c feature-login`
4. Create `login.py` with any content you like
5. Stage and commit: `git add login.py` and `git commit -m "Add login functionality"`
6. Modify `login.py`, then commit again
7. Switch back to main: `git switch main`
8. Run `ls` and notice that `login.py` doesn't exist on main!
9. Switch back to feature-login: `git switch feature-login`
10. Run `ls` again and see that `login.py` is back!
> **Important Notes:**
> - Use `git switch` to change branches (modern Git command)
> - `git switch -c <name>` creates and switches in one command
> - Branches are independent - files in one branch don't affect another until you merge
> - You can switch between branches as many times as you want
## Key Concepts
- **Branch**: A lightweight movable pointer to a commit. Branches allow you to work on different features independently.
- **HEAD**: A pointer to the current branch you're working on. When you switch branches, HEAD moves.
- **main/master**: The default branch name in Git (main is the modern convention, master is older).
- **Feature Branch**: A branch created to develop a specific feature, separate from the main codebase.
## Useful Commands
```bash
git branch # List all branches (* shows current)
git branch <name> # Create a new branch
git switch <branch> # Switch to an existing branch
git switch -c <name> # Create and switch to new branch
git switch - # Switch back to previous branch
git branch -d <name> # Delete a branch (we won't use this yet)
```
## Verification
Once you've completed the challenge, verify your solution:
```powershell
.\verify.ps1
```
The verification script will check that you've created the branch, made commits, and that the branches are independent.
## 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.

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 03 challenge environment.
.DESCRIPTION
This script removes the existing challenge directory and runs
the setup script again to create a fresh challenge environment.
#>
Write-Host "`n=== Resetting Module 03 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
} else {
Write-Host "No existing challenge directory found." -ForegroundColor Cyan
}
Write-Host ""
Write-Host "----------------------------------------" -ForegroundColor Cyan
Write-Host ""
# Run setup script
& "$PSScriptRoot\setup.ps1"

View File

@@ -0,0 +1,85 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 03 challenge environment for learning about branches.
.DESCRIPTION
This script creates a challenge directory with a Git repository that
contains a couple of commits on the main branch. Students will create
a feature branch and make commits on it.
#>
Write-Host "`n=== Setting up Module 03 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Configure git for this repository
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Commit 1: Initial commit
Write-Host "Creating initial project..." -ForegroundColor Green
$mainContent = @"
# main.py - Main application file
def main():
print("Welcome to the Application!")
print("This is the main branch")
if __name__ == "__main__":
main()
"@
Set-Content -Path "main.py" -Value $mainContent
git add .
git commit -m "Initial commit" | Out-Null
# Commit 2: Add main functionality
Write-Host "Adding main functionality..." -ForegroundColor Green
$mainContent = @"
# main.py - Main application file
def main():
print("Welcome to the Application!")
print("This is the main branch")
run_application()
def run_application():
print("Application is running...")
print("Ready for new features!")
if __name__ == "__main__":
main()
"@
Set-Content -Path "main.py" -Value $mainContent
git add .
git commit -m "Add main functionality" | 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 "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. Create a new branch: git checkout -b feature-login" -ForegroundColor White
Write-Host " 3. Create login.py and commit it" -ForegroundColor White
Write-Host " 4. Make another commit on the feature branch" -ForegroundColor White
Write-Host " 5. Switch back to main: git checkout main" -ForegroundColor White
Write-Host " 6. Observe that login.py doesn't exist on main!" -ForegroundColor White
Write-Host " 7. Run '..\verify.ps1' to check your solution" -ForegroundColor White
Write-Host ""

View File

@@ -0,0 +1,105 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 03 challenge solution.
.DESCRIPTION
This script checks that:
- The challenge directory exists
- A Git repository exists
- The feature-login branch exists
- The branch has at least 2 new commits
- login.py exists in the feature branch but not in main
#>
Write-Host "`n=== Verifying Module 03 Solution ===" -ForegroundColor Cyan
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
Set-Location ..
exit 1
}
# Save current branch
$originalBranch = git branch --show-current 2>$null
# Check if feature-login branch exists
$branchExists = git branch --list "feature-login" 2>$null
if ($branchExists) {
Write-Host "[PASS] Branch 'feature-login' exists" -ForegroundColor Green
} else {
Write-Host "[FAIL] Branch 'feature-login' not found" -ForegroundColor Red
Write-Host "[HINT] Create the branch with: git checkout -b feature-login" -ForegroundColor Yellow
$allChecksPassed = $false
Set-Location ..
exit 1
}
# Check if feature-login has commits beyond main
$commitCount = git rev-list main..feature-login --count 2>$null
if ($commitCount -ge 2) {
Write-Host "[PASS] Branch 'feature-login' has $commitCount new commits" -ForegroundColor Green
} else {
Write-Host "[FAIL] Branch 'feature-login' needs at least 2 new commits (found: $commitCount)" -ForegroundColor Red
Write-Host "[HINT] Make sure you've committed login.py and made at least one more commit" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Switch to feature-login and check for login.py
git checkout feature-login 2>$null | Out-Null
if (Test-Path "login.py") {
Write-Host "[PASS] File 'login.py' exists in feature-login branch" -ForegroundColor Green
} else {
Write-Host "[FAIL] File 'login.py' not found in feature-login branch" -ForegroundColor Red
Write-Host "[HINT] Create login.py and commit it to the feature-login branch" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Switch to main and verify login.py doesn't exist
git checkout main 2>$null | Out-Null
if (-not (Test-Path "login.py")) {
Write-Host "[PASS] File 'login.py' does NOT exist in main branch (branches are independent!)" -ForegroundColor Green
} else {
Write-Host "[FAIL] File 'login.py' should not exist in main branch" -ForegroundColor Red
Write-Host "[HINT] Make sure you created login.py only on the feature-login branch" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Switch back to original branch
if ($originalBranch) {
git checkout $originalBranch 2>$null | Out-Null
}
Set-Location ..
# Final summary
if ($allChecksPassed) {
Write-Host "`n" -NoNewline
Write-Host "=====================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Green
Write-Host "`nYou've successfully learned about Git branches!" -ForegroundColor Cyan
Write-Host "You now understand:" -ForegroundColor Cyan
Write-Host " - How to create branches with git checkout -b" -ForegroundColor White
Write-Host " - How to switch between branches" -ForegroundColor White
Write-Host " - That branches are independent lines of development" -ForegroundColor White
Write-Host " - That files in one branch don't affect another" -ForegroundColor White
Write-Host "`nReady for the next module!" -ForegroundColor Green
Write-Host ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

@@ -0,0 +1,97 @@
# Module 04: Merging
## 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
- Use `git merge` to combine branches
## Challenge
### Setup
Run the setup script to create your challenge environment:
```powershell
.\setup.ps1
```
This will create a `challenge/` directory with a Git repository that has a main branch and a feature branch ready to merge.
### Your Task
This challenge has two parts that teach you about the two types of merges in Git:
**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)
**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:**
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`
> **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`
## 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.
## Useful Commands
```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)
```
## Verification
Once you've completed both merges, 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.
## 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.

View File

@@ -0,0 +1,26 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 04 challenge environment.
.DESCRIPTION
This script removes the existing challenge directory and runs
the setup script again to create a fresh challenge environment.
#>
Write-Host "`n=== Resetting Module 04 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
} else {
Write-Host "No existing challenge directory found." -ForegroundColor Cyan
}
Write-Host ""
Write-Host "----------------------------------------" -ForegroundColor Cyan
Write-Host ""
# Run setup script
& "$PSScriptRoot\setup.ps1"

View File

@@ -0,0 +1,128 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 04 challenge environment for learning about merging.
.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.
#>
Write-Host "`n=== Setting up Module 04 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Configure git for this repository
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
def api_handler():
print("API Handler initialized")
return True
"@
Set-Content -Path "api.py" -Value $apiContent
git add .
git commit -m "Initial API setup" | Out-Null
# Commit 2: Add more API functionality on main
$apiContent = @"
# api.py - API module
def api_handler():
print("API Handler initialized")
return True
def get_data():
print("Fetching data from API...")
return {"status": "ok"}
"@
Set-Content -Path "api.py" -Value $apiContent
git add .
git commit -m "Add get_data function" | 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
# Commit on feature-api: Add API 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"
}
return routes
"@
Set-Content -Path "api-routes.py" -Value $routesContent
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
# Switch back to main branch
Write-Host "Switching back to main branch..." -ForegroundColor Green
git checkout main 2>$null | 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 "`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 ""

View File

@@ -0,0 +1,140 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 04 challenge solution.
.DESCRIPTION
This script checks that:
- 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)
#>
Write-Host "`n=== Verifying Module 04 Solution ===" -ForegroundColor Cyan
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
Set-Location ..
exit 1
}
# Check current branch is main
$currentBranch = git branch --show-current 2>$null
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
$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
} else {
Write-Host "[INFO] Branch 'feature-api' 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
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
} 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
$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
$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)
$mergeCommits = git log --merges --oneline 2>$null
if ($mergeCommits) {
Write-Host "[PASS] Merge commit(s) found (three-way merge completed)" -ForegroundColor Green
} 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
$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
} 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
}
Set-Location ..
# Final summary
if ($allChecksPassed) {
Write-Host "`n" -NoNewline
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 "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 ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

@@ -0,0 +1,106 @@
# Module 06: Merge Conflicts
## Learning Objectives
By the end of this module, you will:
- Understand what merge conflicts are and why they occur
- Identify merge conflicts in your repository
- Read and interpret conflict markers
- Resolve merge conflicts manually
- Complete a merge after resolving conflicts
## Challenge Description
You have a repository with a main branch and a feature branch called `update-config`. Both branches have modified the same configuration file in different ways, creating a merge conflict.
Your task is to:
1. Attempt to merge the `update-config` branch into `main`
2. Identify and understand the merge conflict
3. Resolve the conflict by keeping both the timeout setting from `main` AND the debug setting from `update-config`
4. Complete the merge with an appropriate commit message
## Key Concepts
### What Are Merge Conflicts?
A merge conflict occurs when Git cannot automatically combine changes from two branches because they modify the same part of the same file in different ways. When this happens, Git pauses the merge and asks you to manually resolve the conflict.
### Conflict Markers
When a conflict occurs, Git adds special markers to the affected files:
```
<<<<<<< HEAD
Your current branch's changes
=======
The incoming branch's changes
>>>>>>> branch-name
```
- `<<<<<<< HEAD`: Marks the beginning of your current branch's version
- `=======`: Separates the two versions
- `>>>>>>> branch-name`: Marks the end of the incoming branch's version
### Resolving Conflicts
To resolve a conflict:
1. Open the conflicted file in your editor
2. Look for the conflict markers
3. Decide which changes to keep (you can keep one side, the other, or combine both)
4. Remove the conflict markers
5. Stage the resolved file with `git add`
6. Complete the merge with `git commit`
## Useful Commands
```bash
# Merge a branch (may result in conflicts)
git merge <branch-name>
# Check the status during a conflict
git status
# See which files have conflicts
git diff --name-only --diff-filter=U
# View the conflict in detail
git diff
# After resolving, stage the file
git add <file>
# Complete the merge
git commit
# If you want to abort the merge
git merge --abort
# Visualize the branch history
git log --oneline --graph --all
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- The merge conflict was resolved
- The merge was completed successfully
- Both configuration settings were preserved correctly
- The commit history shows the merge
## Tips
- Don't panic when you see a conflict - it's a normal part of working with Git
- Read the conflict markers carefully to understand both versions
- You can use `git status` to see which files have conflicts
- Always test your code after resolving conflicts to ensure it still works
- The conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`) must be completely removed from the final file
## What You'll Learn
Merge conflicts are inevitable when working on a team or even when working alone across multiple branches. Learning to resolve them confidently is an essential Git skill. This module teaches you the fundamentals of conflict resolution that you'll use throughout your development career.

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the merge conflicts 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,97 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the merge conflicts challenge environment.
.DESCRIPTION
Creates a Git repository with a merge conflict scenario involving
a configuration file that has been modified differently on two branches.
#>
# 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 config.json file
$initialConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000
}
}
"@
Set-Content -Path "config.json" -Value $initialConfig
git add config.json
git commit -m "Initial configuration" | Out-Null
# Create feature branch
git branch update-config | Out-Null
# On main branch: Add timeout setting
$mainConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"timeout": 5000
}
}
"@
Set-Content -Path "config.json" -Value $mainConfig
git add config.json
git commit -m "Add timeout configuration" | Out-Null
# Switch to feature branch: Add debug setting (conflicting change)
git checkout update-config | Out-Null
$featureConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"debug": true
}
}
"@
Set-Content -Path "config.json" -Value $featureConfig
git add config.json
git commit -m "Add debug mode configuration" | Out-Null
# Switch back to main branch
git checkout main | 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 now on the 'main' branch." -ForegroundColor Cyan
Write-Host "There is a branch called 'update-config' with conflicting changes." -ForegroundColor Cyan
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. Try to merge the 'update-config' branch into 'main'" -ForegroundColor White
Write-Host "3. Resolve the merge conflict in config.json" -ForegroundColor White
Write-Host "4. Keep BOTH the timeout setting AND the debug setting" -ForegroundColor White
Write-Host "5. Complete the merge" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -0,0 +1,150 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the merge conflicts challenge solution.
.DESCRIPTION
Checks that the user successfully resolved the merge conflict,
kept both configuration settings, and completed the merge.
#>
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 merge
if (Test-Path ".git/MERGE_HEAD") {
Write-Host "[FAIL] Merge is not complete. There are still unresolved conflicts." -ForegroundColor Red
Write-Host "Hint: After resolving conflicts in config.json, use:" -ForegroundColor Yellow
Write-Host " git add config.json" -ForegroundColor White
Write-Host " git commit" -ForegroundColor White
Set-Location ..
exit 1
}
# Check if config.json exists
if (-not (Test-Path "config.json")) {
Write-Host "[FAIL] config.json file not found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Read and parse config.json
try {
$configContent = Get-Content "config.json" -Raw
$config = $configContent | ConvertFrom-Json
} catch {
Write-Host "[FAIL] config.json is not valid JSON." -ForegroundColor Red
Write-Host "Make sure you removed all conflict markers (<<<<<<, ======, >>>>>>)" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check for conflict markers (in case user forgot to remove them)
if ($configContent -match "<<<<<<|======|>>>>>>") {
Write-Host "[FAIL] Conflict markers still present in config.json" -ForegroundColor Red
Write-Host "Remove all lines containing: <<<<<<, ======, >>>>>>" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check if both timeout and debug settings exist
$hasTimeout = $null -ne $config.app.timeout
$hasDebug = $null -ne $config.app.debug
if (-not $hasTimeout) {
Write-Host "[FAIL] The 'timeout' setting is missing from config.json" -ForegroundColor Red
Write-Host "Hint: The resolved file should include both timeout AND debug settings" -ForegroundColor Yellow
Set-Location ..
exit 1
}
if (-not $hasDebug) {
Write-Host "[FAIL] The 'debug' setting is missing from config.json" -ForegroundColor Red
Write-Host "Hint: The resolved file should include both timeout AND debug settings" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Validate the values
if ($config.app.timeout -ne 5000) {
Write-Host "[FAIL] The 'timeout' value should be 5000" -ForegroundColor Red
Set-Location ..
exit 1
}
if ($config.app.debug -ne $true) {
Write-Host "[FAIL] The 'debug' value should be true" -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that merge commit exists
$commitCount = (git rev-list --count HEAD 2>$null)
if ($commitCount -lt 4) {
Write-Host "[FAIL] Not enough commits. Expected at least 4 commits (including merge commit)." -ForegroundColor Red
Write-Host "Current commits: $commitCount" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that the latest commit is a merge commit (has 2 parents)
$parentCount = (git rev-list --parents -n 1 HEAD 2>$null).Split(' ').Count - 1
if ($parentCount -ne 2) {
Write-Host "[FAIL] The latest commit should be a merge commit." -ForegroundColor Red
Write-Host "Hint: Complete the merge with 'git commit' after resolving conflicts" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that both branches are merged
$branches = git branch --merged 2>$null
if ($branches -notmatch "update-config") {
Write-Host "[FAIL] The 'update-config' branch was not merged." -ForegroundColor Red
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 "- Identified the merge conflict" -ForegroundColor White
Write-Host "- Resolved the conflict by keeping both settings" -ForegroundColor White
Write-Host "- Completed the merge with a proper merge commit" -ForegroundColor White
Write-Host "`nYou now understand how to handle merge conflicts!" -ForegroundColor Green
Write-Host "This is a critical skill for collaborative development.`n" -ForegroundColor Green
Set-Location ..
exit 0

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 switch 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 switch 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 switch correct-branch
git cherry-pick <commit-hash>
# Go back and remove from wrong branch
git switch wrong-branch
git reset --hard HEAD~1
```
### Backporting
You need to apply a fix to an older release branch:
```bash
git switch 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,175 @@
#!/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:
def __init__(self):
self.version = '1.0.0'
def start(self):
print('App started')
"@
Set-Content -Path "app.py" -Value $app
git add app.py
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:
def __init__(self):
self.version = '1.0.0'
self.experimental_mode = False
def start(self):
print('App started')
if self.experimental_mode:
self.enable_experimental_features()
def enable_experimental_features(self):
print('Experimental features enabled')
"@
Set-Content -Path "app.py" -Value $appWithExperimental
git add app.py
git commit -m "Add experimental AI features" | Out-Null
# Commit 2: Security bug fix (SHOULD be cherry-picked)
$security = @"
import re
class Security:
@staticmethod
def sanitize_input(input_str):
# Remove potential XSS attacks
return re.sub(r'[<>]', '', input_str)
@staticmethod
def validate_token(token):
if not token or len(token) < 32:
raise ValueError('Invalid security token')
return True
"@
Set-Content -Path "security.py" -Value $security
git add security.py
git commit -m "Fix security vulnerability in input validation" | Out-Null
# Commit 3: Another experimental feature (should NOT be cherry-picked)
$appWithMoreExperimental = @"
class App:
def __init__(self):
self.version = '1.0.0'
self.experimental_mode = False
self.beta_features = []
def start(self):
print('App started')
if self.experimental_mode:
self.enable_experimental_features()
def enable_experimental_features(self):
print('Experimental features enabled')
def add_beta_feature(self, feature):
self.beta_features.append(feature)
"@
Set-Content -Path "app.py" -Value $appWithMoreExperimental
git add app.py
git commit -m "Add beta features framework" | Out-Null
# Commit 4: Performance bug fix (SHOULD be cherry-picked)
$appWithPerformance = @"
class App:
def __init__(self):
self.version = '1.0.0'
self.experimental_mode = False
self.beta_features = []
self.cache = {}
def start(self):
print('App started')
if self.experimental_mode:
self.enable_experimental_features()
def enable_experimental_features(self):
print('Experimental features enabled')
def add_beta_feature(self, feature):
self.beta_features.append(feature)
def get_data(self, key):
# Use cache to improve performance
if key in self.cache:
return self.cache[key]
data = self.fetch_data(key)
self.cache[key] = data
return data
def fetch_data(self, key):
# Simulate data fetching
return {'key': key, 'value': 'data'}
"@
Set-Content -Path "app.py" -Value $appWithPerformance
git add app.py
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.py exists (from the security fix commit)
if (-not (Test-Path "security.py")) {
Write-Host "[FAIL] security.py 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.py has the security fix
$securityContent = Get-Content "security.py" -Raw
if ($securityContent -notmatch "sanitize_input") {
Write-Host "[FAIL] security.py is missing the sanitize_input function." -ForegroundColor Red
Set-Location ..
exit 1
}
if ($securityContent -notmatch "validate_token") {
Write-Host "[FAIL] security.py is missing the validate_token function." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that app.py exists
if (-not (Test-Path "app.py")) {
Write-Host "[FAIL] app.py not found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that app.py has the performance fix (cache) but NOT experimental features
$appContent = Get-Content "app.py" -Raw
# Should have cache (from performance fix)
if ($appContent -notmatch "cache") {
Write-Host "[FAIL] app.py 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 "get_data") {
Write-Host "[FAIL] app.py is missing the get_data method from performance fix." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should NOT have experimental features
if ($appContent -match "experimental_mode") {
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -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 "beta_features") {
Write-Host "[FAIL] app.py contains experimental features (beta_features)." -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 "enable_experimental_features") {
Write-Host "[FAIL] app.py contains experimental features (enable_experimental_features)." -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

View File

@@ -0,0 +1,198 @@
# Module 10: Reset vs Revert
## Learning Objectives
By the end of this module, you will:
- Understand the difference between `git reset` and `git revert`
- Know when to use reset vs revert
- Understand the three modes of reset (--soft, --mixed, --hard)
- Safely undo commits in both local and shared branches
- Understand the risks of rewriting history
## Challenge Description
You have two branches with problematic commits:
1. **local-feature**: A private branch with bad commits that you haven't shared with anyone
2. **shared-feature**: A branch that has been pushed and others might be using
Your task is to:
1. Use `git reset` to remove the bad commit from the local-feature branch (safe because it's not shared)
2. Use `git revert` to undo the bad commit from the shared-feature branch (safe because it preserves history)
## Key Concepts
### Git Reset: Rewriting History
`git reset` moves the branch pointer backward, effectively erasing commits from history. It has three modes:
**--soft**: Moves HEAD, keeps changes staged
```bash
git reset --soft HEAD~1
# Commit is gone, but changes are staged and ready to commit again
```
**--mixed** (default): Moves HEAD, keeps changes unstaged
```bash
git reset HEAD~1
# Commit is gone, changes are in working directory but not staged
```
**--hard**: Moves HEAD, discards all changes
```bash
git reset --hard HEAD~1
# Commit is gone, changes are PERMANENTLY DELETED
```
### Git Revert: Safe Undo
`git revert` creates a NEW commit that undoes the changes from a previous commit. History is preserved.
```bash
git revert <commit-hash>
# Creates a new commit that reverses the specified commit
```
### Visual Comparison
**Before (both branches):**
```
A---B---C---D (D is the bad commit)
```
**After Reset (rewrites history):**
```
A---B---C
```
Commit D is gone. If anyone else had D, they'll have problems.
**After Revert (preserves history):**
```
A---B---C---D---E
```
E is a new commit that undoes D. Everyone can pull E safely.
### When to Use Each
**Use Reset when:**
- The commits haven't been pushed to a shared repository
- You're cleaning up local commits before pushing
- You made a mistake locally and want to start over
- You're working alone on a branch
**Use Revert when:**
- The commits have been pushed to a shared repository
- Others might have based work on these commits
- You want to preserve the complete history
- You need a safe, reversible undo operation
### The Golden Rule
**Never use `git reset` on commits that have been pushed to a shared branch!**
This will cause problems for anyone who has pulled those commits. Use `git revert` instead.
## Useful Commands
```bash
# Reset (for local-only commits)
git reset --soft HEAD~1 # Undo commit, keep changes staged
git reset HEAD~1 # Undo commit, keep changes unstaged
git reset --hard HEAD~1 # Undo commit, discard changes (DANGEROUS!)
# Reset to a specific commit
git reset --hard <commit-hash>
# Revert (for shared commits)
git revert <commit-hash>
git revert HEAD # Revert the last commit
# See what would be affected before resetting
git log --oneline
git diff HEAD~1
# If you reset by mistake, you can sometimes recover with reflog
git reflog
git reset --hard <commit-hash-from-reflog>
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- local-feature branch has the bad commit removed via reset
- shared-feature branch has the bad commit undone via revert
- shared-feature has a revert commit in the history
- All good commits are preserved
## Challenge Steps
1. Navigate to the challenge directory
2. You're on the local-feature branch with a bad commit
3. View commits: `git log --oneline`
4. Use `git reset --hard HEAD~1` to remove the bad commit
5. Switch to shared-feature: `git switch shared-feature`
6. View commits: `git log --oneline`
7. Find the hash of the "Add broken feature" commit
8. Use `git revert <commit-hash>` to undo it safely
9. Run the verification script
## Tips
- `HEAD~1` means "one commit before HEAD"
- `HEAD~2` means "two commits before HEAD"
- Always check `git log` before and after reset/revert
- `git reset --hard` is DANGEROUS - it permanently deletes uncommitted changes
- If you're unsure, use `git reset --soft` instead of `--hard`
- Revert will open an editor for the commit message - you can accept the default
- You can always use `.\reset.ps1` to start over if you make a mistake
## Common Mistakes to Avoid
### Mistake 1: Using Reset on Pushed Commits
```bash
# DON'T DO THIS if the commit was pushed!
git reset --hard HEAD~1
git push --force # This will cause problems for others
```
### Mistake 2: Using --hard Without Checking
```bash
# This DELETES your work permanently!
git reset --hard HEAD~1 # Uncommitted changes are GONE
```
### Mistake 3: Reverting the Wrong Commit
```bash
# Always double-check the commit hash
git log --oneline
git show <commit-hash> # Verify it's the right commit
git revert <commit-hash> # Now revert it
```
## Recovery from Mistakes
If you reset by accident, Git keeps a reflog:
```bash
# See recent HEAD movements
git reflog
# Find the commit you want to restore
# Output looks like:
# abc1234 HEAD@{0}: reset: moving to HEAD~1
# def5678 HEAD@{1}: commit: The commit you just lost
# Restore it
git reset --hard def5678
```
The reflog is your safety net, but it only keeps history for about 30 days.
## What You'll Learn
Understanding when to use reset versus revert is crucial for safe Git usage. Reset is powerful but dangerous when used on shared commits, while revert is always safe but creates additional history. Mastering both commands and knowing which to use in different situations is a hallmark of Git expertise. The rule is simple: if in doubt, use revert - it's always safe.

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the reset vs revert 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,190 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the reset vs revert challenge environment.
.DESCRIPTION
Creates a Git repository with two branches:
- local-feature: A private branch where reset should be used
- shared-feature: A pushed branch where revert should be used
#>
# 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
$app = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
"@
Set-Content -Path "calculator.py" -Value $app
git add calculator.py
git commit -m "Initial calculator implementation" | Out-Null
$readme = @"
# Calculator App
A simple calculator application.
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Add README" | Out-Null
# Create local-feature branch (private, not shared)
git checkout -b local-feature | Out-Null
$appWithMultiply = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
"@
Set-Content -Path "calculator.py" -Value $appWithMultiply
git add calculator.py
git commit -m "Add multiply function" | Out-Null
# Add a bad commit that should be removed with reset
$appWithBadCode = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
# BUG: This is broken and should never have been committed!
def divide(self, a, b):
# Forgot to check for division by zero
return a / b # This will raise ZeroDivisionError for zero!
"@
Set-Content -Path "calculator.py" -Value $appWithBadCode
git add calculator.py
git commit -m "Add broken divide function - DO NOT KEEP" | Out-Null
# Switch back to main for shared-feature branch
git checkout main | Out-Null
# Create shared-feature branch (simulating a pushed/shared branch)
git checkout -b shared-feature | Out-Null
$appWithPower = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def power(self, a, b):
return a ** b
"@
Set-Content -Path "calculator.py" -Value $appWithPower
git add calculator.py
git commit -m "Add power function" | Out-Null
# Add a bad commit that should be reverted (not reset)
$appWithBrokenFeature = @"
import math
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def power(self, a, b):
return a ** b
# BUG: This breaks the calculator!
def square_root(self, a):
# This implementation is wrong for negative numbers
return math.sqrt(a) # Raises ValueError for negative numbers without warning!
"@
Set-Content -Path "calculator.py" -Value $appWithBrokenFeature
git add calculator.py
git commit -m "Add broken feature" | Out-Null
# Add another good commit after the bad one (to show that revert preserves subsequent commits)
$appWithMoreFeatures = @"
import math
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def power(self, a, b):
return a ** b
# BUG: This breaks the calculator!
def square_root(self, a):
# This implementation is wrong for negative numbers
return math.sqrt(a) # Raises ValueError for negative numbers without warning!
def modulo(self, a, b):
return a % b
"@
Set-Content -Path "calculator.py" -Value $appWithMoreFeatures
git add calculator.py
git commit -m "Add modulo function" | Out-Null
# Switch to local-feature for the challenge start
git checkout local-feature | 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 have two branches with bad commits:" -ForegroundColor Cyan
Write-Host "`n1. local-feature (PRIVATE - not shared):" -ForegroundColor Yellow
Write-Host " - Has a broken divide function commit" -ForegroundColor White
Write-Host " - Safe to use 'git reset' to remove it" -ForegroundColor Green
Write-Host "`n2. shared-feature (PUBLIC - shared with team):" -ForegroundColor Yellow
Write-Host " - Has a broken feature commit" -ForegroundColor White
Write-Host " - Must use 'git revert' to undo it safely" -ForegroundColor Green
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. You're on local-feature - view commits: git log --oneline" -ForegroundColor White
Write-Host "3. Remove the bad commit with: git reset --hard HEAD~1" -ForegroundColor White
Write-Host "4. Switch to shared-feature: git checkout shared-feature" -ForegroundColor White
Write-Host "5. Find the 'Add broken feature' commit hash: git log --oneline" -ForegroundColor White
Write-Host "6. Revert it with: git revert <commit-hash>" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -0,0 +1,172 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the reset vs revert challenge solution.
.DESCRIPTION
Checks that the user correctly used reset on the local branch
and revert on the shared branch.
#>
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
}
# Verify local-feature branch
Write-Host "`nChecking local-feature branch..." -ForegroundColor Cyan
git checkout local-feature 2>$null | Out-Null
# Check commit count on local-feature (should be 3: initial + README + multiply)
$localCommitCount = (git rev-list --count local-feature 2>$null)
if ($localCommitCount -ne 3) {
Write-Host "[FAIL] local-feature should have 3 commits, found $localCommitCount" -ForegroundColor Red
if ($localCommitCount -gt 3) {
Write-Host "Hint: The bad commit should be removed. Use 'git reset --hard HEAD~1'" -ForegroundColor Yellow
} else {
Write-Host "Hint: You may have reset too far. Run ../reset.ps1 to start over." -ForegroundColor Yellow
}
Set-Location ..
exit 1
}
# Check that calculator.py exists
if (-not (Test-Path "calculator.py")) {
Write-Host "[FAIL] calculator.py not found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check calculator.py on local-feature
$localCalcContent = Get-Content "calculator.py" -Raw
# Should have multiply function
if ($localCalcContent -notmatch "multiply") {
Write-Host "[FAIL] calculator.py should have the multiply function." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should NOT have divide function (it was in the bad commit that should be reset)
if ($localCalcContent -match "divide") {
Write-Host "[FAIL] calculator.py should NOT have the divide function." -ForegroundColor Red
Write-Host "Hint: Use 'git reset --hard HEAD~1' to remove the bad commit" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check commit messages on local-feature
$localCommits = git log --pretty=format:"%s" local-feature 2>$null
if ($localCommits -match "broken divide") {
Write-Host "[FAIL] The 'broken divide' commit should be removed from local-feature." -ForegroundColor Red
Write-Host "Hint: Use 'git reset --hard HEAD~1' to remove it" -ForegroundColor Yellow
Set-Location ..
exit 1
}
Write-Host "[PASS] local-feature branch correctly reset!" -ForegroundColor Green
# Verify shared-feature branch
Write-Host "`nChecking shared-feature branch..." -ForegroundColor Cyan
git checkout shared-feature 2>$null | Out-Null
# Check commit count on shared-feature
# Should be 6: initial + README + power + broken feature + modulo + revert
$sharedCommitCount = (git rev-list --count shared-feature 2>$null)
if ($sharedCommitCount -ne 6) {
Write-Host "[FAIL] shared-feature should have 6 commits, found $sharedCommitCount" -ForegroundColor Red
if ($sharedCommitCount -lt 6) {
Write-Host "Hint: You should REVERT the bad commit, not reset it." -ForegroundColor Yellow
Write-Host " Revert creates a new commit that undoes the bad one." -ForegroundColor Yellow
Write-Host " Use: git revert <commit-hash>" -ForegroundColor Yellow
} else {
Write-Host "Hint: You should have exactly 6 commits after reverting." -ForegroundColor Yellow
}
Set-Location ..
exit 1
}
# Check that there's a revert commit
$sharedCommits = git log --pretty=format:"%s" shared-feature 2>$null
if ($sharedCommits -notmatch "Revert") {
Write-Host "[FAIL] No revert commit found on shared-feature." -ForegroundColor Red
Write-Host "Hint: Use 'git revert <commit-hash>' to undo the bad commit" -ForegroundColor Yellow
Write-Host " Find the hash with: git log --oneline" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check calculator.py on shared-feature
$sharedCalcContent = Get-Content "calculator.py" -Raw
# Should have power function
if ($sharedCalcContent -notmatch "power") {
Write-Host "[FAIL] calculator.py should have the power function." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should have modulo function (commits after the reverted one should be preserved)
if ($sharedCalcContent -notmatch "modulo") {
Write-Host "[FAIL] calculator.py should have the modulo function." -ForegroundColor Red
Write-Host "Hint: Reverting should preserve commits made after the bad one" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Should NOT have square_root function (it was in the bad commit that should be reverted)
if ($sharedCalcContent -match "square_root") {
Write-Host "[FAIL] calculator.py should NOT have the square_root function." -ForegroundColor Red
Write-Host "Hint: The 'Add broken feature' commit should be reverted" -ForegroundColor Yellow
Write-Host " Use: git revert <commit-hash-of-broken-feature>" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Verify the revert commit specifically reverted the "Add broken feature" commit
$revertCommitMessage = git log --grep="Revert" --pretty=format:"%s" -n 1 2>$null
if ($revertCommitMessage -notmatch "broken feature") {
Write-Host "[FAIL] The revert commit should mention 'broken feature'." -ForegroundColor Red
Write-Host "Hint: Make sure you reverted the correct commit (the one that added square_root)" -ForegroundColor Yellow
Set-Location ..
exit 1
}
Write-Host "[PASS] shared-feature branch correctly reverted!" -ForegroundColor Green
# 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 "- Used 'git reset' on local-feature (private branch)" -ForegroundColor White
Write-Host " Removed the bad commit completely from history" -ForegroundColor White
Write-Host "- Used 'git revert' on shared-feature (public branch)" -ForegroundColor White
Write-Host " Created a new commit that undoes the bad one" -ForegroundColor White
Write-Host " Preserved all history and subsequent commits" -ForegroundColor White
Write-Host "`nYou now understand when to use reset vs revert!" -ForegroundColor Green
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
Write-Host "- Reset rewrites history (use only on private commits)" -ForegroundColor White
Write-Host "- Revert preserves history (safe for shared commits)`n" -ForegroundColor White
Set-Location ..
exit 0

View File

@@ -0,0 +1,199 @@
# Module 11: Stash
## Learning Objectives
By the end of this module, you will:
- Understand what git stash is and when to use it
- Temporarily save work without committing
- Switch between branches without losing uncommitted changes
- Manage multiple stashes
- Apply and remove stashed changes
- Understand the difference between stash pop and stash apply
## Challenge Description
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
Your task is to:
1. Stash your incomplete work
2. Switch to the main branch
3. Fix the urgent bug and commit it
4. Return to your feature branch
5. Restore your stashed changes
6. Complete your feature and commit it
## Key Concepts
### What is Git Stash?
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
### When to Use Stash
Use stash when you need to:
- Switch branches but have uncommitted changes
- Pull updates from remote but have local modifications
- Quickly test something on a clean working directory
- Save work temporarily without creating a commit
- Context-switch between tasks
### Stash vs Commit
**Stash:**
- Temporary storage
- Not part of project history
- Can be applied to different branches
- Easy to discard if not needed
- Local only (not pushed to remote)
**Commit:**
- Permanent part of history
- Creates a snapshot in the project timeline
- Associated with a specific branch
- Should be meaningful and complete
- Can be pushed to remote
### The Stash Stack
Git stash works like a stack (LIFO - Last In, First Out):
```
stash@{0} <- Most recent stash (top of stack)
stash@{1}
stash@{2} <- Oldest stash
```
You can have multiple stashes and apply any of them.
## Useful Commands
```bash
# Stash current changes
git stash
git stash save "description" # With a descriptive message
# Stash including untracked files
git stash -u
# List all stashes
git stash list
# Apply most recent stash and remove it from stack
git stash pop
# Apply most recent stash but keep it in stack
git stash apply
# Apply a specific stash
git stash apply stash@{1}
# Show what's in a stash
git stash show
git stash show -p # Show full diff
# Drop (delete) a stash
git stash drop stash@{0}
# Clear all stashes
git stash clear
# Create a branch from a stash
git stash branch new-branch-name
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- The bug fix commit exists on main
- Your feature is completed on the feature branch
- Changes were properly stashed and restored
- No uncommitted changes remain
## Challenge Steps
1. Navigate to the challenge directory
2. You're on feature-login with uncommitted changes
3. Check status: `git status` (you'll see modified files)
4. Stash your changes: `git stash save "WIP: login feature"`
5. Verify working directory is clean: `git status`
6. Switch to main: `git switch main`
7. View the bug in app.js and fix it (remove the incorrect line)
8. Commit the fix: `git add app.js && git commit -m "Fix critical security bug"`
9. Switch back to feature: `git switch feature-login`
10. Restore your work: `git stash pop`
11. Complete the feature (the TODOs in login.js)
12. Commit your completed feature
13. Run verification
## Tips
- Always use `git stash save "message"` to describe what you're stashing
- Use `git stash list` to see all your stashes
- `git stash pop` applies and removes the stash (use this most often)
- `git stash apply` keeps the stash (useful if you want to apply it to multiple branches)
- Stashes are local - they don't get pushed to remote repositories
- You can stash even if you have changes to different files
- Stash before pulling to avoid merge conflicts
- Use `git stash show -p` to preview what's in a stash before applying
## Common Stash Scenarios
### Scenario 1: Quick Branch Switch
```bash
# Working on feature, need to switch to main
git stash
git switch main
# Do work on main
git switch feature
git stash pop
```
### Scenario 2: Pull with Local Changes
```bash
# You have local changes but need to pull
git stash
git pull
git stash pop
# Resolve any conflicts
```
### Scenario 3: Experimental Changes
```bash
# Try something experimental
git stash # Save current work
# Make experimental changes
# Decide you don't like it
git restore . # Discard experiment
git stash pop # Restore original work
```
### Scenario 4: Apply to Multiple Branches
```bash
# Same fix needed on multiple branches
git stash
git switch branch1
git stash apply
git commit -am "Apply fix"
git switch branch2
git stash apply
git commit -am "Apply fix"
git stash drop # Clean up when done
```
## Stash Conflicts
If applying a stash causes conflicts:
1. Git will mark the conflicts in your files
2. Resolve conflicts manually (like merge conflicts)
3. Stage the resolved files: `git add <file>`
4. The stash is automatically dropped after successful pop
5. If you used `apply`, manually drop it: `git stash drop`
## What You'll Learn
Git stash is an essential tool for managing context switches in your daily workflow. It lets you maintain a clean working directory while preserving incomplete work, making it easy to handle interruptions, urgent fixes, and quick branch switches. Mastering stash makes you more efficient and helps avoid the temptation to make "WIP" commits just to switch branches. Think of stash as your temporary workspace that follows you around.

View File

@@ -0,0 +1,22 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the stash 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,156 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the stash challenge environment.
.DESCRIPTION
Creates a Git repository with work in progress that needs to be stashed
while handling an urgent bug fix.
#>
# 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 application on main
$app = @"
class Application:
def __init__(self):
self.name = 'MyApp'
self.version = '1.0.0'
def start(self):
print('Application started')
self.authenticate()
def authenticate(self):
print('Authentication check')
return True
"@
Set-Content -Path "app.py" -Value $app
git add app.py
git commit -m "Initial application" | Out-Null
$readme = @"
# MyApp
A sample application for learning Git stash.
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Add README" | Out-Null
# Create feature branch for login feature
git checkout -b feature-login | Out-Null
# Start working on login feature
$loginInitial = @"
from datetime import datetime
class LoginService:
def __init__(self):
self.users = {}
def register(self, username, password):
if username in self.users:
raise ValueError('User already exists')
self.users[username] = {'password': password, 'created_at': datetime.now()}
return True
"@
Set-Content -Path "login.py" -Value $loginInitial
git add login.py
git commit -m "Start login service implementation" | Out-Null
# Add a critical bug to main branch (simulating a bug that was introduced)
git checkout main | Out-Null
$appWithBug = @"
class Application:
def __init__(self):
self.name = 'MyApp'
self.version = '1.0.0'
def start(self):
print('Application started')
self.authenticate()
def authenticate(self):
print('Authentication check')
# BUG: This allows unauthenticated access!
return True # Should check actual credentials
"@
Set-Content -Path "app.py" -Value $appWithBug
git add app.py
git commit -m "Update authentication (contains bug)" | Out-Null
# Go back to feature branch
git checkout feature-login | Out-Null
# Create work in progress (uncommitted changes)
$loginWIP = @"
from datetime import datetime
class LoginService:
def __init__(self):
self.users = {}
def register(self, username, password):
if username in self.users:
raise ValueError('User already exists')
self.users[username] = {'password': password, 'created_at': datetime.now()}
return True
# TODO: Complete this method
def login(self, username, password):
if username not in self.users:
raise ValueError('User not found')
# TODO: Verify password
# TODO: Return user session
# TODO: Add logout method
"@
Set-Content -Path "login.py" -Value $loginWIP
# Don't commit - leave as uncommitted changes
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nSituation:" -ForegroundColor Cyan
Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White
Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow
Write-Host "`nUrgent: A critical security bug was found in production (main branch)!" -ForegroundColor Red
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White
Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White
Write-Host "4. Switch to main: git checkout main" -ForegroundColor White
Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White
Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White
Write-Host "7. Switch back: git checkout feature-login" -ForegroundColor White
Write-Host "8. Restore your work: git stash pop" -ForegroundColor White
Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White
Write-Host "10. Commit your completed feature" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -0,0 +1,180 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the stash challenge solution.
.DESCRIPTION
Checks that the user successfully stashed work, fixed the bug on main,
and completed the feature on the feature branch.
#>
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 "feature-login") {
Write-Host "[FAIL] You should be on the 'feature-login' branch." -ForegroundColor Red
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
Write-Host "Hint: Switch back to feature-login after completing the challenge" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check for uncommitted changes on feature-login
$status = git status --porcelain 2>$null
if ($status) {
Write-Host "[FAIL] You have uncommitted changes on feature-login." -ForegroundColor Red
Write-Host "Hint: After restoring from stash, you should complete and commit the feature" -ForegroundColor Yellow
git status --short
Set-Location ..
exit 1
}
# Verify main branch has the security fix
Write-Host "`nChecking main branch for bug fix..." -ForegroundColor Cyan
git checkout main 2>$null | Out-Null
# Check for bug fix commit
$mainCommits = git log --pretty=format:"%s" main 2>$null
if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
Write-Host "[FAIL] No security bug fix commit found on main branch." -ForegroundColor Red
Write-Host "Hint: After stashing, switch to main and commit a bug fix" -ForegroundColor Yellow
git checkout feature-login 2>$null | Out-Null
Set-Location ..
exit 1
}
# Check that app.py has been fixed
if (-not (Test-Path "app.py")) {
Write-Host "[FAIL] app.py not found on main branch." -ForegroundColor Red
git checkout feature-login 2>$null | Out-Null
Set-Location ..
exit 1
}
$appContent = Get-Content "app.py" -Raw
# The bug was "return true" in authenticate - it should be fixed now
# We'll check that the buggy comment is gone or the implementation is improved
if ($appContent -match "allows unauthenticated access") {
Write-Host "[FAIL] The security bug comment still exists in app.py." -ForegroundColor Red
Write-Host "Hint: Remove the bug from app.py and commit the fix" -ForegroundColor Yellow
git checkout feature-login 2>$null | Out-Null
Set-Location ..
exit 1
}
Write-Host "[PASS] Security bug fixed on main!" -ForegroundColor Green
# Switch back to feature-login
Write-Host "`nChecking feature-login branch..." -ForegroundColor Cyan
git checkout feature-login 2>$null | Out-Null
# Check for completed feature commit
$featureCommits = git log --pretty=format:"%s" feature-login 2>$null
$commitCount = ($featureCommits -split "`n").Count
if ($commitCount -lt 3) {
Write-Host "[FAIL] Expected at least 3 commits on feature-login." -ForegroundColor Red
Write-Host "Hint: You should have the initial commits plus your completed feature commit" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that login.py exists
if (-not (Test-Path "login.py")) {
Write-Host "[FAIL] login.py not found on feature-login branch." -ForegroundColor Red
Set-Location ..
exit 1
}
$loginContent = Get-Content "login.py" -Raw
# Check that login method exists and is implemented
if ($loginContent -notmatch "login\(username, password\)") {
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that TODOs are completed (no TODO comments should remain)
if ($loginContent -match "TODO") {
Write-Host "[FAIL] login.py still contains TODO comments." -ForegroundColor Red
Write-Host "Hint: Complete all the TODOs in login.py before committing" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that password verification is implemented
if ($loginContent -notmatch "password") {
Write-Host "[FAIL] login method should verify the password." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that the feature has been committed (not just in working directory)
$lastCommit = git log -1 --pretty=format:"%s" 2>$null
if ($lastCommit -notmatch "login|feature|complete|implement") {
Write-Host "[FAIL] Your completed feature should be committed." -ForegroundColor Red
Write-Host "Last commit: $lastCommit" -ForegroundColor Yellow
Write-Host "Hint: After popping the stash and completing the TODOs, commit the feature" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that no stashes remain (optional check - they should have used pop)
$stashCount = (git stash list 2>$null | Measure-Object).Count
if ($stashCount -gt 0) {
Write-Host "[WARNING] You have $stashCount stash(es) remaining." -ForegroundColor Yellow
Write-Host "Hint: Use 'git stash pop' instead of 'git stash apply' to automatically remove the stash" -ForegroundColor Yellow
Write-Host " Or clean up with 'git stash drop'" -ForegroundColor Yellow
# Don't fail on this, just warn
}
# Verify the login implementation is complete
if ($loginContent -notmatch "logout|session") {
Write-Host "[PARTIAL] login.py is missing logout or session functionality." -ForegroundColor Yellow
Write-Host "Consider adding logout method for a complete implementation" -ForegroundColor Yellow
# Don't fail - this is just a suggestion
}
# 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 "- Stashed your work in progress" -ForegroundColor White
Write-Host "- Switched to main branch cleanly" -ForegroundColor White
Write-Host "- Fixed the critical security bug" -ForegroundColor White
Write-Host "- Returned to your feature branch" -ForegroundColor White
Write-Host "- Restored your stashed work" -ForegroundColor White
Write-Host "- Completed and committed the login feature" -ForegroundColor White
Write-Host "`nYou now understand how to use git stash!" -ForegroundColor Green
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
Write-Host "Stash lets you save incomplete work without committing," -ForegroundColor White
Write-Host "making it easy to context-switch and handle interruptions.`n" -ForegroundColor White
Set-Location ..
exit 0