refactor: move modules into levels
This commit is contained in:
111
01_essentials/01-basics/README.md
Normal file
111
01_essentials/01-basics/README.md
Normal 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.
|
||||
29
01_essentials/01-basics/reset.ps1
Normal file
29
01_essentials/01-basics/reset.ps1
Normal 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"
|
||||
64
01_essentials/01-basics/setup.ps1
Normal file
64
01_essentials/01-basics/setup.ps1
Normal 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 ""
|
||||
90
01_essentials/01-basics/verify.ps1
Normal file
90
01_essentials/01-basics/verify.ps1
Normal 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
|
||||
}
|
||||
186
01_essentials/02-history/README.md
Normal file
186
01_essentials/02-history/README.md
Normal 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.
|
||||
26
01_essentials/02-history/reset.ps1
Normal file
26
01_essentials/02-history/reset.ps1
Normal 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"
|
||||
247
01_essentials/02-history/setup.ps1
Normal file
247
01_essentials/02-history/setup.ps1
Normal 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 ""
|
||||
102
01_essentials/02-history/verify.ps1
Normal file
102
01_essentials/02-history/verify.ps1
Normal 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
|
||||
}
|
||||
92
01_essentials/03-branching/README.md
Normal file
92
01_essentials/03-branching/README.md
Normal 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.
|
||||
26
01_essentials/03-branching/reset.ps1
Normal file
26
01_essentials/03-branching/reset.ps1
Normal 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"
|
||||
85
01_essentials/03-branching/setup.ps1
Normal file
85
01_essentials/03-branching/setup.ps1
Normal 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 ""
|
||||
105
01_essentials/03-branching/verify.ps1
Normal file
105
01_essentials/03-branching/verify.ps1
Normal 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
|
||||
}
|
||||
97
01_essentials/04-merging/README.md
Normal file
97
01_essentials/04-merging/README.md
Normal 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.
|
||||
26
01_essentials/04-merging/reset.ps1
Normal file
26
01_essentials/04-merging/reset.ps1
Normal 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"
|
||||
128
01_essentials/04-merging/setup.ps1
Normal file
128
01_essentials/04-merging/setup.ps1
Normal 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 ""
|
||||
140
01_essentials/04-merging/verify.ps1
Normal file
140
01_essentials/04-merging/verify.ps1
Normal 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
|
||||
}
|
||||
106
01_essentials/05-merge-conflicts/README.md
Normal file
106
01_essentials/05-merge-conflicts/README.md
Normal 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.
|
||||
22
01_essentials/05-merge-conflicts/reset.ps1
Normal file
22
01_essentials/05-merge-conflicts/reset.ps1
Normal 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"
|
||||
97
01_essentials/05-merge-conflicts/setup.ps1
Normal file
97
01_essentials/05-merge-conflicts/setup.ps1
Normal 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
|
||||
150
01_essentials/05-merge-conflicts/verify.ps1
Normal file
150
01_essentials/05-merge-conflicts/verify.ps1
Normal 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
|
||||
160
01_essentials/06-cherry-pick/README.md
Normal file
160
01_essentials/06-cherry-pick/README.md
Normal 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.
|
||||
22
01_essentials/06-cherry-pick/reset.ps1
Normal file
22
01_essentials/06-cherry-pick/reset.ps1
Normal 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"
|
||||
175
01_essentials/06-cherry-pick/setup.ps1
Normal file
175
01_essentials/06-cherry-pick/setup.ps1
Normal 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
|
||||
206
01_essentials/06-cherry-pick/verify.ps1
Normal file
206
01_essentials/06-cherry-pick/verify.ps1
Normal 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
|
||||
198
01_essentials/07-reset-vs-revert/README.md
Normal file
198
01_essentials/07-reset-vs-revert/README.md
Normal 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.
|
||||
22
01_essentials/07-reset-vs-revert/reset.ps1
Normal file
22
01_essentials/07-reset-vs-revert/reset.ps1
Normal 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"
|
||||
190
01_essentials/07-reset-vs-revert/setup.ps1
Normal file
190
01_essentials/07-reset-vs-revert/setup.ps1
Normal 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
|
||||
172
01_essentials/07-reset-vs-revert/verify.ps1
Normal file
172
01_essentials/07-reset-vs-revert/verify.ps1
Normal 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
|
||||
199
01_essentials/08-stash/README.md
Normal file
199
01_essentials/08-stash/README.md
Normal 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.
|
||||
22
01_essentials/08-stash/reset.ps1
Normal file
22
01_essentials/08-stash/reset.ps1
Normal 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"
|
||||
156
01_essentials/08-stash/setup.ps1
Normal file
156
01_essentials/08-stash/setup.ps1
Normal 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
|
||||
180
01_essentials/08-stash/verify.ps1
Normal file
180
01_essentials/08-stash/verify.ps1
Normal 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
|
||||
Reference in New Issue
Block a user