refactor: rename 01_essentials to 01-essentials
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 # ADVANCED: 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
|
||||
}
|
||||
225
01-essentials/02-history/README.md
Normal file
225
01-essentials/02-history/README.md
Normal file
@@ -0,0 +1,225 @@
|
||||
# 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
|
||||
- Use `git diff --staged` to view changes ready to be committed
|
||||
- Understand commit hashes and references
|
||||
- Discover how `git diff` reveals changes not visible in current files
|
||||
|
||||
## 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, including:
|
||||
- Viewing commit history
|
||||
- Examining changes between specific commits
|
||||
- Understanding staged changes
|
||||
- **Finding a secret code hidden in the commit history!** (Only discoverable by using `git diff`)
|
||||
|
||||
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` or `git log --oneline`
|
||||
4. Check repository status: `git status`
|
||||
5. View staged changes: `git diff --staged`
|
||||
6. Try different log formats: `git log --stat`, `git log --graph`
|
||||
7. View specific commits: `git show <commit-hash>`
|
||||
8. Compare specific commits: `git diff <commit1> <commit2> <file>`
|
||||
9. 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)
|
||||
> - Commit hashes can be referenced by their full hash or just the first 7 characters
|
||||
> - Notice that one file is already staged - use `git diff --staged` to see what it contains
|
||||
|
||||
## 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.
|
||||
- **Staging Area**: Where changes wait before being committed. Use `git diff --staged` to see what's ready to commit.
|
||||
- **Working Directory vs Staged vs Committed**:
|
||||
- Working Directory: Files you're currently editing
|
||||
- Staged (Index): Changes marked for the next commit (via `git add`)
|
||||
- Committed: Changes permanently saved in history
|
||||
|
||||
## 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
|
||||
# See what's staged for the next commit
|
||||
git diff --staged
|
||||
|
||||
# Compare changes between specific commits
|
||||
git diff <commit2> <commit4> app.py
|
||||
```
|
||||
|
||||
Pay attention to:
|
||||
- Which lines were added (green, with `+`)
|
||||
- Which lines were removed (red, with `-`)
|
||||
- The surrounding context (white, with space)
|
||||
- How `git diff --staged` shows only changes ready to commit
|
||||
|
||||
## Useful Commands
|
||||
|
||||
### Viewing History
|
||||
|
||||
```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
|
||||
```
|
||||
|
||||
### Comparing Changes with git diff
|
||||
|
||||
```bash
|
||||
# Compare commits
|
||||
git diff <commit1> <commit2> # Compare two commits
|
||||
git diff <commit1> <commit2> <file> # Compare specific file between commits
|
||||
git diff <commit> # Compare commit with current working directory
|
||||
|
||||
# Compare staged changes
|
||||
git diff --staged # Show changes in staging area (ready to commit)
|
||||
git diff --cached # Same as --staged (alternative syntax)
|
||||
git diff # Show unstaged changes in working directory
|
||||
git diff HEAD # Show all changes (staged + unstaged) vs last commit
|
||||
```
|
||||
|
||||
**When to use each `git diff` variant:**
|
||||
- `git diff` - See what you've changed but haven't staged yet
|
||||
- `git diff --staged` - Review what you're about to commit
|
||||
- `git diff HEAD` - See all your changes since the last commit
|
||||
- `git diff <commit1> <commit2>` - Compare any two points in history
|
||||
|
||||
## 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"
|
||||
367
01-essentials/02-history/setup.ps1
Normal file
367
01-essentials/02-history/setup.ps1
Normal file
@@ -0,0 +1,367 @@
|
||||
#!/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
|
||||
# SECRET_CODE: UNICORN
|
||||
|
||||
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
|
||||
|
||||
# Remove the secret code from database.py
|
||||
$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
|
||||
|
||||
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 a staged change scenario
|
||||
Write-Host "Creating staged changes for exploration..." -ForegroundColor Green
|
||||
$configContent = @"
|
||||
# config.py - Configuration settings
|
||||
|
||||
DEBUG_MODE = False
|
||||
DATABASE_URL = "sqlite:///app.db"
|
||||
"@
|
||||
Set-Content -Path "config.py" -Value $configContent
|
||||
git add config.py
|
||||
|
||||
# Create answers.md template
|
||||
Write-Host "Creating answers.md template..." -ForegroundColor Green
|
||||
$answersTemplate = @"
|
||||
# Git History Exploration - Answers
|
||||
|
||||
Welcome! Answer the following questions by exploring the Git repository history using Git commands.
|
||||
|
||||
**Instructions:**
|
||||
- Replace the "Write your answer here" comments with your actual answers
|
||||
- You can use any Git commands to explore the repository
|
||||
- The hints section at the bottom provides helpful commands
|
||||
|
||||
---
|
||||
|
||||
## Question 1: How many commits are in the repository?
|
||||
|
||||
**Suggested commands:**
|
||||
``````bash
|
||||
git log --oneline
|
||||
# Or count them with:
|
||||
git rev-list --count HEAD
|
||||
``````
|
||||
|
||||
**Your Answer:**
|
||||
|
||||
<!-- Write your answer here -->
|
||||
|
||||
---
|
||||
|
||||
## Question 2: What was the commit message for the third commit?
|
||||
|
||||
**Note:** Count from the first/oldest commit (the one at the bottom of `git log`)
|
||||
|
||||
**Suggested commands:**
|
||||
``````bash
|
||||
git log --oneline --reverse # Shows oldest first
|
||||
git log # Shows newest first
|
||||
``````
|
||||
|
||||
**Your Answer:**
|
||||
|
||||
<!-- Write your answer here -->
|
||||
|
||||
---
|
||||
|
||||
## Question 3: Which files were modified in the "Fix authentication bug" commit?
|
||||
|
||||
**Note:** There are multiple files - list all of them!
|
||||
|
||||
**Suggested commands:**
|
||||
``````bash
|
||||
git log --stat # Shows files changed in each commit
|
||||
git show <commit-hash> --name-only # Shows only filenames for a commit
|
||||
``````
|
||||
|
||||
**Your Answer:**
|
||||
|
||||
<!-- Write your answer here (list all files) -->
|
||||
|
||||
---
|
||||
|
||||
## Question 4: What new file is currently staged (ready to be committed)?
|
||||
|
||||
**Suggested commands:**
|
||||
``````bash
|
||||
git status # Shows staged, unstaged, and untracked files
|
||||
git diff --staged # Shows content of staged changes
|
||||
``````
|
||||
|
||||
**Your Answer:**
|
||||
|
||||
<!-- Write your answer here -->
|
||||
|
||||
---
|
||||
|
||||
## Question 5: Find the secret code hidden in the commit history! 🔍
|
||||
|
||||
**The Challenge:**
|
||||
A secret code was added in one commit and then removed in a later commit. It doesn't exist in the current files - you can ONLY find it by comparing commits with `git diff`.
|
||||
|
||||
**Hint:** The secret code is in `database.py` and exists in the third commit but was removed in the fourth commit.
|
||||
|
||||
**Suggested commands:**
|
||||
``````bash
|
||||
# First, get the commit hashes
|
||||
git log --oneline
|
||||
|
||||
# Then compare the third and fourth commits
|
||||
git diff <commit3-hash> <commit4-hash> database.py
|
||||
# Look for lines that were removed (marked with - in red)
|
||||
``````
|
||||
|
||||
**Your Answer (what is the secret code?):**
|
||||
|
||||
<!-- Write your answer here -->
|
||||
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference - Useful Commands
|
||||
|
||||
**Viewing History:**
|
||||
``````bash
|
||||
git log # View commit history (newest first)
|
||||
git log --oneline # Compact one-line format
|
||||
git log --reverse # Show oldest commits first
|
||||
git log --stat # Show files changed in each commit
|
||||
git log --graph --all # Visual branch graph
|
||||
``````
|
||||
|
||||
**Viewing Specific Commits:**
|
||||
``````bash
|
||||
git show <commit-hash> # View commit details
|
||||
git show <commit-hash> --stat # Show files changed
|
||||
git show <commit-hash>:file.txt # View file from specific commit
|
||||
``````
|
||||
|
||||
**Comparing Changes:**
|
||||
``````bash
|
||||
git diff <commit1> <commit2> # Compare two commits
|
||||
git diff <commit1> <commit2> file # Compare specific file
|
||||
git diff --staged # Show staged changes
|
||||
git diff # Show unstaged changes
|
||||
git diff HEAD # Show all changes since last commit
|
||||
``````
|
||||
|
||||
**Repository Status:**
|
||||
``````bash
|
||||
git status # Show working tree status
|
||||
git ls-files # List all tracked files
|
||||
``````
|
||||
|
||||
**Pro Tip:** You can use just the first 7 characters of a commit hash (e.g., `a1b2c3d` instead of the full hash)
|
||||
|
||||
---
|
||||
|
||||
When you're done, run ``..\verify.ps1`` to check your answers!
|
||||
"@
|
||||
|
||||
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 ""
|
||||
120
01-essentials/02-history/verify.ps1
Normal file
120
01-essentials/02-history/verify.ps1
Normal file
@@ -0,0 +1,120 @@
|
||||
#!/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 both "auth" and "database" keywords for files modified in bug fix
|
||||
$hasAuth = $answersLower -match "auth"
|
||||
$hasDatabase = $answersLower -match "database"
|
||||
|
||||
if ($hasAuth -and $hasDatabase) {
|
||||
Write-Host "[PASS] Both files identified for bug fix commit" -ForegroundColor Green
|
||||
} elseif ($hasAuth -or $hasDatabase) {
|
||||
Write-Host "[PARTIAL] Only one file found - there are TWO files modified in this commit" -ForegroundColor Yellow
|
||||
Write-Host "[HINT] Use 'git log --stat' or 'git show <commit-hash> --name-only' to see ALL files changed" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
Write-Host "[FAIL] Files 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: Contains "config" keyword for staged file
|
||||
if ($answersLower -match "config") {
|
||||
Write-Host "[PASS] Staged file identified" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Staged file not identified" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use 'git status' or 'git diff --staged' to see staged changes" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check 5: Contains "unicorn" for the secret code
|
||||
if ($answersLower -match "unicorn") {
|
||||
Write-Host "[PASS] Secret code found!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Secret code not found" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use 'git diff <commit3> <commit4> database.py' to find the secret code" -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 " - Check staged changes with git diff --staged" -ForegroundColor White
|
||||
Write-Host " - Compare changes between specific 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
|
||||
}
|
||||
839
01-essentials/03-branching-and-merging/README.md
Normal file
839
01-essentials/03-branching-and-merging/README.md
Normal file
@@ -0,0 +1,839 @@
|
||||
# Module 03: Branching and Merging
|
||||
|
||||
## About This Module
|
||||
|
||||
Welcome to Module 03! This module is different from the others - it uses a **checkpoint system** that lets you work through three related concepts in one continuous repository:
|
||||
|
||||
1. **Branching Basics** - Create and work with feature branches
|
||||
2. **Merging Branches** - Combine branches together
|
||||
3. **Resolving Merge Conflicts** - Fix conflicts when Git can't merge automatically
|
||||
|
||||
Instead of three separate modules, you'll progress through checkpoints in a single Git repository, building on each previous section. You can jump between checkpoints, skip ahead, or restart any section at any time!
|
||||
|
||||
### Why Checkpoints?
|
||||
|
||||
Branching, merging, and conflict resolution are naturally connected - you can't understand merging without branches, and you can't master conflicts without trying to merge. The checkpoint system lets you learn these concepts as a continuous workflow, just like real development.
|
||||
|
||||
## Quick Start
|
||||
|
||||
### Setup
|
||||
|
||||
Create the challenge environment:
|
||||
|
||||
```bash
|
||||
.\setup.ps1
|
||||
```
|
||||
|
||||
This creates a complete Git repository with all checkpoints ready.
|
||||
|
||||
### Working with Checkpoints
|
||||
|
||||
**View available checkpoints:**
|
||||
```bash
|
||||
.\reset.ps1
|
||||
```
|
||||
|
||||
**Jump to a specific checkpoint:**
|
||||
```bash
|
||||
.\reset.ps1 start # Checkpoint 1: Branching Basics
|
||||
.\reset.ps1 merge # Checkpoint 2: Merging Branches
|
||||
.\reset.ps1 merge-conflict # Checkpoint 3: Resolving Conflicts
|
||||
```
|
||||
|
||||
**Verify your progress:**
|
||||
```bash
|
||||
.\verify.ps1 # Verify all checkpoints complete
|
||||
.\verify.ps1 start # Verify Checkpoint 1 only
|
||||
.\verify.ps1 merge # Verify Checkpoint 2 only
|
||||
.\verify.ps1 merge-conflict # Verify Checkpoint 3 only
|
||||
```
|
||||
|
||||
### Recommended Workflow
|
||||
|
||||
Complete checkpoints in order:
|
||||
1. Start with Checkpoint 1 (Branching Basics)
|
||||
2. Progress to Checkpoint 2 (Merging)
|
||||
3. Finish with Checkpoint 3 (Merge Conflicts)
|
||||
|
||||
Or skip to any checkpoint if you already know the earlier concepts!
|
||||
|
||||
---
|
||||
|
||||
## Checkpoint 1: Branching Basics
|
||||
|
||||
### Learning Objectives
|
||||
|
||||
- Understand what a branch is in Git
|
||||
- Create new branches with `git switch -c`
|
||||
- Switch between branches with `git switch`
|
||||
- View all branches with `git branch`
|
||||
- Understand that branches are independent lines of development
|
||||
|
||||
### Your Task
|
||||
|
||||
Create a feature branch called `feature-login`, add a `login.py` file, and make commits to demonstrate that branches allow independent development.
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Navigate to the challenge directory: `cd challenge`
|
||||
2. Create a new branch: `git switch -c feature-login`
|
||||
3. Create a file: `login.py` (with any content you like)
|
||||
4. Commit your file: `git add login.py && git commit -m "Add login module"`
|
||||
5. Make another change to `login.py` and commit it
|
||||
6. Switch back to main: `git switch main`
|
||||
7. Notice that `login.py` doesn't exist on main!
|
||||
8. Switch back to your feature: `git switch feature-login`
|
||||
9. Notice that `login.py` exists again!
|
||||
|
||||
**Verify:** Run `.\verify.ps1 start` to check your solution.
|
||||
|
||||
### What is a Branch?
|
||||
|
||||
A **branch** in Git is an independent line of development. Think of it as a parallel universe for your code - you can make changes without affecting the main timeline.
|
||||
|
||||
**Visual representation:**
|
||||
|
||||
```
|
||||
main: A---B---C
|
||||
\
|
||||
feature-login: D---E
|
||||
```
|
||||
|
||||
- Both branches share commits A and B
|
||||
- Branch `main` continues with commit C
|
||||
- Branch `feature-login` goes in a different direction with commits D and E
|
||||
- Changes in one branch don't affect the other!
|
||||
|
||||
### Why Use Branches?
|
||||
|
||||
Branches let you:
|
||||
- **Experiment safely** - Try new ideas without breaking main
|
||||
- **Work in parallel** - Multiple features can be developed simultaneously
|
||||
- **Organize work** - Each feature/fix gets its own branch
|
||||
- **Collaborate better** - Team members work on separate branches
|
||||
|
||||
### Key Concepts
|
||||
|
||||
- **Branch**: A lightweight movable pointer to a commit
|
||||
- **HEAD**: A pointer showing which branch you're currently on
|
||||
- **main**: The default branch (formerly called "master")
|
||||
- **Feature branch**: A branch created for a specific feature or task
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# View all branches (current branch marked with *)
|
||||
git branch
|
||||
|
||||
# Create a new branch
|
||||
git branch feature-login
|
||||
|
||||
# Switch to a branch
|
||||
git switch feature-login
|
||||
|
||||
# Create AND switch in one command
|
||||
git switch -c feature-login
|
||||
|
||||
# Switch back to previous branch
|
||||
git switch -
|
||||
|
||||
# Delete a branch (only if merged)
|
||||
git branch -d feature-login
|
||||
|
||||
# Force delete a branch
|
||||
git branch -D feature-login
|
||||
```
|
||||
|
||||
### Understanding HEAD
|
||||
|
||||
`HEAD` is Git's way of saying "you are here." It points to your current branch.
|
||||
|
||||
When you run `git switch main`, HEAD moves to point to main.
|
||||
When you run `git switch feature-login`, HEAD moves to point to feature-login.
|
||||
|
||||
---
|
||||
|
||||
## Checkpoint 2: Merging Branches
|
||||
|
||||
**Prerequisites:** Complete Checkpoint 1 OR run `.\reset.ps1 merge`
|
||||
|
||||
### Learning Objectives
|
||||
|
||||
- Understand what merging means in Git
|
||||
- Merge a feature branch back into main
|
||||
- Use `git merge` to combine branches
|
||||
- Understand merge commits
|
||||
- Visualize merged branches with `git log --graph`
|
||||
|
||||
### Your Task
|
||||
|
||||
You've completed work on your `feature-login` branch. Now merge it back into `main` to include the login functionality in your main codebase.
|
||||
|
||||
**Scenario:**
|
||||
- You created the `feature-login` branch and added login functionality
|
||||
- Meanwhile, development continued on `main` (README and app.py were added)
|
||||
- Now you need to merge your login feature into main
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Make sure you're in the challenge directory: `cd challenge`
|
||||
2. Check which branch you're on: `git branch`
|
||||
3. Switch to main if needed: `git switch main`
|
||||
4. View the branch structure: `git log --oneline --graph --all`
|
||||
5. Merge feature-login into main: `git merge feature-login`
|
||||
6. View the result: `git log --oneline --graph --all`
|
||||
|
||||
**Verify:** Run `.\verify.ps1 merge` to check your solution.
|
||||
|
||||
### What is Merging?
|
||||
|
||||
**Merging** is the process of combining changes from one branch into another.
|
||||
|
||||
Think of it like combining two streams into one river - all the water (code) flows together.
|
||||
|
||||
#### Before Merging
|
||||
|
||||
You have two branches with different work:
|
||||
|
||||
```
|
||||
main: A---B---C---D
|
||||
\
|
||||
feature-login: E---F
|
||||
```
|
||||
|
||||
- Main branch progressed with commits C and D
|
||||
- Feature-login branch has commits E and F
|
||||
- They diverged at commit B
|
||||
|
||||
#### After Merging
|
||||
|
||||
You bring the feature branch into main:
|
||||
|
||||
```
|
||||
main: A---B---C---D---M
|
||||
\ /
|
||||
feature-login: E-----F
|
||||
```
|
||||
|
||||
- Commit M is a **merge commit** - it combines both branches
|
||||
- Main now has all the work from both branches
|
||||
- Your login feature is now part of main!
|
||||
|
||||
### How to Merge
|
||||
|
||||
Merging is simple - just two steps:
|
||||
|
||||
**1. Switch to the branch you want to merge INTO:**
|
||||
```bash
|
||||
git switch main
|
||||
```
|
||||
This is the branch that will receive the changes.
|
||||
|
||||
**2. Merge the other branch:**
|
||||
```bash
|
||||
git merge feature-login
|
||||
```
|
||||
This brings changes from `feature-login` into `main`.
|
||||
|
||||
**That's it!** Git automatically combines the changes.
|
||||
|
||||
### Understanding Merge Commits
|
||||
|
||||
When you merge, Git creates a special commit called a **merge commit**.
|
||||
|
||||
**What makes it special?**
|
||||
- It has TWO parent commits (one from each branch)
|
||||
- It represents the point where branches come back together
|
||||
- The message typically says "Merge branch 'feature-login'"
|
||||
|
||||
**See your merge commit:**
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
Look for the merge commit at the top - it will say something like:
|
||||
```
|
||||
abc1234 Merge branch 'feature-login'
|
||||
```
|
||||
|
||||
### Types of Merges
|
||||
|
||||
**Three-way merge** (what you just did):
|
||||
- Both branches have new commits
|
||||
- Git creates a merge commit
|
||||
- History shows both branches clearly
|
||||
|
||||
**Fast-forward merge**:
|
||||
- Main hasn't changed since the branch was created
|
||||
- Git just moves the main pointer forward
|
||||
- No merge commit needed!
|
||||
|
||||
```
|
||||
# Before (fast-forward merge)
|
||||
main: A---B
|
||||
\
|
||||
feature: C---D
|
||||
|
||||
# After (main just moves forward)
|
||||
main: A---B---C---D
|
||||
```
|
||||
|
||||
### Visualizing Branches
|
||||
|
||||
The `--graph` flag is your best friend:
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
**What the graph shows:**
|
||||
- `*` = A commit
|
||||
- `|` = A branch line
|
||||
- `/` and `\` = Branches splitting/joining
|
||||
- Branch names in parentheses
|
||||
|
||||
**Example output:**
|
||||
```
|
||||
* a1b2c3d (HEAD -> main) Merge branch 'feature-login'
|
||||
|\
|
||||
| * e4f5g6h (feature-login) Add password validation
|
||||
| * i7j8k9l Add login module
|
||||
* | m1n2o3p Add README documentation
|
||||
* | q4r5s6t Add app.py entry point
|
||||
|/
|
||||
* u7v8w9x Add main functionality
|
||||
* y1z2a3b Initial commit
|
||||
```
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Merge a branch into your current branch
|
||||
git merge <branch-name>
|
||||
|
||||
# Abort a merge if something goes wrong
|
||||
git merge --abort
|
||||
|
||||
# View merge commits only
|
||||
git log --merges
|
||||
|
||||
# View branch structure
|
||||
git log --oneline --graph --all
|
||||
|
||||
# See which branches have been merged into main
|
||||
git branch --merged main
|
||||
|
||||
# See which branches haven't been merged
|
||||
git branch --no-merged main
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Checkpoint 3: Resolving Merge Conflicts
|
||||
|
||||
**Prerequisites:** Complete Checkpoint 2 OR run `.\reset.ps1 merge-conflict`
|
||||
|
||||
### Learning Objectives
|
||||
|
||||
- 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
|
||||
|
||||
### Your Task
|
||||
|
||||
You have an `update-config` branch that modified `config.json`, and the main branch also modified `config.json` in a different way. When you try to merge, Git can't automatically combine them - you'll need to resolve the conflict manually.
|
||||
|
||||
**Your mission:**
|
||||
1. Attempt to merge the `update-config` branch into `main`
|
||||
2. Git will tell you there's a conflict - don't panic!
|
||||
3. Resolve the conflict by keeping BOTH settings (timeout AND debug)
|
||||
4. Complete the merge
|
||||
|
||||
**Steps:**
|
||||
|
||||
1. Make sure you're in challenge directory: `cd challenge`
|
||||
2. Verify you're on main: `git branch`
|
||||
3. Try to merge: `git merge update-config`
|
||||
4. Git will report a conflict!
|
||||
5. Open `config.json` in your text editor
|
||||
6. Follow the resolution guide below
|
||||
7. Save the file
|
||||
8. Stage the resolved file: `git add config.json`
|
||||
9. Complete the merge: `git commit`
|
||||
|
||||
**Verify:** Run `.\verify.ps1 merge-conflict` to check your solution.
|
||||
|
||||
### What Are Merge Conflicts?
|
||||
|
||||
A **merge conflict** occurs when Git cannot automatically combine changes because both branches modified the same part of the same file.
|
||||
|
||||
**Example scenario:**
|
||||
```
|
||||
main branch: changes line 5 to: "timeout": 5000
|
||||
update-config: changes line 5 to: "debug": true
|
||||
```
|
||||
|
||||
Git doesn't know which one you want (or if you want both)! So it asks you to decide.
|
||||
|
||||
**When do conflicts happen?**
|
||||
- ✅ Two branches modify the same lines in a file
|
||||
- ✅ One branch deletes a file that another branch modifies
|
||||
- ✅ Complex changes Git can't merge automatically
|
||||
- ❌ Different files are changed (no conflict!)
|
||||
- ❌ Different parts of the same file are changed (no conflict!)
|
||||
|
||||
**Don't fear conflicts!** They're a normal part of collaborative development. Git just needs your help to decide what the final code should look like.
|
||||
|
||||
### Step-by-Step: Resolving Your First Conflict
|
||||
|
||||
#### Step 1: Attempt the Merge
|
||||
|
||||
```bash
|
||||
cd challenge
|
||||
git merge update-config
|
||||
```
|
||||
|
||||
**You'll see:**
|
||||
```
|
||||
Auto-merging config.json
|
||||
CONFLICT (content): Merge conflict in config.json
|
||||
Automatic merge failed; fix conflicts and then commit the result.
|
||||
```
|
||||
|
||||
**Don't panic!** This is normal. Git is just asking for your help.
|
||||
|
||||
#### Step 2: Check What Happened
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
**You'll see:**
|
||||
```
|
||||
On branch main
|
||||
You have unmerged paths.
|
||||
(fix conflicts and run "git commit")
|
||||
(use "git merge --abort" to abort the merge)
|
||||
|
||||
Unmerged paths:
|
||||
(use "git add <file>..." to mark resolution)
|
||||
both modified: config.json
|
||||
```
|
||||
|
||||
This tells you that `config.json` needs your attention!
|
||||
|
||||
#### Step 3: Open the Conflicted File
|
||||
|
||||
Open `config.json` in your text editor. You'll see special **conflict markers**:
|
||||
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"version": "1.0.0",
|
||||
"port": 3000,
|
||||
<<<<<<< HEAD
|
||||
"timeout": 5000
|
||||
=======
|
||||
"debug": true
|
||||
>>>>>>> update-config
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Step 4: Understand the Conflict Markers
|
||||
|
||||
```
|
||||
<<<<<<< HEAD
|
||||
"timeout": 5000 ← Your current branch (main)
|
||||
=======
|
||||
"debug": true ← The branch you're merging (update-config)
|
||||
>>>>>>> update-config
|
||||
```
|
||||
|
||||
**What each marker means:**
|
||||
- `<<<<<<< HEAD` - Start of your changes (current branch)
|
||||
- `=======` - Separator between the two versions
|
||||
- `>>>>>>> update-config` - End of their changes (branch being merged)
|
||||
|
||||
#### Step 5: Decide What to Keep
|
||||
|
||||
You have three options:
|
||||
|
||||
**Option 1: Keep ONLY your changes (timeout)**
|
||||
```json
|
||||
"timeout": 5000
|
||||
```
|
||||
|
||||
**Option 2: Keep ONLY their changes (debug)**
|
||||
```json
|
||||
"debug": true
|
||||
```
|
||||
|
||||
**Option 3: Keep BOTH changes** ← This is what we want!
|
||||
```json
|
||||
"timeout": 5000,
|
||||
"debug": true
|
||||
```
|
||||
|
||||
For this challenge, choose **Option 3** - keep both settings!
|
||||
|
||||
#### Step 6: Edit the File
|
||||
|
||||
Delete ALL the conflict markers and keep both settings:
|
||||
|
||||
**Before (with conflict markers):**
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"version": "1.0.0",
|
||||
"port": 3000,
|
||||
<<<<<<< HEAD
|
||||
"timeout": 5000
|
||||
=======
|
||||
"debug": true
|
||||
>>>>>>> update-config
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**After (resolved):**
|
||||
```json
|
||||
{
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"version": "1.0.0",
|
||||
"port": 3000,
|
||||
"timeout": 5000,
|
||||
"debug": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Important:**
|
||||
- Remove `<<<<<<< HEAD`
|
||||
- Remove `=======`
|
||||
- Remove `>>>>>>> update-config`
|
||||
- Keep both the timeout and debug settings
|
||||
- Ensure valid JSON syntax (notice the comma after timeout!)
|
||||
|
||||
#### Step 7: Save the File
|
||||
|
||||
Save `config.json` with your changes.
|
||||
|
||||
#### Step 8: Stage the Resolved File
|
||||
|
||||
Tell Git you've resolved the conflict:
|
||||
|
||||
```bash
|
||||
git add config.json
|
||||
```
|
||||
|
||||
#### Step 9: Check Status
|
||||
|
||||
```bash
|
||||
git status
|
||||
```
|
||||
|
||||
**You'll see:**
|
||||
```
|
||||
On branch main
|
||||
All conflicts fixed but you are still merging.
|
||||
(use "git commit" to conclude merge)
|
||||
```
|
||||
|
||||
Perfect! Git confirms the conflict is resolved.
|
||||
|
||||
#### Step 10: Complete the Merge
|
||||
|
||||
Commit the merge:
|
||||
|
||||
```bash
|
||||
git commit
|
||||
```
|
||||
|
||||
Git will open an editor with a default merge message. You can accept it or customize it.
|
||||
|
||||
**Done!** Your merge is complete!
|
||||
|
||||
### Common Mistakes to Avoid
|
||||
|
||||
❌ **Forgetting to remove conflict markers**
|
||||
```json
|
||||
<<<<<<< HEAD ← Don't leave these in!
|
||||
"timeout": 5000,
|
||||
"debug": true
|
||||
>>>>>>> update-config ← Don't leave these in!
|
||||
```
|
||||
This breaks your code! Always remove ALL markers.
|
||||
|
||||
❌ **Committing without staging**
|
||||
```bash
|
||||
git commit # Error! You didn't add the file
|
||||
```
|
||||
Always `git add` the resolved file first!
|
||||
|
||||
❌ **Keeping only one side when both are needed**
|
||||
If you delete one setting, you lose that work. For this challenge, you need BOTH!
|
||||
|
||||
❌ **Breaking syntax**
|
||||
```json
|
||||
"timeout": 5000 ← Missing comma!
|
||||
"debug": true
|
||||
```
|
||||
Always verify your file is valid after resolving!
|
||||
|
||||
### Aborting a Merge
|
||||
|
||||
Changed your mind? You can abort the merge anytime:
|
||||
|
||||
```bash
|
||||
git merge --abort
|
||||
```
|
||||
|
||||
This returns your repository to the state before you started the merge. No harm done!
|
||||
|
||||
### Useful Commands
|
||||
|
||||
```bash
|
||||
# Attempt a merge
|
||||
git merge <branch-name>
|
||||
|
||||
# Check which files have conflicts
|
||||
git status
|
||||
|
||||
# Abort the merge and start over
|
||||
git merge --abort
|
||||
|
||||
# After resolving conflicts:
|
||||
git add <resolved-file>
|
||||
git commit
|
||||
|
||||
# View conflicts in a different style
|
||||
git diff --ours # Your changes
|
||||
git diff --theirs # Their changes
|
||||
git diff --base # Original version
|
||||
```
|
||||
|
||||
### Pro Tips
|
||||
|
||||
💡 **Prevent conflicts**
|
||||
- Pull changes frequently: `git pull`
|
||||
- Communicate with your team about who's working on what
|
||||
- Keep branches short-lived and merge often
|
||||
|
||||
💡 **Make conflicts easier**
|
||||
- Work on different files when possible
|
||||
- If you must edit the same file, coordinate with teammates
|
||||
- Make small, focused commits
|
||||
|
||||
💡 **When stuck**
|
||||
- Read the conflict markers carefully
|
||||
- Look at `git log` to understand what each side changed
|
||||
- Ask a teammate to review your resolution
|
||||
- Use a merge tool: `git mergetool`
|
||||
|
||||
---
|
||||
|
||||
## Complete Command Reference
|
||||
|
||||
### Branching
|
||||
|
||||
```bash
|
||||
git branch # List all branches
|
||||
git branch feature-name # Create a new branch
|
||||
git switch branch-name # Switch to a branch
|
||||
git switch -c feature-name # Create and switch
|
||||
git switch - # Switch to previous branch
|
||||
git branch -d feature-name # Delete branch (if merged)
|
||||
git branch -D feature-name # Force delete branch
|
||||
```
|
||||
|
||||
### Merging
|
||||
|
||||
```bash
|
||||
git merge branch-name # Merge a branch into current branch
|
||||
git merge --no-ff branch-name # Force a merge commit
|
||||
git merge --abort # Abort a merge in progress
|
||||
git log --merges # View only merge commits
|
||||
```
|
||||
|
||||
### Viewing History
|
||||
|
||||
```bash
|
||||
git log --oneline --graph --all # Visual branch structure
|
||||
git log --oneline # Compact commit list
|
||||
git log --graph --decorate --all # Detailed branch view
|
||||
git log main..feature-login # Commits in feature not in main
|
||||
git diff main...feature-login # Changes between branches
|
||||
```
|
||||
|
||||
### Conflict Resolution
|
||||
|
||||
```bash
|
||||
git status # See conflicted files
|
||||
git diff # View conflicts
|
||||
git add resolved-file # Mark file as resolved
|
||||
git commit # Complete the merge
|
||||
git merge --abort # Give up and start over
|
||||
```
|
||||
|
||||
### Checkpoint Commands (This Module)
|
||||
|
||||
```bash
|
||||
.\reset.ps1 # Show available checkpoints
|
||||
.\reset.ps1 start # Jump to Checkpoint 1
|
||||
.\reset.ps1 merge # Jump to Checkpoint 2
|
||||
.\reset.ps1 merge-conflict # Jump to Checkpoint 3
|
||||
.\verify.ps1 # Verify all complete
|
||||
.\verify.ps1 start # Verify Checkpoint 1
|
||||
.\verify.ps1 merge # Verify Checkpoint 2
|
||||
.\verify.ps1 merge-conflict # Verify Checkpoint 3
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "I'm on the wrong branch!"
|
||||
|
||||
```bash
|
||||
git switch main # Switch to main
|
||||
git branch # Verify current branch
|
||||
```
|
||||
|
||||
### "I made commits on the wrong branch!"
|
||||
|
||||
Don't panic! You can move them:
|
||||
|
||||
```bash
|
||||
# You're on main but should be on feature-login
|
||||
git switch feature-login # Switch to correct branch
|
||||
git merge main # Bring the commits over
|
||||
git switch main
|
||||
git reset --hard HEAD~1 # Remove from main (careful!)
|
||||
```
|
||||
|
||||
Or use cherry-pick (covered in a later module).
|
||||
|
||||
### "The merge created a mess!"
|
||||
|
||||
Abort and try again:
|
||||
|
||||
```bash
|
||||
git merge --abort
|
||||
git status # Verify you're back to clean state
|
||||
```
|
||||
|
||||
### "I want to start this checkpoint over!"
|
||||
|
||||
Use the reset script:
|
||||
|
||||
```bash
|
||||
.\reset.ps1 start # Go back to Checkpoint 1
|
||||
```
|
||||
|
||||
This resets your repository to the beginning of that checkpoint.
|
||||
|
||||
### "I can't find my branch!"
|
||||
|
||||
List all branches:
|
||||
|
||||
```bash
|
||||
git branch --all # Shows all branches including remote
|
||||
```
|
||||
|
||||
The branch might have been deleted after merging (this is normal!).
|
||||
|
||||
### "How do I know which checkpoint I'm on?"
|
||||
|
||||
```bash
|
||||
.\reset.ps1 # Shows current checkpoint
|
||||
git log --oneline --graph --all --decorate # Shows all tags/branches
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Real-World Workflow Example
|
||||
|
||||
Here's how professional developers use these skills:
|
||||
|
||||
**Day 1: Start a new feature**
|
||||
```bash
|
||||
git switch main
|
||||
git pull # Get latest changes
|
||||
git switch -c feature-dark-mode # New feature branch
|
||||
# ... make changes ...
|
||||
git add .
|
||||
git commit -m "Add dark mode toggle"
|
||||
```
|
||||
|
||||
**Day 2: Continue work**
|
||||
```bash
|
||||
git switch feature-dark-mode # Resume work
|
||||
# ... make more changes ...
|
||||
git add .
|
||||
git commit -m "Add dark mode styles"
|
||||
```
|
||||
|
||||
**Day 3: Ready to merge**
|
||||
```bash
|
||||
git switch main
|
||||
git pull # Get latest main
|
||||
git switch feature-dark-mode
|
||||
git merge main # Bring main's changes into feature
|
||||
# Resolve any conflicts
|
||||
git switch main
|
||||
git merge feature-dark-mode # Merge feature into main
|
||||
git push # Share with team
|
||||
git branch -d feature-dark-mode # Clean up
|
||||
```
|
||||
|
||||
**This is exactly what you just practiced!**
|
||||
|
||||
---
|
||||
|
||||
## What You've Learned
|
||||
|
||||
By completing all three checkpoints, you now understand:
|
||||
|
||||
### Checkpoint 1: Branching Basics
|
||||
- ✅ Branches create independent lines of development
|
||||
- ✅ `git switch -c` creates and switches to a new branch
|
||||
- ✅ Changes in one branch don't affect others
|
||||
- ✅ Branches are lightweight and easy to create
|
||||
|
||||
### Checkpoint 2: Merging Branches
|
||||
- ✅ Merging combines work from two branches
|
||||
- ✅ Merge commits have two parent commits
|
||||
- ✅ `git merge` brings changes into your current branch
|
||||
- ✅ Three-way merges create a merge commit
|
||||
|
||||
### Checkpoint 3: Resolving Merge Conflicts
|
||||
- ✅ Conflicts happen when the same lines are changed differently
|
||||
- ✅ Conflict markers show both versions
|
||||
- ✅ You choose what the final code should look like
|
||||
- ✅ Conflicts are normal and easy to resolve with practice
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
**Completed the module?** Great work! You're ready to move on.
|
||||
|
||||
**Want more practice?** Jump to any checkpoint and try again:
|
||||
```bash
|
||||
.\reset.ps1 start # Practice branching
|
||||
.\reset.ps1 merge # Practice merging
|
||||
.\reset.ps1 merge-conflict # Practice conflict resolution
|
||||
```
|
||||
|
||||
**Ready for the next module?**
|
||||
Continue to Module 04 to learn about cherry-picking specific commits!
|
||||
|
||||
---
|
||||
|
||||
**Need help?** Review the relevant checkpoint section above, or run `git status` to see what Git suggests!
|
||||
216
01-essentials/03-branching-and-merging/reset.ps1
Executable file
216
01-essentials/03-branching-and-merging/reset.ps1
Executable file
@@ -0,0 +1,216 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Resets the challenge environment to a specific checkpoint.
|
||||
|
||||
.DESCRIPTION
|
||||
This script allows you to jump to any checkpoint in the module,
|
||||
resetting your repository to that state. Useful for skipping ahead,
|
||||
starting over, or practicing specific sections.
|
||||
|
||||
.PARAMETER Checkpoint
|
||||
The checkpoint to reset to: start, merge, or merge-conflict.
|
||||
If not specified, displays help information.
|
||||
|
||||
.EXAMPLE
|
||||
.\reset.ps1
|
||||
Shows available checkpoints and current status.
|
||||
|
||||
.EXAMPLE
|
||||
.\reset.ps1 start
|
||||
Resets to the beginning (branching basics section).
|
||||
|
||||
.EXAMPLE
|
||||
.\reset.ps1 merge
|
||||
Jumps to the merging section (feature-login branch already exists).
|
||||
|
||||
.EXAMPLE
|
||||
.\reset.ps1 merge-conflict
|
||||
Jumps to the conflict resolution section (merge already complete).
|
||||
#>
|
||||
|
||||
param(
|
||||
[ValidateSet('start', 'merge', 'merge-conflict', '')]
|
||||
[string]$Checkpoint = ''
|
||||
)
|
||||
|
||||
# Checkpoint to tag mapping
|
||||
$checkpointTags = @{
|
||||
'start' = 'checkpoint-start'
|
||||
'merge' = 'checkpoint-merge'
|
||||
'merge-conflict' = 'checkpoint-merge-conflict'
|
||||
}
|
||||
|
||||
# Checkpoint descriptions
|
||||
$checkpointDescriptions = @{
|
||||
'start' = 'Branching Basics - Create and work with feature branches'
|
||||
'merge' = 'Merging Branches - Merge feature-login into main'
|
||||
'merge-conflict' = 'Resolving Conflicts - Fix merge conflicts in config.json'
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Display help if no checkpoint specified
|
||||
# ============================================================================
|
||||
if ($Checkpoint -eq '') {
|
||||
Write-Host "`n=== Module 03: Branching and Merging - Checkpoints ===" -ForegroundColor Cyan
|
||||
Write-Host "`nAvailable checkpoints:" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
foreach ($key in @('start', 'merge', 'merge-conflict')) {
|
||||
$desc = $checkpointDescriptions[$key]
|
||||
Write-Host " $key" -ForegroundColor Green -NoNewline
|
||||
Write-Host " - $desc" -ForegroundColor White
|
||||
}
|
||||
|
||||
Write-Host "`nUsage:" -ForegroundColor Cyan
|
||||
Write-Host " .\reset.ps1 <checkpoint>" -ForegroundColor White
|
||||
Write-Host ""
|
||||
Write-Host "Examples:" -ForegroundColor Cyan
|
||||
Write-Host " .\reset.ps1 start # Start from the beginning" -ForegroundColor White
|
||||
Write-Host " .\reset.ps1 merge # Jump to merging section" -ForegroundColor White
|
||||
Write-Host " .\reset.ps1 merge-conflict # Jump to conflict resolution" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
# Try to detect current checkpoint
|
||||
if (Test-Path "challenge/.git") {
|
||||
Push-Location "challenge"
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
$currentCommit = git rev-parse HEAD 2>$null
|
||||
|
||||
# Check which checkpoint we're at
|
||||
$currentCheckpoint = $null
|
||||
foreach ($cp in @('start', 'merge', 'merge-conflict')) {
|
||||
$tagCommit = git rev-parse $checkpointTags[$cp] 2>$null
|
||||
if ($currentCommit -eq $tagCommit) {
|
||||
$currentCheckpoint = $cp
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if ($currentCheckpoint) {
|
||||
Write-Host "Current checkpoint: " -ForegroundColor Yellow -NoNewline
|
||||
Write-Host "$currentCheckpoint" -ForegroundColor Green -NoNewline
|
||||
Write-Host " (on branch $currentBranch)" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "Current status: " -ForegroundColor Yellow -NoNewline
|
||||
Write-Host "In progress (on branch $currentBranch)" -ForegroundColor White
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
exit 0
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Validate challenge directory exists
|
||||
# ============================================================================
|
||||
if (-not (Test-Path "challenge")) {
|
||||
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
||||
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not (Test-Path "challenge/.git")) {
|
||||
Write-Host "[ERROR] No git repository found in challenge directory." -ForegroundColor Red
|
||||
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Navigate to challenge directory
|
||||
Push-Location "challenge"
|
||||
|
||||
# ============================================================================
|
||||
# Verify the checkpoint tag exists
|
||||
# ============================================================================
|
||||
$targetTag = $checkpointTags[$Checkpoint]
|
||||
$tagExists = git tag -l $targetTag
|
||||
|
||||
if (-not $tagExists) {
|
||||
Write-Host "[ERROR] Checkpoint tag '$targetTag' not found." -ForegroundColor Red
|
||||
Write-Host "Run ..\setup.ps1 to recreate the challenge environment." -ForegroundColor Yellow
|
||||
Pop-Location
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Check for uncommitted changes
|
||||
# ============================================================================
|
||||
$statusOutput = git status --porcelain 2>$null
|
||||
|
||||
if ($statusOutput) {
|
||||
Write-Host "`n[WARNING] You have uncommitted changes!" -ForegroundColor Yellow
|
||||
Write-Host "The following changes will be lost:" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
git status --short
|
||||
Write-Host ""
|
||||
|
||||
$response = Read-Host "Continue and discard all changes? (y/N)"
|
||||
if ($response -ne 'y' -and $response -ne 'Y') {
|
||||
Write-Host "`nReset cancelled." -ForegroundColor Cyan
|
||||
Pop-Location
|
||||
exit 0
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Reset to checkpoint
|
||||
# ============================================================================
|
||||
Write-Host "`nResetting to checkpoint: $Checkpoint" -ForegroundColor Cyan
|
||||
Write-Host "Description: $($checkpointDescriptions[$Checkpoint])" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
try {
|
||||
# Reset to the checkpoint tag
|
||||
git reset --hard $targetTag 2>&1 | Out-Null
|
||||
|
||||
# Clean untracked files
|
||||
git clean -fd 2>&1 | Out-Null
|
||||
|
||||
# Ensure we're on main branch
|
||||
$currentBranch = git branch --show-current
|
||||
if ($currentBranch -ne 'main') {
|
||||
git switch main 2>&1 | Out-Null
|
||||
git reset --hard $targetTag 2>&1 | Out-Null
|
||||
}
|
||||
|
||||
Write-Host "[SUCCESS] Reset to checkpoint '$Checkpoint' complete!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Show what to do next
|
||||
switch ($Checkpoint) {
|
||||
'start' {
|
||||
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Create a new branch: git switch -c feature-login" -ForegroundColor White
|
||||
Write-Host " 2. Create login.py and make 2+ commits" -ForegroundColor White
|
||||
Write-Host " 3. Verify: ..\verify.ps1 start" -ForegroundColor White
|
||||
}
|
||||
'merge' {
|
||||
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. View branch structure: git log --oneline --graph --all" -ForegroundColor White
|
||||
Write-Host " 2. Merge feature-login: git merge feature-login" -ForegroundColor White
|
||||
Write-Host " 3. Verify: ..\verify.ps1 merge" -ForegroundColor White
|
||||
}
|
||||
'merge-conflict' {
|
||||
Write-Host "Next steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Attempt merge: git merge update-config" -ForegroundColor White
|
||||
Write-Host " 2. Resolve conflicts in config.json" -ForegroundColor White
|
||||
Write-Host " 3. Complete merge: git add config.json && git commit" -ForegroundColor White
|
||||
Write-Host " 4. Verify: ..\verify.ps1 merge-conflict" -ForegroundColor White
|
||||
}
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "View current state: git log --oneline --graph --all" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
} catch {
|
||||
Write-Host "[ERROR] Failed to reset to checkpoint." -ForegroundColor Red
|
||||
Write-Host $_.Exception.Message -ForegroundColor Red
|
||||
Pop-Location
|
||||
exit 1
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
exit 0
|
||||
279
01-essentials/03-branching-and-merging/setup.ps1
Executable file
279
01-essentials/03-branching-and-merging/setup.ps1
Executable file
@@ -0,0 +1,279 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets up the Module 03 checkpoint-based challenge environment.
|
||||
|
||||
.DESCRIPTION
|
||||
This script creates a challenge directory with a complete Git repository
|
||||
containing all commits and checkpoints for learning branching, merging,
|
||||
and merge conflict resolution in one continuous workflow.
|
||||
|
||||
The script creates three checkpoints:
|
||||
- checkpoint-start: Beginning of branching basics
|
||||
- checkpoint-merge: Beginning of merging section
|
||||
- checkpoint-merge-conflict: Beginning of conflict resolution
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Setting up Module 03: Branching and Merging ===" -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"
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 1: Branching Basics - Initial commits on main
|
||||
# ============================================================================
|
||||
Write-Host "`nPhase 1: Creating initial project structure..." -ForegroundColor Cyan
|
||||
|
||||
# Commit 1: Initial commit
|
||||
$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
|
||||
$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
|
||||
|
||||
# Tag checkpoint-start (students begin here - will create feature-login)
|
||||
Write-Host "Creating checkpoint: start" -ForegroundColor Green
|
||||
git tag checkpoint-start
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 2: Create feature-login branch (what students will do in checkpoint 1)
|
||||
# ============================================================================
|
||||
Write-Host "Phase 2: Creating feature-login branch..." -ForegroundColor Cyan
|
||||
|
||||
# Create and switch to feature-login branch
|
||||
git switch -c feature-login | Out-Null
|
||||
|
||||
# Commit 3: Add login module
|
||||
$loginContent = @"
|
||||
# login.py - User login module
|
||||
|
||||
def login(username, password):
|
||||
"""Authenticate a user."""
|
||||
print(f"Authenticating user: {username}")
|
||||
# TODO: Add actual authentication logic
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
"""Log out a user."""
|
||||
print(f"Logging out user: {username}")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "login.py" -Value $loginContent
|
||||
git add .
|
||||
git commit -m "Add login module" | Out-Null
|
||||
|
||||
# Commit 4: Add password validation
|
||||
$loginContent = @"
|
||||
# login.py - User login module
|
||||
|
||||
def validate_password(password):
|
||||
"""Validate password strength."""
|
||||
if len(password) < 8:
|
||||
return False
|
||||
return True
|
||||
|
||||
def login(username, password):
|
||||
"""Authenticate a user."""
|
||||
if not validate_password(password):
|
||||
print("Password too weak!")
|
||||
return False
|
||||
print(f"Authenticating user: {username}")
|
||||
# TODO: Add actual authentication logic
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
"""Log out a user."""
|
||||
print(f"Logging out user: {username}")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "login.py" -Value $loginContent
|
||||
git add .
|
||||
git commit -m "Add password validation" | Out-Null
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
|
||||
# Now create divergence - add commits to main while feature-login exists
|
||||
Write-Host "Creating divergent history on main..." -ForegroundColor Cyan
|
||||
|
||||
# Commit 5: Add app.py with basic functionality
|
||||
$appContent = @"
|
||||
# app.py - Main application entry point
|
||||
|
||||
from main import main
|
||||
|
||||
def run():
|
||||
"""Run the application."""
|
||||
print("Starting application...")
|
||||
main()
|
||||
print("Application finished.")
|
||||
|
||||
if __name__ == "__main__":
|
||||
run()
|
||||
"@
|
||||
Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add app.py entry point" | Out-Null
|
||||
|
||||
# Commit 6: Add README
|
||||
$readmeContent = @"
|
||||
# My Application
|
||||
|
||||
Welcome to my application!
|
||||
|
||||
## Features
|
||||
|
||||
- Main functionality
|
||||
- More features coming soon
|
||||
|
||||
## Setup
|
||||
|
||||
Run: python app.py
|
||||
"@
|
||||
Set-Content -Path "README.md" -Value $readmeContent
|
||||
git add .
|
||||
git commit -m "Add README documentation" | Out-Null
|
||||
|
||||
# Tag checkpoint-merge (students begin merging here - divergent branches ready)
|
||||
Write-Host "Creating checkpoint: merge" -ForegroundColor Green
|
||||
git tag checkpoint-merge
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 3: Merge feature-login into main (what students will do in checkpoint 2)
|
||||
# ============================================================================
|
||||
Write-Host "Phase 3: Merging feature-login into main..." -ForegroundColor Cyan
|
||||
|
||||
# Merge feature-login into main (will create three-way merge commit)
|
||||
git merge feature-login --no-edit | Out-Null
|
||||
|
||||
# ============================================================================
|
||||
# PHASE 4: Create conflict scenario (what students will do in checkpoint 3)
|
||||
# ============================================================================
|
||||
Write-Host "Phase 4: Creating merge conflict scenario..." -ForegroundColor Cyan
|
||||
|
||||
# Create config.json file on main
|
||||
$initialConfig = @"
|
||||
{
|
||||
"app": {
|
||||
"name": "MyApp",
|
||||
"version": "1.0.0",
|
||||
"port": 3000
|
||||
}
|
||||
}
|
||||
"@
|
||||
Set-Content -Path "config.json" -Value $initialConfig
|
||||
git add config.json
|
||||
git commit -m "Add initial configuration" | 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
|
||||
|
||||
# Create update-config branch from the commit before timeout was added
|
||||
git switch -c update-config HEAD~1 | Out-Null
|
||||
|
||||
# On update-config branch: Add debug setting (conflicting change)
|
||||
$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
|
||||
git switch main | Out-Null
|
||||
|
||||
# Tag checkpoint-merge-conflict (students begin conflict resolution here - on main with timeout, update-config has debug)
|
||||
Write-Host "Creating checkpoint: merge-conflict" -ForegroundColor Green
|
||||
git tag checkpoint-merge-conflict
|
||||
|
||||
# ============================================================================
|
||||
# Reset to checkpoint-start so students begin at the beginning
|
||||
# ============================================================================
|
||||
Write-Host "`nResetting to checkpoint-start..." -ForegroundColor Yellow
|
||||
git reset --hard checkpoint-start | Out-Null
|
||||
git clean -fd | 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 "`nThis module uses a CHECKPOINT SYSTEM:" -ForegroundColor Yellow
|
||||
Write-Host " You'll work through 3 sections in one continuous repository:" -ForegroundColor White
|
||||
Write-Host " 1. Branching Basics (checkpoint: start)" -ForegroundColor White
|
||||
Write-Host " 2. Merging Branches (checkpoint: merge)" -ForegroundColor White
|
||||
Write-Host " 3. Resolving Merge Conflicts (checkpoint: merge-conflict)" -ForegroundColor White
|
||||
Write-Host "`nCommands:" -ForegroundColor Cyan
|
||||
Write-Host " .\reset.ps1 - Show available checkpoints" -ForegroundColor White
|
||||
Write-Host " .\reset.ps1 start - Jump to branching section" -ForegroundColor White
|
||||
Write-Host " .\reset.ps1 merge - Jump to merging section" -ForegroundColor White
|
||||
Write-Host " .\verify.ps1 - Verify all sections complete" -ForegroundColor White
|
||||
Write-Host " .\verify.ps1 start - Verify only branching section" -ForegroundColor White
|
||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White
|
||||
Write-Host " 2. cd challenge" -ForegroundColor White
|
||||
Write-Host " 3. Start with Checkpoint 1: Branching Basics" -ForegroundColor White
|
||||
Write-Host ""
|
||||
320
01-essentials/03-branching-and-merging/verify.ps1
Executable file
320
01-essentials/03-branching-and-merging/verify.ps1
Executable file
@@ -0,0 +1,320 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies the Module 03 challenge solution (checkpoint-aware).
|
||||
|
||||
.DESCRIPTION
|
||||
This script can verify completion of individual checkpoints or
|
||||
the entire module. Without arguments, it verifies all checkpoints.
|
||||
|
||||
.PARAMETER Checkpoint
|
||||
The checkpoint to verify: start, merge, or merge-conflict.
|
||||
If not specified, verifies all checkpoints.
|
||||
|
||||
.EXAMPLE
|
||||
.\verify.ps1
|
||||
Verifies all three checkpoints are complete.
|
||||
|
||||
.EXAMPLE
|
||||
.\verify.ps1 start
|
||||
Verifies only the branching basics checkpoint.
|
||||
|
||||
.EXAMPLE
|
||||
.\verify.ps1 merge
|
||||
Verifies only the merging checkpoint.
|
||||
#>
|
||||
|
||||
param(
|
||||
[ValidateSet('start', 'merge', 'merge-conflict', '')]
|
||||
[string]$Checkpoint = ''
|
||||
)
|
||||
|
||||
$script:allChecksPassed = $true
|
||||
|
||||
# ============================================================================
|
||||
# Helper Functions
|
||||
# ============================================================================
|
||||
|
||||
function Write-Pass {
|
||||
param([string]$Message)
|
||||
Write-Host "[PASS] $Message" -ForegroundColor Green
|
||||
}
|
||||
|
||||
function Write-Fail {
|
||||
param([string]$Message)
|
||||
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
||||
$script:allChecksPassed = $false
|
||||
}
|
||||
|
||||
function Write-Hint {
|
||||
param([string]$Message)
|
||||
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Checkpoint 1: Branching Basics Verification
|
||||
# ============================================================================
|
||||
|
||||
function Verify-Branching {
|
||||
Write-Host "`n=== Checkpoint 1: Branching Basics ===" -ForegroundColor Cyan
|
||||
|
||||
# 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-Pass "Branch 'feature-login' exists"
|
||||
} else {
|
||||
Write-Fail "Branch 'feature-login' not found"
|
||||
Write-Hint "Create the branch with: git switch -c feature-login"
|
||||
return
|
||||
}
|
||||
|
||||
# Check if feature-login has commits beyond main (or if they've been merged)
|
||||
$commitCount = git rev-list main..feature-login --count 2>$null
|
||||
$mergeCommitExists = (git log --merges --oneline 2>$null | Select-String "Merge.*feature-login")
|
||||
|
||||
if ($mergeCommitExists -and $commitCount -eq 0) {
|
||||
# Commits were merged into main - this is correct!
|
||||
Write-Pass "Branch 'feature-login' commits have been merged into main"
|
||||
} elseif ($commitCount -ge 2) {
|
||||
Write-Pass "Branch 'feature-login' has $commitCount new commits"
|
||||
} else {
|
||||
Write-Fail "Branch 'feature-login' needs at least 2 new commits (found: $commitCount)"
|
||||
Write-Hint "Make sure you've committed login.py and made at least one more commit"
|
||||
}
|
||||
|
||||
# Switch to feature-login and check for login.py
|
||||
git switch feature-login 2>$null | Out-Null
|
||||
if (Test-Path "login.py") {
|
||||
Write-Pass "File 'login.py' exists in feature-login branch"
|
||||
} else {
|
||||
Write-Fail "File 'login.py' not found in feature-login branch"
|
||||
Write-Hint "Create login.py and commit it to the feature-login branch"
|
||||
}
|
||||
|
||||
# Switch to main and verify login.py doesn't exist there yet (unless merged)
|
||||
git switch main 2>$null | Out-Null
|
||||
|
||||
# Check if merge happened - if so, login.py can exist on main
|
||||
$mergeCommitExists = (git log --merges --oneline 2>$null | Select-String "Merge.*feature-login")
|
||||
|
||||
if (-not $mergeCommitExists) {
|
||||
# No merge yet - login.py should NOT be on main
|
||||
if (-not (Test-Path "login.py")) {
|
||||
Write-Pass "File 'login.py' does NOT exist in main branch (branches are independent!)"
|
||||
} else {
|
||||
Write-Fail "File 'login.py' should not exist in main branch yet (before merge)"
|
||||
Write-Hint "Make sure you created login.py only on the feature-login branch"
|
||||
}
|
||||
}
|
||||
|
||||
# Switch back to original branch
|
||||
if ($originalBranch) {
|
||||
git switch $originalBranch 2>$null | Out-Null
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Checkpoint 2: Merging Verification
|
||||
# ============================================================================
|
||||
|
||||
function Verify-Merging {
|
||||
Write-Host "`n=== Checkpoint 2: Merging Branches ===" -ForegroundColor Cyan
|
||||
|
||||
# Check current branch is main
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -eq "main") {
|
||||
Write-Pass "Currently on main branch"
|
||||
} else {
|
||||
Write-Fail "Should be on main branch (currently on: $currentBranch)"
|
||||
Write-Hint "Switch to main with: git switch main"
|
||||
}
|
||||
|
||||
# Check if login.py exists on main (indicates merge happened)
|
||||
if (Test-Path "login.py") {
|
||||
Write-Pass "File 'login.py' exists on main branch (merged successfully)"
|
||||
} else {
|
||||
Write-Fail "File 'login.py' not found on main branch"
|
||||
Write-Hint "Merge feature-login into main with: git merge feature-login"
|
||||
}
|
||||
|
||||
# Check for merge commit
|
||||
$mergeCommitExists = (git log --merges --oneline 2>$null | Select-String "Merge.*feature-login")
|
||||
|
||||
if ($mergeCommitExists) {
|
||||
Write-Pass "Merge commit exists"
|
||||
} else {
|
||||
Write-Fail "No merge commit found"
|
||||
Write-Hint "Create a merge commit with: git merge feature-login"
|
||||
}
|
||||
|
||||
# Check commit count (should have both branches' commits)
|
||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
||||
if ($commitCount -ge 6) {
|
||||
Write-Pass "Repository has $commitCount commits (merge complete)"
|
||||
} else {
|
||||
Write-Fail "Repository should have at least 6 commits after merge (found: $commitCount)"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Checkpoint 3: Merge Conflicts Verification
|
||||
# ============================================================================
|
||||
|
||||
function Verify-MergeConflicts {
|
||||
Write-Host "`n=== Checkpoint 3: Resolving Merge Conflicts ===" -ForegroundColor Cyan
|
||||
|
||||
# Check current branch is main
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -eq "main") {
|
||||
Write-Pass "Currently on main branch"
|
||||
} else {
|
||||
Write-Fail "Should be on main branch (currently on: $currentBranch)"
|
||||
Write-Hint "Switch to main with: git switch main"
|
||||
}
|
||||
|
||||
# Check that merge is not in progress
|
||||
if (Test-Path ".git/MERGE_HEAD") {
|
||||
Write-Fail "Merge is still in progress (conflicts not resolved)"
|
||||
Write-Hint "Resolve conflicts in config.json, then: git add config.json && git commit"
|
||||
return
|
||||
} else {
|
||||
Write-Pass "No merge in progress (conflicts resolved)"
|
||||
}
|
||||
|
||||
# Check if config.json exists
|
||||
if (Test-Path "config.json") {
|
||||
Write-Pass "File 'config.json' exists"
|
||||
} else {
|
||||
Write-Fail "File 'config.json' not found"
|
||||
Write-Hint "Merge update-config branch with: git merge update-config"
|
||||
return
|
||||
}
|
||||
|
||||
# Verify config.json is valid JSON
|
||||
try {
|
||||
$configContent = Get-Content "config.json" -Raw
|
||||
$config = $configContent | ConvertFrom-Json -ErrorAction Stop
|
||||
Write-Pass "File 'config.json' is valid JSON"
|
||||
} catch {
|
||||
Write-Fail "File 'config.json' is not valid JSON"
|
||||
Write-Hint "Make sure you removed all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||
return
|
||||
}
|
||||
|
||||
# Check for conflict markers
|
||||
if ($configContent -match '<<<<<<<|=======|>>>>>>>') {
|
||||
Write-Fail "Conflict markers still present in config.json"
|
||||
Write-Hint "Remove all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||
return
|
||||
} else {
|
||||
Write-Pass "No conflict markers in config.json"
|
||||
}
|
||||
|
||||
# Verify both settings are present (timeout and debug)
|
||||
if ($config.app.timeout -eq 5000) {
|
||||
Write-Pass "Timeout setting preserved (5000)"
|
||||
} else {
|
||||
Write-Fail "Timeout setting missing or incorrect"
|
||||
Write-Hint "Keep the timeout: 5000 setting from main branch"
|
||||
}
|
||||
|
||||
if ($config.app.debug -eq $true) {
|
||||
Write-Pass "Debug setting preserved (true)"
|
||||
} else {
|
||||
Write-Fail "Debug setting missing or incorrect"
|
||||
Write-Hint "Keep the debug: true setting from update-config branch"
|
||||
}
|
||||
|
||||
# Verify merge commit exists for update-config
|
||||
$updateConfigMerge = (git log --merges --oneline 2>$null | Select-String "Merge.*update-config")
|
||||
if ($updateConfigMerge) {
|
||||
Write-Pass "Merge commit exists for update-config branch"
|
||||
} else {
|
||||
Write-Fail "No merge commit found for update-config"
|
||||
Write-Hint "Complete the merge with: git commit (after resolving conflicts)"
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# Main Script Logic
|
||||
# ============================================================================
|
||||
|
||||
# Check if challenge directory exists
|
||||
if (-not (Test-Path "challenge")) {
|
||||
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
||||
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
Push-Location "challenge"
|
||||
|
||||
# Check if git repository exists
|
||||
if (-not (Test-Path ".git")) {
|
||||
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
||||
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||
Pop-Location
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Run appropriate verification
|
||||
if ($Checkpoint -eq '') {
|
||||
# Verify all checkpoints
|
||||
Write-Host "`n=== Verifying All Checkpoints ===" -ForegroundColor Cyan
|
||||
|
||||
Verify-Branching
|
||||
Verify-Merging
|
||||
Verify-MergeConflicts
|
||||
|
||||
} else {
|
||||
# Verify specific checkpoint
|
||||
switch ($Checkpoint) {
|
||||
'start' { Verify-Branching }
|
||||
'merge' { Verify-Merging }
|
||||
'merge-conflict' { Verify-MergeConflicts }
|
||||
}
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
|
||||
# Final summary
|
||||
Write-Host ""
|
||||
if ($script:allChecksPassed) {
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
|
||||
if ($Checkpoint -eq '') {
|
||||
Write-Host "`nYou've completed the entire module!" -ForegroundColor Cyan
|
||||
Write-Host "You've mastered:" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Creating and working with branches" -ForegroundColor White
|
||||
Write-Host " ✓ Merging branches together" -ForegroundColor White
|
||||
Write-Host " ✓ Resolving merge conflicts" -ForegroundColor White
|
||||
Write-Host "`nReady for the next module!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`nCheckpoint '$Checkpoint' complete!" -ForegroundColor Cyan
|
||||
|
||||
switch ($Checkpoint) {
|
||||
'start' {
|
||||
Write-Host "Next: Move to the merging checkpoint" -ForegroundColor White
|
||||
Write-Host " ..\reset.ps1 merge OR continue to merge feature-login" -ForegroundColor Yellow
|
||||
}
|
||||
'merge' {
|
||||
Write-Host "Next: Move to the conflict resolution checkpoint" -ForegroundColor White
|
||||
Write-Host " ..\reset.ps1 merge-conflict" -ForegroundColor Yellow
|
||||
}
|
||||
'merge-conflict' {
|
||||
Write-Host "Module complete! Ready for the next module!" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "[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
|
||||
}
|
||||
160
01-essentials/04-cherry-pick/README.md
Normal file
160
01-essentials/04-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/04-cherry-pick/reset.ps1
Normal file
22
01-essentials/04-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/04-cherry-pick/setup.ps1
Normal file
175
01-essentials/04-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/04-cherry-pick/verify.ps1
Normal file
206
01-essentials/04-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
|
||||
638
01-essentials/05-revert/README.md
Normal file
638
01-essentials/05-revert/README.md
Normal file
@@ -0,0 +1,638 @@
|
||||
# Module 05: Git Revert - Safe Undoing
|
||||
|
||||
## About This Module
|
||||
|
||||
Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
|
||||
|
||||
**Why revert is important:**
|
||||
- ✅ Safe for shared/pushed commits
|
||||
- ✅ Preserves complete history and audit trail
|
||||
- ✅ Transparent to your team
|
||||
- ✅ Can be undone itself if needed
|
||||
- ✅ Works with any commit in history
|
||||
|
||||
**Key principle:** Revert doesn't erase mistakes—it documents how you fixed them.
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
By completing this module, you will:
|
||||
|
||||
1. Revert regular commits safely while preserving surrounding changes
|
||||
2. Revert merge commits using the `-m` flag
|
||||
3. Understand merge commit parent numbering
|
||||
4. Handle the re-merge problem that occurs after reverting merges
|
||||
5. Revert multiple commits at once
|
||||
6. Know when to use revert vs. other undo strategies
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting this module, you should be comfortable with:
|
||||
- Creating commits (`git commit`)
|
||||
- Viewing commit history (`git log`)
|
||||
- Understanding branches and merging (Module 03)
|
||||
|
||||
## Setup
|
||||
|
||||
Run the setup script to create the challenge environment:
|
||||
|
||||
```powershell
|
||||
./setup.ps1
|
||||
```
|
||||
|
||||
This creates a `challenge/` directory with three branches demonstrating different revert scenarios:
|
||||
- `regular-revert` - Basic commit reversion
|
||||
- `merge-revert` - Merge commit reversion
|
||||
- `multi-revert` - Multiple commit reversion
|
||||
|
||||
## Challenge 1: Reverting a Regular Commit
|
||||
|
||||
### Scenario
|
||||
|
||||
You're working on a calculator application. A developer added a `divide` function that crashes when dividing by zero. The bug was discovered after subsequent commits were made, so you can't just delete it—you need to revert it while keeping the commits that came after.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Navigate to the challenge directory:
|
||||
```bash
|
||||
cd challenge
|
||||
```
|
||||
|
||||
2. You should be on the `regular-revert` branch. View the commit history:
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
3. Find the commit with the broken divide function (message: "Add broken divide function - needs to be reverted!")
|
||||
|
||||
4. Revert that specific commit:
|
||||
```bash
|
||||
git revert <commit-hash>
|
||||
```
|
||||
|
||||
5. Git will open your editor for the revert commit message. The default message is fine—save and close.
|
||||
|
||||
### What to Observe
|
||||
|
||||
After reverting, check:
|
||||
|
||||
```bash
|
||||
# View the new revert commit
|
||||
git log --oneline
|
||||
|
||||
# Check that divide function is gone
|
||||
cat calculator.py | grep "def divide" # Should return nothing
|
||||
|
||||
# Check that modulo function still exists (it came after the bad commit)
|
||||
cat calculator.py | grep "def modulo" # Should find it
|
||||
|
||||
# Check that multiply function still exists (it came before the bad commit)
|
||||
cat calculator.py | grep "def multiply" # Should find it
|
||||
```
|
||||
|
||||
**Key insight:** Revert creates a new commit that undoes the changes from the target commit, but leaves all other commits intact.
|
||||
|
||||
### Understanding the Timeline
|
||||
|
||||
```
|
||||
Before revert:
|
||||
main.py (initial) → multiply (good) → divide (BAD) → modulo (good)
|
||||
↑
|
||||
We want to undo THIS
|
||||
|
||||
After revert:
|
||||
main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → revert divide (new commit)
|
||||
↑
|
||||
Removes divide, keeps modulo
|
||||
```
|
||||
|
||||
The revert commit adds a new point in history that undoes the divide changes.
|
||||
|
||||
## Challenge 2: Reverting a Merge Commit
|
||||
|
||||
### Scenario
|
||||
|
||||
Your team merged a `feature-auth` branch that added authentication functionality. After deployment, you discovered the authentication system has critical security issues. You need to revert the entire merge while the security team redesigns the feature.
|
||||
|
||||
**This is different from reverting a regular commit!** Merge commits have **two parents**, so you must tell Git which parent to keep.
|
||||
|
||||
### Understanding Merge Commit Parents
|
||||
|
||||
When you merge a feature branch into main:
|
||||
|
||||
```
|
||||
feature-auth (parent 2)
|
||||
↓
|
||||
C---D
|
||||
/ \
|
||||
A---B-----M ← Merge commit (has TWO parents)
|
||||
↑
|
||||
parent 1 (main)
|
||||
```
|
||||
|
||||
The merge commit `M` has:
|
||||
- **Parent 1**: The branch you merged INTO (main)
|
||||
- **Parent 2**: The branch you merged FROM (feature-auth)
|
||||
|
||||
When reverting a merge, you must specify which parent to keep using the `-m` flag:
|
||||
- `-m 1` means "keep parent 1" (main) - **Most common**
|
||||
- `-m 2` means "keep parent 2" (feature-auth) - Rare
|
||||
|
||||
**In practice:** You almost always use `-m 1` to keep the main branch and undo the feature branch changes.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the merge-revert branch:
|
||||
```bash
|
||||
git switch merge-revert
|
||||
```
|
||||
|
||||
2. View the commit history and find the merge commit:
|
||||
```bash
|
||||
git log --oneline --graph
|
||||
```
|
||||
|
||||
Look for: "Merge feature-auth branch"
|
||||
|
||||
3. Revert the merge commit using `-m 1`:
|
||||
```bash
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `-m 1` tells Git to keep parent 1 (main branch)
|
||||
- This undoes all changes from the feature-auth branch
|
||||
- Creates a new "revert merge" commit
|
||||
|
||||
4. Save the default commit message and check the result:
|
||||
```bash
|
||||
# Verify auth.py is gone
|
||||
ls auth.py # Should not exist
|
||||
|
||||
# Verify calculator.py no longer imports auth
|
||||
cat calculator.py | grep "from auth" # Should return nothing
|
||||
```
|
||||
|
||||
### What Happens Without -m?
|
||||
|
||||
If you try to revert a merge commit without the `-m` flag:
|
||||
|
||||
```bash
|
||||
git revert <merge-commit-hash>
|
||||
# Error: commit <hash> is a merge but no -m option was given
|
||||
```
|
||||
|
||||
Git doesn't know which parent you want to keep, so it refuses to proceed.
|
||||
|
||||
### The Re-Merge Problem
|
||||
|
||||
**Important gotcha:** After reverting a merge, you **cannot simply re-merge** the same branch!
|
||||
|
||||
Here's why:
|
||||
|
||||
```
|
||||
Initial merge:
|
||||
A---B---M (merged feature-auth)
|
||||
↑
|
||||
All changes from feature-auth are now in main
|
||||
|
||||
After revert:
|
||||
A---B---M---R (reverted merge)
|
||||
↑
|
||||
Changes removed, but Git remembers they were merged
|
||||
|
||||
Attempting to re-merge:
|
||||
A---B---M---R---M2 (try to merge feature-auth again)
|
||||
↑
|
||||
Git thinks: "I already merged these commits,
|
||||
nothing new to add!" (Empty merge)
|
||||
```
|
||||
|
||||
**Solutions if you need to re-merge:**
|
||||
|
||||
1. **Revert the revert** (recommended):
|
||||
```bash
|
||||
git revert <revert-commit-hash>
|
||||
```
|
||||
This brings back all the feature-auth changes.
|
||||
|
||||
2. **Cherry-pick new commits** from the feature branch:
|
||||
```bash
|
||||
git cherry-pick <new-commits>
|
||||
```
|
||||
|
||||
3. **Merge with --no-ff** and resolve conflicts manually (advanced).
|
||||
|
||||
### When to Revert Merges
|
||||
|
||||
Revert merge commits when:
|
||||
- ✅ Feature causes production issues
|
||||
- ✅ Need to temporarily remove a feature
|
||||
- ✅ Discovered critical bugs after merging
|
||||
- ✅ Security issues require immediate rollback
|
||||
|
||||
Don't revert merges when:
|
||||
- ❌ You just need to fix a small bug (fix it with a new commit instead)
|
||||
- ❌ You plan to re-merge the same branch soon (use reset if local, or revert-the-revert later)
|
||||
|
||||
## Challenge 3: Reverting Multiple Commits
|
||||
|
||||
### Scenario
|
||||
|
||||
Two separate commits added broken mathematical functions (`square_root` and `logarithm`). Both have critical bugs and need to be removed. You can revert multiple commits at once.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the multi-revert branch:
|
||||
```bash
|
||||
git switch multi-revert
|
||||
```
|
||||
|
||||
2. View the commit history:
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
Find the two commits:
|
||||
- "Add broken square_root - REVERT THIS!"
|
||||
- "Add broken logarithm - REVERT THIS TOO!"
|
||||
|
||||
3. Revert both commits in one command:
|
||||
```bash
|
||||
git revert <commit-hash-1> <commit-hash-2>
|
||||
```
|
||||
|
||||
**Important:** List commits from **oldest to newest** for cleanest history.
|
||||
|
||||
Alternatively, revert them one at a time:
|
||||
```bash
|
||||
git revert <commit-hash-1>
|
||||
git revert <commit-hash-2>
|
||||
```
|
||||
|
||||
4. Git will prompt for a commit message for each revert. Accept the defaults.
|
||||
|
||||
5. Verify the result:
|
||||
```bash
|
||||
# Check that both bad functions are gone
|
||||
cat calculator.py | grep "def square_root" # Should return nothing
|
||||
cat calculator.py | grep "def logarithm" # Should return nothing
|
||||
|
||||
# Check that good functions remain
|
||||
cat calculator.py | grep "def power" # Should find it
|
||||
cat calculator.py | grep "def absolute" # Should find it
|
||||
```
|
||||
|
||||
### Multi-Revert Strategies
|
||||
|
||||
**Reverting a range of commits:**
|
||||
|
||||
```bash
|
||||
# Revert commits from A to B (inclusive)
|
||||
git revert A^..B
|
||||
|
||||
# Example: Revert last 3 commits
|
||||
git revert HEAD~3..HEAD
|
||||
```
|
||||
|
||||
**Reverting without auto-commit:**
|
||||
|
||||
```bash
|
||||
# Stage revert changes without committing
|
||||
git revert --no-commit <commit-hash>
|
||||
|
||||
# Review changes
|
||||
git diff --staged
|
||||
|
||||
# Commit when ready
|
||||
git commit
|
||||
```
|
||||
|
||||
This is useful when reverting multiple commits and you want one combined revert commit.
|
||||
|
||||
## Verification
|
||||
|
||||
Verify your solutions by running the verification script:
|
||||
|
||||
```bash
|
||||
cd .. # Return to module directory
|
||||
./verify.ps1
|
||||
```
|
||||
|
||||
The script checks that:
|
||||
- ✅ Revert commits were created (not destructive deletion)
|
||||
- ✅ Bad code is removed
|
||||
- ✅ Good code before and after is preserved
|
||||
- ✅ Merge commits still exist in history
|
||||
- ✅ Proper use of `-m` flag for merge reverts
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Basic Revert
|
||||
|
||||
```bash
|
||||
# Revert a specific commit
|
||||
git revert <commit-hash>
|
||||
|
||||
# Revert the most recent commit
|
||||
git revert HEAD
|
||||
|
||||
# Revert the second-to-last commit
|
||||
git revert HEAD~1
|
||||
```
|
||||
|
||||
### Merge Commit Revert
|
||||
|
||||
```bash
|
||||
# Revert a merge commit (keep parent 1)
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
|
||||
# Revert a merge commit (keep parent 2) - rare
|
||||
git revert -m 2 <merge-commit-hash>
|
||||
```
|
||||
|
||||
### Multiple Commits
|
||||
|
||||
```bash
|
||||
# Revert multiple specific commits
|
||||
git revert <hash1> <hash2> <hash3>
|
||||
|
||||
# Revert a range of commits (oldest^..newest)
|
||||
git revert <oldest-hash>^..<newest-hash>
|
||||
|
||||
# Revert last 3 commits
|
||||
git revert HEAD~3..HEAD
|
||||
```
|
||||
|
||||
### Revert Options
|
||||
|
||||
```bash
|
||||
# Revert but don't commit automatically
|
||||
git revert --no-commit <commit-hash>
|
||||
|
||||
# Revert and edit the commit message
|
||||
git revert --edit <commit-hash>
|
||||
|
||||
# Revert without opening editor (use default message)
|
||||
git revert --no-edit <commit-hash>
|
||||
|
||||
# Abort a revert in progress (if conflicts)
|
||||
git revert --abort
|
||||
|
||||
# Continue revert after resolving conflicts
|
||||
git revert --continue
|
||||
```
|
||||
|
||||
## When to Use Git Revert
|
||||
|
||||
Use `git revert` when:
|
||||
|
||||
- ✅ **Commits are already pushed** - Safe for shared history
|
||||
- ✅ **Working in a team** - Transparent to everyone
|
||||
- ✅ **Need audit trail** - Shows what was undone and why
|
||||
- ✅ **Public repositories** - Can't rewrite public history
|
||||
- ✅ **Undoing old commits** - Can revert commits from weeks ago
|
||||
- ✅ **Production hotfixes** - Safe emergency rollback
|
||||
|
||||
**Golden Rule:** If others might have your commits, use revert.
|
||||
|
||||
## When NOT to Use Git Revert
|
||||
|
||||
Consider alternatives when:
|
||||
|
||||
- ❌ **Commits are still local** - Use `git reset` instead (Module 06)
|
||||
- ❌ **Just want to edit a commit** - Use `git commit --amend`
|
||||
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup
|
||||
- ❌ **Need to combine commits** - Use interactive rebase
|
||||
- ❌ **Reverting creates complex conflicts** - Might need manual fix forward
|
||||
|
||||
## Revert vs. Reset vs. Rebase
|
||||
|
||||
| Command | History | Safety | Use Case |
|
||||
|---------|---------|--------|----------|
|
||||
| **revert** | Preserves | ✅ Safe | Undo pushed commits |
|
||||
| **reset** | Erases | ⚠️ Dangerous | Clean up local commits |
|
||||
| **rebase** | Rewrites | ⚠️ Dangerous | Polish commit history |
|
||||
|
||||
**This module teaches revert.** You'll learn reset in Module 06.
|
||||
|
||||
## Handling Revert Conflicts
|
||||
|
||||
Sometimes reverting causes conflicts if subsequent changes touched the same code:
|
||||
|
||||
```bash
|
||||
# Start revert
|
||||
git revert <commit-hash>
|
||||
|
||||
# If conflicts occur:
|
||||
# Conflict in calculator.py
|
||||
# CONFLICT (content): Merge conflict in calculator.py
|
||||
```
|
||||
|
||||
**To resolve:**
|
||||
|
||||
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
|
||||
2. Stage resolved files:
|
||||
```bash
|
||||
git add <resolved-files>
|
||||
```
|
||||
3. Continue the revert:
|
||||
```bash
|
||||
git revert --continue
|
||||
```
|
||||
|
||||
Or abort if you change your mind:
|
||||
```bash
|
||||
git revert --abort
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. Forgetting -m for Merge Commits
|
||||
|
||||
```bash
|
||||
# ❌ Wrong - will fail
|
||||
git revert <merge-commit>
|
||||
|
||||
# ✅ Correct
|
||||
git revert -m 1 <merge-commit>
|
||||
```
|
||||
|
||||
### 2. Trying to Re-Merge After Revert
|
||||
|
||||
```bash
|
||||
# After reverting a merge:
|
||||
git revert -m 1 <merge-commit>
|
||||
|
||||
# ❌ This won't work as expected
|
||||
git merge feature-branch # Empty merge!
|
||||
|
||||
# ✅ Do this instead
|
||||
git revert <the-revert-commit> # Revert the revert
|
||||
```
|
||||
|
||||
### 3. Using Reset on Pushed Commits
|
||||
|
||||
```bash
|
||||
# ❌ NEVER do this with pushed commits
|
||||
git reset --hard HEAD~3
|
||||
|
||||
# ✅ Do this instead
|
||||
git revert HEAD~3..HEAD
|
||||
```
|
||||
|
||||
### 4. Reverting Commits in Wrong Order
|
||||
|
||||
When reverting multiple related commits, revert from newest to oldest:
|
||||
|
||||
```bash
|
||||
# If you have: A → B → C (and C depends on B)
|
||||
|
||||
# ✅ Correct order
|
||||
git revert C
|
||||
git revert B
|
||||
|
||||
# ❌ Wrong order (may cause conflicts)
|
||||
git revert B # Conflict! C still references B
|
||||
git revert C
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Write clear revert messages:**
|
||||
```bash
|
||||
git revert <hash> -m "Revert authentication - security issue #1234"
|
||||
```
|
||||
|
||||
2. **Link to issue tracking:**
|
||||
```
|
||||
Revert "Add new payment system"
|
||||
|
||||
This reverts commit abc123.
|
||||
|
||||
Critical bug in payment processing.
|
||||
See bug tracker: ISSUE-1234
|
||||
```
|
||||
|
||||
3. **Test after reverting:**
|
||||
- Run your test suite
|
||||
- Verify the application still works
|
||||
- Check no unintended changes occurred
|
||||
|
||||
4. **Communicate with team:**
|
||||
- Announce reverts in team chat
|
||||
- Explain why the revert was necessary
|
||||
- Provide timeline for re-introducing the feature
|
||||
|
||||
5. **Keep reverts focused:**
|
||||
- Revert the minimum necessary
|
||||
- Don't bundle multiple unrelated reverts
|
||||
- One problem = one revert commit
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Commit is a merge but no -m option was given"
|
||||
|
||||
**Problem:** Trying to revert a merge commit without `-m`.
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
```
|
||||
|
||||
### "Empty Revert / No Changes"
|
||||
|
||||
**Problem:** Revert doesn't seem to do anything.
|
||||
|
||||
**Possible causes:**
|
||||
- Commit was already reverted
|
||||
- Subsequent commits already undid the changes
|
||||
- Wrong commit hash
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check what the commit actually changed
|
||||
git show <commit-hash>
|
||||
|
||||
# Check if already reverted
|
||||
git log --grep="Revert"
|
||||
```
|
||||
|
||||
### "Conflicts During Revert"
|
||||
|
||||
**Problem:** Revert causes merge conflicts.
|
||||
|
||||
**Why:** Subsequent commits modified the same code.
|
||||
|
||||
**Solution:**
|
||||
1. Manually resolve conflicts in affected files
|
||||
2. `git add <resolved-files>`
|
||||
3. `git revert --continue`
|
||||
|
||||
Or consider fixing forward with a new commit instead of reverting.
|
||||
|
||||
### "Can't Re-Merge After Reverting Merge"
|
||||
|
||||
**Problem:** After reverting a merge, re-merging the branch brings no changes.
|
||||
|
||||
**Solution:** Revert the revert commit:
|
||||
```bash
|
||||
# Find the revert commit
|
||||
git log --oneline
|
||||
|
||||
# Revert the revert (brings changes back)
|
||||
git revert <revert-commit-hash>
|
||||
```
|
||||
|
||||
## Advanced: Revert Internals
|
||||
|
||||
Understanding what revert does under the hood:
|
||||
|
||||
```bash
|
||||
# Revert creates a new commit with inverse changes
|
||||
git revert <commit-hash>
|
||||
|
||||
# This is equivalent to:
|
||||
git diff <commit-hash>^..<commit-hash> > changes.patch
|
||||
patch -R < changes.patch # Apply in reverse
|
||||
git add .
|
||||
git commit -m "Revert '<original message>'"
|
||||
```
|
||||
|
||||
**Key insight:** Revert computes the diff of the target commit, inverts it, and applies it as a new commit.
|
||||
|
||||
## Going Further
|
||||
|
||||
Now that you understand revert, you're ready for:
|
||||
|
||||
- **Module 06: Git Reset** - Learn the dangerous but powerful local history rewriting
|
||||
- **Module 07: Git Stash** - Temporarily set aside uncommitted changes
|
||||
- **Module 08: Multiplayer Git** - Collaborate with advanced workflows
|
||||
|
||||
## Summary
|
||||
|
||||
You've learned:
|
||||
|
||||
- ✅ `git revert` creates new commits that undo previous changes
|
||||
- ✅ Revert is safe for shared/pushed commits
|
||||
- ✅ Merge commits require `-m 1` or `-m 2` flag
|
||||
- ✅ Parent 1 = branch merged into, Parent 2 = branch merged from
|
||||
- ✅ Can't simply re-merge after reverting a merge
|
||||
- ✅ Multiple commits can be reverted in one command
|
||||
- ✅ Revert preserves complete history for audit trails
|
||||
|
||||
**The Golden Rule of Revert:** Use revert for any commit that might be shared with others.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete all three challenge scenarios
|
||||
2. Run `./verify.ps1` to check your solutions
|
||||
3. Experiment with reverting different commits
|
||||
4. Move on to Module 06: Git Reset (dangerous but powerful!)
|
||||
|
||||
---
|
||||
|
||||
**Need Help?**
|
||||
- Review the command reference above
|
||||
- Check the troubleshooting section
|
||||
- Re-run `./setup.ps1` to start fresh
|
||||
- Practice reverting in different orders to understand the behavior
|
||||
24
01-essentials/05-revert/reset.ps1
Normal file
24
01-essentials/05-revert/reset.ps1
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Resets the Module 05 challenge environment to start fresh.
|
||||
|
||||
.DESCRIPTION
|
||||
This script removes the challenge directory and re-runs setup.ps1
|
||||
to create a fresh challenge environment.
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Resetting Module 05: Git Revert Challenge ===" -ForegroundColor Cyan
|
||||
|
||||
# Check if challenge directory exists
|
||||
if (Test-Path "challenge") {
|
||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
||||
Remove-Item -Recurse -Force "challenge"
|
||||
Write-Host "[OK] Challenge directory removed" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] No existing challenge directory found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Run setup to create fresh environment
|
||||
Write-Host "`nRunning setup to create fresh challenge environment..." -ForegroundColor Cyan
|
||||
& "$PSScriptRoot/setup.ps1"
|
||||
373
01-essentials/05-revert/setup.ps1
Normal file
373
01-essentials/05-revert/setup.ps1
Normal file
@@ -0,0 +1,373 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets up the Module 05 challenge environment for learning git revert.
|
||||
|
||||
.DESCRIPTION
|
||||
This script creates a challenge directory with three branches demonstrating
|
||||
different revert scenarios:
|
||||
- regular-revert: Basic revert of a single bad commit
|
||||
- merge-revert: Reverting a merge commit with -m flag
|
||||
- multi-revert: Reverting multiple commits at once
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Setting up Module 05: Git Revert 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"
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Regular Revert (Basic)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 1: Creating regular-revert branch..." -ForegroundColor Cyan
|
||||
|
||||
# Initial commit
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Initial calculator implementation" | Out-Null
|
||||
|
||||
# Create regular-revert branch
|
||||
git switch -c regular-revert | Out-Null
|
||||
|
||||
# Good commit: Add multiply
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add multiply function" | Out-Null
|
||||
|
||||
# BAD commit: Add broken divide function
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
def divide(a, b):
|
||||
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
|
||||
return a / b # This will crash if b is 0!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add broken divide function - needs to be reverted!" | Out-Null
|
||||
|
||||
# Good commit: Add modulo (after bad commit)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
def divide(a, b):
|
||||
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
|
||||
return a / b # This will crash if b is 0!
|
||||
|
||||
def modulo(a, b):
|
||||
"""Return remainder of a divided by b."""
|
||||
return a % b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add modulo function" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Merge Revert (Merge Commit with -m flag)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 2: Creating merge-revert scenario..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
|
||||
# Create merge-revert branch
|
||||
git switch -c merge-revert | Out-Null
|
||||
|
||||
# Create a feature branch to merge
|
||||
git switch -c feature-auth | Out-Null
|
||||
|
||||
# Add auth functionality
|
||||
$authContent = @"
|
||||
# auth.py - Authentication module
|
||||
|
||||
def login(username, password):
|
||||
\"\"\"Login user.\"\"\"
|
||||
print(f"Logging in {username}...")
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
\"\"\"Logout user.\"\"\"
|
||||
print(f"Logging out {username}...")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "auth.py" -Value $authContent
|
||||
git add .
|
||||
git commit -m "Add authentication module" | Out-Null
|
||||
|
||||
# Add password validation
|
||||
$authContent = @"
|
||||
# auth.py - Authentication module
|
||||
|
||||
def validate_password(password):
|
||||
\"\"\"Validate password strength.\"\"\"
|
||||
return len(password) >= 8
|
||||
|
||||
def login(username, password):
|
||||
\"\"\"Login user.\"\"\"
|
||||
if not validate_password(password):
|
||||
print("Password too weak!")
|
||||
return False
|
||||
print(f"Logging in {username}...")
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
\"\"\"Logout user.\"\"\"
|
||||
print(f"Logging out {username}...")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "auth.py" -Value $authContent
|
||||
git add .
|
||||
git commit -m "Add password validation" | Out-Null
|
||||
|
||||
# Integrate auth into calculator (part of the feature branch)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
from auth import login
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def secure_divide(a, b, username):
|
||||
"""Secure divide - requires authentication."""
|
||||
if login(username, "password123"):
|
||||
return a / b
|
||||
return None
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Integrate auth into calculator" | Out-Null
|
||||
|
||||
# Switch back to merge-revert and merge feature-auth
|
||||
git switch merge-revert | Out-Null
|
||||
git merge feature-auth --no-ff -m "Merge feature-auth branch" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] merge-revert branch with merge commit to revert" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Multi Revert (Multiple Bad Commits)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 3: Creating multi-revert branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
|
||||
# Create multi-revert branch
|
||||
git switch -c multi-revert | Out-Null
|
||||
|
||||
# Reset calculator to simple version
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Reset to basic calculator" | Out-Null
|
||||
|
||||
# Good commit: Add power function
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add power function" | Out-Null
|
||||
|
||||
# BAD commit 1: Add broken square_root
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add broken square_root - REVERT THIS!" | Out-Null
|
||||
|
||||
# BAD commit 2: Add broken logarithm
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
|
||||
def logarithm(a):
|
||||
"""BROKEN: Doesn't handle zero or negative numbers!"""
|
||||
import math
|
||||
return math.log(a) # This crashes for a <= 0!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null
|
||||
|
||||
# Good commit: Add absolute value (after bad commits)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
|
||||
def logarithm(a):
|
||||
"""BROKEN: Doesn't handle zero or negative numbers!"""
|
||||
import math
|
||||
return math.log(a) # This crashes for a <= 0!
|
||||
|
||||
def absolute(a):
|
||||
"""Return absolute value of a."""
|
||||
return abs(a)
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Add absolute value function" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] multi-revert branch with two bad commits to revert" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# Return to regular-revert to start
|
||||
# ============================================================================
|
||||
git switch regular-revert | Out-Null
|
||||
|
||||
# Return to module directory
|
||||
Set-Location ..
|
||||
|
||||
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||
Write-Host "`nThree revert scenarios have been created:" -ForegroundColor Cyan
|
||||
Write-Host " 1. regular-revert - Revert a single bad commit (basic)" -ForegroundColor White
|
||||
Write-Host " 2. merge-revert - Revert a merge commit with -m flag" -ForegroundColor White
|
||||
Write-Host " 3. multi-revert - Revert multiple bad commits" -ForegroundColor White
|
||||
Write-Host "`nYou are currently on the 'regular-revert' branch." -ForegroundColor Cyan
|
||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
||||
Write-Host " 2. Read the README.md for detailed instructions" -ForegroundColor White
|
||||
Write-Host " 3. Complete each revert challenge" -ForegroundColor White
|
||||
Write-Host " 4. Run '..\verify.ps1' to check your solutions" -ForegroundColor White
|
||||
Write-Host ""
|
||||
226
01-essentials/05-revert/verify.ps1
Normal file
226
01-essentials/05-revert/verify.ps1
Normal file
@@ -0,0 +1,226 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies the Module 05 challenge solutions.
|
||||
|
||||
.DESCRIPTION
|
||||
Checks that all three revert scenarios have been completed correctly:
|
||||
- regular-revert: Single commit reverted
|
||||
- merge-revert: Merge commit reverted with -m flag
|
||||
- multi-revert: Multiple commits reverted
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Verifying Module 05: Git Revert Solutions ===" -ForegroundColor Cyan
|
||||
|
||||
$allChecksPassed = $true
|
||||
$originalDir = Get-Location
|
||||
|
||||
# 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] Not a git repository. Run setup.ps1 first." -ForegroundColor Red
|
||||
Set-Location $originalDir
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Regular Revert Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 1: Regular Revert ===" -ForegroundColor Cyan
|
||||
|
||||
git switch regular-revert 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] regular-revert branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Check that a revert commit exists
|
||||
$revertCommit = git log --oneline --grep="Revert" 2>$null
|
||||
if ($revertCommit) {
|
||||
Write-Host "[PASS] Revert commit found" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] No revert commit found" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git revert <commit-hash>" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that calculator.py exists
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# Check that divide function is NOT in the code (was reverted)
|
||||
if ($calcContent -notmatch "def divide") {
|
||||
Write-Host "[PASS] Broken divide function successfully reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] divide function still exists (should be reverted)" -ForegroundColor Red
|
||||
Write-Host "[HINT] The bad commit should be reverted, removing the divide function" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that modulo function still exists (should be preserved)
|
||||
if ($calcContent -match "def modulo") {
|
||||
Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] modulo function missing (should still exist)" -ForegroundColor Red
|
||||
Write-Host "[HINT] Only revert the bad commit, not the good ones after it" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that multiply function exists (should be preserved)
|
||||
if ($calcContent -match "def multiply") {
|
||||
Write-Host "[PASS] multiply function preserved (good commit before bad one)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] multiply function missing" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
} else {
|
||||
Write-Host "[FAIL] calculator.py not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Merge Revert Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 2: Merge Revert ===" -ForegroundColor Cyan
|
||||
|
||||
git switch merge-revert 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] merge-revert branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Check that a revert commit for the merge exists
|
||||
$revertMerge = git log --oneline --grep="Revert.*Merge" 2>$null
|
||||
if ($revertMerge) {
|
||||
Write-Host "[PASS] Merge revert commit found" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] No merge revert commit found" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git revert -m 1 <merge-commit-hash>" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that the original merge commit still exists (revert doesn't erase it)
|
||||
$mergeCommit = git log --merges --oneline --grep="Merge feature-auth" 2>$null
|
||||
if ($mergeCommit) {
|
||||
Write-Host "[PASS] Original merge commit still in history (not erased)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Original merge commit not found (this is OK if you used a different approach)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Check that auth.py no longer exists or its effects are reverted
|
||||
if (-not (Test-Path "auth.py")) {
|
||||
Write-Host "[PASS] auth.py removed (merge reverted successfully)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] auth.py still exists (check if merge was fully reverted)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Check that calculator.py exists
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# After reverting the merge, calculator shouldn't import auth
|
||||
if ($calcContent -notmatch "from auth import") {
|
||||
Write-Host "[PASS] Auth integration reverted from calculator.py" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] calculator.py still imports auth (merge not fully reverted)" -ForegroundColor Red
|
||||
Write-Host "[HINT] Reverting the merge should remove the auth integration" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Multi Revert Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 3: Multi Revert ===" -ForegroundColor Cyan
|
||||
|
||||
git switch multi-revert 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] multi-revert branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Count revert commits
|
||||
$revertCommits = git log --oneline --grep="Revert" 2>$null
|
||||
$revertCount = ($revertCommits | Measure-Object).Count
|
||||
|
||||
if ($revertCount -ge 2) {
|
||||
Write-Host "[PASS] Found $revertCount revert commits (expected at least 2)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Found only $revertCount revert commit(s), need at least 2" -ForegroundColor Red
|
||||
Write-Host "[HINT] Revert both bad commits: git revert <commit1> <commit2>" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check calculator.py content
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# Check that square_root is NOT in code (reverted)
|
||||
if ($calcContent -notmatch "def square_root") {
|
||||
Write-Host "[PASS] Broken square_root function reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] square_root function still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that logarithm is NOT in code (reverted)
|
||||
if ($calcContent -notmatch "def logarithm") {
|
||||
Write-Host "[PASS] Broken logarithm function reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] logarithm function still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that power function still exists (good commit before bad ones)
|
||||
if ($calcContent -match "def power") {
|
||||
Write-Host "[PASS] power function preserved" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] power function missing (should still exist)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that absolute function still exists (good commit after bad ones)
|
||||
if ($calcContent -match "def absolute") {
|
||||
Write-Host "[PASS] absolute function preserved" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] absolute function missing (should still exist)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
} else {
|
||||
Write-Host "[FAIL] calculator.py not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
|
||||
Set-Location $originalDir
|
||||
|
||||
# Final summary
|
||||
Write-Host ""
|
||||
if ($allChecksPassed) {
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host " CONGRATULATIONS! ALL SCENARIOS PASSED!" -ForegroundColor Green
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host "`nYou've mastered git revert!" -ForegroundColor Cyan
|
||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Reverting regular commits safely" -ForegroundColor White
|
||||
Write-Host " ✓ Reverting merge commits with -m flag" -ForegroundColor White
|
||||
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
|
||||
Write-Host " ✓ Preserving history while undoing changes" -ForegroundColor White
|
||||
Write-Host "`nReady for Module 06: Git Reset!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "[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
|
||||
}
|
||||
717
01-essentials/06-reset/README.md
Normal file
717
01-essentials/06-reset/README.md
Normal file
@@ -0,0 +1,717 @@
|
||||
# Module 06: Git Reset - Dangerous History Rewriting
|
||||
|
||||
## ⚠️ CRITICAL SAFETY WARNING ⚠️
|
||||
|
||||
**Git reset is DESTRUCTIVE and DANGEROUS when misused!**
|
||||
|
||||
Before using `git reset`, always ask yourself:
|
||||
|
||||
```
|
||||
Have I pushed these commits to a remote repository?
|
||||
├─ YES → ❌ DO NOT USE RESET!
|
||||
│ Use git revert instead (Module 05)
|
||||
│ Rewriting pushed history breaks collaboration!
|
||||
│
|
||||
└─ NO → ✅ Proceed with reset (local cleanup only)
|
||||
Choose your mode carefully:
|
||||
--soft (safest), --mixed (moderate), --hard (DANGEROUS)
|
||||
```
|
||||
|
||||
**The Golden Rule:** NEVER reset commits that have been pushed/shared.
|
||||
|
||||
## About This Module
|
||||
|
||||
Welcome to Module 06, where you'll learn the powerful but dangerous `git reset` command. Unlike `git revert` (Module 05) which safely creates new commits, **reset erases commits from history**.
|
||||
|
||||
**Why reset exists:**
|
||||
- ✅ Clean up messy local commit history before pushing
|
||||
- ✅ Undo commits you haven't shared yet
|
||||
- ✅ Unstage files from the staging area
|
||||
- ✅ Recover from mistakes (with reflog)
|
||||
|
||||
**Why reset is dangerous:**
|
||||
- ⚠️ Erases commits permanently (without reflog)
|
||||
- ⚠️ Breaks repositories if used on pushed commits
|
||||
- ⚠️ Can lose work if used incorrectly
|
||||
- ⚠️ Confuses teammates if they have your commits
|
||||
|
||||
**Key principle:** Reset is for polishing LOCAL history before sharing.
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
By completing this module, you will:
|
||||
|
||||
1. Understand the three reset modes: --soft, --mixed, --hard
|
||||
2. Reset commits while keeping changes staged (--soft)
|
||||
3. Reset commits and unstage changes (--mixed)
|
||||
4. Reset commits and discard everything (--hard)
|
||||
5. Know when reset is appropriate (local only!)
|
||||
6. Understand when to use revert instead
|
||||
7. Use reflog to recover from mistakes
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting this module, you should:
|
||||
- Be comfortable with commits and staging (`git add`, `git commit`)
|
||||
- Understand `git revert` from Module 05
|
||||
- **Know the difference between local and pushed commits!**
|
||||
|
||||
## Setup
|
||||
|
||||
Run the setup script to create the challenge environment:
|
||||
|
||||
```powershell
|
||||
./setup.ps1
|
||||
```
|
||||
|
||||
This creates a `challenge/` directory with three branches demonstrating different reset modes:
|
||||
- `soft-reset` - Reset with --soft (keep changes staged)
|
||||
- `mixed-reset` - Reset with --mixed (unstage changes)
|
||||
- `hard-reset` - Reset with --hard (discard everything)
|
||||
|
||||
**Remember:** These are all LOCAL commits that have NEVER been pushed!
|
||||
|
||||
## Understanding Reset Modes
|
||||
|
||||
Git reset has three modes that control what happens to your changes:
|
||||
|
||||
| Mode | Commits | Staging Area | Working Directory |
|
||||
|------|---------|--------------|-------------------|
|
||||
| **--soft** | ✂️ Removed | ✅ Kept (staged) | ✅ Kept |
|
||||
| **--mixed** (default) | ✂️ Removed | ✂️ Cleared | ✅ Kept (unstaged) |
|
||||
| **--hard** | ✂️ Removed | ✂️ Cleared | ✂️ **LOST!** |
|
||||
|
||||
**Visual explanation:**
|
||||
|
||||
```
|
||||
Before reset (3 commits):
|
||||
A → B → C → HEAD
|
||||
|
||||
After git reset --soft HEAD~1:
|
||||
A → B → HEAD
|
||||
↑
|
||||
C's changes are staged
|
||||
|
||||
After git reset --mixed HEAD~1 (or just git reset HEAD~1):
|
||||
A → B → HEAD
|
||||
↑
|
||||
C's changes are unstaged (in working directory)
|
||||
|
||||
After git reset --hard HEAD~1:
|
||||
A → B → HEAD
|
||||
↑
|
||||
C's changes are GONE (discarded completely!)
|
||||
```
|
||||
|
||||
## Challenge 1: Soft Reset (Safest)
|
||||
|
||||
### Scenario
|
||||
|
||||
You committed "feature C" but immediately realized the implementation is wrong. You want to undo the commit but keep the changes staged so you can edit and re-commit them properly.
|
||||
|
||||
**Use case:** Fixing the last commit's message or contents.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Navigate to the challenge directory:
|
||||
```bash
|
||||
cd challenge
|
||||
```
|
||||
|
||||
2. You should be on the `soft-reset` branch. View the commits:
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
You should see:
|
||||
- "Add feature C - needs better implementation!"
|
||||
- "Add feature B"
|
||||
- "Add feature A"
|
||||
- "Initial project setup"
|
||||
|
||||
3. View the current state:
|
||||
```bash
|
||||
git status
|
||||
# Should be clean
|
||||
```
|
||||
|
||||
4. Reset the last commit with --soft:
|
||||
```bash
|
||||
git reset --soft HEAD~1
|
||||
```
|
||||
|
||||
5. Check what happened:
|
||||
```bash
|
||||
# Commit is gone
|
||||
git log --oneline
|
||||
# Should only show 3 commits now (feature C commit removed)
|
||||
|
||||
# Changes are still staged
|
||||
git status
|
||||
# Should show "Changes to be committed"
|
||||
|
||||
# View the staged changes
|
||||
git diff --cached
|
||||
# Should show feature C code ready to be re-committed
|
||||
```
|
||||
|
||||
### What to Observe
|
||||
|
||||
After `--soft` reset:
|
||||
- ✅ Commit removed from history
|
||||
- ✅ Changes remain in staging area
|
||||
- ✅ Working directory unchanged
|
||||
- ✅ Ready to edit and re-commit
|
||||
|
||||
**When to use --soft:**
|
||||
- Fix the last commit message (though `commit --amend` is simpler)
|
||||
- Combine multiple commits into one
|
||||
- Re-do a commit with better changes
|
||||
|
||||
## Challenge 2: Mixed Reset (Default, Moderate)
|
||||
|
||||
### Scenario
|
||||
|
||||
You committed two experimental features that aren't ready. You want to remove both commits and have the changes back in your working directory (unstaged) so you can review and selectively re-commit them.
|
||||
|
||||
**Use case:** Undoing commits and starting over with more careful staging.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the mixed-reset branch:
|
||||
```bash
|
||||
git switch mixed-reset
|
||||
```
|
||||
|
||||
2. View the commits:
|
||||
```bash
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
You should see:
|
||||
- "Add debug mode - REMOVE THIS TOO!"
|
||||
- "Add experimental feature X - REMOVE THIS!"
|
||||
- "Add logging system"
|
||||
- "Add application lifecycle"
|
||||
|
||||
3. Reset the last TWO commits (default is --mixed):
|
||||
```bash
|
||||
git reset HEAD~2
|
||||
# This is equivalent to: git reset --mixed HEAD~2
|
||||
```
|
||||
|
||||
4. Check what happened:
|
||||
```bash
|
||||
# Commits are gone
|
||||
git log --oneline
|
||||
# Should only show 2 commits (lifecycle + logging)
|
||||
|
||||
# NO staged changes
|
||||
git diff --cached
|
||||
# Should be empty
|
||||
|
||||
# Changes are in working directory (unstaged)
|
||||
git status
|
||||
# Should show "Changes not staged for commit"
|
||||
|
||||
# View the unstaged changes
|
||||
git diff
|
||||
# Should show experimental and debug code
|
||||
```
|
||||
|
||||
### What to Observe
|
||||
|
||||
After `--mixed` reset (the default):
|
||||
- ✅ Commits removed from history
|
||||
- ✅ Staging area cleared
|
||||
- ✅ Changes moved to working directory (unstaged)
|
||||
- ✅ Can selectively stage and re-commit parts
|
||||
|
||||
**When to use --mixed (default):**
|
||||
- Undo commits and start over with clean staging
|
||||
- Split one large commit into multiple smaller ones
|
||||
- Review changes before re-committing
|
||||
- Most common reset mode for cleanup
|
||||
|
||||
## Challenge 3: Hard Reset (MOST DANGEROUS!)
|
||||
|
||||
### ⚠️ EXTREME CAUTION REQUIRED ⚠️
|
||||
|
||||
**This will PERMANENTLY DELETE your work!**
|
||||
|
||||
Only use `--hard` when you're absolutely sure you want to throw away changes.
|
||||
|
||||
### Scenario
|
||||
|
||||
You committed completely broken code that you want to discard entirely. There's no salvaging it—you just want it gone.
|
||||
|
||||
**Use case:** Throwing away failed experiments or completely wrong code.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the hard-reset branch:
|
||||
```bash
|
||||
git switch hard-reset
|
||||
```
|
||||
|
||||
2. View the commits and the broken code:
|
||||
```bash
|
||||
git log --oneline
|
||||
# Shows "Add broken helper D - DISCARD COMPLETELY!"
|
||||
|
||||
cat utils.py
|
||||
# Shows the broken helper_d function
|
||||
```
|
||||
|
||||
3. Reset the last commit with --hard:
|
||||
```bash
|
||||
git reset --hard HEAD~1
|
||||
```
|
||||
|
||||
**WARNING:** This will permanently discard all changes from that commit!
|
||||
|
||||
4. Check what happened:
|
||||
```bash
|
||||
# Commit is gone
|
||||
git log --oneline
|
||||
# Should only show 2 commits
|
||||
|
||||
# NO staged changes
|
||||
git diff --cached
|
||||
# Empty
|
||||
|
||||
# NO unstaged changes
|
||||
git diff
|
||||
# Empty
|
||||
|
||||
# Working directory clean
|
||||
git status
|
||||
# "nothing to commit, working tree clean"
|
||||
|
||||
# File doesn't have broken code
|
||||
cat utils.py
|
||||
# helper_d is completely gone
|
||||
```
|
||||
|
||||
### What to Observe
|
||||
|
||||
After `--hard` reset:
|
||||
- ✅ Commit removed from history
|
||||
- ✅ Staging area cleared
|
||||
- ✅ Working directory reset to match
|
||||
- ⚠️ All changes from that commit PERMANENTLY DELETED
|
||||
|
||||
**When to use --hard:**
|
||||
- Discarding failed experiments completely
|
||||
- Throwing away work you don't want (CAREFUL!)
|
||||
- Cleaning up after mistakes (use reflog to recover if needed)
|
||||
- Resetting to a known good state
|
||||
|
||||
**⚠️ WARNING:** Files in the discarded commit are NOT gone forever—they're still in reflog for about 90 days. See "Recovery with Reflog" section below.
|
||||
|
||||
## Understanding HEAD~N Syntax
|
||||
|
||||
When resetting, you specify where to reset to:
|
||||
|
||||
```bash
|
||||
# Reset to the commit before HEAD
|
||||
git reset HEAD~1
|
||||
|
||||
# Reset to 2 commits before HEAD
|
||||
git reset HEAD~2
|
||||
|
||||
# Reset to 3 commits before HEAD
|
||||
git reset HEAD~3
|
||||
|
||||
# Reset to a specific commit hash
|
||||
git reset abc123
|
||||
|
||||
# Reset to a branch
|
||||
git reset main
|
||||
```
|
||||
|
||||
**Visualization:**
|
||||
|
||||
```
|
||||
HEAD~3 HEAD~2 HEAD~1 HEAD
|
||||
↓ ↓ ↓ ↓
|
||||
A → B → C → D → E
|
||||
↑
|
||||
Current commit
|
||||
```
|
||||
|
||||
- `git reset HEAD~1` moves HEAD from E to D
|
||||
- `git reset HEAD~2` moves HEAD from E to C
|
||||
- `git reset abc123` moves HEAD to that specific commit
|
||||
|
||||
## Verification
|
||||
|
||||
Verify your solutions by running the verification script:
|
||||
|
||||
```bash
|
||||
cd .. # Return to module directory
|
||||
./verify.ps1
|
||||
```
|
||||
|
||||
The script checks that:
|
||||
- ✅ Commits were reset (count decreased)
|
||||
- ✅ --soft: Changes remain staged
|
||||
- ✅ --mixed: Changes are unstaged
|
||||
- ✅ --hard: Everything is clean
|
||||
|
||||
## Recovery with Reflog
|
||||
|
||||
**Good news:** Even `--hard` reset doesn't immediately destroy commits!
|
||||
|
||||
Git keeps a "reflog" (reference log) of where HEAD has been for about 90 days. You can use this to recover "lost" commits.
|
||||
|
||||
### How to Recover from a Reset
|
||||
|
||||
1. View the reflog:
|
||||
```bash
|
||||
git reflog
|
||||
```
|
||||
|
||||
Output example:
|
||||
```
|
||||
abc123 HEAD@{0}: reset: moving to HEAD~1
|
||||
def456 HEAD@{1}: commit: Add broken helper D
|
||||
...
|
||||
```
|
||||
|
||||
2. Find the commit you want to recover (def456 in this example)
|
||||
|
||||
3. Reset back to it:
|
||||
```bash
|
||||
git reset def456
|
||||
# Or use the reflog reference:
|
||||
git reset HEAD@{1}
|
||||
```
|
||||
|
||||
4. Your "lost" commit is back!
|
||||
|
||||
### Reflog Safety Net
|
||||
|
||||
**Important:**
|
||||
- Reflog entries expire after ~90 days (configurable)
|
||||
- Reflog is LOCAL to your repository (not shared)
|
||||
- `git gc` can clean up old reflog entries
|
||||
- If you really lose a commit, check reflog first!
|
||||
|
||||
**Pro tip:** Before doing dangerous operations, note your current commit hash:
|
||||
```bash
|
||||
git log --oneline | head -1
|
||||
# abc123 Current work
|
||||
```
|
||||
|
||||
## When to Use Git Reset
|
||||
|
||||
Use `git reset` when:
|
||||
|
||||
- ✅ **Commits are LOCAL only** (never pushed)
|
||||
- ✅ **Cleaning up messy history** before sharing
|
||||
- ✅ **Undoing recent commits** you don't want
|
||||
- ✅ **Combining commits** into one clean commit
|
||||
- ✅ **Unstaging files** (mixed mode)
|
||||
- ✅ **Polishing commit history** before pull request
|
||||
|
||||
**Golden Rule:** Only reset commits that are local to your machine!
|
||||
|
||||
## When NOT to Use Git Reset
|
||||
|
||||
DO NOT use `git reset` when:
|
||||
|
||||
- ❌ **Commits are pushed/shared** with others
|
||||
- ❌ **Teammates have your commits** (breaks their repos)
|
||||
- ❌ **In public repositories** (use revert instead)
|
||||
- ❌ **Unsure if pushed** (check `git log origin/main`)
|
||||
- ❌ **On main/master branch** after push
|
||||
- ❌ **Need audit trail** of changes
|
||||
|
||||
**Use git revert instead** (Module 05) for pushed commits!
|
||||
|
||||
## Decision Tree: Reset vs Revert
|
||||
|
||||
```
|
||||
Need to undo a commit?
|
||||
│
|
||||
├─ Have you pushed this commit?
|
||||
│ │
|
||||
│ ├─ YES → Use git revert (Module 05)
|
||||
│ │ Safe for shared history
|
||||
│ │ Preserves complete audit trail
|
||||
│ │
|
||||
│ └─ NO → Can use git reset (local only)
|
||||
│ │
|
||||
│ ├─ Want to keep changes?
|
||||
│ │ │
|
||||
│ │ ├─ Keep staged → git reset --soft
|
||||
│ │ └─ Keep unstaged → git reset --mixed
|
||||
│ │
|
||||
│ └─ Discard everything? → git reset --hard
|
||||
│ (CAREFUL!)
|
||||
```
|
||||
|
||||
## Reset vs Revert vs Rebase
|
||||
|
||||
| Command | History | Safety | Use Case |
|
||||
|---------|---------|--------|----------|
|
||||
| **reset** | Erases | ⚠️ Dangerous | Local cleanup before push |
|
||||
| **revert** | Preserves | ✅ Safe | Undo pushed commits |
|
||||
| **rebase** | Rewrites | ⚠️ Dangerous | Polish history before push |
|
||||
|
||||
**This module teaches reset.** You learned revert in Module 05.
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Basic Reset
|
||||
|
||||
```bash
|
||||
# Reset last commit, keep changes staged
|
||||
git reset --soft HEAD~1
|
||||
|
||||
# Reset last commit, unstage changes (default)
|
||||
git reset HEAD~1
|
||||
git reset --mixed HEAD~1 # Same as above
|
||||
|
||||
# Reset last commit, discard everything (DANGEROUS!)
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# Reset multiple commits
|
||||
git reset --soft HEAD~3 # Last 3 commits
|
||||
|
||||
# Reset to specific commit
|
||||
git reset --soft abc123
|
||||
```
|
||||
|
||||
### Unstaging Files
|
||||
|
||||
```bash
|
||||
# Unstage a specific file (common use of reset)
|
||||
git reset HEAD filename.txt
|
||||
|
||||
# Unstage all files
|
||||
git reset HEAD .
|
||||
|
||||
# This is the same as:
|
||||
git restore --staged filename.txt # Modern syntax
|
||||
```
|
||||
|
||||
### Reflog and Recovery
|
||||
|
||||
```bash
|
||||
# View reflog
|
||||
git reflog
|
||||
|
||||
# Recover from reset
|
||||
git reset --hard HEAD@{1}
|
||||
git reset --hard abc123
|
||||
```
|
||||
|
||||
### Check Before Reset
|
||||
|
||||
```bash
|
||||
# Check if commits are pushed
|
||||
git log origin/main..HEAD
|
||||
# If output is empty, commits are pushed (DO NOT RESET)
|
||||
# If output shows commits, they're local (safe to reset)
|
||||
|
||||
# Another way to check
|
||||
git log --oneline --graph --all
|
||||
# Look for origin/main marker
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. Resetting Pushed Commits
|
||||
|
||||
```bash
|
||||
# ❌ NEVER do this if you've pushed!
|
||||
git push
|
||||
# ... time passes ...
|
||||
git reset --hard HEAD~3 # BREAKS teammate repos!
|
||||
|
||||
# ✅ Do this instead
|
||||
git revert HEAD~3..HEAD # Safe for shared history
|
||||
```
|
||||
|
||||
### 2. Using --hard Without Thinking
|
||||
|
||||
```bash
|
||||
# ❌ Dangerous - loses work!
|
||||
git reset --hard HEAD~1
|
||||
|
||||
# ✅ Better - keep changes to review
|
||||
git reset --mixed HEAD~1
|
||||
# Now you can review changes and decide
|
||||
```
|
||||
|
||||
### 3. Resetting Without Checking If Pushed
|
||||
|
||||
```bash
|
||||
# ❌ Risky - are these commits pushed?
|
||||
git reset HEAD~5
|
||||
|
||||
# ✅ Check first
|
||||
git log origin/main..HEAD # Local commits only
|
||||
git reset HEAD~5 # Now safe if output showed commits
|
||||
```
|
||||
|
||||
### 4. Forgetting Reflog Exists
|
||||
|
||||
```bash
|
||||
# ❌ Panic after accidental --hard reset
|
||||
# "I lost my work!"
|
||||
|
||||
# ✅ Check reflog first!
|
||||
git reflog # Find the "lost" commit
|
||||
git reset --hard HEAD@{1} # Recover it
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
1. **Always check if commits are pushed before reset:**
|
||||
```bash
|
||||
git log origin/main..HEAD
|
||||
```
|
||||
|
||||
2. **Prefer --mixed over --hard:**
|
||||
- You can always discard changes later
|
||||
- Hard to recover if you use --hard by mistake
|
||||
|
||||
3. **Commit often locally, reset before push:**
|
||||
- Make many small local commits
|
||||
- Reset/squash into clean commits before pushing
|
||||
|
||||
4. **Use descriptive commit messages even for local commits:**
|
||||
- Helps when reviewing before reset
|
||||
- Useful when checking reflog
|
||||
|
||||
5. **Know your escape hatch:**
|
||||
```bash
|
||||
git reflog # Your safety net!
|
||||
```
|
||||
|
||||
6. **Communicate with team:**
|
||||
- NEVER reset shared branches (main, develop, etc.)
|
||||
- Only reset your personal feature branches
|
||||
- Only before pushing!
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "I accidentally reset with --hard and lost work!"
|
||||
|
||||
**Solution:** Check reflog:
|
||||
```bash
|
||||
git reflog
|
||||
# Find the commit before your reset
|
||||
git reset --hard HEAD@{1} # Or the commit hash
|
||||
```
|
||||
|
||||
**Prevention:** Always use --mixed first, then discard if really needed.
|
||||
|
||||
### "I reset but teammates still have my commits"
|
||||
|
||||
**Problem:** You reset and pushed with --force after they pulled.
|
||||
|
||||
**Impact:** Their repository is now broken/inconsistent.
|
||||
|
||||
**Solution:** Communicate! They need to:
|
||||
```bash
|
||||
git fetch
|
||||
git reset --hard origin/main # Or whatever branch
|
||||
```
|
||||
|
||||
**Prevention:** NEVER reset pushed commits!
|
||||
|
||||
### "Reset didn't do what I expected"
|
||||
|
||||
**Issue:** Wrong mode or wrong HEAD~N count.
|
||||
|
||||
**Solution:** Check current state:
|
||||
```bash
|
||||
git status
|
||||
git diff
|
||||
git diff --cached
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
Undo the reset:
|
||||
```bash
|
||||
git reflog
|
||||
git reset HEAD@{1} # Go back to before your reset
|
||||
```
|
||||
|
||||
### "Can't reset - 'fatal: ambiguous argument HEAD~1'"
|
||||
|
||||
**Issue:** No commits to reset (probably first commit).
|
||||
|
||||
**Solution:** You can't reset before the first commit. If you want to remove the first commit entirely:
|
||||
```bash
|
||||
rm -rf .git # Nuclear option - deletes entire repo
|
||||
git init # Start over
|
||||
```
|
||||
|
||||
## Advanced: Reset Internals
|
||||
|
||||
Understanding what reset does under the hood:
|
||||
|
||||
```bash
|
||||
# Reset moves the branch pointer
|
||||
# Before:
|
||||
main → A → B → C (HEAD)
|
||||
|
||||
# After git reset --soft HEAD~1:
|
||||
main → A → B (HEAD)
|
||||
↑
|
||||
C still exists in reflog, just not in branch history
|
||||
|
||||
# The commit object C is still in .git/objects
|
||||
# It's just unreachable from any branch
|
||||
```
|
||||
|
||||
**Key insight:** Reset moves the HEAD and branch pointers backward. The commits still exist temporarily in reflog until garbage collection.
|
||||
|
||||
## Going Further
|
||||
|
||||
Now that you understand reset, you're ready for:
|
||||
|
||||
- **Module 07: Git Stash** - Temporarily save uncommitted work
|
||||
- **Module 08: Multiplayer Git** - Collaborate with complex workflows
|
||||
- **Interactive Rebase** - Advanced history polishing (beyond this workshop)
|
||||
|
||||
## Summary
|
||||
|
||||
You've learned:
|
||||
|
||||
- ✅ `git reset` rewrites history by moving HEAD backward
|
||||
- ✅ `--soft` keeps changes staged (safest)
|
||||
- ✅ `--mixed` (default) unstages changes
|
||||
- ✅ `--hard` discards everything (most dangerous)
|
||||
- ✅ NEVER reset pushed/shared commits
|
||||
- ✅ Use reflog to recover from mistakes
|
||||
- ✅ Check if commits are pushed before resetting
|
||||
- ✅ Use revert (Module 05) for shared commits
|
||||
|
||||
**The Critical Rule:** Reset is for LOCAL commits ONLY. Once you push, use revert!
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete all three challenge scenarios
|
||||
2. Run `./verify.ps1` to check your solutions
|
||||
3. Practice checking if commits are pushed before reset
|
||||
4. Move on to Module 07: Git Stash
|
||||
|
||||
---
|
||||
|
||||
**⚠️ FINAL REMINDER ⚠️**
|
||||
|
||||
**Before any `git reset` command, ask yourself:**
|
||||
|
||||
> "Have I pushed these commits?"
|
||||
|
||||
If YES → Use `git revert` instead!
|
||||
|
||||
If NO → Proceed carefully, choose the right mode.
|
||||
|
||||
**When in doubt, use --mixed instead of --hard!**
|
||||
24
01-essentials/06-reset/reset.ps1
Normal file
24
01-essentials/06-reset/reset.ps1
Normal file
@@ -0,0 +1,24 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Resets the Module 06 challenge environment to start fresh.
|
||||
|
||||
.DESCRIPTION
|
||||
This script removes the challenge directory and re-runs setup.ps1
|
||||
to create a fresh challenge environment.
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Resetting Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
|
||||
|
||||
# Check if challenge directory exists
|
||||
if (Test-Path "challenge") {
|
||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
||||
Remove-Item -Recurse -Force "challenge"
|
||||
Write-Host "[OK] Challenge directory removed" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] No existing challenge directory found" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Run setup to create fresh environment
|
||||
Write-Host "`nRunning setup to create fresh challenge environment..." -ForegroundColor Cyan
|
||||
& "$PSScriptRoot/setup.ps1"
|
||||
348
01-essentials/06-reset/setup.ps1
Normal file
348
01-essentials/06-reset/setup.ps1
Normal file
@@ -0,0 +1,348 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets up the Module 06 challenge environment for learning git reset.
|
||||
|
||||
.DESCRIPTION
|
||||
This script creates a challenge directory with three branches demonstrating
|
||||
different reset scenarios:
|
||||
- soft-reset: Reset with --soft (keeps changes staged)
|
||||
- mixed-reset: Reset with --mixed (unstages changes)
|
||||
- hard-reset: Reset with --hard (discards everything) + reflog recovery
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Setting up Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
|
||||
Write-Host "⚠️ WARNING: Git reset is DANGEROUS - use with extreme caution! ⚠️" -ForegroundColor Red
|
||||
|
||||
# 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"
|
||||
|
||||
# ============================================================================
|
||||
# Create initial commit (shared by all scenarios)
|
||||
# ============================================================================
|
||||
$readmeContent = @"
|
||||
# Git Reset Practice
|
||||
|
||||
This repository contains practice scenarios for learning git reset.
|
||||
"@
|
||||
Set-Content -Path "README.md" -Value $readmeContent
|
||||
git add .
|
||||
git commit -m "Initial commit" | Out-Null
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Soft Reset (--soft)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 1: Creating soft-reset branch..." -ForegroundColor Cyan
|
||||
|
||||
# Create soft-reset branch from initial commit
|
||||
git switch -c soft-reset | Out-Null
|
||||
|
||||
# Build up scenario 1 commits
|
||||
$projectContent = @"
|
||||
# project.py - Main project file
|
||||
|
||||
def initialize():
|
||||
"""Initialize the project."""
|
||||
print("Project initialized")
|
||||
|
||||
def main():
|
||||
initialize()
|
||||
print("Running application...")
|
||||
"@
|
||||
Set-Content -Path "project.py" -Value $projectContent
|
||||
git add .
|
||||
git commit -m "Initial project setup" | Out-Null
|
||||
|
||||
# Good commit: Add feature A
|
||||
$projectContent = @"
|
||||
# project.py - Main project file
|
||||
|
||||
def initialize():
|
||||
"""Initialize the project."""
|
||||
print("Project initialized")
|
||||
|
||||
def feature_a():
|
||||
"""Feature A implementation."""
|
||||
print("Feature A is working")
|
||||
|
||||
def main():
|
||||
initialize()
|
||||
feature_a()
|
||||
print("Running application...")
|
||||
"@
|
||||
Set-Content -Path "project.py" -Value $projectContent
|
||||
git add .
|
||||
git commit -m "Add feature A" | Out-Null
|
||||
|
||||
# Good commit: Add feature B
|
||||
$projectContent = @"
|
||||
# project.py - Main project file
|
||||
|
||||
def initialize():
|
||||
"""Initialize the project."""
|
||||
print("Project initialized")
|
||||
|
||||
def feature_a():
|
||||
"""Feature A implementation."""
|
||||
print("Feature A is working")
|
||||
|
||||
def feature_b():
|
||||
"""Feature B implementation."""
|
||||
print("Feature B is working")
|
||||
|
||||
def main():
|
||||
initialize()
|
||||
feature_a()
|
||||
feature_b()
|
||||
print("Running application...")
|
||||
"@
|
||||
Set-Content -Path "project.py" -Value $projectContent
|
||||
git add .
|
||||
git commit -m "Add feature B" | Out-Null
|
||||
|
||||
# BAD commit: Add feature C (wrong implementation)
|
||||
$projectContent = @"
|
||||
# project.py - Main project file
|
||||
|
||||
def initialize():
|
||||
"""Initialize the project."""
|
||||
print("Project initialized")
|
||||
|
||||
def feature_a():
|
||||
"""Feature A implementation."""
|
||||
print("Feature A is working")
|
||||
|
||||
def feature_b():
|
||||
"""Feature B implementation."""
|
||||
print("Feature B is working")
|
||||
|
||||
def feature_c():
|
||||
"""Feature C implementation - WRONG!"""
|
||||
print("Feature C has bugs!") # This needs to be re-implemented
|
||||
|
||||
def main():
|
||||
initialize()
|
||||
feature_a()
|
||||
feature_b()
|
||||
feature_c()
|
||||
print("Running application...")
|
||||
"@
|
||||
Set-Content -Path "project.py" -Value $projectContent
|
||||
git add .
|
||||
git commit -m "Add feature C - needs better implementation!" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] soft-reset branch with commit to reset --soft" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Mixed Reset (--mixed, default)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 2: Creating mixed-reset branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to initial commit and create mixed-reset branch
|
||||
git switch main | Out-Null
|
||||
git switch -c mixed-reset | Out-Null
|
||||
|
||||
# Build up scenario 2 commits
|
||||
$appContent = @"
|
||||
# app.py - Application entry point
|
||||
|
||||
def start():
|
||||
"""Start the application."""
|
||||
print("Application started")
|
||||
|
||||
def stop():
|
||||
"""Stop the application."""
|
||||
print("Application stopped")
|
||||
"@
|
||||
Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add application lifecycle" | Out-Null
|
||||
|
||||
# Good commit: Add logging
|
||||
$appContent = @"
|
||||
# app.py - Application entry point
|
||||
|
||||
def log(message):
|
||||
"""Log a message."""
|
||||
print(f"[LOG] {message}")
|
||||
|
||||
def start():
|
||||
"""Start the application."""
|
||||
log("Application started")
|
||||
|
||||
def stop():
|
||||
"""Stop the application."""
|
||||
log("Application stopped")
|
||||
"@
|
||||
Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add logging system" | Out-Null
|
||||
|
||||
# BAD commit 1: Add experimental feature X
|
||||
$appContent = @"
|
||||
# app.py - Application entry point
|
||||
|
||||
def log(message):
|
||||
"""Log a message."""
|
||||
print(f"[LOG] {message}")
|
||||
|
||||
def experimental_feature_x():
|
||||
"""Experimental feature - NOT READY!"""
|
||||
log("Feature X is experimental and buggy")
|
||||
|
||||
def start():
|
||||
"""Start the application."""
|
||||
log("Application started")
|
||||
experimental_feature_x()
|
||||
|
||||
def stop():
|
||||
"""Stop the application."""
|
||||
log("Application stopped")
|
||||
"@
|
||||
Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add experimental feature X - REMOVE THIS!" | Out-Null
|
||||
|
||||
# BAD commit 2: Add debug mode (also not ready)
|
||||
$appContent = @"
|
||||
# app.py - Application entry point
|
||||
|
||||
DEBUG_MODE = True # Should not be committed!
|
||||
|
||||
def log(message):
|
||||
"""Log a message."""
|
||||
print(f"[LOG] {message}")
|
||||
|
||||
def experimental_feature_x():
|
||||
"""Experimental feature - NOT READY!"""
|
||||
log("Feature X is experimental and buggy")
|
||||
|
||||
def start():
|
||||
"""Start the application."""
|
||||
if DEBUG_MODE:
|
||||
log("DEBUG MODE ACTIVE!")
|
||||
log("Application started")
|
||||
experimental_feature_x()
|
||||
|
||||
def stop():
|
||||
"""Stop the application."""
|
||||
log("Application stopped")
|
||||
"@
|
||||
Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add debug mode - REMOVE THIS TOO!" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] mixed-reset branch with commits to reset --mixed" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Hard Reset (--hard) + Reflog Recovery
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 3: Creating hard-reset branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main and create hard-reset branch
|
||||
git switch main | Out-Null
|
||||
git switch -c hard-reset | Out-Null
|
||||
|
||||
# Reset to basic state
|
||||
$utilsContent = @"
|
||||
# utils.py - Utility functions
|
||||
|
||||
def helper_a():
|
||||
"""Helper function A."""
|
||||
return "Helper A"
|
||||
|
||||
def helper_b():
|
||||
"""Helper function B."""
|
||||
return "Helper B"
|
||||
"@
|
||||
Set-Content -Path "utils.py" -Value $utilsContent
|
||||
git add .
|
||||
git commit -m "Add utility helpers" | Out-Null
|
||||
|
||||
# Good commit: Add helper C
|
||||
$utilsContent = @"
|
||||
# utils.py - Utility functions
|
||||
|
||||
def helper_a():
|
||||
"""Helper function A."""
|
||||
return "Helper A"
|
||||
|
||||
def helper_b():
|
||||
"""Helper function B."""
|
||||
return "Helper B"
|
||||
|
||||
def helper_c():
|
||||
"""Helper function C."""
|
||||
return "Helper C"
|
||||
"@
|
||||
Set-Content -Path "utils.py" -Value $utilsContent
|
||||
git add .
|
||||
git commit -m "Add helper C" | Out-Null
|
||||
|
||||
# BAD commit: Add broken helper D (completely wrong)
|
||||
$utilsContent = @"
|
||||
# utils.py - Utility functions
|
||||
|
||||
def helper_a():
|
||||
"""Helper function A."""
|
||||
return "Helper A"
|
||||
|
||||
def helper_b():
|
||||
"""Helper function B."""
|
||||
return "Helper B"
|
||||
|
||||
def helper_c():
|
||||
"""Helper function C."""
|
||||
return "Helper C"
|
||||
|
||||
def helper_d():
|
||||
"""COMPLETELY BROKEN - throw away!"""
|
||||
# This is all wrong and needs to be discarded
|
||||
broken_code = "This doesn't even make sense"
|
||||
return broken_code.nonexistent_method() # Will crash!
|
||||
"@
|
||||
Set-Content -Path "utils.py" -Value $utilsContent
|
||||
git add .
|
||||
git commit -m "Add broken helper D - DISCARD COMPLETELY!" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] hard-reset branch with commit to reset --hard" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# Return to soft-reset to start
|
||||
# ============================================================================
|
||||
git switch soft-reset | Out-Null
|
||||
|
||||
# Return to module directory
|
||||
Set-Location ..
|
||||
|
||||
Write-Host "`n=== Setup Complete! ===`n" -ForegroundColor Green
|
||||
Write-Host "Three reset scenarios have been created:" -ForegroundColor Cyan
|
||||
Write-Host " 1. soft-reset - Reset --soft (keep changes staged)" -ForegroundColor White
|
||||
Write-Host " 2. mixed-reset - Reset --mixed (unstage changes)" -ForegroundColor White
|
||||
Write-Host " 3. hard-reset - Reset --hard (discard everything) + reflog recovery" -ForegroundColor White
|
||||
Write-Host "`n⚠️ CRITICAL SAFETY REMINDER ⚠️" -ForegroundColor Red
|
||||
Write-Host "NEVER use git reset on commits that have been PUSHED!" -ForegroundColor Red
|
||||
Write-Host "These scenarios are LOCAL ONLY for practice." -ForegroundColor Yellow
|
||||
Write-Host "`nYou are currently on the 'soft-reset' branch." -ForegroundColor Cyan
|
||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
||||
Write-Host " 2. Read the README.md for detailed instructions" -ForegroundColor White
|
||||
Write-Host " 3. Complete each reset challenge" -ForegroundColor White
|
||||
Write-Host " 4. Run '..\\verify.ps1' to check your solutions" -ForegroundColor White
|
||||
Write-Host ""
|
||||
231
01-essentials/06-reset/verify.ps1
Normal file
231
01-essentials/06-reset/verify.ps1
Normal file
@@ -0,0 +1,231 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies the Module 06 challenge solutions.
|
||||
|
||||
.DESCRIPTION
|
||||
Checks that all three reset scenarios have been completed correctly:
|
||||
- soft-reset: Commit reset but changes remain staged
|
||||
- mixed-reset: Commits reset and changes unstaged
|
||||
- hard-reset: Everything reset and discarded
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Verifying Module 06: Git Reset Solutions ===" -ForegroundColor Cyan
|
||||
Write-Host "⚠️ Remember: NEVER reset pushed commits! ⚠️" -ForegroundColor Red
|
||||
|
||||
$allChecksPassed = $true
|
||||
$originalDir = Get-Location
|
||||
|
||||
# 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] Not a git repository. Run setup.ps1 first." -ForegroundColor Red
|
||||
Set-Location $originalDir
|
||||
exit 1
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Soft Reset Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 1: Soft Reset ===`n" -ForegroundColor Cyan
|
||||
|
||||
git switch soft-reset 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] soft-reset branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Count commits (should be 4: Initial + project setup + feature A + feature B)
|
||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
||||
|
||||
if ($commitCount -eq 4) {
|
||||
Write-Host "[PASS] Commit count is 4 (feature C commit was reset)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Expected 4 commits, found $commitCount" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git reset --soft HEAD~1" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check if changes are staged
|
||||
$stagedChanges = git diff --cached --name-only 2>$null
|
||||
if ($stagedChanges) {
|
||||
Write-Host "[PASS] Changes are staged (feature C code in staging area)" -ForegroundColor Green
|
||||
|
||||
# Verify the staged changes contain feature C code
|
||||
$stagedContent = git diff --cached 2>$null
|
||||
if ($stagedContent -match "feature_c") {
|
||||
Write-Host "[PASS] Staged changes contain feature C code" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Staged changes don't seem to contain feature C" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[FAIL] No staged changes found" -ForegroundColor Red
|
||||
Write-Host "[HINT] After --soft reset, changes should remain staged" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check working directory has no unstaged changes to tracked files
|
||||
$unstagedChanges = git diff --name-only 2>$null
|
||||
if (-not $unstagedChanges) {
|
||||
Write-Host "[PASS] No unstaged changes (all changes are staged)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Found unstaged changes (expected only staged changes)" -ForegroundColor Yellow
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Mixed Reset Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 2: Mixed Reset ===`n" -ForegroundColor Cyan
|
||||
|
||||
git switch mixed-reset 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] mixed-reset branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Count commits (should be 3: Initial + lifecycle + logging, bad commits removed)
|
||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
||||
|
||||
if ($commitCount -eq 3) {
|
||||
Write-Host "[PASS] Commit count is 3 (both bad commits were reset)" -ForegroundColor Green
|
||||
} elseif ($commitCount -eq 4) {
|
||||
Write-Host "[INFO] Commit count is 4 (one commit reset, need to reset one more)" -ForegroundColor Yellow
|
||||
Write-Host "[HINT] Use: git reset HEAD~1 (or git reset --mixed HEAD~1)" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git reset --mixed HEAD~2 to remove both bad commits" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that there are NO staged changes
|
||||
$stagedChanges = git diff --cached --name-only 2>$null
|
||||
if (-not $stagedChanges) {
|
||||
Write-Host "[PASS] No staged changes (--mixed unstages everything)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Found staged changes (--mixed should unstage)" -ForegroundColor Red
|
||||
Write-Host "[HINT] After --mixed reset, changes should be unstaged" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that there ARE unstaged changes in working directory
|
||||
$unstagedChanges = git diff --name-only 2>$null
|
||||
if ($unstagedChanges) {
|
||||
Write-Host "[PASS] Unstaged changes present in working directory" -ForegroundColor Green
|
||||
|
||||
# Verify unstaged changes contain the experimental/debug code
|
||||
$workingContent = git diff 2>$null
|
||||
if ($workingContent -match "experimental|DEBUG") {
|
||||
Write-Host "[PASS] Unstaged changes contain the reset code" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Unstaged changes don't contain expected code" -ForegroundColor Yellow
|
||||
}
|
||||
} else {
|
||||
Write-Host "[FAIL] No unstaged changes found" -ForegroundColor Red
|
||||
Write-Host "[HINT] After --mixed reset, changes should be in working directory (unstaged)" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Hard Reset Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 3: Hard Reset ===`n" -ForegroundColor Cyan
|
||||
|
||||
git switch hard-reset 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] hard-reset branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Count commits (should be 3: Initial + utilities + helper C, bad commit removed)
|
||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
||||
|
||||
if ($commitCount -eq 3) {
|
||||
Write-Host "[PASS] Commit count is 3 (broken commit was reset)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git reset --hard HEAD~1" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that there are NO staged changes
|
||||
$stagedChanges = git diff --cached --name-only 2>$null
|
||||
if (-not $stagedChanges) {
|
||||
Write-Host "[PASS] No staged changes (--hard discards everything)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Found staged changes (--hard should discard all)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that there are NO unstaged changes
|
||||
$unstagedChanges = git diff --name-only 2>$null
|
||||
if (-not $unstagedChanges) {
|
||||
Write-Host "[PASS] No unstaged changes (--hard discards everything)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Found unstaged changes (--hard should discard all)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check working directory is clean
|
||||
$statusOutput = git status --porcelain 2>$null
|
||||
if (-not $statusOutput) {
|
||||
Write-Host "[PASS] Working directory is completely clean" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Working directory has some changes" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Verify the file doesn't have the broken code
|
||||
if (Test-Path "utils.py") {
|
||||
$utilsContent = Get-Content "utils.py" -Raw
|
||||
if ($utilsContent -notmatch "helper_d") {
|
||||
Write-Host "[PASS] Broken helper_d function is gone" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Broken helper_d still exists (wasn't reset)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
if ($utilsContent -match "helper_c") {
|
||||
Write-Host "[PASS] Good helper_c function is preserved" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] Good helper_c function missing" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Set-Location $originalDir
|
||||
|
||||
# Final summary
|
||||
Write-Host ""
|
||||
if ($allChecksPassed) {
|
||||
Write-Host "==========================================" -ForegroundColor Green
|
||||
Write-Host " CONGRATULATIONS! ALL SCENARIOS PASSED!" -ForegroundColor Green
|
||||
Write-Host "==========================================" -ForegroundColor Green
|
||||
Write-Host "`nYou've mastered git reset!" -ForegroundColor Cyan
|
||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Resetting commits with --soft (keep staged)" -ForegroundColor White
|
||||
Write-Host " ✓ Resetting commits with --mixed (unstage)" -ForegroundColor White
|
||||
Write-Host " ✓ Resetting commits with --hard (discard all)" -ForegroundColor White
|
||||
Write-Host " ✓ The DANGER of reset on shared history" -ForegroundColor White
|
||||
Write-Host "`n⚠️ CRITICAL REMINDER ⚠️" -ForegroundColor Red
|
||||
Write-Host "NEVER use 'git reset' on commits you've already PUSHED!" -ForegroundColor Red
|
||||
Write-Host "Always use 'git revert' (Module 05) for shared commits!" -ForegroundColor Yellow
|
||||
Write-Host "`nReady for Module 07: Git Stash!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "[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 "[REMINDER] Reset is ONLY for local, un-pushed commits!" -ForegroundColor Yellow
|
||||
Write-Host ""
|
||||
exit 1
|
||||
}
|
||||
199
01-essentials/07-stash/README.md
Normal file
199
01-essentials/07-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/07-stash/reset.ps1
Normal file
22
01-essentials/07-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/07-stash/setup.ps1
Normal file
156
01-essentials/07-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/07-stash/verify.ps1
Normal file
180
01-essentials/07-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
|
||||
904
01-essentials/08-multiplayer/FACILITATOR-SETUP.md
Normal file
904
01-essentials/08-multiplayer/FACILITATOR-SETUP.md
Normal file
@@ -0,0 +1,904 @@
|
||||
# Facilitator Setup Guide - The Great Print Project
|
||||
|
||||
This guide helps workshop facilitators set up the cloud-based multiplayer Git module.
|
||||
|
||||
## Overview
|
||||
|
||||
The Great Print Project is a collaborative Git exercise where pairs of students work together on a shared repository hosted on your Gitea server at **https://git.frod.dk/multiplayer**.
|
||||
|
||||
**What participants will do:**
|
||||
- Clone a real repository from your server
|
||||
- Collaborate with partners on shared branches
|
||||
- Deliberately create and resolve merge conflicts
|
||||
- Create pull requests and review code
|
||||
- Experience the full collaborative Git workflow
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Gitea Server Setup
|
||||
|
||||
You should have:
|
||||
- Gitea running at https://git.frod.dk/multiplayer
|
||||
- Admin access to create repositories and users
|
||||
- HTTPS or SSH access enabled for Git operations
|
||||
|
||||
**Need to set up Gitea?** See the main workshop's `GITEA-SETUP.md` for Docker + Cloudflare Tunnel instructions.
|
||||
|
||||
### Workshop Materials
|
||||
|
||||
Participants need:
|
||||
- Access to the module README in `01_essentials/09-multiplayer/README.md`
|
||||
- Git installed (version 2.23+)
|
||||
- Python 3.6+ (to run the print project)
|
||||
- Text editor
|
||||
|
||||
---
|
||||
|
||||
## Pre-Workshop Setup
|
||||
|
||||
### Step 1: Create User Accounts
|
||||
|
||||
Create individual Gitea accounts for each participant.
|
||||
|
||||
**Recommended naming:**
|
||||
- `student01`, `student02`, `student03`, etc.
|
||||
- Or use their real names/emails if preferred
|
||||
|
||||
**Two approaches:**
|
||||
|
||||
**Option A: Manual account creation**
|
||||
1. Go to Gitea admin panel
|
||||
2. Create users one by one
|
||||
3. Set initial passwords (students can change later)
|
||||
4. Provide credentials to students
|
||||
|
||||
**Option B: Self-registration** (if you trust your network)
|
||||
1. Enable self-registration in Gitea settings
|
||||
2. Provide registration URL to students
|
||||
3. They create their own accounts
|
||||
4. You verify and approve accounts
|
||||
|
||||
**Access tokens (recommended for HTTPS):**
|
||||
- Have students create personal access tokens after logging in
|
||||
- Settings → Applications → Generate New Token
|
||||
- Token needs `repo` scope
|
||||
- Students use token as password when pushing/pulling
|
||||
|
||||
### Step 2: Create the Repository
|
||||
|
||||
Create the shared repository: **great-print-project**
|
||||
|
||||
**Via Gitea web UI:**
|
||||
1. Log in as admin or organization account
|
||||
2. Click "+" → "New Repository"
|
||||
3. **Name:** `great-print-project`
|
||||
4. **Owner:** `multiplayer` (organization) or your admin account
|
||||
5. **Visibility:** Private (only visible to students you add)
|
||||
6. **Initialize:** Check "Initialize this repository with selected files"
|
||||
7. **README:** Yes
|
||||
8. **License:** None
|
||||
9. **.gitignore:** Python
|
||||
10. Click "Create Repository"
|
||||
|
||||
**Via command line (alternative):**
|
||||
```bash
|
||||
# Create local directory
|
||||
mkdir great-print-project
|
||||
cd great-print-project
|
||||
|
||||
# Initialize git
|
||||
git init
|
||||
|
||||
# Add files (see Step 3)
|
||||
git add .
|
||||
git commit -m "Initial commit: The Great Print Project"
|
||||
|
||||
# Create bare repo on server
|
||||
ssh user@git.frod.dk
|
||||
cd /path/to/gitea/repositories/multiplayer
|
||||
git init --bare great-print-project.git
|
||||
exit
|
||||
|
||||
# Push to server
|
||||
git remote add origin git@git.frod.dk:multiplayer/great-print-project.git
|
||||
git push -u origin main
|
||||
```
|
||||
|
||||
### Step 3: Add Starter Code to Repository
|
||||
|
||||
Commit these four files to the repository:
|
||||
|
||||
#### File 1: main.py
|
||||
|
||||
```python
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
The Great Print Project
|
||||
A collaborative Git exercise
|
||||
|
||||
When everyone completes their assigned functions,
|
||||
this program will print the complete alphabet and numbers!
|
||||
"""
|
||||
|
||||
from letters import print_letters
|
||||
from numbers import print_numbers
|
||||
|
||||
def main():
|
||||
print("=" * 50)
|
||||
print(" THE GREAT PRINT PROJECT")
|
||||
print("=" * 50)
|
||||
print("\nLetters:")
|
||||
print_letters()
|
||||
print() # New line after letters
|
||||
|
||||
print("\nNumbers:")
|
||||
print_numbers()
|
||||
print() # New line after numbers
|
||||
|
||||
print("\n" + "=" * 50)
|
||||
print(" PROJECT COMPLETE!")
|
||||
print("=" * 50)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
#### File 2: letters.py
|
||||
|
||||
```python
|
||||
"""
|
||||
Letter Printing Functions
|
||||
Each pair completes their assigned functions.
|
||||
|
||||
Expected output: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
"""
|
||||
|
||||
def print_a():
|
||||
"""Print letter A - EXAMPLE (already completed)"""
|
||||
print("A", end=" ")
|
||||
|
||||
def print_b():
|
||||
# TODO: Pair 1 - implement this function
|
||||
pass
|
||||
|
||||
def print_c():
|
||||
# TODO: Pair 1 - implement this function
|
||||
pass
|
||||
|
||||
def print_d():
|
||||
# TODO: Pair 1 - implement this function
|
||||
pass
|
||||
|
||||
def print_e():
|
||||
# TODO: Pair 2 - implement this function
|
||||
pass
|
||||
|
||||
def print_f():
|
||||
# TODO: Pair 2 - implement this function
|
||||
pass
|
||||
|
||||
def print_g():
|
||||
# TODO: Pair 2 - implement this function
|
||||
pass
|
||||
|
||||
def print_h():
|
||||
# TODO: Pair 3 - implement this function
|
||||
pass
|
||||
|
||||
def print_i():
|
||||
# TODO: Pair 3 - implement this function
|
||||
pass
|
||||
|
||||
def print_j():
|
||||
# TODO: Pair 3 - implement this function
|
||||
pass
|
||||
|
||||
def print_k():
|
||||
# TODO: Pair 4 - implement this function
|
||||
pass
|
||||
|
||||
def print_l():
|
||||
# TODO: Pair 4 - implement this function
|
||||
pass
|
||||
|
||||
def print_m():
|
||||
# TODO: Pair 4 - implement this function
|
||||
pass
|
||||
|
||||
def print_n():
|
||||
# TODO: Pair 5 - implement this function
|
||||
pass
|
||||
|
||||
def print_o():
|
||||
# TODO: Pair 5 - implement this function
|
||||
pass
|
||||
|
||||
def print_p():
|
||||
# TODO: Pair 5 - implement this function
|
||||
pass
|
||||
|
||||
def print_q():
|
||||
# TODO: Pair 6 - implement this function
|
||||
pass
|
||||
|
||||
def print_r():
|
||||
# TODO: Pair 6 - implement this function
|
||||
pass
|
||||
|
||||
def print_s():
|
||||
# TODO: Pair 6 - implement this function
|
||||
pass
|
||||
|
||||
def print_t():
|
||||
# TODO: Pair 7 - implement this function
|
||||
pass
|
||||
|
||||
def print_u():
|
||||
# TODO: Pair 7 - implement this function
|
||||
pass
|
||||
|
||||
def print_v():
|
||||
# TODO: Pair 7 - implement this function
|
||||
pass
|
||||
|
||||
def print_w():
|
||||
# TODO: Pair 8 - implement this function
|
||||
pass
|
||||
|
||||
def print_x():
|
||||
# TODO: Pair 8 - implement this function
|
||||
pass
|
||||
|
||||
def print_y():
|
||||
# TODO: Pair 8 - implement this function
|
||||
pass
|
||||
|
||||
def print_z():
|
||||
# TODO: Pair 9 - implement this function
|
||||
pass
|
||||
|
||||
|
||||
def print_letters():
|
||||
"""Print all letters A-Z"""
|
||||
print_a()
|
||||
print_b()
|
||||
print_c()
|
||||
print_d()
|
||||
print_e()
|
||||
print_f()
|
||||
print_g()
|
||||
print_h()
|
||||
print_i()
|
||||
print_j()
|
||||
print_k()
|
||||
print_l()
|
||||
print_m()
|
||||
print_n()
|
||||
print_o()
|
||||
print_p()
|
||||
print_q()
|
||||
print_r()
|
||||
print_s()
|
||||
print_t()
|
||||
print_u()
|
||||
print_v()
|
||||
print_w()
|
||||
print_x()
|
||||
print_y()
|
||||
print_z()
|
||||
```
|
||||
|
||||
#### File 3: numbers.py
|
||||
|
||||
```python
|
||||
"""
|
||||
Number Printing Functions
|
||||
Each pair completes their assigned functions.
|
||||
|
||||
Expected output: 0 1 2 3 4 5 6 7 8 9
|
||||
"""
|
||||
|
||||
def print_0():
|
||||
# TODO: Pair 9 - implement this function
|
||||
pass
|
||||
|
||||
def print_1():
|
||||
# TODO: Pair 10 - implement this function
|
||||
pass
|
||||
|
||||
def print_2():
|
||||
# TODO: Pair 10 - implement this function
|
||||
pass
|
||||
|
||||
def print_3():
|
||||
# TODO: Pair 10 - implement this function
|
||||
pass
|
||||
|
||||
def print_4():
|
||||
# TODO: Pair 11 - implement this function
|
||||
pass
|
||||
|
||||
def print_5():
|
||||
# TODO: Pair 11 - implement this function
|
||||
pass
|
||||
|
||||
def print_6():
|
||||
# TODO: Pair 11 - implement this function
|
||||
pass
|
||||
|
||||
def print_7():
|
||||
# TODO: Pair 12 - implement this function
|
||||
pass
|
||||
|
||||
def print_8():
|
||||
# TODO: Pair 12 - implement this function
|
||||
pass
|
||||
|
||||
def print_9():
|
||||
# TODO: Pair 12 - implement this function
|
||||
pass
|
||||
|
||||
|
||||
def print_numbers():
|
||||
"""Print all numbers 0-9"""
|
||||
print_0()
|
||||
print_1()
|
||||
print_2()
|
||||
print_3()
|
||||
print_4()
|
||||
print_5()
|
||||
print_6()
|
||||
print_7()
|
||||
print_8()
|
||||
print_9()
|
||||
```
|
||||
|
||||
#### File 4: assignments.md
|
||||
|
||||
```markdown
|
||||
# Pair Assignments
|
||||
|
||||
## How This Works
|
||||
|
||||
Each pair is assigned 3 functions to implement. You'll work together on a shared branch.
|
||||
|
||||
**Important:** Check with your facilitator for your pair number and assignment!
|
||||
|
||||
---
|
||||
|
||||
## Assignments
|
||||
|
||||
### Pair 1
|
||||
- **Functions:** `print_b()`, `print_c()`, `print_d()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-1-bcd`
|
||||
|
||||
### Pair 2
|
||||
- **Functions:** `print_e()`, `print_f()`, `print_g()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-2-efg`
|
||||
|
||||
### Pair 3
|
||||
- **Functions:** `print_h()`, `print_i()`, `print_j()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-3-hij`
|
||||
|
||||
### Pair 4
|
||||
- **Functions:** `print_k()`, `print_l()`, `print_m()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-4-klm`
|
||||
|
||||
### Pair 5
|
||||
- **Functions:** `print_n()`, `print_o()`, `print_p()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-5-nop`
|
||||
|
||||
### Pair 6
|
||||
- **Functions:** `print_q()`, `print_r()`, `print_s()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-6-qrs`
|
||||
|
||||
### Pair 7
|
||||
- **Functions:** `print_t()`, `print_u()`, `print_v()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-7-tuv`
|
||||
|
||||
### Pair 8
|
||||
- **Functions:** `print_w()`, `print_x()`, `print_y()`
|
||||
- **File:** `letters.py`
|
||||
- **Branch:** `pair-8-wxy`
|
||||
|
||||
### Pair 9
|
||||
- **Functions:** `print_z()`, `print_0()`, `print_1()`
|
||||
- **Files:** `letters.py`, `numbers.py`
|
||||
- **Branch:** `pair-9-z01`
|
||||
|
||||
### Pair 10
|
||||
- **Functions:** `print_2()`, `print_3()`, `print_4()`
|
||||
- **File:** `numbers.py`
|
||||
- **Branch:** `pair-10-234`
|
||||
|
||||
### Pair 11
|
||||
- **Functions:** `print_5()`, `print_6()`, `print_7()`
|
||||
- **File:** `numbers.py`
|
||||
- **Branch:** `pair-11-567`
|
||||
|
||||
### Pair 12
|
||||
- **Functions:** `print_8()`, `print_9()`
|
||||
- **File:** `numbers.py`
|
||||
- **Branch:** `pair-12-89`
|
||||
|
||||
---
|
||||
|
||||
## Example Implementation
|
||||
|
||||
```python
|
||||
def print_a():
|
||||
"""Print letter A - EXAMPLE (already completed)"""
|
||||
print("A", end=" ")
|
||||
```
|
||||
|
||||
**Your functions should follow the same pattern:**
|
||||
|
||||
```python
|
||||
def print_x():
|
||||
"""Print letter/number X"""
|
||||
print("X", end=" ")
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Testing
|
||||
|
||||
After implementing your functions, test with:
|
||||
|
||||
```bash
|
||||
python main.py
|
||||
```
|
||||
|
||||
You should see your letters/numbers in the output!
|
||||
|
||||
---
|
||||
|
||||
## Questions?
|
||||
|
||||
Ask your facilitator for:
|
||||
- Your pair number
|
||||
- Your Gitea credentials
|
||||
- Help with authentication setup
|
||||
- Any Git or Python issues
|
||||
```
|
||||
|
||||
#### File 5: README.md (in repository)
|
||||
|
||||
```markdown
|
||||
# The Great Print Project 🎯
|
||||
|
||||
A collaborative Git exercise for learning teamwork with version control!
|
||||
|
||||
## Goal
|
||||
|
||||
When everyone completes their assigned functions, running `python main.py` will print:
|
||||
|
||||
```
|
||||
==================================================
|
||||
THE GREAT PRINT PROJECT
|
||||
==================================================
|
||||
|
||||
Letters:
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
|
||||
Numbers:
|
||||
0 1 2 3 4 5 6 7 8 9
|
||||
|
||||
==================================================
|
||||
PROJECT COMPLETE!
|
||||
==================================================
|
||||
```
|
||||
|
||||
## Your Mission
|
||||
|
||||
1. Find your pair assignment in `assignments.md`
|
||||
2. Clone this repository
|
||||
3. Create your feature branch
|
||||
4. Implement your assigned functions
|
||||
5. Practice collaboration: push, pull, resolve conflicts
|
||||
6. Create a pull request
|
||||
7. Celebrate when your code is merged!
|
||||
|
||||
## Quick Start
|
||||
|
||||
```bash
|
||||
# Clone the repository
|
||||
git clone https://git.frod.dk/multiplayer/great-print-project.git
|
||||
cd great-print-project
|
||||
|
||||
# Check your assignment
|
||||
cat assignments.md
|
||||
|
||||
# Create your branch (replace X with your pair number)
|
||||
git switch -c pair-X-feature
|
||||
|
||||
# Edit your file (letters.py or numbers.py)
|
||||
# Implement your functions
|
||||
|
||||
# Test it
|
||||
python main.py
|
||||
|
||||
# Commit and push
|
||||
git add .
|
||||
git commit -m "Implement print_x() functions"
|
||||
git push -u origin pair-X-feature
|
||||
```
|
||||
|
||||
## Files
|
||||
|
||||
- **main.py** - Orchestrator (runs the whole program)
|
||||
- **letters.py** - Functions for printing A-Z
|
||||
- **numbers.py** - Functions for printing 0-9
|
||||
- **assignments.md** - See which functions your pair should implement
|
||||
|
||||
## Need Help?
|
||||
|
||||
See the module README in the workshop repository for detailed step-by-step instructions!
|
||||
|
||||
**Happy Collaborating! 🚀**
|
||||
```
|
||||
|
||||
**Commit these files:**
|
||||
|
||||
```bash
|
||||
git add main.py letters.py numbers.py assignments.md README.md
|
||||
git commit -m "Add Great Print Project starter code"
|
||||
git push origin main
|
||||
```
|
||||
|
||||
### Step 4: Grant Student Access
|
||||
|
||||
Add students as collaborators with write access:
|
||||
|
||||
**Via Gitea web UI:**
|
||||
1. Go to repository → Settings → Collaborators
|
||||
2. Add each student account
|
||||
3. Set permission level: **Write** (allows push, pull, branch creation)
|
||||
|
||||
**Important:** Students need **Write** access to:
|
||||
- Create branches
|
||||
- Push commits
|
||||
- Create pull requests
|
||||
|
||||
### Step 5: Configure Branch Protection (Optional)
|
||||
|
||||
To prevent accidental pushes to main:
|
||||
|
||||
1. Repository → Settings → Branches
|
||||
2. Add protection rule for `main` branch
|
||||
3. Settings:
|
||||
- **Block direct pushes:** Yes (requires pull requests)
|
||||
- **Require PR reviews:** Optional (you can review PRs yourself)
|
||||
- **Auto-merge:** Disabled (you merge manually or students do)
|
||||
|
||||
This ensures students:
|
||||
- MUST use feature branches
|
||||
- MUST create pull requests
|
||||
- Can't accidentally break main
|
||||
|
||||
**For beginners:** Consider allowing students to merge their own PRs after approval to complete the full workflow.
|
||||
|
||||
### Step 6: Test the Setup
|
||||
|
||||
Before the workshop, test as a student would:
|
||||
|
||||
```bash
|
||||
# Clone as a test student
|
||||
git clone https://git.frod.dk/multiplayer/great-print-project.git
|
||||
cd great-print-project
|
||||
|
||||
# Run the program
|
||||
python main.py
|
||||
# Should show only "A" with missing letters/numbers
|
||||
|
||||
# Create test branch
|
||||
git switch -c test-branch
|
||||
|
||||
# Edit letters.py, add print_b()
|
||||
def print_b():
|
||||
print("B", end=" ")
|
||||
|
||||
# Commit and push
|
||||
git add letters.py
|
||||
git commit -m "Test commit"
|
||||
git push -u origin test-branch
|
||||
|
||||
# Create test pull request
|
||||
# (Do this via web UI)
|
||||
|
||||
# Clean up test branch after
|
||||
git push origin --delete test-branch
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## During the Workshop
|
||||
|
||||
### Pairing Students
|
||||
|
||||
**Strategies for assigning pairs:**
|
||||
|
||||
**Option 1: Random pairing**
|
||||
- Use a random number generator
|
||||
- Pair students as they arrive
|
||||
|
||||
**Option 2: Skill-based pairing**
|
||||
- Mix experienced and beginner students
|
||||
- Balance pair capabilities
|
||||
|
||||
**Option 3: Let them choose**
|
||||
- Students pick their own partners
|
||||
- Good for building team dynamics
|
||||
|
||||
**Announce pairs clearly:**
|
||||
- Write on board/screen: "Pair 1: Alice & Bob"
|
||||
- Provide printed assignment sheet
|
||||
- Update `assignments.md` in repo if needed
|
||||
|
||||
### Timeline
|
||||
|
||||
**Suggested schedule for 2-hour session:**
|
||||
|
||||
- **0:00-0:10** (10 min): Introduction, distribute credentials
|
||||
- **0:10-0:20** (10 min): Students clone repo, verify access
|
||||
- **0:20-0:35** (15 min): Part 1 - Getting Started
|
||||
- **0:35-0:55** (20 min): Part 2 - First Contribution
|
||||
- **0:55-1:25** (30 min): Part 3 - Conflict Exercise (key learning!)
|
||||
- **1:25-1:45** (20 min): Part 4 - Pull Requests
|
||||
- **1:45-2:00** (15 min): Part 5 - Syncing, Q&A, wrap-up
|
||||
|
||||
### Monitoring Progress
|
||||
|
||||
**Use Gitea to track:**
|
||||
|
||||
1. **Branches created:** Repository → Branches
|
||||
- Should see `pair-1-bcd`, `pair-2-efg`, etc.
|
||||
|
||||
2. **Commits:** Repository → Commits
|
||||
- Each pair should have multiple commits
|
||||
|
||||
3. **Pull requests:** Repository → Pull Requests
|
||||
- Should see one PR per pair
|
||||
|
||||
**Walk around the room:**
|
||||
- Check screens for conflict markers
|
||||
- Ask pairs how they're resolving conflicts
|
||||
- Ensure both partners are engaged
|
||||
|
||||
**Common issues to watch for:**
|
||||
- Partners not using the same branch name
|
||||
- Forgetting to pull before pushing
|
||||
- Not removing conflict markers completely
|
||||
- Committing to main instead of feature branch
|
||||
|
||||
### Managing Pull Requests
|
||||
|
||||
**Your role:**
|
||||
|
||||
**Option A: Review and merge yourself**
|
||||
- Teaches students what good reviews look like
|
||||
- Ensures quality before merging
|
||||
- More facilitator work
|
||||
|
||||
**Option B: Students merge their own**
|
||||
- More autonomous learning
|
||||
- Students experience complete workflow
|
||||
- Risk of messy main branch
|
||||
|
||||
**Recommended approach:**
|
||||
1. First 2-3 PRs: You review and merge (demonstrate good practices)
|
||||
2. Remaining PRs: Students review each other, you approve
|
||||
3. Students can merge after approval
|
||||
|
||||
**What to check in PR reviews:**
|
||||
- Functions implemented correctly
|
||||
- No conflict markers in code
|
||||
- Code follows pattern (e.g., `print("X", end=" ")`)
|
||||
- Meaningful commit messages
|
||||
|
||||
### Handling Problems
|
||||
|
||||
**Common issues and solutions:**
|
||||
|
||||
**Problem: "I can't push!"**
|
||||
- Check they're authenticated (token or SSH key)
|
||||
- Check they pulled latest changes first
|
||||
- Check branch name matches their partner's
|
||||
|
||||
**Problem: "Merge conflict won't resolve!"**
|
||||
- Walk through Part 3 step-by-step with them
|
||||
- Show them the conflict markers
|
||||
- Verify they removed ALL markers
|
||||
- Run `python main.py` together to test
|
||||
|
||||
**Problem: "We both committed to main!"**
|
||||
- Have them create proper feature branch
|
||||
- Use `git cherry-pick` to move commits
|
||||
- Reset main to origin/main
|
||||
|
||||
**Problem: "GitHub Desktop / GUI tool shows something different"**
|
||||
- Recommend command line for this exercise
|
||||
- GUIs can hide important details during conflicts
|
||||
|
||||
### Celebrating Success
|
||||
|
||||
When all pairs have merged:
|
||||
|
||||
```bash
|
||||
git pull origin main
|
||||
python main.py
|
||||
```
|
||||
|
||||
**Everyone should see:**
|
||||
```
|
||||
==================================================
|
||||
THE GREAT PRINT PROJECT
|
||||
==================================================
|
||||
|
||||
Letters:
|
||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
||||
|
||||
Numbers:
|
||||
0 1 2 3 4 5 6 7 8 9
|
||||
|
||||
==================================================
|
||||
PROJECT COMPLETE!
|
||||
==================================================
|
||||
```
|
||||
|
||||
**Take a screenshot!** Share it with the class. This is a genuine collaborative achievement.
|
||||
|
||||
---
|
||||
|
||||
## Post-Workshop
|
||||
|
||||
### Cleanup (Optional)
|
||||
|
||||
**Keep the repository for future workshops:**
|
||||
- Delete all feature branches: `git push origin --delete pair-1-bcd` (etc.)
|
||||
- Reset main to initial state
|
||||
- Reuse for next cohort
|
||||
|
||||
**Archive the session:**
|
||||
- Export final repository state
|
||||
- Take screenshots of successful PRs
|
||||
- Save for portfolio/examples
|
||||
|
||||
### Student Takeaways
|
||||
|
||||
Provide students:
|
||||
- Link to repository (they can clone for reference)
|
||||
- Completion certificate (if applicable)
|
||||
- Next steps: contributing to open source, Git resources
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Gitea Server Issues
|
||||
|
||||
**Problem: Server unreachable**
|
||||
- Check Cloudflare Tunnel is running: `cloudflared tunnel info`
|
||||
- Verify Gitea container is up: `docker ps`
|
||||
- Check firewall rules
|
||||
|
||||
**Problem: SSH not working**
|
||||
- Verify SSH port is exposed in docker-compose.yml
|
||||
- Check Cloudflare Tunnel config includes SSH
|
||||
- Test: `ssh -T git@git.frod.dk`
|
||||
|
||||
**Problem: HTTPS clone fails**
|
||||
- Check certificate validity
|
||||
- Try `GIT_SSL_NO_VERIFY=true git clone ...` (temporary workaround)
|
||||
- Configure Gitea to use proper HTTPS certificates
|
||||
|
||||
### Authentication Issues
|
||||
|
||||
**Problem: Students can't log in**
|
||||
- Verify accounts created and active
|
||||
- Reset passwords if needed
|
||||
- Check email verification isn't blocking (disable for workshop)
|
||||
|
||||
**Problem: Push fails with authentication error**
|
||||
- HTTPS: Ensure students use access token, not password
|
||||
- SSH: Verify keys added to Gitea account
|
||||
- Check repo permissions (must be Write, not Read)
|
||||
|
||||
### Git Workflow Issues
|
||||
|
||||
**Problem: Students create PR but can't merge**
|
||||
- Check branch protection rules
|
||||
- Verify they have Write access
|
||||
- Ensure PR doesn't have conflicts
|
||||
|
||||
**Problem: Main branch gets messy**
|
||||
- Reset to last good commit: `git reset --hard <commit>`
|
||||
- Force push: `git push --force origin main` (CAREFUL!)
|
||||
- Or start fresh: delete repo, recreate with starter code
|
||||
|
||||
---
|
||||
|
||||
## Tips for Success
|
||||
|
||||
### Before Workshop
|
||||
|
||||
- Test the entire flow yourself as a student
|
||||
- Prepare credential sheets for each student
|
||||
- Have backup plan if server goes down (local git exercise)
|
||||
- Prepare slides explaining merge conflicts visually
|
||||
|
||||
### During Workshop
|
||||
|
||||
- **Start on time** - respect everyone's schedule
|
||||
- **Pair programming** - ensure both partners engage
|
||||
- **Encourage talking** - best conflicts are resolved by discussion
|
||||
- **Celebrate small wins** - first push, first conflict resolution
|
||||
- **Walk the room** - see screens, answer questions live
|
||||
|
||||
### After Workshop
|
||||
|
||||
- Gather feedback - what worked, what didn't
|
||||
- Note timing - were parts too rushed or too slow?
|
||||
- Archive successful PRs as examples
|
||||
- Plan improvements for next session
|
||||
|
||||
---
|
||||
|
||||
## Scaling Considerations
|
||||
|
||||
### Small Groups (4-8 students, 2-4 pairs)
|
||||
|
||||
- More hands-on facilitator time
|
||||
- Can review all PRs in detail
|
||||
- Easier to monitor progress
|
||||
|
||||
### Medium Groups (10-20 students, 5-10 pairs)
|
||||
|
||||
- Recommended size
|
||||
- Good mix of collaboration and individual attention
|
||||
- Helps if you have a teaching assistant
|
||||
|
||||
### Large Groups (20+ students, 10+ pairs)
|
||||
|
||||
- Consider multiple repositories (split into groups of 12 pairs max)
|
||||
- Recruit teaching assistants to help monitor
|
||||
- Use breakout rooms (if online)
|
||||
- Automate more (less PR review, more self-merging)
|
||||
|
||||
---
|
||||
|
||||
## Additional Resources
|
||||
|
||||
### For You (Facilitator)
|
||||
|
||||
- Gitea documentation: https://docs.gitea.io/
|
||||
- Pro Git book (free): https://git-scm.com/book/en/v2
|
||||
- Teaching Git: https://git-scm.com/doc
|
||||
|
||||
### For Students
|
||||
|
||||
- Git cheatsheet (included in workshop repo)
|
||||
- Interactive Git tutorial: https://learngitbranching.js.org/
|
||||
- Oh Shit Git: https://ohshitgit.com/ (recovering from mistakes)
|
||||
|
||||
---
|
||||
|
||||
## Questions or Issues?
|
||||
|
||||
This guide should cover most scenarios. If you encounter issues not listed here:
|
||||
|
||||
1. Check Gitea logs: `docker logs gitea-container-name`
|
||||
2. Test with minimal setup (single test student)
|
||||
3. Consult Gitea documentation
|
||||
4. Reach out to workshop repository maintainers
|
||||
|
||||
**Good luck with your workshop! The multiplayer module is where Git skills really come alive.**
|
||||
1408
01-essentials/08-multiplayer/README.md
Normal file
1408
01-essentials/08-multiplayer/README.md
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user