53 Commits

Author SHA1 Message Date
Bjarke Sporring
e2e36690ea Revert "refactor: remove advanced for now"
This reverts commit 356b6268ba.
2026-01-15 17:29:41 +01:00
Bjarke Sporring
356b6268ba refactor: remove advanced for now 2026-01-15 17:27:35 +01:00
Bjarke Sporring
bd69774191 feat: drastically simplify the multiplayer module 2026-01-15 17:25:33 +01:00
Bjarke Sporring
fcaf97f60b refactor: simplify the multiplayer part 2026-01-15 17:03:51 +01:00
Bjarke Sporring
2a5eb137f6 fix: stash challenge 2026-01-15 16:42:45 +01:00
Bjarke Sporring
b0d2d43c8b fix: stash issues 2026-01-15 16:37:57 +01:00
Bjarke Sporring
cdd695b250 fix: cleanup revert module 2026-01-15 16:29:06 +01:00
Bjarke Sporring
0474a6de0e fix: remove revert-middle challenge 2026-01-15 16:23:32 +01:00
Bjarke Sporring
575e083f33 refactor: rewrite the merge-revert section
It is an advanced and difficult revert to accomplish and should probably
be done through a reset instead, which means that we're modifying
history which is dangerous and so should be handled by someone who
understands these dangers.
2026-01-15 16:11:24 +01:00
Bjarke Sporring
aa24c50b45 fix: revert conflict 2026-01-15 15:50:54 +01:00
Bjarke Sporring
939bd397f1 refactor: move 08 down a step 2026-01-15 15:42:05 +01:00
Bjarke Sporring
3130874981 fix: cleanup cherry-picking tasks and tips 2026-01-15 15:41:21 +01:00
Bjarke Sporring
a34b7d155f refactor: move reset to advanced 2026-01-15 15:22:45 +01:00
Bjarke Sporring
1c20a06c21 fix: cherry-pick conflict 2026-01-15 15:20:32 +01:00
Bjarke Sporring
988ce3bc92 fix: remove subproject "challenge" folders 2026-01-15 14:31:30 +01:00
Bjarke Sporring
9fbdd941fa feat: check for mainbranch 2026-01-15 14:31:00 +01:00
Bjarke Sporring
40341d21a7 fix: add suggestion for a new file for branching and merging 2026-01-15 14:30:08 +01:00
Bjarke Sporring
8a82253fc2 fix: add better explanation of merges 2026-01-15 14:22:11 +01:00
Bjarke Sporring
f0522e14bc feat: setup merge and diff for git 2026-01-15 14:07:25 +01:00
Bjarke Sporring
0183a06134 feat: set git defaultBranch to main 2026-01-15 13:55:55 +01:00
Bjarke Sporring
c3d9d3337c refactor: add better instructions for branching and merging 2026-01-15 13:47:30 +01:00
Bjarke Sporring
ced65740a3 fix: naming of main branch write host 2026-01-15 13:16:34 +01:00
Bjarke Sporring
bf07cb1868 refactor: check for main branch 2026-01-15 13:14:38 +01:00
Bjarke Sporring
7b638d27de refactor: we're breaking out merge-conflicts 2026-01-15 12:51:43 +01:00
Bjarke Sporring
ea5cbccc75 refactor: simplify the multiplayer part of Git 2026-01-15 12:04:20 +01:00
Bjarke Sporring
76c4182186 feat: update README to include SSH guidelines 2026-01-15 12:03:50 +01:00
Bjarke Sporring
5b4c544fee feat: add a WHAT-IS-GIT file that describes the essentials of what GIT is 2026-01-15 12:03:12 +01:00
Bjarke Sporring
74a23dbbca feat: add commit message best practices 2026-01-15 12:02:25 +01:00
Bjarke Sporring
25077cd589 feat: add a simple best practices for when working together 2026-01-15 12:02:13 +01:00
Bjarke Sporring
8b3ba808d1 feat: add azure devops ssh setup guidelines 2026-01-15 12:01:23 +01:00
Bjarke Sporring
9e03a9624a fix: verification of answers for history 2026-01-15 12:01:07 +01:00
Bjarke Sporring
b2b8a2cfff feat: make the steps more explicit 2026-01-15 11:45:58 +01:00
Bjarke Sporring
7e2f8d64fb refactor: simplify user choice. Install and clone split 2026-01-15 10:29:34 +01:00
Bjarke Sporring
eadf8cfe6a fix: version checks 2026-01-15 10:23:15 +01:00
Bjarke Sporring
9e22f84a53 fix: init of prereqs 2026-01-15 10:14:15 +01:00
Bjarke Sporring
daa787842a fix: remove the statement? 2026-01-15 10:11:40 +01:00
Bjarke Sporring
a392e8c97d feat: add a "what do you want" step 2026-01-15 10:09:11 +01:00
Bjarke Sporring
009a3a9104 fix: proper version formatting for install version check 2026-01-15 10:03:21 +01:00
Bjarke Sporring
32a0e89f72 fix: let's just try to find a version inside the version string 2026-01-15 09:55:54 +01:00
Bjarke Sporring
91c46718c6 fix: version regex for git and other semver stuff 2026-01-14 17:48:57 +01:00
Bjarke Sporring
c99e238814 fix: version match 2026-01-14 17:42:40 +01:00
Bjarke Sporring
2633ee2b71 Add clear terminal opening guidance for VSCode
- Enhance instructions for opening integrated terminal in VSCode
- Provide multiple methods: Ctrl+backtick, menu, and Command Palette
- Add visual separation with headers for important information
- Emphasize that terminal opening is REQUIRED for next steps
- Include detailed fallback instructions if VSCode command fails
- Make terminal guidance impossible to miss with multiple clear options
- Remove ambiguity about how to access terminal in VSCode
2026-01-14 17:29:22 +01:00
Bjarke Sporring
c28151cc19 Fix version parsing in Install-Package for Windows Git format
- Update regex to extract only semantic version numbers (x.y.z)
- Prevents matching entire string like '2.52.0.windows.1'
- Uses '^(\d+(?:\.\d+){1,2})' to match version at start of string only
- Extracts '2.52.0' from '2.52.0.windows.1' for proper version comparison
- Handles Windows Git version suffixes correctly
- Maintains compatibility with standard version formats
2026-01-14 17:24:33 +01:00
Bjarke Sporring
07faa14b7a Remove Python installation and add one-shot install instructions
- Remove all Python-related installation logic since Module 08 is Git-only
- Update synopsis to mention repository cloning capability
- Add one-shot installation instructions using Invoke-RestMethod
- Remove Python from results tracking, installation prompts, and verification
- Simplify installation summary without Python references
- Update examples to show both one-shot and local execution methods
2026-01-14 17:12:03 +01:00
Bjarke Sporring
09f25d6eae Add repository cloning and VSCode opening to install.ps1
- Ask user if they want to clone workshop to Documents/git-workshop
- Automatically clone repository to Documents folder for convenience
- Open VSCode in the workshop directory after cloning
- Handle existing repositories (update if already cloned)
- Provide quick start commands for VSCode terminal
- Fallback to manual instructions if user declines or cloning fails
- Complete end-to-end setup experience
2026-01-14 17:03:04 +01:00
Bjarke Sporring
cbefeaf4d2 refactor: move install-prerequisites to install.ps1 2026-01-14 17:00:02 +01:00
Bjarke Sporring
7066e648d5 refactor: do not exit on script finish 2026-01-14 16:57:54 +01:00
Bjarke Sporring
14cfc2feeb Fix Git version check to handle Windows formatting
- Update Test-GitVersion to properly parse Git versions with Windows suffixes
- Handle formats like '2.52.0.windows.1' correctly
- Parse major and minor version numbers separately for accurate comparison
- Now correctly identifies versions 2.23+ regardless of Windows-specific suffixes
- Add better error message when version parsing fails
2026-01-14 16:56:29 +01:00
Bjarke Sporring
734b49bc7d Add user confirmation for PowerShell 7 terminal integration
- Ask user before setting PowerShell 7 as default VSCode terminal
- Make PowerShell terminal setup recommended but optional
- Add separate tracking for terminal integration result
- Update installation summary to show configuration status
- Provide clearer feedback on what was configured
2026-01-14 16:53:25 +01:00
Bjarke Sporring
985c4a0a8a Remove GitLens and GitGraph extensions from VSCode setup
- Keep only essential PowerShell extension and PowerShell 7 integration
- Simplify VSCode setup to focus on core requirements
- Reduce installation time and complexity
- Users can install additional Git extensions manually if desired
2026-01-14 16:51:32 +01:00
Bjarke Sporring
f55cb444e7 Add VSCode extensions and PowerShell 7 integration setup
- Added function to install VSCode extensions via command line
- Install PowerShell extension for better PowerShell support in VSCode
- Install GitLens and Git Graph extensions for enhanced Git experience
- Configure VSCode to use PowerShell 7 as default terminal
- Update progress tracking to include extension installation step
- Add extension results to installation summary
2026-01-14 16:49:35 +01:00
Bjarke Sporring
6d2e099eb4 Add progress bars to install-prerequisites.ps1
- Added overall progress tracking for required installations (1/3, 2/3, 3/3)
- Added progress indicators for optional installations
- Added individual package installation progress with status updates
- Progress bars show clear visual feedback during installation process
- Helps users understand how far along the installation is
2026-01-14 16:37:39 +01:00
Bjarke Sporring
f696044461 Simplify README installation instructions
- Remove complex installation options
- List prerequisites with direct winget commands
- Keep oneshot installation as recommended option
- Add manual setup steps for clarity
- Focus on getting users started quickly
2026-01-14 16:35:59 +01:00
68 changed files with 4905 additions and 5754 deletions

View File

@@ -29,20 +29,25 @@ Your goal is to commit both `welcome.txt` and `instructions.txt` to a git reposi
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`
4. Stage the file you want to commit: `git add welcome.txt` (or `git add .` to stage all files)
5. Check the status again and see the difference `git status`. Notice the file is now *staged* and ready to be committed.
6. Create a commit: `git commit -m "add welcome.txt"`
5. Check the status again and see the difference `git status`. Notice the file no longer appears in the output.
7. Stage the next file: `git add instructions.txt` (or `git add .` to stage all files)
8. Check the status again and see the difference `git status`. Notice the file is now *staged* and ready to be committed.
9. Create a commit: `git commit -m "add instructions.txt"`
10. Check the status again and see the difference `git status`. Notice that the files are now not shown in status. If and when you change something about the file you will once again see it in the `git status` command.
**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!
- You can commit both files together in one commit, or separately in multiple commits (use `git add .` to add all files in the folder) - 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
- **Staging Area**: A preparation area for your next commit, you first add the files to the stage, and then you commit the files to repository.
- **Commit**: A snapshot of your staged changes
### Useful Commands

View File

@@ -42,7 +42,7 @@ if (-not (Test-Path "answers.md")) {
$answersLower = $answers.ToLower()
# Check 1: Contains "5" or "five" for commit count
if ($answersLower -match "5|five") {
if ($answersLower -match "5|five|fem") {
Write-Host "[PASS] Correct commit count found" -ForegroundColor Green
} else {
Write-Host "[FAIL] Commit count not found or incorrect" -ForegroundColor Red
@@ -76,7 +76,7 @@ if (-not (Test-Path "answers.md")) {
}
# Check 4: Contains "config" keyword for staged file
if ($answersLower -match "config") {
if ($answersLower -match "config.py") {
Write-Host "[PASS] Staged file identified" -ForegroundColor Green
} else {
Write-Host "[FAIL] Staged file not identified" -ForegroundColor Red

File diff suppressed because it is too large Load Diff

View File

@@ -1,216 +1,23 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the challenge environment to a specific checkpoint.
Resets the Module 03 challenge environment.
.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).
This script removes the challenge directory, allowing you to start fresh.
Run setup.ps1 again after resetting to recreate the environment.
#>
param(
[ValidateSet('start', 'merge', 'merge-conflict', '')]
[string]$Checkpoint = ''
)
Write-Host "`n=== Resetting Module 03 Challenge ===" -ForegroundColor Cyan
# Checkpoint to tag mapping
$checkpointTags = @{
'start' = 'checkpoint-start'
'merge' = 'checkpoint-merge'
'merge-conflict' = 'checkpoint-merge-conflict'
if (Test-Path "challenge") {
Write-Host "Removing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
Write-Host "`n[SUCCESS] Challenge environment reset complete!" -ForegroundColor Green
Write-Host "`nRun .\setup.ps1 to create a fresh challenge environment." -ForegroundColor Cyan
Write-Host ""
} else {
Write-Host "`n[INFO] No challenge directory found. Nothing to reset." -ForegroundColor Yellow
Write-Host "Run .\setup.ps1 to create the challenge environment." -ForegroundColor Cyan
Write-Host ""
}
# 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

View File

@@ -1,17 +1,13 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 03 checkpoint-based challenge environment.
Sets up the Module 03 challenge environment for branching and merging.
.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
This script creates a challenge directory with a Git repository containing
a realistic project history with multiple merged branches. Students will see
what branching and merging looks like in practice, then create their own
branches to experiment.
#>
Write-Host "`n=== Setting up Module 03: Branching and Merging ===" -ForegroundColor Cyan
@@ -35,114 +31,89 @@ git init | Out-Null
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after that
$mainBranch = git branch --show-current
if (-not $mainBranch) {
# Fallback: Get default branch name from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use "main"
$mainBranch = "main"
}
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# ============================================================================
# PHASE 1: Branching Basics - Initial commits on main
# Create a realistic project history with multiple merged branches
# ============================================================================
Write-Host "`nPhase 1: Creating initial project structure..." -ForegroundColor Cyan
Write-Host "Creating project history with multiple branches..." -ForegroundColor Cyan
# Commit 1: Initial commit
$mainContent = @"
# main.py - Main application file
# Initial commits on main
$readmeContent = @"
# My Application
def main():
print("Welcome to the Application!")
print("This is the main branch")
if __name__ == "__main__":
main()
A sample application for learning Git branching and merging.
"@
Set-Content -Path "main.py" -Value $mainContent
Set-Content -Path "README.md" -Value $readmeContent
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
git commit -m "Add main application file" | Out-Null
# ============================================================================
# PHASE 2: Create feature-login branch (what students will do in checkpoint 1)
# Branch 1: feature-login (will be merged)
# ============================================================================
Write-Host "Phase 2: Creating feature-login branch..." -ForegroundColor Cyan
# Create and switch to feature-login branch
Write-Host "Creating feature-login branch..." -ForegroundColor Cyan
git switch -c feature-login | Out-Null
# Commit 3: Add login module
$loginContent = @"
# login.py - User login module
# login.py - User authentication
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}")
print(f"Logging in 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
# login.py - User authentication
def validate_password(password):
"""Validate password strength."""
if len(password) < 8:
return False
return True
return len(password) >= 8
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}")
print(f"Logging in 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
# Switch back to main branch
git switch $mainBranch | 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
# app.py - Application entry point
from main import main
@@ -150,7 +121,6 @@ def run():
"""Run the application."""
print("Starting application...")
main()
print("Application finished.")
if __name__ == "__main__":
run()
@@ -159,16 +129,65 @@ Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add app.py entry point" | Out-Null
# Commit 6: Add README
# Merge feature-login into $mainBranch
Write-Host "Merging feature-login into $mainBranch..." -ForegroundColor Green
git merge feature-login --no-edit | Out-Null
# ============================================================================
# Branch 2: feature-api (will be merged)
# ============================================================================
Write-Host "Creating feature-api branch..." -ForegroundColor Cyan
git switch -c feature-api | Out-Null
$apiContent = @"
# api.py - API endpoints
def get_data():
"""Retrieve data from API."""
return {"status": "ok", "data": []}
def post_data(data):
"""Send data to API."""
print(f"Posting data: {data}")
return {"status": "ok"}
"@
Set-Content -Path "api.py" -Value $apiContent
git add .
git commit -m "Add API module" | Out-Null
$apiContent = @"
# api.py - API endpoints
def get_data():
"""Retrieve data from API."""
return {"status": "ok", "data": []}
def post_data(data):
"""Send data to API."""
print(f"Posting data: {data}")
return {"status": "ok"}
def delete_data(id):
"""Delete data by ID."""
print(f"Deleting data: {id}")
return {"status": "ok"}
"@
Set-Content -Path "api.py" -Value $apiContent
git add .
git commit -m "Add delete endpoint to API" | Out-Null
# Switch back to main branch and add documentation
git switch $mainBranch | Out-Null
$readmeContent = @"
# My Application
Welcome to my application!
A sample application for learning Git branching and merging.
## Features
- Main functionality
- More features coming soon
- User authentication
- Main application logic
## Setup
@@ -176,104 +195,121 @@ Run: python app.py
"@
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Add README documentation" | Out-Null
git commit -m "Update README with setup instructions" | Out-Null
# Tag checkpoint-merge (students begin merging here - divergent branches ready)
Write-Host "Creating checkpoint: merge" -ForegroundColor Green
git tag checkpoint-merge
# Merge feature-api into $mainBranch
Write-Host "Merging feature-api into $mainBranch..." -ForegroundColor Green
git merge feature-api --no-edit | Out-Null
# ============================================================================
# PHASE 3: Merge feature-login into main (what students will do in checkpoint 2)
# Branch 3: feature-database (will be merged)
# ============================================================================
Write-Host "Phase 3: Merging feature-login into main..." -ForegroundColor Cyan
Write-Host "Creating feature-database branch..." -ForegroundColor Cyan
git switch -c feature-database | Out-Null
# Merge feature-login into main (will create three-way merge commit)
git merge feature-login --no-edit | Out-Null
$dbContent = @"
# database.py - Database operations
# ============================================================================
# PHASE 4: Create conflict scenario (what students will do in checkpoint 3)
# ============================================================================
Write-Host "Phase 4: Creating merge conflict scenario..." -ForegroundColor Cyan
class Database:
def __init__(self):
self.connection = None
# Create config.json file on main
$initialConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000
}
}
def connect(self):
"""Connect to database."""
print("Connecting to database...")
self.connection = True
def query(self, sql):
"""Execute SQL query."""
print(f"Executing: {sql}")
return []
"@
Set-Content -Path "config.json" -Value $initialConfig
git add config.json
git commit -m "Add initial configuration" | Out-Null
Set-Content -Path "database.py" -Value $dbContent
git add .
git commit -m "Add database module" | Out-Null
# On main branch: Add timeout setting
$mainConfig = @"
$dbContent = @"
# database.py - Database operations
class Database:
def __init__(self):
self.connection = None
def connect(self):
"""Connect to database."""
print("Connecting to database...")
self.connection = True
def disconnect(self):
"""Disconnect from database."""
print("Disconnecting from database...")
self.connection = None
def query(self, sql):
"""Execute SQL query."""
print(f"Executing: {sql}")
return []
"@
Set-Content -Path "database.py" -Value $dbContent
git add .
git commit -m "Add disconnect method" | Out-Null
# Switch to main branch and add another commit (to create divergent history)
git switch $mainBranch | Out-Null
$configContent = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"timeout": 5000
"debug": false
}
}
"@
Set-Content -Path "config.json" -Value $mainConfig
git add config.json
git commit -m "Add timeout configuration" | Out-Null
Set-Content -Path "config.json" -Value $configContent
git add .
git commit -m "Add configuration file" | Out-Null
# Create update-config branch from the commit before timeout was added
git switch -c update-config HEAD~1 | Out-Null
# Merge feature-database (will be three-way merge since main diverged)
Write-Host "Merging feature-database into $mainBranch..." -ForegroundColor Green
git merge feature-database --no-edit | Out-Null
# On update-config branch: Add debug setting (conflicting change)
$featureConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"debug": true
}
}
# Final update on main
$readmeContent = @"
# My Application
A sample application for learning Git branching and merging.
## Features
- User authentication
- API endpoints
- Database operations
- Main application logic
## Setup
Run: python app.py
## Requirements
- Python 3.6+
"@
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
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Update README with all features" | 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 "`nThe repository contains a realistic project history:" -ForegroundColor Yellow
Write-Host " - Multiple feature branches (login, api, database)" -ForegroundColor White
Write-Host " - All branches have been merged into $mainBranch" -ForegroundColor White
Write-Host " - View the history: git log --oneline --graph --all" -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 " 3. Explore the repository history: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 4. Create your own branches and practice merging!" -ForegroundColor White
Write-Host ""

View File

@@ -1,34 +1,15 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 03 challenge solution (checkpoint-aware).
Verifies the Module 03 challenge solution.
.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.
This script checks that you've practiced branching and merging by:
- Creating at least one new branch (beyond the example branches)
- Making commits on your branch
- Merging your branch into main
#>
param(
[ValidateSet('start', 'merge', 'merge-conflict', '')]
[string]$Checkpoint = ''
)
$script:allChecksPassed = $true
# ============================================================================
@@ -51,198 +32,15 @@ function Write-Hint {
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
}
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Cyan
}
# ============================================================================
# Checkpoint 2: Merging Verification
# Check challenge directory exists
# ============================================================================
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
@@ -251,7 +49,6 @@ if (-not (Test-Path "challenge")) {
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
@@ -259,62 +56,122 @@ if (-not (Test-Path ".git")) {
exit 1
}
# Run appropriate verification
if ($Checkpoint -eq '') {
# Verify all checkpoints
Write-Host "`n=== Verifying All Checkpoints ===" -ForegroundColor Cyan
Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundColor Cyan
Verify-Branching
Verify-Merging
Verify-MergeConflicts
} else {
# Verify specific checkpoint
switch ($Checkpoint) {
'start' { Verify-Branching }
'merge' { Verify-Merging }
'merge-conflict' { Verify-MergeConflicts }
# ============================================================================
# Detect the main branch name (could be main, master, etc.)
# ============================================================================
# Try to get the default branch from remote origin first
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
if (-not $mainBranch) {
# Fallback: try to detect from local branches
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
# Get the default branch from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use the first branch
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# ============================================================================
# Count initial setup commits (should be 15 commits from setup)
# ============================================================================
$initialCommitCount = 15
# ============================================================================
# Check for new commits beyond setup
# ============================================================================
Write-Host "`nChecking your work..." -ForegroundColor Cyan
$totalCommits = [int](git rev-list --count HEAD 2>$null)
if ($totalCommits -gt $initialCommitCount) {
$newCommits = $totalCommits - $initialCommitCount
Write-Pass "Found $newCommits new commit(s) beyond the initial setup"
} else {
Write-Fail "No new commits found"
Write-Hint "Create a branch, make some commits, and merge it into main"
Write-Hint "Example: git switch -c my-feature"
}
# ============================================================================
# Check for branches (excluding the example branches)
# ============================================================================
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
$exampleBranches = @($mainBranch, 'feature-login', 'feature-api', 'feature-database')
$studentBranches = $allBranches | Where-Object { $_ -notin $exampleBranches }
if ($studentBranches.Count -gt 0) {
Write-Pass "Created $($studentBranches.Count) new branch(es): $($studentBranches -join ', ')"
} else {
Write-Info "No new branches found (it's OK if you deleted them after merging)"
Write-Hint "To practice: git switch -c your-branch-name"
}
# ============================================================================
# Check for merge commits by the student
# ============================================================================
$setupUser = "Workshop Student"
$mergeCommits = git log --merges --format="%s" 2>$null
# Count how many merge commits exist beyond the initial 3
$totalMerges = ($mergeCommits | Measure-Object).Count
$setupMerges = 3 # feature-login, feature-api, feature-database
if ($totalMerges -gt $setupMerges) {
$studentMerges = $totalMerges - $setupMerges
Write-Pass "Performed $studentMerges merge(s) of your own work"
} else {
Write-Fail "No merge commits found beyond the example merges"
Write-Hint "Create a branch, add commits, then merge it: git merge your-branch-name"
}
# ============================================================================
# Check current branch
# ============================================================================
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq $mainBranch) {
Write-Pass "Currently on $mainBranch branch"
} else {
Write-Info "Currently on '$currentBranch' branch"
Write-Hint "Typically you merge feature branches INTO $mainBranch"
}
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 "`nYou've successfully practiced:" -ForegroundColor Cyan
Write-Host " ✓ Creating branches" -ForegroundColor White
Write-Host " ✓ Making commits on 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 "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
Write-Host ""
Write-Host "Quick guide:" -ForegroundColor Cyan
Write-Host " 1. Create a branch: git switch -c my-feature" -ForegroundColor White
Write-Host " 2. Make changes and commit them" -ForegroundColor White
Write-Host " 3. Switch to $mainBranch : git switch $mainBranch" -ForegroundColor White
Write-Host " 4. Merge your branch: git merge my-feature" -ForegroundColor White
Write-Host " 5. Run this verify script again" -ForegroundColor White
Write-Host ""
exit 1
}

View File

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

View File

@@ -0,0 +1,487 @@
# Module 04: Merge Conflicts
## Learning Objectives
By the end of this module, you will:
- Understand what merge conflicts are and why they occur
- Use `git diff` to discover changes between branches
- Identify merge conflicts in your repository
- Read and interpret conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`)
- Resolve merge conflicts manually
- Complete a merge after resolving conflicts
## Setup
Create the challenge environment:
```bash
.\setup.ps1
```
This creates a repository with two feature branches that have conflicting changes.
## Overview
A **merge conflict** occurs when Git cannot automatically combine changes because both branches modified the same part of the same file in different ways.
**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.
## Your Task
### Part 1: Discover the Changes
Before merging, it's good practice to see what each branch changed:
```bash
cd challenge
# Check which branch you're on
git branch
# View all branches
git branch --all
```
You'll see three branches: `main`, `add-timeout`, and `add-debug`.
**Discover what each branch changed:**
```bash
# Compare main with add-timeout
git diff main add-timeout
# Compare main with add-debug
git diff main add-debug
# Compare the two feature branches directly
git diff add-timeout add-debug
```
**What did you discover?**
- Both branches modified `config.json`
- They both added a line in the same location (after `"port": 3000`)
- One adds `"timeout": 5000`
- The other adds `"debug": true`
This is a recipe for a conflict!
### Part 2: Merge the First Branch (No Conflict)
Let's merge `add-timeout` first:
```bash
# Make sure you're on main
git switch main
# Merge the first branch
git merge add-timeout
```
**Success!** This merge works because main hasn't changed since add-timeout was created.
```bash
# View the updated config
cat config.json
# Check the history
git log --oneline --graph --all
```
### Part 3: Try to Merge the Second Branch (Conflict!)
Now let's try to merge `add-debug`:
```bash
# Still on main
git merge add-debug
```
💥 **Boom!** 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 expected. Git is asking for your help.
### Part 4: Check the Status
```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!
### Part 5: Open and Examine the Conflicted File
Open `config.json` in your text editor:
```bash
# On Windows
notepad config.json
# Or use VS Code
code config.json
```
You'll see special **conflict markers**:
```json
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
<<<<<<< HEAD
"timeout": 5000
=======
"debug": true
>>>>>>> add-debug
}
}
```
### Part 6: Understand the Conflict Markers
Let's break down what you're seeing:
```
<<<<<<< HEAD
"timeout": 5000 ← Your current branch (main, which has add-timeout merged)
=======
"debug": true ← The branch you're merging (add-debug)
>>>>>>> add-debug
```
**What each marker means:**
- `<<<<<<< HEAD` - Start of your changes (current branch)
- `=======` - Separator between the two versions
- `>>>>>>> add-debug` - End of their changes (branch being merged)
### Part 7: Resolve the Conflict
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****Do this!**
```json
"timeout": 5000,
"debug": true
```
### Part 8: Edit the File
For this challenge, we want **both settings**, so:
1. Delete ALL the conflict markers:
- Remove `<<<<<<< HEAD`
- Remove `=======`
- Remove `>>>>>>> add-debug`
2. Keep both settings:
**Before (with conflict markers):**
```json
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
<<<<<<< HEAD
"timeout": 5000
=======
"debug": true
>>>>>>> add-debug
}
}
```
**After (resolved):**
```json
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"timeout": 5000,
"debug": true
}
}
```
**Important:**
- Remove ALL markers
- Add a comma after `"timeout": 5000` (for valid JSON)
- Ensure the file is valid JSON
3. Save the file
### Part 9: Mark the Conflict as Resolved
Tell Git you've resolved the conflict:
```bash
# Stage the resolved file
git add config.json
# Check status
git status
```
You should 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.
### Part 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, then save and close.
**Done!** Your merge is complete!
```bash
# View the final result
cat config.json
# View the history
git log --oneline --graph --all
```
You should see both `timeout` and `debug` in the config!
### Part 11: Verify Your Solution
From the module directory (not inside challenge/):
```bash
.\verify.ps1
```
## Understanding Conflict Markers
### Anatomy of a Conflict
```
<<<<<<< HEAD ← Marker: Start of your version
Your changes here
======= ← Marker: Separator
Their changes here
>>>>>>> branch-name ← Marker: End of their version
```
### Common Conflict Patterns
**Simple conflict:**
```
<<<<<<< HEAD
print("Hello")
=======
print("Hi")
>>>>>>> feature
```
Decision: Which greeting do you want?
**Both are needed:**
```
<<<<<<< HEAD
timeout: 5000
=======
debug: true
>>>>>>> feature
```
Decision: Keep both (add comma)!
**Deletion conflict:**
```
<<<<<<< HEAD
# Function deleted on your branch
=======
def old_function():
pass
>>>>>>> feature
```
Decision: Delete or keep the function?
## Common Mistakes to Avoid
**Forgetting to remove conflict markers**
```json
<<<<<<< HEAD Don't leave these in!
"timeout": 5000,
"debug": true
>>>>>>> add-debug 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!
**Breaking syntax**
```json
"timeout": 5000 Missing comma!
"debug": true
```
Always verify your file is valid after resolving!
**Not testing the result**
Always check that your resolved code works!
## Aborting a Merge
Changed your mind? You can abort the merge anytime before committing:
```bash
git merge --abort
```
This returns your repository to the state before you started the merge. No harm done!
## Key Commands
```bash
# Discover changes before merging
git diff branch1 branch2
# 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
💡 **Use `git diff` first**
Always compare branches before merging:
```bash
git diff main..feature-branch
```
💡 **Prevent conflicts**
- Pull changes frequently
- 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
- Make small, focused commits
- If editing the same file, coordinate with teammates
💡 **When stuck**
- Read the conflict markers carefully
- Look at `git log` to understand what each side changed
- Use `git diff` to see the changes
- Ask a teammate to review your resolution
- Use a merge tool: `git mergetool`
## Merge Tools
Git supports visual merge tools that make resolving conflicts easier:
```bash
# Configure a merge tool (one-time setup)
git config --global merge.tool vscode # or meld, kdiff3, etc.
# Use the merge tool during a conflict
git mergetool
```
This opens a visual interface showing both versions side-by-side.
## Real-World Scenario
This exercise simulates a common real-world situation:
**Scenario:** Two developers working on the same file
- Alice adds a timeout configuration
- Bob adds debug mode configuration
- Both push their changes
- When Bob tries to merge, he gets a conflict
- Bob resolves it by keeping both changes
- Everyone's work is preserved!
This happens all the time in team development. Conflicts are normal!
## What You've Learned
After completing this module, you understand:
- ✅ Merge conflicts happen when the same lines are changed differently
-`git diff` helps you discover changes before merging
- ✅ Conflict markers show both versions
- ✅ You decide what the final code should look like
- ✅ Remove all markers before committing
- ✅ Test your resolution to ensure it works
- ✅ Conflicts are normal and easy to resolve with practice
## Next Steps
Ready to continue? The next module covers **cherry-picking** - selectively applying specific commits from one branch to another.
To start over:
```bash
.\reset.ps1
.\setup.ps1
```
**Need help?** Review the steps above, or run `git status` to see what Git suggests!

View File

@@ -0,0 +1,23 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 04 challenge environment.
.DESCRIPTION
This script removes the challenge directory, allowing you to start fresh.
Run setup.ps1 again after resetting to recreate the environment.
#>
Write-Host "`n=== Resetting Module 04 Challenge ===" -ForegroundColor Cyan
if (Test-Path "challenge") {
Write-Host "Removing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
Write-Host "`n[SUCCESS] Challenge environment reset complete!" -ForegroundColor Green
Write-Host "`nRun .\setup.ps1 to create a fresh challenge environment." -ForegroundColor Cyan
Write-Host ""
} else {
Write-Host "`n[INFO] No challenge directory found. Nothing to reset." -ForegroundColor Yellow
Write-Host "Run .\setup.ps1 to create the challenge environment." -ForegroundColor Cyan
Write-Host ""
}

View File

@@ -0,0 +1,138 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 04 challenge environment for merge conflicts.
.DESCRIPTION
This script creates a challenge directory with a Git repository containing
two feature branches that have conflicting changes to the same file.
Students will learn to identify, understand, and resolve merge conflicts.
#>
Write-Host "`n=== Setting up Module 04: Merge Conflicts ===" -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"
# Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after that
$mainBranch = git branch --show-current
if (-not $mainBranch) {
# Fallback: Get default branch name from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use "main"
$mainBranch = "main"
}
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# ============================================================================
# Create base project
# ============================================================================
Write-Host "Creating base project..." -ForegroundColor Cyan
# Initial commit with config file
$configContent = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000
}
}
"@
Set-Content -Path "config.json" -Value $configContent
git add .
git commit -m "Initial commit with config" | Out-Null
# Add README
$readmeContent = @"
# My Application
A simple application for learning merge conflicts.
## Configuration
Edit `config.json` to configure the application.
"@
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Add README" | Out-Null
# ============================================================================
# Branch 1: add-timeout (adds timeout setting)
# ============================================================================
Write-Host "Creating add-timeout branch..." -ForegroundColor Cyan
git switch -c add-timeout | Out-Null
$timeoutConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"timeout": 5000
}
}
"@
Set-Content -Path "config.json" -Value $timeoutConfig
git add .
git commit -m "Add timeout configuration" | Out-Null
# ============================================================================
# Branch 2: add-debug (adds debug setting - CONFLICTS with timeout!)
# ============================================================================
Write-Host "Creating add-debug branch..." -ForegroundColor Cyan
git switch $mainBranch | Out-Null
git switch -c add-debug | Out-Null
$debugConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"debug": true
}
}
"@
Set-Content -Path "config.json" -Value $debugConfig
git add .
git commit -m "Add debug mode configuration" | Out-Null
# Switch back to main branch
git switch $mainBranch | 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 "`nThe repository contains:" -ForegroundColor Yellow
Write-Host " - $mainBranch branch: base configuration" -ForegroundColor White
Write-Host " - add-timeout branch: adds timeout setting" -ForegroundColor White
Write-Host " - add-debug branch: adds debug setting" -ForegroundColor White
Write-Host "`nBoth branches modify the same part of config.json!" -ForegroundColor Red
Write-Host "This will cause a merge conflict when you try to merge both." -ForegroundColor Red
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. Follow the guide to discover and resolve the conflict" -ForegroundColor White
Write-Host ""

View File

@@ -0,0 +1,232 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 04 challenge solution.
.DESCRIPTION
This script checks that you've successfully resolved the merge conflict by:
- Merging both branches into main
- Resolving the conflict in config.json
- Keeping both timeout and debug settings
- Ensuring valid JSON syntax
#>
$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
}
function Write-Info {
param([string]$Message)
Write-Host "[INFO] $Message" -ForegroundColor Cyan
}
# ============================================================================
# Check 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"
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
}
Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cyan
# ============================================================================
# Detect the main branch name (could be main, master, etc.)
# ============================================================================
# Try to get the default branch from remote origin first
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
if (-not $mainBranch) {
# Fallback: try to detect from local branches
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
# Get the default branch from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use the first branch
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# ============================================================================
# Check current branch
# ============================================================================
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq $mainBranch) {
Write-Pass "Currently on $mainBranch branch"
} else {
Write-Fail "Should be on $mainBranch branch (currently on: $currentBranch)"
Write-Hint "Switch to $mainBranch with: git switch $mainBranch"
}
# ============================================================================
# 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"
Pop-Location
exit 1
} else {
Write-Pass "No merge in progress (conflicts resolved)"
}
# ============================================================================
# Check if config.json exists
# ============================================================================
if (-not (Test-Path "config.json")) {
Write-Fail "File 'config.json' not found"
Write-Hint "The config.json file should exist"
Pop-Location
exit 1
}
# ============================================================================
# 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 (<<<<<<<, =======, >>>>>>>)"
Write-Hint "Check for missing commas or brackets"
Pop-Location
exit 1
}
# ============================================================================
# Check for conflict markers
# ============================================================================
if ($configContent -match '<<<<<<<|=======|>>>>>>>') {
Write-Fail "Conflict markers still present in config.json"
Write-Hint "Remove all conflict markers (<<<<<<<, =======, >>>>>>>)"
Pop-Location
exit 1
} 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 add-timeout 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 add-debug branch"
}
# ============================================================================
# Verify both branches were merged
# ============================================================================
$addTimeoutMerged = git log --oneline --grep="add-timeout" 2>$null | Select-String "Merge"
$addDebugMerged = git log --oneline --grep="add-debug" 2>$null | Select-String "Merge"
if ($addTimeoutMerged) {
Write-Pass "add-timeout branch has been merged"
} else {
# Check if it was a fast-forward merge (commits exist but no merge commit)
$timeoutCommit = git log --oneline --grep="Add timeout configuration" 2>$null
if ($timeoutCommit) {
Write-Pass "add-timeout branch changes are in main"
} else {
Write-Fail "add-timeout branch not merged"
Write-Hint "Merge with: git merge add-timeout"
}
}
if ($addDebugMerged) {
Write-Pass "add-debug branch has been merged"
} else {
Write-Fail "add-debug branch not merged"
Write-Hint "Merge with: git merge add-debug"
}
# ============================================================================
# Check commit count (should have both merges)
# ============================================================================
$totalCommits = [int](git rev-list --count HEAD 2>$null)
if ($totalCommits -ge 5) {
Write-Pass "Repository has $totalCommits commits (all merges complete)"
} else {
Write-Info "Repository has $totalCommits commits"
Write-Hint "Make sure both branches are merged"
}
Pop-Location
# ============================================================================
# Final summary
# ============================================================================
Write-Host ""
if ($script:allChecksPassed) {
Write-Host "=========================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=========================================" -ForegroundColor Green
Write-Host "`nYou've successfully:" -ForegroundColor Cyan
Write-Host " ✓ Discovered changes using git diff" -ForegroundColor White
Write-Host " ✓ Merged the first branch" -ForegroundColor White
Write-Host " ✓ Encountered a merge conflict" -ForegroundColor White
Write-Host " ✓ Resolved the conflict by keeping both changes" -ForegroundColor White
Write-Host " ✓ Completed the merge" -ForegroundColor White
Write-Host "`nYou're now ready to handle merge conflicts in real projects!" -ForegroundColor Green
Write-Host ""
exit 0
} else {
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
Write-Host ""
Write-Host "Quick guide:" -ForegroundColor Cyan
Write-Host " 1. Make sure you're on $mainBranch : git switch $mainBranch" -ForegroundColor White
Write-Host " 2. Merge first branch: git merge add-timeout" -ForegroundColor White
Write-Host " 3. Merge second branch: git merge add-debug" -ForegroundColor White
Write-Host " 4. Resolve conflict: edit config.json, remove markers, keep both settings" -ForegroundColor White
Write-Host " 5. Stage resolved file: git add config.json" -ForegroundColor White
Write-Host " 6. Complete merge: git commit" -ForegroundColor White
Write-Host " 7. Run this verify script again" -ForegroundColor White
Write-Host ""
exit 1
}

View File

@@ -0,0 +1,363 @@
# Module 05: 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
- Understand common use cases for cherry-picking
- Learn how cherry-pick creates new commits with different hashes
## Setup
Create the challenge environment:
```pwsh
.\setup.ps1
```
This creates a repository with a `development` branch containing both bug fixes and experimental features.
## Overview
**Cherry-pick** allows you to copy specific commits from one branch to another. Unlike merging (which brings ALL commits), cherry-pick lets you be surgical about exactly which changes you want to apply.
Think of it like picking cherries from a tree - you select only the ripe ones you want, leaving the rest behind.
### Why Use Cherry-Pick?
- **Selective deployment** - Apply critical bug fixes without merging unfinished features
- **Hotfixes** - Quickly move a fix from development to production
- **Backporting** - Apply fixes to older release branches
- **Wrong branch** - Move commits you accidentally made on the wrong branch
- **Duplicate commits** - Apply the same fix across multiple branches
## Your Task
### The Scenario
You're working on a project where:
- The `main` branch is stable and in production
- The `development` branch has new features being tested
- Development has critical bug fixes that need to go to production NOW
- But development also has experimental features that aren't ready yet
You need to cherry-pick ONLY the bug fixes to main, leaving the experimental features behind.
### Part 1: Explore the Development Branch
First, see what commits are on the development branch:
```pwsh
cd challenge
# View all commits on development branch
git log --oneline development
# View the full commit graph
git log --oneline --graph --all
```
**Study the commits:**
- Look for commits with "Fix" in the message (these are bug fixes)
- Look for commits with "experimental" or "beta" (these should stay on development)
- Note the commit hashes (the 7-character codes like `abc1234`)
**Inspect specific commits:**
```pwsh
# See what files a commit changed
git show <commit-hash>
# Example:
# git show abc1234
```
You should see:
- 2 commits that fix bugs (security and performance)
- 2 commits that add experimental features
### Part 2: Switch to Main Branch
Before cherry-picking, you need to be on the target branch (main):
```pwsh
# Switch to main branch
git switch main
# Verify you're on main
git branch
```
The `*` should be next to `main`.
**Check what's currently on main:**
```pwsh
# See main's commits
git log --oneline
# See what files exist
ls
```
Main should only have the initial app and README - no bug fixes yet, no experimental features.
### Part 3: Cherry-Pick the Bug Fixes
Now copy the bug fix commits from development to main:
1. Find the security fix commit hash by looking at your earlier `git log --oneline --graph --all`
- Look for a commit message like "Fix security vulnerability in input validation"
- Note its hash (first 7 characters)
2. Cherry-pick the security fix:
```pwsh
git cherry-pick <security-fix-hash>
# Example if the hash is abc1234:
# git cherry-pick abc1234
```
3. Verify it worked: Check that security.py, with `ls` or check your file explorer in VSCode, now exists and check that the commit has been added to the main branch with `git log --oneline --graph --all`
4. Find the performance fix commit hash
- Look for "Fix performance issue with data caching"
- Note its hash
5. Cherry-pick the performance fix:
```pwsh
git cherry-pick <performance-fix-hash>
```
6. Verify both fixes are now on main:
```pwsh
# You should see both security.py and cache.py
ls
# View the graph showing both branches
git log --oneline --graph --all
```
### Part 4: Verify Your Solution
Check that you completed the challenge correctly:
```pwsh
# From inside the module directory
.\verify.ps1
```
The verification checks:
- ✅ You're on the main branch
- ✅ Security fix is applied to main
- ✅ Performance fix is applied to main
- ✅ Experimental features are NOT on main
- ✅ Development branch still has all commits
## Understanding Cherry-Pick
### What Actually Happens?
When you cherry-pick a commit, Git:
1. Looks at what changed in that specific commit
2. Applies those same changes to your current branch
3. Creates a NEW commit with those changes
```
Before cherry-pick:
development: A---B---C---D
/
main: E---F
After: git switch main && git cherry-pick C
development: A---B---C---D
/
main: E---F---C'
```
Notice:
- `C'` is a NEW commit (different hash than original `C`)
- Original `C` still exists on development
- Main now has the changes from C, but not B or D
### Cherry-Pick vs Merge
**Merge brings everything:**
```pwsh
git switch main
git merge development
# Result: A, B, C, and D all come to main
```
**Cherry-pick is selective:**
```pwsh
git switch main
git cherry-pick C
# Result: Only C comes to main (as C')
```
### Important: New Commits, New Hashes
Cherry-picked commits are COPIES, not moves:
- Original commit stays on source branch
- New commit created on target branch
- Different commit hash (because different parent)
- Same changes, same message, different identity
## Key Commands
### Viewing Commits
```pwsh
# See commits on another branch
git log branch-name --oneline
# See what a specific commit changed
git show <commit-hash>
# See commit graph
git log --oneline --graph --all
# See only commit message (not changes)
git log --oneline
```
### Cherry-Picking
```pwsh
# Cherry-pick a single commit
git cherry-pick <commit-hash>
# Cherry-pick multiple commits (in order)
git cherry-pick <hash1> <hash2> <hash3>
# Cherry-pick a range of commits
git cherry-pick <start-hash>..<end-hash>
# Abort a cherry-pick if something goes wrong
git cherry-pick --abort
```
### After Cherry-Pick
```pwsh
# Verify the commit was added
git log --oneline
# See what files changed
git show HEAD
# Compare branches
git log main..development --oneline
```
## Common Workflows
### Hotfix to Production
Critical bug found in production:
```pwsh
# You're on feature-new-ui branch
# You just committed a critical security fix
git log --oneline
# Note the hash of your fix commit
# Switch to production branch
git switch production
# Apply just that fix
git cherry-pick <fix-commit-hash>
# Deploy to production
# Your fix is live, but new UI stays in development
```
### Backporting to Old Versions
```pwsh
# You fixed a bug on main
git switch main
git log --oneline
# Note the fix commit hash
# Apply to older release branch
git switch release-2.5
git cherry-pick <fix-commit-hash>
# Apply to even older release
git switch release-2.0
git cherry-pick <fix-commit-hash>
# Same fix now on three branches!
```
## Troubleshooting
### "I can't remember the commit hash!"
```pwsh
# See commits on the source branch
git log development --oneline
# Search for specific text in commit messages
git log development --oneline --grep="security"
# See recent commits with more detail
git log development --oneline -n 10
```
### "I cherry-picked in the wrong order!"
Order matters! If commit B depends on commit A, cherry-pick A first:
```pwsh
# Wrong order might cause issues
git cherry-pick B # Might fail if it needs changes from A
# Correct order
git cherry-pick A
git cherry-pick B
```
### "How do I see what will change before cherry-picking?"
```pwsh
# See what changes are in a commit
git show <commit-hash>
# Compare your current branch with a commit
git diff HEAD <commit-hash>
```
## Tips for Success
💡 **Copy the commit hashes** - Write them down before switching branches
💡 **Cherry-pick oldest first** - Apply commits in chronological order
💡 **Check your branch** - Always verify you're on the target branch first with `git branch`
💡 **Verify after each pick** - Run `git log --oneline` to confirm it worked
💡 **Use the graph** - `git log --oneline --graph --all` shows the full picture
💡 **Original stays put** - Cherry-pick copies, doesn't move commits
## What You've Learned
After completing this module, you understand:
- ✅ Cherry-pick copies specific commits between branches
- ✅ `git cherry-pick <hash>` applies a commit to current branch
- ✅ Cherry-picked commits get new hashes but same changes
- ✅ Use cherry-pick for selective deployment of changes
- ✅ Cherry-pick is different from merge (selective vs all)
- ✅ Original commit stays on source branch
## Next Steps
Ready to continue? Cherry-pick is a powerful tool for selective change management. Next modules will cover more advanced Git operations.
To start over:
```pwsh
.\reset.ps1
```
**Need help?** Run `git status` to see what Git suggests, or `git log --oneline --graph --all` to see the full picture!

View File

@@ -26,7 +26,8 @@ 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
# Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after the first commit below
$app = @"
class App:
def __init__(self):
@@ -40,6 +41,14 @@ Set-Content -Path "app.py" -Value $app
git add app.py
git commit -m "Initial app implementation" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
$readme = @"
# Application
@@ -119,40 +128,29 @@ 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:
# This commit adds caching to the existing file to fix performance
# It should apply cleanly to main since it doesn't depend on experimental features
$performanceCode = @"
class DataCache:
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):
def get(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
return None
def fetch_data(self, key):
# Simulate data fetching
return {'key': key, 'value': 'data'}
def set(self, key, value):
self.cache[key] = value
def clear(self):
self.cache.clear()
"@
Set-Content -Path "app.py" -Value $appWithPerformance
git add app.py
Set-Content -Path "cache.py" -Value $performanceCode
git add cache.py
git commit -m "Fix performance issue with data caching" | Out-Null
# Return to module directory
@@ -164,12 +162,13 @@ 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 "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
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 "4. Switch to $mainBranch branch: git checkout $mainBranch" -ForegroundColor White
Write-Host "5. Cherry-pick ONLY the bug fix commits to $mainBranch" -ForegroundColor White
Write-Host "6. Do NOT bring the experimental features to $mainBranch" -ForegroundColor White
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -32,12 +32,27 @@ if (-not (Test-Path ".git")) {
exit 1
}
# Detect the main branch name
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# 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
if ($currentBranch -ne $mainBranch) {
Write-Host "[FAIL] You should be on the '$mainBranch' branch." -ForegroundColor Red
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
Write-Host "Hint: Use 'git checkout main' to switch to main branch" -ForegroundColor Yellow
Write-Host "Hint: Use 'git checkout $mainBranch' to switch to $mainBranch branch" -ForegroundColor Yellow
Set-Location ..
exit 1
}
@@ -54,15 +69,15 @@ if (Test-Path ".git/CHERRY_PICK_HEAD") {
}
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
$mainCommitCount = (git rev-list --count main 2>$null)
$mainCommitCount = (git rev-list --count $mainBranch 2>$null)
if ($mainCommitCount -ne 4) {
Write-Host "[FAIL] Expected 4 commits on main branch, found $mainCommitCount" -ForegroundColor Red
Write-Host "[FAIL] Expected 4 commits on $mainBranch branch, found $mainCommitCount" -ForegroundColor Red
if ($mainCommitCount -lt 4) {
Write-Host "Hint: You should cherry-pick 2 bug fix commits to main" -ForegroundColor Yellow
Write-Host "Hint: You should cherry-pick 2 bug fix commits to $mainBranch" -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 "`nExpected commits on ${mainBranch}:" -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
@@ -72,9 +87,9 @@ if ($mainCommitCount -ne 4) {
}
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
$mergeCommits = git log --merges --oneline main 2>$null
$mergeCommits = git log --merges --oneline $mainBranch 2>$null
if ($mergeCommits) {
Write-Host "[FAIL] Found merge commits on main. You should use cherry-pick, not merge." -ForegroundColor Red
Write-Host "[FAIL] Found merge commits on $mainBranch. 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
@@ -82,7 +97,7 @@ if ($mergeCommits) {
# 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 "[FAIL] security.py not found on $mainBranch branch." -ForegroundColor Red
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
Set-Location ..
exit 1
@@ -110,23 +125,32 @@ if (-not (Test-Path "app.py")) {
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
# Check that cache.py exists (from performance fix)
if (-not (Test-Path "cache.py")) {
Write-Host "[FAIL] cache.py not found on $mainBranch branch." -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
# Check that cache.py has the DataCache class
$cacheContent = Get-Content "cache.py" -Raw
if ($cacheContent -notmatch "DataCache") {
Write-Host "[FAIL] cache.py is missing the DataCache class." -ForegroundColor Red
Set-Location ..
exit 1
}
if ($cacheContent -notmatch "def get\(") {
Write-Host "[FAIL] cache.py is missing the get method." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that app.py does NOT have experimental features
$appContent = Get-Content "app.py" -Raw
# Should NOT have experimental features
if ($appContent -match "experimental_mode") {
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -ForegroundColor Red
@@ -151,7 +175,7 @@ if ($appContent -match "enable_experimental_features") {
}
# Check commit messages to verify cherry-picks
$commits = git log --pretty=format:"%s" main 2>$null
$commits = git log --pretty=format:"%s" $mainBranch 2>$null
$commitArray = $commits -split "`n"
$hasSecurityFix = $false
@@ -167,14 +191,14 @@ foreach ($commit in $commitArray) {
}
if (-not $hasSecurityFix) {
Write-Host "[FAIL] Security fix commit not found on main branch." -ForegroundColor Red
Write-Host "[FAIL] Security fix commit not found on $mainBranch 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 "[FAIL] Performance fix commit not found on $mainBranch branch." -ForegroundColor Red
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
Set-Location ..
exit 1
@@ -194,8 +218,8 @@ 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 "- Cherry-picked the security vulnerability fix to $mainBranch" -ForegroundColor White
Write-Host "- Cherry-picked the performance issue fix to $mainBranch" -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

View File

@@ -1,638 +0,0 @@
# 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

View File

@@ -1,373 +0,0 @@
#!/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 ""

View File

@@ -0,0 +1,400 @@
# Module 06: Git Revert - Safe Undoing
## About This Module
Welcome to Module 06, 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 commits safely while preserving surrounding changes
2. Understand how revert creates new commits instead of erasing history
3. Revert multiple commits at once
4. 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 (Module 03)
## Setup
Run the setup script to create the challenge environment:
```pwsh
.\setup.ps1
```
This creates a `challenge/` directory with two branches demonstrating different revert scenarios:
- `regular-revert` - Basic 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:**
```pwsh
cd challenge
```
2. **Check which branch you're on** (you should be on `regular-revert`):
```pwsh
git branch
```
The `*` should be next to `regular-revert`.
3. **View the commit history:**
```pwsh
git log --oneline
```
4. **Find the commit with message:** "Add broken divide function - needs to be reverted!"
- Note the commit hash (the 7-character code at the start, like `a1b2c3d`)
- Write it down or copy it
5. **Revert that specific commit** (replace `<commit-hash>` with the actual hash):
```pwsh
git revert <commit-hash>
```
6. **Visual Studio Code will open** with the revert commit message:
- The default message is fine (it says "Revert 'Add broken divide function...'")
- Close the editor window to accept the commit message
- Git will create the revert commit
### What to Observe
After reverting, check your work:
```pwsh
# View the new revert commit in history
git log --oneline
# Check that divide.py file is gone (reverted)
ls
# You should see calculator.py but NOT divide.py
# Check that modulo function still exists in calculator.py (it came after the bad commit)
cat calculator.py
# You should see def modulo
# Check that multiply function still exists (it came before the bad commit)
# (You already see it when you cat the file above)
```
**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 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:**
```pwsh
git switch multi-revert
```
2. **View the commit history:**
```pwsh
git log --oneline
```
Find the two bad commits:
- "Add broken square_root - REVERT THIS!"
- "Add broken logarithm - REVERT THIS TOO!"
Note both commit hashes (write them down)
3. **Revert both commits in one command** (replace with actual hashes):
```pwsh
git revert <commit-hash-1> <commit-hash-2>
```
**Important:** List commits from **oldest to newest** for cleanest history (square_root first, then logarithm).
**Alternatively**, revert them one at a time:
```pwsh
git revert <commit-hash-1>
git revert <commit-hash-2>
```
4. **Visual Studio Code will open TWICE** (once for each revert):
- Close the editor each time to accept the default commit message
- Git will create two revert commits
5. **Verify the result:**
```pwsh
# View files - sqrt.py and logarithm.py should be gone
ls
# You should see calculator.py but NOT sqrt.py or logarithm.py
# Check that good functions remain in calculator.py
cat calculator.py
# You should see def power and def absolute
```
### Multi-Revert Strategies
**Reverting a range of commits:**
```pwsh
# 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:**
```pwsh
# 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
After completing both challenges, verify your solutions:
```pwsh
cd .. # Return to module directory (if you're in challenge/)
.\verify.ps1
```
Or from inside the challenge directory:
```pwsh
..\verify.ps1
```
The script checks that:
- ✅ Revert commits were created (not destructive deletion)
- ✅ Bad code is removed
- ✅ Good code before and after is preserved
## Command Reference
### Basic Revert
```pwsh
# 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
```
### Reverting Old Commits
```pwsh
# Revert a specific commit from any point in history
git revert <commit-hash>
# Revert a commit from 5 commits ago
git revert HEAD~5
# View what a commit changed before reverting
git show <commit-hash>
```
### Multiple Commits
```pwsh
# 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
```pwsh
# 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 (advanced module)
- ❌ **Just want to edit a commit** - Use `git commit --amend`
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup, but more dangerous, stick to revert if in doubt
- ❌ **Need to combine commits** - Use interactive rebase IF nothing has been pushed to cloud
- ❌ **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:
```pwsh
# 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:
```pwsh
git add <resolved-files>
```
3. Continue the revert:
```pwsh
git revert --continue
```
Or abort if you change your mind:
```pwsh
git revert --abort
```
## Common Mistakes
### 1. Using Reset on Pushed Commits
```pwsh
# ❌ NEVER do this with pushed commits. Or at least try your best to avoid it.
git reset --hard HEAD~3
# ✅ Do this instead
git revert HEAD~3..HEAD
```
### 2. Reverting Commits in Wrong Order
When reverting multiple related commits, revert from newest to oldest:
```pwsh
# 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:**
```pwsh
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

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 05 challenge environment to start fresh.
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 05: Git Revert Challenge ===" -ForegroundColor Cyan
Write-Host "`n=== Resetting Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
# Check if challenge directory exists
if (Test-Path "challenge") {

View File

@@ -0,0 +1,199 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 06 challenge environment for learning git revert.
.DESCRIPTION
This script creates a challenge directory with two branches demonstrating
different revert scenarios:
- regular-revert: Basic revert of a single bad commit
- multi-revert: Reverting multiple commits at once
#>
Write-Host "`n=== Setting up Module 06: 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"
# Detect the default branch name after first commit (created below)
# Will be detected after the initial commit in SCENARIO 1
# ============================================================================
# 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
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# Create regular-revert branch
git switch -c regular-revert | Out-Null
# Good commit: Add multiply using append
$multiplyFunc = @"
def multiply(a, b):
"""Multiply two numbers."""
return a * b
"@
Add-Content -Path "calculator.py" -Value $multiplyFunc
git add .
git commit -m "Add multiply function" | Out-Null
# BAD commit: Add broken divide function using separate file
$divideContent = @"
# divide.py - Division functionality
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 "divide.py" -Value $divideContent
git add .
git commit -m "Add broken divide function - needs to be reverted!" | Out-Null
# Good commit: Add modulo (after bad commit) using append
$moduloFunc = @"
def modulo(a, b):
"""Return remainder of a divided by b."""
return a % b
"@
Add-Content -Path "calculator.py" -Value $moduloFunc
git add .
git commit -m "Add modulo function" | Out-Null
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
# ============================================================================
# SCENARIO 2: Multi Revert (Multiple Bad Commits)
# ============================================================================
Write-Host "`nScenario 2: Creating multi-revert branch..." -ForegroundColor Cyan
# Switch back to main
git switch $mainBranch | 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 using append
$powerFunc = @"
def power(a, b):
"""Raise a to the power of b."""
return a ** b
"@
Add-Content -Path "calculator.py" -Value $powerFunc
git add .
git commit -m "Add power function" | Out-Null
# BAD commit 1: Add broken square_root in separate file
$sqrtContent = @"
# sqrt.py - Square root functionality
def square_root(a):
"""BROKEN: Returns wrong result for negative numbers!"""
return a ** 0.5 # This returns NaN for negative numbers!
"@
Set-Content -Path "sqrt.py" -Value $sqrtContent
git add .
git commit -m "Add broken square_root - REVERT THIS!" | Out-Null
# BAD commit 2: Add broken logarithm in separate file
$logContent = @"
# logarithm.py - Logarithm functionality
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 "logarithm.py" -Value $logContent
git add .
git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null
# Good commit: Add absolute value (after bad commits) using append
$absoluteFunc = @"
def absolute(a):
"""Return absolute value of a."""
return abs(a)
"@
Add-Content -Path "calculator.py" -Value $absoluteFunc
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 "`nTwo revert scenarios have been created:" -ForegroundColor Cyan
Write-Host " 1. regular-revert - Revert a single bad commit" -ForegroundColor White
Write-Host " 2. 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 ""

View File

@@ -1,16 +1,15 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 05 challenge solutions.
Verifies the Module 06 challenge solutions.
.DESCRIPTION
Checks that all three revert scenarios have been completed correctly:
Checks that both 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
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
$allChecksPassed = $true
$originalDir = Get-Location
@@ -51,19 +50,19 @@ if ($LASTEXITCODE -ne 0) {
$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
# Check that divide.py is removed (was reverted)
if (-not (Test-Path "divide.py")) {
Write-Host "[PASS] Broken divide.py successfully reverted (file removed)" -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
Write-Host "[FAIL] divide.py still exists (should be reverted)" -ForegroundColor Red
Write-Host "[HINT] The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that calculator.py exists and has correct content
if (Test-Path "calculator.py") {
$calcContent = Get-Content "calculator.py" -Raw
# 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
@@ -87,60 +86,9 @@ if ($LASTEXITCODE -ne 0) {
}
# ============================================================================
# SCENARIO 2: Merge Revert Verification
# SCENARIO 2: Multi 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
Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
git switch multi-revert 2>&1 | Out-Null
@@ -160,26 +108,26 @@ if ($LASTEXITCODE -ne 0) {
$allChecksPassed = $false
}
# Check that sqrt.py is removed (reverted)
if (-not (Test-Path "sqrt.py")) {
Write-Host "[PASS] Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[FAIL] sqrt.py still exists (should be reverted)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that logarithm.py is removed (reverted)
if (-not (Test-Path "logarithm.py")) {
Write-Host "[PASS] Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[FAIL] logarithm.py still exists (should be reverted)" -ForegroundColor Red
$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
@@ -211,11 +159,10 @@ if ($allChecksPassed) {
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 commits safely without erasing history" -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 " ✓ Preserving all other commits while undoing specific changes" -ForegroundColor White
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
Write-Host ""
exit 0
} else {

View File

@@ -1,4 +1,4 @@
# Module 11: Stash
# Module 07: Git Stash - Temporary Storage
## Learning Objectives
@@ -64,12 +64,112 @@ stash@{2} <- Oldest stash
You can have multiple stashes and apply any of them.
## Useful Commands
## Setup
```bash
# Stash current changes
Run the setup script to create the challenge environment:
```pwsh
.\setup.ps1
```
This creates a `challenge/` directory where you're working on a login feature with uncommitted changes, and a critical bug needs fixing on main.
## Your Task
### The Scenario
You're working on a login feature on the `feature-login` branch. Your work is incomplete (has TODOs), so it's not ready to commit.
Suddenly, your teammate reports a **critical security bug** in production! You need to:
1. Temporarily save your incomplete work
2. Switch to the main branch
3. Fix the urgent bug
4. Return to your feature and continue working
### Step-by-Step Instructions
1. **Navigate to the challenge directory:**
```pwsh
cd challenge
```
2. **Check your current status:**
```pwsh
git status
```
You should see modified `login.py` (uncommitted changes)
3. **Stash your work with a message:**
```pwsh
git stash save "WIP: login feature"
```
4. **Verify working directory is clean:**
```pwsh
git status
```
Should say "nothing to commit, working tree clean"
5. **Switch to main branch:**
```pwsh
git switch main
```
6. **Open app.py and find the bug:**
```pwsh
cat app.py
```
Look for the comment "# BUG: This allows unauthenticated access!"
7. **Fix the bug** by editing app.py:
- Remove the buggy comment line
- You can leave the implementation as-is or improve it
- The important thing is removing the comment that says "allows unauthenticated access"
8. **Commit the fix:**
```pwsh
git add app.py
git commit -m "Fix critical security bug"
```
9. **Switch back to your feature branch:**
```pwsh
git switch feature-login
```
10. **Restore your stashed work:**
```pwsh
git stash pop
```
This applies the stash and removes it from the stash stack
11. **Complete the TODOs in login.py:**
- Open login.py in your editor
- Complete the login method (verify password and return session)
- Add a logout method
- Remove all TODO comments
12. **Commit your completed feature:**
```pwsh
git add login.py
git commit -m "Complete login feature"
```
13. **Verify your solution:**
```pwsh
..\verify.ps1
```
## Key Stash Commands
### Basic Operations
```pwsh
# Stash current changes with a message
git stash save "description"
# Stash without a message (not recommended)
git stash
git stash save "description" # With a descriptive message
# Stash including untracked files
git stash -u
@@ -77,7 +177,17 @@ git stash -u
# List all stashes
git stash list
# Apply most recent stash and remove it from stack
# Show what's in the most recent stash
git stash show
# Show full diff of stash
git stash show -p
```
### Applying Stashes
```pwsh
# Apply most recent stash and remove it (RECOMMENDED)
git stash pop
# Apply most recent stash but keep it in stack
@@ -86,114 +196,192 @@ 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
# Apply a specific stash by number
git stash apply 1
```
# Drop (delete) a stash
git stash drop stash@{0}
### Managing Stashes
```pwsh
# Drop (delete) the most recent stash
git stash drop
# Drop a specific stash
git stash drop stash@{1}
# Clear all stashes
git stash clear
# Create a branch from a stash
# Create a new branch from a stash
git stash branch new-branch-name
```
## Understanding Stash vs Pop vs Apply
### Stash Pop (Recommended)
```pwsh
git stash pop
```
- Applies the stash to your working directory
- **Removes** the stash from the stack
- Use this most of the time
### Stash Apply (Keep Stash)
```pwsh
git stash apply
```
- Applies the stash to your working directory
- **Keeps** the stash in the stack
- Useful if you want to apply the same changes to multiple branches
### When to Use Which
**Use `pop` when:**
- You're done with the stash and won't need it again (99% of the time)
- You want to keep your stash list clean
**Use `apply` when:**
- You want to test the same changes on different branches
- You're not sure if you want to keep the stash yet
## Verification
Run the verification script to check your solution:
```bash
```pwsh
..\verify.ps1
```
Or from the module directory:
```pwsh
.\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
- ✅ The bug fix commit exists on main
- ✅ Your feature is completed on the feature-login branch
- ✅ All TODOs are removed from login.py
- ✅ No uncommitted changes remain
## Challenge Steps
## Troubleshooting
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
### "Cannot switch branches - you have uncommitted changes"
## Tips
**Problem:** Git won't let you switch branches with uncommitted changes.
- 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
**Solution:**
```pwsh
# Stash your changes first
git stash save "work in progress"
## Common Stash Scenarios
# Now you can switch
git switch other-branch
### 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
# When you come back, restore your work
git switch original-branch
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
### "I don't remember what's in my stash"
**Problem:** You stashed something but forgot what it was.
**Solution:**
```pwsh
# List all stashes
git stash list
# Show summary of what changed
git stash show stash@{0}
# Show full diff
git stash show -p stash@{0}
```
### 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
```
### "Stash conflicts when I apply"
### 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
```
**Problem:** Applying a stash causes merge conflicts.
## Stash Conflicts
If applying a stash causes conflicts:
1. Git will mark the conflicts in your files
2. Resolve conflicts manually (like merge conflicts)
**Solution:**
1. Git marks conflicts in your files with `<<<<<<<` markers
2. Open the files and resolve conflicts manually
3. Stage the resolved files: `git add <file>`
4. The stash is automatically dropped after successful pop
4. If you used `pop`, the stash is automatically dropped
5. If you used `apply`, manually drop it: `git stash drop`
## What You'll Learn
### "I accidentally cleared my stash"
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.
**Problem:** You deleted a stash you still needed.
**Unfortunately:** Stashes are hard to recover once deleted. Lessons learned:
- Use `git stash pop` instead of `git stash drop` when you're unsure
- Use `git stash show -p` to preview before dropping
- Consider committing work instead of stashing for important changes
## Tips for Success
💡 **Always add a message** - `git stash save "your message"` helps you remember what you stashed
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
💡 **Stashes are local** - They don't get pushed to remote repositories
💡 **Clean working directory** - Always verify with `git status` after stashing
## Common Use Cases
### Quick Branch Switch
```pwsh
# You're working on feature-A
git stash save "feature A progress"
git switch hotfix-branch
# Fix the issue, commit
git switch feature-A
git stash pop
```
### Pull with Local Changes
```pwsh
# You have uncommitted changes
git stash save "local changes"
git pull
git stash pop
# Resolve conflicts if any
```
### Test Clean State
```pwsh
# Stash changes to test on clean code
git stash save "testing clean state"
# Run tests
git stash pop # Restore your changes
```
## What You've Learned
After completing this module, you understand:
- ✅ Stash temporarily saves uncommitted changes
- ✅ Stash lets you switch contexts without committing
- ✅ `git stash pop` applies and removes the stash
- ✅ `git stash apply` applies but keeps the stash
- ✅ Stashes are local and not pushed to remote
- ✅ Stash is essential for handling interruptions and urgent fixes
**Key Takeaway:** Stash is your "temporary clipboard" for incomplete work. It helps you stay productive when you need to context-switch without making messy "WIP" commits.
## Next Steps
Ready to continue? You've now mastered the essential Git commands for daily development:
- Committing and history
- Branching and merging
- Cherry-picking specific changes
- Safely reverting commits
- Temporarily stashing work
Move on to **Module 08: Multiplayer Git** to practice collaborating with others!
To start over:
```pwsh
.\reset.ps1
```

View File

@@ -25,6 +25,9 @@ git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Detect the default branch name after first commit (created below)
# Will be detected after the initial commit
# Create initial application on main
$app = @"
class Application:
@@ -45,6 +48,14 @@ Set-Content -Path "app.py" -Value $app
git add app.py
git commit -m "Initial application" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
$readme = @"
# MyApp
@@ -78,7 +89,7 @@ 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
git checkout $mainBranch | Out-Null
$appWithBug = @"
class Application:
@@ -140,16 +151,17 @@ 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 "`nUrgent: A critical security bug was found in production ($mainBranch branch)!" -ForegroundColor Red
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
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 "4. Switch to ${mainBranch}: git switch $mainBranch" -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 "7. Switch back: git switch 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

View File

@@ -32,6 +32,21 @@ if (-not (Test-Path ".git")) {
exit 1
}
# Detect the main branch name
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# Check current branch
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -ne "feature-login") {
@@ -53,14 +68,22 @@ if ($status) {
}
# Verify main branch has the security fix
Write-Host "`nChecking main branch for bug fix..." -ForegroundColor Cyan
git checkout main 2>$null | Out-Null
Write-Host "`nChecking $mainBranch branch for bug fix..." -ForegroundColor Cyan
git checkout $mainBranch 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
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
$hasSecurityFix = $false
foreach ($commit in $mainCommits) {
if ($commit -match "security|Fix.*bug") {
$hasSecurityFix = $true
break
}
}
if (-not $hasSecurityFix) {
Write-Host "[FAIL] No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
Write-Host "Hint: After stashing, switch to $mainBranch and commit a bug fix" -ForegroundColor Yellow
git checkout feature-login 2>$null | Out-Null
Set-Location ..
exit 1
@@ -68,7 +91,7 @@ if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
# 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
Write-Host "[FAIL] app.py not found on $mainBranch branch." -ForegroundColor Red
git checkout feature-login 2>$null | Out-Null
Set-Location ..
exit 1
@@ -113,7 +136,7 @@ if (-not (Test-Path "login.py")) {
$loginContent = Get-Content "login.py" -Raw
# Check that login method exists and is implemented
if ($loginContent -notmatch "login\(username, password\)") {
if ($loginContent -notmatch "def login") {
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
Set-Location ..
exit 1

View File

@@ -0,0 +1,239 @@
# Facilitator Setup Guide
This guide helps workshop facilitators set up the cloud-based multiplayer Git module using Azure DevOps.
## Overview
The Number Challenge is a collaborative Git exercise where students work together on a shared repository hosted on **Azure DevOps**.
**What participants will do:**
- Clone a real repository from Azure DevOps
- Collaborate to sort numbers 0-20 into the correct order
- Experience push/pull workflow and merge conflicts
- Learn to communicate and coordinate with teammates
- Use SSH keys for secure authentication
---
## Prerequisites
### Azure DevOps Setup
You need:
- **Azure DevOps Organization** - Free tier is sufficient
- Sign up at [dev.azure.com](https://dev.azure.com)
- **Project created** within your organization
- **Admin access** to create repositories and manage users
### Workshop Materials
Participants need:
- Git installed (version 2.23+)
- VS Code (or any text editor)
- SSH keys configured
---
## Pre-Workshop Setup
### Step 1: Add User Accounts
Add workshop participants to your Azure DevOps organization.
1. Navigate to **Organization Settings****Users**
2. Click **Add users**
3. Enter participant email addresses (Microsoft accounts)
4. Select your workshop project
5. Select **Access level**: Stakeholder (free) or Basic
6. Click **Add**
### Step 2: Create the Repository
Create the shared repository: **number-challenge**
1. Sign in to Azure DevOps at [dev.azure.com](https://dev.azure.com)
2. Navigate to your **Project**
3. Click **Repos** in the left navigation
4. Click the repo dropdown → **New repository**
5. Fill in details:
- **Name:** `number-challenge`
- **Add a README:** Checked
6. Click **Create**
### Step 3: Add the Starter File
Create `numbers.txt` with numbers 0-20 in random order.
**Option A: Via Azure DevOps web UI**
1. In your repository, click **+ New** → **File**
2. Name it `numbers.txt`
3. Add this content (numbers 0-20 shuffled):
```
17
3
12
8
19
1
14
6
11
0
20
9
4
16
2
18
7
13
5
15
10
```
4. Click **Commit**
**Option B: Via command line**
```powershell
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/number-challenge
cd number-challenge
# Create numbers.txt with shuffled numbers
@"
17
3
12
8
19
1
14
6
11
0
20
9
4
16
2
18
7
13
5
15
10
"@ | Out-File -FilePath numbers.txt -Encoding UTF8
git add numbers.txt
git commit -m "feat: add shuffled numbers for challenge"
git push
```
### Step 4: Verify Student Access
Students added to the project automatically have access. Verify:
1. Go to **Project Settings****Repositories****number-challenge**
2. Click **Security** tab
3. Verify project team has **Contribute** permission
---
## During the Workshop
### Getting Started
1. Ensure all students have cloned the repository
2. Have everyone open `numbers.txt` to see the shuffled numbers
3. Explain the goal: sort numbers 0-20 into correct order
### The Exercise Flow
1. **Students pull** the latest changes
2. **One person** moves a number to its correct position
3. **They commit and push**
4. **Others pull** and see the change
5. **Repeat** until sorted
### Creating Conflicts (The Learning Moment)
Conflicts happen naturally when multiple people edit at once. You can encourage this:
- Have two students deliberately edit at the same time
- Watch them experience the push rejection
- Guide them through pulling and resolving the conflict
### Monitoring Progress
Check progress in Azure DevOps:
- **Repos → Commits**: See who's contributing
- **Repos → Files → numbers.txt**: See current state
### Common Issues
**"I can't push!"**
- Did they pull first? Run `git pull`
- Is SSH set up? Check with `ssh -T git@ssh.dev.azure.com`
**"Merge conflict!"**
- Walk them through removing conflict markers
- Help them understand both sides of the conflict
**"Numbers are duplicated/missing!"**
- Someone resolved a conflict incorrectly
- Have the team review and fix together
---
## Success
When complete, `numbers.txt` should contain:
```
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
Celebrate the team's success!
---
## Post-Workshop Cleanup
To reuse the repository:
1. Reset `numbers.txt` to shuffled state
2. Or delete and recreate the repository
---
## Tips
- **Keep groups small** (2 people per repository) for more interaction
- **Encourage communication** - the exercise works best when people talk
- **Let conflicts happen** - they're the best learning opportunity
- **Walk the room** - help students who get stuck
- **Point students to 03_TASKS.md** - Simple explanations of clone, push, pull, and fetch for beginners

View File

@@ -0,0 +1,264 @@
# Azure DevOps SSH Setup - Best Practices Guide
This guide provides comprehensive instructions for setting up SSH authentication with Azure DevOps. SSH is the recommended authentication method for secure Git operations.
## Why SSH is Best Practice
SSH (Secure Shell) keys provide a secure way to authenticate with Azure DevOps without exposing passwords or tokens. Here's why SSH is the security best practice:
**Security Benefits:**
- **No Password Exposure**: Your credentials never travel over the network
- **Strong Encryption**: Uses RSA cryptographic algorithms
- **No Credential Prompts**: Seamless authentication after initial setup
- **Revocable**: Individual keys can be removed without changing passwords
- **Auditable**: Track which key was used for each operation
---
## Prerequisites
Before starting, ensure you have:
- **Git 2.23 or higher** installed
```powershell
git --version
```
- **Azure DevOps account** with access to your organization/project
- If you don't have one, create a free account at [dev.azure.com](https://dev.azure.com)
- **PowerShell 7+ or Bash terminal** for running commands
```powershell
pwsh --version
```
---
## Step 1: Generate SSH Key Pair
SSH authentication uses a key pair: a private key (stays on your computer) and a public key (uploaded to Azure DevOps).
### Generate RSA Key
Open your terminal and run:
```powershell
ssh-keygen -t rsa
```
**Note about RSA:** Azure DevOps currently only supports RSA SSH keys. While newer algorithms like Ed25519 offer better security and performance, they are not yet supported by Azure DevOps. See the note at the end of this guide for more information.
### Save Location
When prompted for the file location, press `Enter` to accept the default:
```
Enter file in which to save the key (/Users/yourname/.ssh/id_rsa):
```
**Default locations:**
- **Windows**: `C:\Users\YourName\.ssh\id_rsa` and `C:\Users\YourName\.ssh\id_rsa.pub`
### Passphrase (Optional but Recommended)
You'll be prompted to enter a passphrase, just press `Enter` no password is needed:
```
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
```
### Verify Key Generation
Check that your keys were created:
**Linux/Mac:**
**Windows PowerShell:**
```powershell
dir $HOME\.ssh\
```
You should see two files:
- `id_rsa` - Private key (NEVER share this)
- `id_rsa.pub` - Public key (safe to share for upload to Azure DevOps)
---
## Step 2: Add SSH Public Key to Azure DevOps
Now you'll upload your public key to Azure DevOps.
### Navigate to SSH Public Keys Settings
1. Sign in to Azure DevOps at [https://dev.azure.com](https://dev.azure.com)
2. Click your **profile icon** in the top-right corner
3. Select **User settings** from the dropdown menu
4. Click **SSH Public Keys**
![Azure DevOps - User Settings Menu](./images/02_ssh_option.png)
*Navigate to your user settings by clicking the profile icon in the top-right corner*
### Add New SSH Key
5. Click the **+ New Key** button
![Azure DevOps - Add SSH Public Key Dialog](./images/03_add_new_key.png)
*Click '+ New Key' to begin adding your SSH public key*
### Copy Your Public Key
Open your terminal and display your public key:
**Linux/Mac:**
```bash
cat ~/.ssh/id_rsa.pub
```
**Windows PowerShell:**
```powershell
type $HOME\.ssh\id_rsa.pub
```
**Windows Command Prompt:**
```cmd
type %USERPROFILE%\.ssh\id_rsa.pub
```
The output will look like this:
```
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2YbXnrSK5TTflZSwUv9KUedvI4p3JJ4dHgwp/SeJGqMNWnOMDbzQQzYT7E39w9Q8ItrdWsK4vRLGY2B1rQ+BpS6nn4KhTanMXLTaUFDlg6I1Yn5S3cTTe8dMAoa14j3CZfoSoRRgK8E+ktNb0o0nBMuZJlLkgEtPIz28fwU1vcHoSK7jFp5KL0pjf37RYZeHkbpI7hdCG2qHtdrC35gzdirYPJOekErF5VFRrLZaIRSSsX0V4XzwY2k1hxM037o/h6qcTLWfi5ugbyrdscL8BmhdGNH4Giwqd1k3MwSyiswRuAuclYv27oKnFVBRT+n649px4g3Vqa8dh014wM2HDjMGENIkHx0hcV9BWdfBfTSCJengmosGW+wQfmaNUo4WpAbwZD73ALNsoLg5Yl1tB6ZZ5mHwLRY3LG2BbQZMZRCELUyvbh8ZsRksNN/2zcS44RIQdObV8/4hcLse30+NQ7GRaMnJeAMRz4Rpzbb02y3w0wNQFp/evj1nN4WTz6l8= your@email.com
```
**Copy the entire output** (from `ssh-rsa` to your email address).
### Paste and Name Your Key
![Azure DevOps - Add SSH Public Key Dialog](./images/04_copy_paste_key.png)
6. In the Azure DevOps dialog:
- **Name**: Give your key a descriptive name (e.g., "Workshop Laptop 2026", "Home Desktop", "Work MacBook")
- **Public Key Data**: Paste the entire public key you just copied
7. Click **Save**
**Naming tip**: Use names that help you identify which machine uses each key. This makes it easier to revoke keys later if needed.
---
## Step 3: Using SSH with Git
Now that SSH is configured, you can use it for all Git operations.
### Clone a Repository with SSH
To clone a repository using SSH:
```bash
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Example** (replace placeholders with your actual values):
```bash
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
```
**How to find your SSH URL:**
1. Navigate to your repository in Azure DevOps
2. Click **Clone** in the top-right
3. Select **SSH** from the dropdown
4. Copy the SSH URL
![Azure DevOps - Get SSH Clone URL](./images/azure-devops-clone-ssh.png)
*Select SSH from the clone dialog to get your repository's SSH URL*
### Convert Existing HTTPS Repository to SSH
If you already cloned a repository using HTTPS, you can switch it to SSH:
```bash
cd /path/to/your/repository
git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Verify the change:**
```bash
git remote -v
```
You should see SSH URLs:
```
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (fetch)
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (push)
```
### Daily Git Operations
All standard Git commands now work seamlessly with SSH:
```bash
# Pull latest changes
git pull
# Push your commits
git push
# Fetch from remote
git fetch
# Push a new branch
git push -u origin feature-branch
```
**No more credential prompts!** SSH authentication happens automatically.
---
## Additional Resources
- **Azure DevOps SSH Documentation**: [https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
- **SSH Key Best Practices**: [https://security.stackexchange.com/questions/tagged/ssh-keys](https://security.stackexchange.com/questions/tagged/ssh-keys)
- **Git with SSH**: [https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key)
---
## Quick Reference
### Common Commands
```bash
# Generate RSA key
ssh-keygen -t
# Display public key (Linux/Mac)
cat ~/.ssh/id_rsa.pub
# Display public key (Windows)
type $HOME\.ssh\id_rsa.pub
# Test SSH connection
ssh -T git@ssh.dev.azure.com
# Clone with SSH
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
# Convert HTTPS to SSH
git remote set-url origin git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
# Check remote URL
git remote -v
```
### SSH URL Format
```
git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Example:**
```
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
```
---
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.

View File

@@ -0,0 +1,167 @@
# Multiplayer Git
Work with others using branches and pull requests.
## Goal
Learn to collaborate on a shared repository using:
- **Branches** - work independently without breaking main
- **Pull Requests** - review and merge changes safely
## The Workflow
```
1. Create branch → 2. Make changes → 3. Push branch
6. Delete branch ← 5. Merge PR ← 4. Create PR
```
This is how professional teams work together on code.
---
## Step 1: Clone the Repository
Get the repository URL from your facilitator, then:
```powershell
git clone <repository-url>
code <repository-name>
```
---
## Step 2: Create a Branch
Never work directly on `main`. Create your own branch:
```powershell
git switch -c <branch>
```
This creates a new branch and switches to it.
---
## Step 3: Make Changes
1. Open `numbers.txt` in VS Code
2. Move one number to its correct position
3. Save the file (`Ctrl+S`)
---
## Step 4: Commit and Push
```powershell
git add .
git commit -m "fix: move 7 to correct position"
git push <branch-name>
```
Your branch is now on Azure DevOps.
---
## Step 5: Create a Pull Request
[Detailed guide](https://learn.microsoft.com/en-us/azure/devops/repos/git/pull-requests?view=azure-devops&tabs=browser#create-a-pull-request)
1. Go to Azure DevOps in your browser
2. Navigate to **Repos****Pull Requests** 3. Click **New Pull Request**
4. Set:
- **Source branch:** `<branch-name>`
- **Target branch:** `main`
5. Add a title describing your change
6. Click **Create**
---
## Step 6: Review and Merge
1. Review the changes shown in the PR (Person B)
2. If everything looks good, click **Complete**
3. Select **Complete merge**
4. Your changes are now in `main`
---
## Step 7: Update Your Local Main
After merging, update your local copy:
```powershell
git switch main
git pull
```
---
## Step 8: Repeat
1. Create a new branch for your next change
2. Make changes, commit, push
3. Create another PR
4. Continue until all numbers are sorted
---
## Step 9: Create a merge conflict
1. Both people should create a branch with changes to `feature-1` and `feature-2`, you task is to change the position of number 5. Where you place it is up to you.
2. Now both people should push their respective branch `git push <the-branch>`
3. Now merge `feature-1` branch first, going throught the Pull Request flow.
4. Then merge `feature-2` branch second, and notice you'll get a MERGE CONFLICT.
5. It is not the owner of `feature-2` branch to resolve the conflict. This is done by merge the `main` branch into `feature-2` locally and so the owner of `feature-2` has to do the following
```pwsh
# First get the latest changes on main
git switch main
git pull
# Then go back to the branch you can from
git switch feature-2
# Now we resolve the merge. We're merging the main branch INTO the feature-2 branch.
git merge main
# Resolve the merge conflict in numbers.txt
# Once resolved
git add numbers.txt
git commit
# VSCode will open up with a default message of "Merge main into feature-2"
# finish the commit. And push the changes
git push
```
6. Now the owner of `feature-2` can checkout the pull request on azure again and see that the merge conflict has been resolved and can therefore "Complete" the merge request, using the button in the top right corner with the name "Complete"
## Quick Reference
| Command | What It Does |
|---------|--------------|
| `git switch -c <name>` | Create and switch to new branch |
| `git push -u origin <branch>` | Push branch to Azure DevOps |
| `git switch main` | Switch to main branch |
| `git pull` | Get latest changes from remote |
---
## Common Issues
### "My PR has conflicts"
1. Update your branch with latest main:
```powershell
git switch main
git pull
git switch <branch-name>
git merge main
```
2. Resolve conflicts in VS Code
3. Commit and push again
### "I need to make more changes to my PR"
Just commit and push to the same branch - the PR updates automatically:
```powershell
git add .
git commit -m "fix: address review feedback"
git push
```

View File

@@ -1,904 +0,0 @@
# 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.**

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

View File

@@ -0,0 +1,11 @@
0
1
2
3
4
5
6
7
8
9
10

View File

@@ -33,6 +33,9 @@ git init | Out-Null
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Detect the default branch name after first commit (created below)
# Will be detected after the initial commit
# ============================================================================
# Create initial commit (shared by all scenarios)
# ============================================================================
@@ -45,6 +48,14 @@ Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Initial commit" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# ============================================================================
# SCENARIO 1: Soft Reset (--soft)
# ============================================================================
@@ -155,7 +166,7 @@ Write-Host "[CREATED] soft-reset branch with commit to reset --soft" -Foreground
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 $mainBranch | Out-Null
git switch -c mixed-reset | Out-Null
# Build up scenario 2 commits
@@ -256,7 +267,7 @@ Write-Host "[CREATED] mixed-reset branch with commits to reset --mixed" -Foregro
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 $mainBranch | Out-Null
git switch -c hard-reset | Out-Null
# Reset to basic state

146
BEST-PRACTICES.md Normal file
View File

@@ -0,0 +1,146 @@
# Best Practices for Cloud-Based Git Collaboration
When multiple people work on the same project, merge conflicts are inevitable. But with good habits, you can dramatically reduce how often they happen and how painful they are to resolve.
## Pull Early, Pull Often
The most common cause of merge conflicts is working on outdated code. The longer you work without syncing, the more your code drifts from what others are doing.
**Do this:**
```bash
# Start every work session by pulling
git pull
# Pull again before pushing
git pull
git push
```
**Why it works:** Small, frequent syncs mean small, manageable conflicts. A conflict in 3 lines is easy to fix. A conflict in 300 lines is a nightmare.
## Keep Commits Small and Focused
Large commits that touch many files are conflict magnets.
**Instead of this:**
```
"Implemented user authentication, fixed navbar, updated styles, refactored database"
```
**Do this:**
```
"Add login form to auth page"
"Add password validation"
"Connect login form to auth API"
"Add logout button to navbar"
```
**Why it works:**
- Smaller changes = smaller chance of overlap with others
- If a conflict does happen, it's easier to understand and resolve
- Easier to review, revert, or cherry-pick specific changes
## Communicate About Shared Files
Some files are conflict hotspots because everyone needs to edit them:
- Configuration files
- Route definitions
- Database schemas
- Shared constants or types
**Do this:**
- Tell your team when you're editing shared files
- Make those changes in dedicated commits
- Push changes to shared files quickly - don't let them sit
## Use Short-Lived Branches
Long-running branches drift further from the main branch every day.
```
main: A───B───C───D───E───F───G───H
\
feature: X───────────────────────Y
(2 weeks of drift = painful merge)
```
**Better approach:**
```
main: A───B───C───D───E───F───G───H
\ \ \
feature: X───────Y───────Z
(merge main frequently)
```
**Do this:**
- Merge main into your branch regularly (daily if active)
- Keep features small enough to complete in days, not weeks
- Break large features into smaller incremental changes
## Organize Code to Minimize Overlap
How you structure your code affects how often people collide.
**Conflict-prone structure:**
```
src/
app.py # 2000 lines, everyone edits this
utils.py # 500 lines of mixed utilities
```
**Better structure:**
```
src/
auth/
login.py
logout.py
users/
profile.py
settings.py
utils/
dates.py
strings.py
```
**Why it works:** When each file has a clear purpose, different team members naturally work in different files.
## Avoid Reformatting Wars
Nothing creates unnecessary conflicts like two people reformatting the same file differently.
**Do this:**
- Agree on code formatting standards as a team
- Use automatic formatters (Prettier, Black, etc.)
- Configure your editor to format on save
- Run formatters before committing
**Important:** If you need to reformat a file, do it in a dedicated commit with no other changes. This keeps the reformatting separate from your actual work.
## Coordinate Large Refactors
Renaming a widely-used function or moving files around will conflict with almost everyone's work.
**Do this:**
- Announce refactors to the team before starting
- Do them quickly and push immediately
- Consider doing them when others aren't actively working
- Keep refactoring commits separate from feature work
## The Golden Rules
1. **Sync frequently** - Pull before you start, pull before you push
2. **Commit small** - Many small commits beat one large commit
3. **Talk to your team** - A quick message prevents hours of conflict resolution
4. **Stay focused** - One branch = one purpose
5. **Push promptly** - Don't sit on finished work
## When Conflicts Do Happen
Even with best practices, conflicts will occur. When they do:
1. **Don't panic** - Conflicts are normal, not failures
2. **Read carefully** - Understand both sides before choosing
3. **Test after resolving** - Make sure the merged code actually works
4. **Ask if unsure** - If you don't understand the other person's code, ask them
Remember: merge conflicts are a communication problem as much as a technical one. The best tool for reducing conflicts is talking to your team.

123
COMMIT-MESSAGES.md Normal file
View File

@@ -0,0 +1,123 @@
# Writing Good Commit Messages
A good commit message explains **what** the change does and **why** it matters. Your future self (and your teammates) will thank you.
## The Golden Rule: Write the Intent
Write your message as a command - what will this commit **do** when applied?
**Good (imperative, present tense):**
```
Add login button to navbar
Fix crash when username is empty
Remove unused database connection
```
**Avoid (past tense, describing what you did):**
```
Added login button to navbar
Fixed crash when username is empty
Removed unused database connection
```
**Why?** Think of it as completing this sentence:
> "If applied, this commit will... **add login button to navbar**"
## Use Prefixes to Categorize
Start your message with a prefix that tells readers what kind of change this is:
| Prefix | Use For | Example |
|--------|---------|---------|
| `feat:` | New features | `feat: add password reset flow` |
| `fix:` | Bug fixes | `fix: prevent duplicate form submission` |
| `docs:` | Documentation only | `docs: add API examples to README` |
| `style:` | Formatting, no code change | `style: fix indentation in auth module` |
| `refactor:` | Code change that doesn't fix or add | `refactor: extract validation logic` |
| `test:` | Adding or fixing tests | `test: add unit tests for login` |
| `chore:` | Maintenance, dependencies | `chore: update pytest to 8.0` |
## Keep It Short
The first line should be **50 characters or less**. This ensures it displays properly in:
- Git log output
- GitHub/Azure DevOps commit lists
- Email notifications
```
fix: resolve memory leak in image processing
│ │
└──────────── 45 characters ─────────────────┘
```
## Add Details When Needed
For complex changes, add a blank line and then more context:
```
fix: prevent crash when user uploads empty file
The application crashed because we tried to read the first byte
of an empty file. Now we check file size before processing.
Closes #142
```
Use the body to explain:
- **Why** this change was necessary
- **What** was the problem or context
- **How** does this approach solve it (if not obvious)
## Examples
**Simple bug fix:**
```
fix: correct tax calculation for EU customers
```
**New feature:**
```
feat: add dark mode toggle to settings
```
**With context:**
```
refactor: split user service into smaller modules
The user service had grown to 800 lines and was handling
authentication, profile management, and notifications.
Split into three focused modules for maintainability.
```
**Documentation:**
```
docs: add setup instructions for Windows
```
## Quick Reference
```
<prefix>: <what this commit does>
[optional body: why and how]
[optional footer: references issues]
```
**Checklist:**
- [ ] Starts with a prefix (`feat:`, `fix:`, etc.)
- [ ] Uses imperative mood ("add" not "added")
- [ ] First line under 50 characters
- [ ] Explains why, not just what (for complex changes)
## Common Mistakes
| Instead of... | Write... |
|---------------|----------|
| `fixed bug` | `fix: prevent null pointer in search` |
| `updates` | `feat: add email notifications` |
| `WIP` | `feat: add basic form validation` |
| `stuff` | `chore: clean up unused imports` |
| `fix: fix the bug` | `fix: handle empty input in calculator` |
Your commit history tells the story of your project. Make it a story worth reading.

121
README.md
View File

@@ -47,14 +47,15 @@ Advanced Git workflows for power users:
### For Module 08: Multiplayer Git
**This module is different!** It uses a real Git server for authentic collaboration:
**This module is different!** It uses Azure DevOps for authentic cloud-based collaboration:
1. Navigate to `01-essentials/08-multiplayer`
2. Read the `README.md` for complete instructions
3. **No setup script** - you'll clone from https://git.frod.dk/multiplayer
3. **No setup script** - you'll clone from Azure DevOps (URL provided by facilitator)
4. Work with a partner on shared branches
5. Experience real merge conflicts and pull requests
6. **No verify script** - success is visual (your code appears in the final output)
6. Use SSH keys for secure authentication (best practice)
7. **No verify script** - success is visual (your code appears in the final output)
**Facilitators**: See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for server setup and workshop guidance.
@@ -77,30 +78,45 @@ Then run scripts using:
## Requirements
### Installation
### Prerequisites
**Quick Automated Installation (Windows 11):**
Install these tools before starting:
**PowerShell 7+**
```powershell
winget install Microsoft.PowerShell
```
**Git 2.23+**
```powershell
winget install Git.Git
```
**Visual Studio Code**
```powershell
winget install Microsoft.VisualStudioCode
```
### Quick Start
**Option 1: Oneshot Installation (Recommended)**
Download, install prerequisites, and clone the repository in one command:
Install everything and clone the repository in one command:
```powershell
irm https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | iex
```
This will:
- Install PowerShell 7, Git 2.23+, and Visual Studio Code
- Clone the git-workshop repository to `~/git-workshop`
- Leave you ready to start the first module
**Option 2: Local Installation**
Clone the repository first, then run:
```powershell
.\install-prerequisites.ps1
```
**Manual Installation:** See [INSTALLATION.md](INSTALLATION.md) for complete step-by-step installation instructions including PowerShell 7, Git, and Visual Studio Code.
**Option 2: Manual Setup**
1. Install the prerequisites above
2. Clone this repository:
```powershell
git clone https://git.frod.dk/floppydiscen/git-workshop.git
```
3. Configure Git:
```powershell
git config --global user.name "Your Name"
git config --global user.email "your.email@example.com"
```
**Quick Check:**
@@ -270,7 +286,8 @@ By completing this workshop, you'll be able to:
- ✅ **Collaborate with teammates on shared repositories**
- ✅ **Resolve real merge conflicts in a team environment**
- ✅ **Create and review pull requests**
- ✅ **Use Git on a real cloud server (Gitea)**
- ✅ **Use Git on a real cloud server (Azure DevOps)**
- ✅ **Use SSH keys for secure Git authentication**
### From Advanced Track:
- ✅ Rebase to maintain clean, linear history
@@ -321,37 +338,46 @@ The workshop format combines instructor-led sessions with self-paced hands-on mo
### Setting Up Module 08: Multiplayer Git
Module 08 requires a Git server for authentic collaboration. You have two options:
Module 08 requires a Git server for authentic collaboration using **Azure DevOps**.
**Option 1: Self-Hosted Gitea Server (Recommended)**
**Azure DevOps Setup**
Run your own Git server with Gitea using Docker and Cloudflare Tunnel:
Use Azure DevOps as the cloud-based Git platform for this module:
**Benefits:**
- 💰 Completely free (no cloud costs)
- 🔒 Full control over your data
- 🌐 Accessible from anywhere via Cloudflare Tunnel
- 🚀 Quick setup with Docker Compose
- 👥 Perfect for workshops with 2-24 students
- 💰 Free tier supports up to 5 users with full access
- 🌐 Cloud-hosted - no server maintenance required
- 🔒 Enterprise-grade security and reliability
- 🔑 Built-in SSH key support (industry best practice)
- 👥 Perfect for workshops with any number of students (use Stakeholder licenses for >5 users)
- 📊 Built-in pull request workflows and code review tools
**Setup:**
1. See [GITEA-SETUP.md](GITEA-SETUP.md) for complete Gitea + Docker + Cloudflare Tunnel instructions
2. See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for detailed workshop preparation:
- Creating student accounts
- Setting up The Great Print Project repository
**Setup Steps:**
1. **Create Azure DevOps Organization** (if you don't have one):
- Sign up at [dev.azure.com](https://dev.azure.com) with a Microsoft account
- Create a new organization for your workshop
2. **Set up SSH authentication** (recommended for all users):
- See [AZURE-DEVOPS-SSH-SETUP.md](AZURE-DEVOPS-SSH-SETUP.md) for complete SSH key setup instructions
- SSH provides secure, passwordless authentication (industry standard)
3. **Configure workshop repository and users**:
- See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for detailed workshop preparation:
- Adding student accounts to Azure DevOps
- Creating The Great Print Project repository
- Configuring branch policies
- Pairing students
- Monitoring progress
- Troubleshooting common issues
- Troubleshooting SSH and authentication issues
**Option 2: Azure DevOps / GitHub / GitLab**
**Alternative: GitHub / GitLab / Bitbucket**
You can also use existing cloud Git platforms:
- Create organization/group for the workshop
- Set up repository with starter code (see facilitator guide)
- Create user accounts for students
- Configure permissions
**Both options work - Gitea gives you more control and is free for any number of students.**
While this workshop uses Azure DevOps, the skills learned apply to any Git platform:
- The workflow is identical across all platforms
- SSH authentication works the same way everywhere
- Pull request concepts transfer directly
- Students can apply these skills to any Git hosting service
---
@@ -365,7 +391,7 @@ git-workshop/
├── GIT-CHEATSHEET.md # Quick reference for all Git commands
├── WORKSHOP-AGENDA.md # Facilitator guide for running workshops
├── PRESENTATION-OUTLINE.md # Slide deck outline
├── GITEA-SETUP.md # Self-hosted Git server setup
├── AZURE-DEVOPS-SSH-SETUP.md # SSH authentication best practices for Azure DevOps
├── install-glow.ps1 # Install glow markdown renderer
├── 01-essentials/ # Core Git skills (8 modules)
@@ -391,15 +417,16 @@ git-workshop/
## What's Unique About This Workshop
### The Great Print Project (Module 07)
### The Great Print Project (Module 08)
Unlike any other Git tutorial, Module 08 provides **real collaborative experience**:
- **Real Git server**: Not simulated - actual cloud repository at https://git.frod.dk/multiplayer
- **Real Git server**: Not simulated - actual Azure DevOps cloud repository
- **Real teammates**: Work in pairs on shared branches
- **Real conflicts**: Both partners edit the same code and must resolve conflicts together
- **Real pull requests**: Create PRs, review code, merge to main
- **Real success**: When all pairs merge, run `python main.py` and see everyone's contributions!
- **Real security**: Use SSH keys for authentication (industry best practice)
**The challenge**: Each pair implements 3 Python functions (e.g., `print_b()`, `print_c()`, `print_d()`) in a shared repository. When complete, the program prints the alphabet A-Z and numbers 0-9.
@@ -444,8 +471,8 @@ A: Absolutely! See the "For Workshop Facilitators" section above. The materials
**Q: Do I need internet access?**
A: Modules 01-07 work completely offline. Module 08 requires internet to access the Git server.
**Q: What if I prefer GitHub/GitLab instead of Gitea?**
A: The skills are identical across all Git platforms. Module 08 uses Gitea but everything you learn applies to GitHub, GitLab, Bitbucket, etc.
**Q: What if I prefer GitHub/GitLab instead of Azure DevOps?**
A: The skills are identical across all Git platforms. Module 08 uses Azure DevOps but everything you learn applies directly to GitHub, GitLab, Bitbucket, and any other Git hosting service. The SSH authentication, pull request workflow, and collaboration patterns are the same everywhere.
---

124
WHAT-IS-GIT.md Normal file
View File

@@ -0,0 +1,124 @@
# What is Git?
Git is a tool that tracks changes to your files over time. Think of it as an "undo history" for your entire project that you can browse, search, and share with others.
## Commits: Snapshots of Your Project
A **commit** is like taking a photo of your entire project at a specific moment in time.
Every time you make a commit, Git:
1. Records what **all** your files look like right now
2. Adds a message describing what changed
3. Notes who made the change and when
4. Links back to the previous commit
### Every Commit Contains Everything
This is important: each commit is a complete snapshot of **all** files in your project - not just the files you changed. You can check out any commit and see the entire project exactly as it was.
But wait - doesn't that waste a lot of space? No! Git is clever about this.
### Unchanged Files Are Reused
If a file hasn't changed since the last commit, Git doesn't store a new copy. Instead, it simply points to the version it already has:
```
Commit 1 Commit 2 Commit 3
───────── ───────── ─────────
README.md ─────────────────────────────> (same)
app.py ────────> app.py (v2) ───────> (same)
config.json ──────> (same) ────────────> config.json (v3)
```
In this example:
- `README.md` never changed - all three commits refer to the same stored version
- `app.py` changed in Commit 2, so a new version was stored
- `config.json` changed in Commit 3, so a new version was stored
This means:
- Every commit gives you the **complete picture** of your project
- Git only stores **new content** when files actually change
- Going back to any point in history is instant - no need to "replay" changes
```
Commit 3 Commit 2 Commit 1
| | |
v v v
[Add login] <-- [Fix bug] <-- [First version]
```
Each commit points back to its parent, creating a chain of history. You can always go back and see exactly what your project looked like at any point.
## The Magic of Checksums
Here's where Git gets clever. Every commit gets a unique ID called a **checksum** (or "hash"). It looks like this:
```
a1b2c3d4e5f6g7h8i9j0...
```
This ID is calculated from the **contents** of the commit - the files, the message, the author, and the parent commit's ID.
Why does this matter?
### Verification
If even one character changes in a file, the checksum becomes completely different. This means:
- Git instantly knows if something has been corrupted or tampered with
- You can trust that what you downloaded is exactly what was uploaded
### Finding Differences
When you connect to another copy of the repository, Git compares checksums:
```
Your computer: Server:
Commit A Commit A (same checksum = identical)
Commit B Commit B (same checksum = identical)
Commit C (missing) (you have something new!)
```
Git doesn't need to compare every file. It just compares the short checksums to instantly know what's different.
## Distributed: Everyone Has a Full Copy
Unlike older systems where one central server held all the history, Git is **distributed**. This means:
- Every person has a complete copy of the entire project history
- You can work offline - commit, browse history, create branches
- If the server disappears, anyone's copy can restore everything
- You sync with others by exchanging commits
```
[Alice's Computer] [Bob's Computer]
| |
Full history Full history
All branches All branches
| |
+---- [Shared Server] ------+
|
Full history
All branches
```
When Alice pushes her new commits to the server, Bob can pull them down. The checksums ensure nothing gets lost or corrupted in transit.
## Putting It All Together
1. **You work** - Edit files, create new ones, delete old ones
2. **You commit** - Take a snapshot with a descriptive message
3. **Git calculates** - Creates a unique checksum for this commit
4. **You push** - Send your commits to a shared server
5. **Others pull** - Download your commits using checksums to verify
6. **History grows** - The chain of commits gets longer
That's it! Git is essentially a distributed database of snapshots, connected together and verified by checksums. Everything else - branches, merges, rebasing - builds on these simple ideas.
## Key Takeaways
- **Commit** = A snapshot of your project at one moment
- **Checksum** = A unique fingerprint calculated from the content
- **Distributed** = Everyone has a full copy, not just the server
- **History** = A chain of commits, each pointing to its parent
You don't need to understand every detail to use Git effectively. Just remember: commit often, write clear messages, and sync with your team regularly.

View File

@@ -1,463 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Installs all prerequisites for the Git Workshop using winget.
.DESCRIPTION
This script automates the installation of required tools for the Git Workshop:
- PowerShell 7 (cross-platform PowerShell)
- Git 2.23+ (version control system)
- Visual Studio Code (code editor with Git integration)
Optional tools (with user prompts):
- Python 3.12 (for Module 08: Multiplayer Git)
- Windows Terminal (modern terminal experience)
The script checks for existing installations, shows clear progress, and verifies
each installation succeeded. At the end, it displays Git configuration instructions.
.EXAMPLE
PS> .\install-prerequisites.ps1
Runs the installation script with interactive prompts.
.NOTES
Requires Windows 11 with winget (App Installer) available.
Some installations may require administrator privileges.
#>
[CmdletBinding()]
param()
Set-StrictMode -Version Latest
$ErrorActionPreference = 'Continue' # Continue on errors to show all results
#region Helper Functions
function Write-ColorMessage {
param(
[string]$Message,
[string]$Color = 'White'
)
Write-Host $Message -ForegroundColor $Color
}
function Write-Step {
param([string]$Message)
Write-ColorMessage "`n=== $Message ===" -Color Cyan
}
function Write-Success {
param([string]$Message)
Write-ColorMessage "$Message" -Color Green
}
function Write-Warning {
param([string]$Message)
Write-ColorMessage "$Message" -Color Yellow
}
function Write-Error {
param([string]$Message)
Write-ColorMessage "$Message" -Color Red
}
function Test-CommandExists {
param([string]$Command)
$oldPreference = $ErrorActionPreference
$ErrorActionPreference = 'SilentlyContinue'
try {
if (Get-Command $Command -ErrorAction SilentlyContinue) {
return $true
}
return $false
}
finally {
$ErrorActionPreference = $oldPreference
}
}
function Get-InstalledVersion {
param(
[string]$Command,
[string]$VersionArg = '--version'
)
try {
$output = & $Command $VersionArg 2>&1 | Select-Object -First 1
return $output.ToString().Trim()
}
catch {
return $null
}
}
function Test-WingetAvailable {
if (-not (Test-CommandExists 'winget')) {
Write-Error "winget is not available on this system."
Write-Host "`nTo fix this:" -ForegroundColor Yellow
Write-Host " 1. Update Windows 11 to the latest version (Settings → Windows Update)" -ForegroundColor White
Write-Host " 2. Install 'App Installer' from the Microsoft Store" -ForegroundColor White
Write-Host " 3. Restart your computer and run this script again" -ForegroundColor White
return $false
}
return $true
}
function Install-Package {
param(
[string]$Name,
[string]$WingetId,
[string]$CheckCommand,
[string]$MinVersion = $null,
[string]$AdditionalArgs = ''
)
Write-Step "Installing $Name"
# Check if already installed
if (Test-CommandExists $CheckCommand) {
$version = Get-InstalledVersion $CheckCommand
Write-Success "$Name is already installed: $version"
if ($MinVersion -and $version) {
# Basic version check (not perfect but good enough for common cases)
if ($version -match '(\d+\.[\d.]+)') {
$installedVersion = $matches[1]
if ([version]$installedVersion -lt [version]$MinVersion) {
Write-Warning "Version $installedVersion is below minimum required version $MinVersion"
Write-Host " Attempting to upgrade..." -ForegroundColor Cyan
}
else {
return $true
}
}
}
else {
return $true
}
}
# Install using winget
Write-Host " Installing via winget: $WingetId" -ForegroundColor Cyan
$installCmd = "winget install --id $WingetId --source winget --silent $AdditionalArgs".Trim()
Write-Host " Running: $installCmd" -ForegroundColor Gray
try {
$result = Invoke-Expression $installCmd 2>&1
# Check if installation succeeded
Start-Sleep -Seconds 2 # Give the system time to register the new command
# Refresh environment variables in current session
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
if (Test-CommandExists $CheckCommand) {
$version = Get-InstalledVersion $CheckCommand
Write-Success "$Name installed successfully: $version"
return $true
}
else {
Write-Warning "$Name installation completed, but command '$CheckCommand' not found."
Write-Host " You may need to restart your terminal or computer." -ForegroundColor Yellow
return $false
}
}
catch {
Write-Error "Failed to install $Name`: $_"
return $false
}
}
function Test-GitVersion {
if (-not (Test-CommandExists 'git')) {
return $false
}
$version = Get-InstalledVersion 'git'
if ($version -match 'git version (\d+\.\d+)') {
$versionNumber = [decimal]$matches[1]
if ($versionNumber -ge 2.23) {
return $true
}
else {
Write-Warning "Git version $versionNumber is below required version 2.23"
return $false
}
}
return $false
}
function Get-UserConfirmation {
param([string]$Prompt)
while ($true) {
$response = Read-Host "$Prompt (y/n)"
$response = $response.Trim().ToLower()
if ($response -eq 'y' -or $response -eq 'yes') {
return $true
}
elseif ($response -eq 'n' -or $response -eq 'no') {
return $false
}
else {
Write-Host "Please enter 'y' or 'n'" -ForegroundColor Yellow
}
}
}
#endregion
#region Main Script
Write-Host @"
Git Workshop - Prerequisites Installation Script
"@ -ForegroundColor Cyan
Write-Host "This script will install the required tools for the Git Workshop:" -ForegroundColor White
Write-Host " • PowerShell 7 (cross-platform PowerShell)" -ForegroundColor White
Write-Host " • Git 2.23+ (version control system)" -ForegroundColor White
Write-Host " • Visual Studio Code (code editor)" -ForegroundColor White
Write-Host ""
Write-Host "You will be prompted for optional tools:" -ForegroundColor White
Write-Host " • Python 3.12 (for Module 08: Multiplayer Git)" -ForegroundColor White
Write-Host " • Windows Terminal (modern terminal experience)" -ForegroundColor White
Write-Host ""
# Check for winget
Write-Step "Checking Prerequisites"
if (-not (Test-WingetAvailable)) {
Write-Host "`nInstallation cannot continue without winget." -ForegroundColor Red
exit 1
}
Write-Success "winget is available"
# Track installation results
$results = @{
PowerShell = $false
Git = $false
VSCode = $false
Python = $null # null = not attempted, true = success, false = failed
WindowsTerminal = $null
}
Write-Host "`nStarting installation..." -ForegroundColor Cyan
Write-Host "Note: Some installations may take a few minutes." -ForegroundColor Gray
Write-Host ""
#region Required Installations
# Install PowerShell 7
$results.PowerShell = Install-Package `
-Name "PowerShell 7" `
-WingetId "Microsoft.PowerShell" `
-CheckCommand "pwsh"
# Install Git
$results.Git = Install-Package `
-Name "Git" `
-WingetId "Git.Git" `
-CheckCommand "git" `
-MinVersion "2.23" `
-AdditionalArgs "-e"
# Verify Git version specifically
if ($results.Git) {
if (-not (Test-GitVersion)) {
Write-Warning "Git is installed but version may be below 2.23"
$results.Git = $false
}
}
# Install Visual Studio Code
$results.VSCode = Install-Package `
-Name "Visual Studio Code" `
-WingetId "Microsoft.VisualStudioCode" `
-CheckCommand "code"
#endregion
#region Optional Installations
# Python 3.12 (optional)
Write-Host ""
if (Get-UserConfirmation "Do you want to install Python 3.12? (Required for Module 08: Multiplayer Git)") {
$results.Python = Install-Package `
-Name "Python 3.12" `
-WingetId "Python.Python.3.12" `
-CheckCommand "python"
}
else {
Write-Host " Skipping Python installation." -ForegroundColor Gray
$results.Python = $null
}
# Windows Terminal (optional)
Write-Host ""
if (Get-UserConfirmation "Do you want to install Windows Terminal? (Highly recommended for better terminal experience)") {
$results.WindowsTerminal = Install-Package `
-Name "Windows Terminal" `
-WingetId "Microsoft.WindowsTerminal" `
-CheckCommand "wt"
}
else {
Write-Host " Skipping Windows Terminal installation." -ForegroundColor Gray
$results.WindowsTerminal = $null
}
#endregion
#region Installation Summary
Write-Step "Installation Summary"
$allRequired = $results.PowerShell -and $results.Git -and $results.VSCode
Write-Host ""
Write-Host "Required Tools:" -ForegroundColor White
if ($results.PowerShell) {
Write-Success "PowerShell 7"
}
else {
Write-Error "PowerShell 7 - Installation failed or needs restart"
}
if ($results.Git) {
Write-Success "Git 2.23+"
}
else {
Write-Error "Git 2.23+ - Installation failed or needs restart"
}
if ($results.VSCode) {
Write-Success "Visual Studio Code"
}
else {
Write-Error "Visual Studio Code - Installation failed or needs restart"
}
if ($results.Python -ne $null) {
Write-Host ""
Write-Host "Optional Tools:" -ForegroundColor White
if ($results.Python) {
Write-Success "Python 3.12"
}
else {
Write-Error "Python 3.12 - Installation failed or needs restart"
}
}
if ($results.WindowsTerminal -ne $null) {
if ($results.Python -eq $null) {
Write-Host ""
Write-Host "Optional Tools:" -ForegroundColor White
}
if ($results.WindowsTerminal) {
Write-Success "Windows Terminal"
}
else {
Write-Error "Windows Terminal - Installation failed or needs restart"
}
}
#endregion
#region Next Steps
Write-Step "Next Steps"
if ($allRequired) {
Write-Host ""
Write-Success "All required tools installed successfully!"
Write-Host ""
Write-Host "IMPORTANT: Configure Git before your first commit:" -ForegroundColor Yellow
Write-Host ""
Write-Host " git config --global user.name `"Your Name`"" -ForegroundColor White
Write-Host " git config --global user.email `"your.email@example.com`"" -ForegroundColor White
Write-Host ""
Write-Host "Optional: Set VS Code as Git's default editor:" -ForegroundColor Cyan
Write-Host " git config --global core.editor `"code --wait`"" -ForegroundColor White
Write-Host ""
Write-Host "Verify your installation:" -ForegroundColor Cyan
Write-Host " pwsh --version" -ForegroundColor White
Write-Host " git --version" -ForegroundColor White
Write-Host " code --version" -ForegroundColor White
if ($results.Python) {
Write-Host " python --version" -ForegroundColor White
}
Write-Host ""
Write-Host "Set PowerShell execution policy (if needed):" -ForegroundColor Cyan
Write-Host " Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser" -ForegroundColor White
Write-Host ""
Write-Host "Recommended VS Code Extensions:" -ForegroundColor Cyan
Write-Host " • GitLens - Supercharge Git capabilities" -ForegroundColor White
Write-Host " • Git Graph - View Git history visually" -ForegroundColor White
Write-Host " • PowerShell - Better PowerShell support (from Microsoft)" -ForegroundColor White
Write-Host ""
Write-Host " Install via: Ctrl+Shift+X in VS Code" -ForegroundColor Gray
Write-Host ""
Write-Host "You're ready to start the workshop!" -ForegroundColor Green
Write-Host " cd path\to\git-workshop" -ForegroundColor White
Write-Host " cd 01-essentials\01-basics" -ForegroundColor White
Write-Host " .\setup.ps1" -ForegroundColor White
Write-Host ""
}
else {
Write-Host ""
Write-Warning "Some required installations failed or need verification."
Write-Host ""
Write-Host "Troubleshooting steps:" -ForegroundColor Yellow
Write-Host " 1. Close and reopen your terminal (or restart your computer)" -ForegroundColor White
Write-Host " 2. Run this script again: .\install-prerequisites.ps1" -ForegroundColor White
Write-Host " 3. If issues persist, try manual installation:" -ForegroundColor White
Write-Host " See INSTALLATION.md for detailed instructions" -ForegroundColor White
Write-Host ""
if (-not $results.Git) {
Write-Host "For Git issues:" -ForegroundColor Yellow
Write-Host " • Restart terminal after installation (PATH needs to refresh)" -ForegroundColor White
Write-Host " • Manual download: https://git-scm.com/downloads" -ForegroundColor White
Write-Host ""
}
if (-not $results.VSCode) {
Write-Host "For VS Code issues:" -ForegroundColor Yellow
Write-Host " • Ensure 'Add to PATH' option is enabled during installation" -ForegroundColor White
Write-Host " • Manual download: https://code.visualstudio.com/" -ForegroundColor White
Write-Host ""
}
if (-not $results.PowerShell) {
Write-Host "For PowerShell 7 issues:" -ForegroundColor Yellow
Write-Host " • Manual download: https://github.com/PowerShell/PowerShell/releases/latest" -ForegroundColor White
Write-Host " • Download the file ending in '-win-x64.msi'" -ForegroundColor White
Write-Host ""
}
exit 1
}
#endregion
exit 0

File diff suppressed because it is too large Load Diff