Compare commits

7 Commits

Author SHA1 Message Date
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
36 changed files with 4402 additions and 3654 deletions

File diff suppressed because it is too large Load Diff

Submodule 01-essentials/03-branching-and-merging/challenge added at 8ae52cc25c

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
@@ -36,113 +32,75 @@ git config user.name "Workshop Student"
git config user.email "student@example.com"
# ============================================================================
# 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
# Switch back to main and make more commits
git switch main | Out-Null
# Now create divergence - add commits to main while feature-login exists
Write-Host "Creating divergent history on main..." -ForegroundColor Cyan
# Commit 5: Add app.py with basic functionality
$appContent = @"
# app.py - Main application entry point
# app.py - Application entry point
from main import main
@@ -150,7 +108,6 @@ def run():
"""Run the application."""
print("Starting application...")
main()
print("Application finished.")
if __name__ == "__main__":
run()
@@ -159,16 +116,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 main
Write-Host "Merging feature-login into main..." -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 and add documentation
git switch main | 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 +182,107 @@ 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 main
Write-Host "Merging feature-api into main..." -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 = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"timeout": 5000
}
}
$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 "config.json" -Value $mainConfig
git add config.json
git commit -m "Add timeout configuration" | Out-Null
Set-Content -Path "database.py" -Value $dbContent
git add .
git commit -m "Add disconnect method" | Out-Null
# Create update-config branch from the commit before timeout was added
git switch -c update-config HEAD~1 | Out-Null
# On update-config branch: Add debug setting (conflicting change)
$featureConfig = @"
{
"app": {
"name": "MyApp",
"version": "1.0.0",
"port": 3000,
"debug": true
}
}
"@
Set-Content -Path "config.json" -Value $featureConfig
git add config.json
git commit -m "Add debug mode configuration" | Out-Null
# Switch back to main
# Switch to main and merge
git switch main | Out-Null
Write-Host "Merging feature-database into main..." -ForegroundColor Green
git merge feature-database --no-edit | 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
# Final update on main
$readmeContent = @"
# My Application
# ============================================================================
# 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
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 "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 main" -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,98 @@ 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
# ============================================================================
# Count initial setup commits (should be 13 commits from setup)
# ============================================================================
$initialCommitCount = 13
# ============================================================================
# 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 {
# Verify specific checkpoint
switch ($Checkpoint) {
'start' { Verify-Branching }
'merge' { Verify-Merging }
'merge-conflict' { Verify-MergeConflicts }
}
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 = @('main', '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 "main") {
Write-Pass "Currently on main branch"
} else {
Write-Info "Currently on '$currentBranch' branch"
Write-Hint "Typically you merge feature branches INTO main"
}
Pop-Location
# ============================================================================
# Final summary
# ============================================================================
Write-Host ""
if ($script:allChecksPassed) {
Write-Host "=========================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=========================================" -ForegroundColor Green
if ($Checkpoint -eq '') {
Write-Host "`nYou've completed the entire module!" -ForegroundColor Cyan
Write-Host "You've mastered:" -ForegroundColor Cyan
Write-Host " ✓ Creating and working with branches" -ForegroundColor White
Write-Host " ✓ Merging branches together" -ForegroundColor White
Write-Host " ✓ Resolving merge conflicts" -ForegroundColor White
Write-Host "`nReady for the next module!" -ForegroundColor Green
} else {
Write-Host "`nCheckpoint '$Checkpoint' complete!" -ForegroundColor Cyan
switch ($Checkpoint) {
'start' {
Write-Host "Next: Move to the merging checkpoint" -ForegroundColor White
Write-Host " ..\reset.ps1 merge OR continue to merge feature-login" -ForegroundColor Yellow
}
'merge' {
Write-Host "Next: Move to the conflict resolution checkpoint" -ForegroundColor White
Write-Host " ..\reset.ps1 merge-conflict" -ForegroundColor Yellow
}
'merge-conflict' {
Write-Host "Module complete! Ready for the next module!" -ForegroundColor Green
}
}
}
Write-Host "`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 "`nReady 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 main: git switch main" -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

@@ -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!

Submodule 01-essentials/04-merge-conflict/challenge added at 676b08e650

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,125 @@
#!/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"
# ============================================================================
# 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 main | 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
git switch main | 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 " - main 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,208 @@
#!/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
# ============================================================================
# Check current branch
# ============================================================================
$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"
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 main: git switch main" -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

@@ -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

View File

@@ -0,0 +1,254 @@
# 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** (4-8 people) 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
---
## Troubleshooting
### SSH Issues
- Verify SSH key added to Azure DevOps (User Settings → SSH Public Keys)
- Test: `ssh -T git@ssh.dev.azure.com`
### Permission Issues
- Check user is added to project
- Verify Contribute permission on repository
### Service Issues
- Check status: https://status.dev.azure.com

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,395 @@
# Multiplayer Git Tasks
These tasks walk you through collaborating with Git in the cloud. You'll clone a shared repository, make changes, and sync with your teammates.
## Prerequisites
Before starting, make sure you have:
- [ ] An account on the team's Azure DevOps project
- [ ] SSH key configured (ask your facilitator if you need help)
- [ ] Git installed on your computer
---
## Task 1: Clone the Repository
Cloning creates a local copy of a remote repository on your computer.
### Steps
1. Get the SSH URL from Azure DevOps:
- Navigate to the repository
- Click **Clone**
- Select **SSH**
- Copy the URL
2. Open PowerShell and run:
```powershell
git clone <paste-the-url-here>
```
3. Open the folder in VS Code:
```powershell
code <repository-name>
```
4. Open the VS Code terminal (`` Ctrl+` ``) and verify the clone worked:
```powershell
git status
git log --oneline --graph --all
```
### What Just Happened?
```
Azure DevOps Your Computer
┌─────────────┐ ┌─────────────┐
│ Repository │ ───── clone ──> │ Repository │
│ (original) │ │ (copy) │
└─────────────┘ └─────────────┘
```
You now have:
- A complete copy of all files
- The entire commit history
- A connection back to the original (called "origin")
---
## Task 2: Make Changes and Push
Pushing sends your local commits to the remote repository.
### Steps
1. In VS Code, create a new file:
- Click **File → New File** (or `Ctrl+N`)
- Add some content, for example: `Hello from <your-name>`
- Save as `hello-<your-name>.txt` (use `Ctrl+S`)
2. In the VS Code terminal, stage and commit your change:
```powershell
git add .
git commit -m "feat: add greeting from <your-name>"
```
3. Push to the remote:
```powershell
git push
```
### What Just Happened?
```
Your Computer Azure DevOps
┌─────────────┐ ┌─────────────┐
│ Commit A │ │ Commit A │
│ Commit B │ ───── push ───> │ Commit B │
│ Commit C │ (new!) │ Commit C │
└─────────────┘ └─────────────┘
```
Your new commit is now on the server. Others can see it and download it.
---
## Task 3: Pull Changes from Others
Pulling downloads new commits from the remote and merges them into your branch.
### Steps
1. Check if there are new changes:
```powershell
git status
```
Look for "Your branch is behind..."
2. Pull the changes:
```powershell
git pull
```
3. See what's new:
```powershell
git log --oneline -10
```
### What Just Happened?
```
Azure DevOps Your Computer
┌─────────────┐ ┌─────────────┐
│ Commit A │ │ Commit A │
│ Commit B │ │ Commit B │
│ Commit C │ ───── pull ───> │ Commit C │
│ Commit D │ (new!) │ Commit D │
└─────────────┘ └─────────────┘
```
Your local repository now has all the commits from the remote.
---
## Task 4: The Push-Pull Dance
When working with others, you'll often need to pull before you can push.
### The Scenario
You made a commit, but someone else pushed while you were working:
```
Azure DevOps: A ── B ── C ── D (teammate's commit)
Your Computer: A ── B ── C ── E (your commit)
```
### Steps
1. Try to push:
```powershell
git push
```
This will fail with: "Updates were rejected because the remote contains work that you do not have locally"
2. Pull first:
```powershell
git pull
```
3. Now push:
```powershell
git push
```
### What Happened?
```
Before pull:
Remote: A ── B ── C ── D
Local: A ── B ── C ── E
After pull (Git merges automatically):
Local: A ── B ── C ── D ── M
\ /
E ───┘
After push:
Remote: A ── B ── C ── D ── M
\ /
E ───┘
```
---
## Task 5: Understanding Fetch
Fetch downloads changes but does **not** merge them. This lets you see what's new before deciding what to do.
### Steps
1. Fetch updates from the remote:
```powershell
git fetch
```
2. See what's different:
```powershell
git log HEAD..origin/main --oneline
```
This shows commits on the remote that you don't have locally.
3. When ready, merge:
```powershell
git merge origin/main
```
### Fetch vs Pull
| Command | Downloads | Merges | Safe to run anytime? |
|---------|-----------|--------|----------------------|
| `git fetch` | Yes | No | Yes |
| `git pull` | Yes | Yes | Usually |
**Think of it this way:**
- `fetch` = "Show me what's new"
- `pull` = "Give me what's new" (same as `fetch` + `merge`)
---
## Task 6: Working with Branches
Branches let you work on features without affecting the main code.
### Steps
1. Create and switch to a new branch:
```powershell
git switch -c feature/<your-name>-greeting
```
2. In VS Code, create a new file:
- Click **File → New File** (or `Ctrl+N`)
- Add some content, for example: `A special greeting`
- Save as `special.txt` (use `Ctrl+S`)
3. Stage and commit:
```powershell
git add .
git commit -m "feat: add special greeting"
```
4. Push your branch to the remote:
```powershell
git push -u origin feature/<your-name>-greeting
```
The `-u` flag sets up tracking so future pushes are simpler.
5. Go back to main:
```powershell
git switch main
```
---
## Task 7: The Number Challenge
This is the main collaborative exercise. Your team will work together to sort numbers 0-20 into the correct order.
### The Setup
The repository contains a file called `numbers.txt` with numbers 0-20 in random order:
```
17
3
12
8
...
```
Your goal: Work as a team to rearrange the numbers so they appear in order from 0 to 20.
### The Rules
1. **Each person moves ONE number per commit**
2. **You must pull before making changes**
3. **Communicate with your team** - decide who moves which number
### Steps
1. Pull the latest changes:
```powershell
git pull
```
2. Open `numbers.txt` in VS Code
3. Find a number that's out of place and move it to the correct position
- For example, if `5` is at the bottom, move it between `4` and `6`
4. Save the file (`Ctrl+S`)
5. Commit your change with a clear message:
```powershell
git add numbers.txt
git commit -m "fix: move 5 to correct position"
```
6. Push your change:
```powershell
git push
```
7. If push fails (someone else pushed first):
```powershell
git pull
```
Resolve any conflicts, then push again.
8. Repeat until all numbers are in order!
### Handling Conflicts
When two people edit the same part of the file, you'll see conflict markers:
```
<<<<<<< HEAD
4
5
6
=======
4
6
>>>>>>> origin/main
```
To resolve:
1. Decide what the correct order should be
2. Remove the conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`)
3. Keep only the correct content:
```
4
5
6
```
4. Save, commit, and push
### Success
When complete, `numbers.txt` should look like:
```
0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
```
Celebrate with your team!
---
## Quick Reference
| Command | What It Does |
|---------|--------------|
| `git clone <url>` | Download a repository |
| `git push` | Upload your commits |
| `git pull` | Download and merge commits |
| `git fetch` | Download commits (don't merge) |
| `git switch -c <name>` | Create and switch to a branch |
| `git push -u origin <branch>` | Push a new branch |
---
## Common Issues
### "Permission denied (publickey)"
Your SSH key isn't set up correctly. See the SSH setup guide or ask your facilitator.
### "Updates were rejected"
Someone pushed before you. Run `git pull` first, then `git push`.
### "Merge conflict"
Two people edited the same lines. See BEST-PRACTICES.md for how to handle this.
### "There is no tracking information"
Run `git push -u origin <branch-name>` to set up tracking.

628
AZURE-DEVOPS-SSH-SETUP.md Normal file
View File

@@ -0,0 +1,628 @@
# 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
- **Better for Automation**: Scripts and CI/CD pipelines benefit from passwordless authentication
- **Revocable**: Individual keys can be removed without changing passwords
- **Auditable**: Track which key was used for each operation
**Comparison with HTTPS/PAT:**
- HTTPS with Personal Access Tokens (PAT) requires storing tokens, which can be accidentally committed to repositories
- SSH keys separate your authentication (private key stays on your machine) from the service
- SSH connections are faster after initial setup (no token validation on every request)
---
## 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 -b 4096 -C "your.email@example.com"
```
**Important notes:**
- Replace `your.email@example.com` with your actual email address
- The `-C` flag adds a comment to help identify the key later
- The `-b 4096` flag specifies a 4096-bit key size for enhanced security
**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:**
- **Linux/Mac**: `~/.ssh/id_rsa`
- **Windows**: `C:\Users\YourName\.ssh\id_rsa`
### 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:
```
**Passphrase pros and cons:**
- **With passphrase**: Extra security layer - even if someone steals your private key, they can't use it without the passphrase
- **Without passphrase**: More convenient - no prompt when pushing/pulling (but less secure if your machine is compromised)
**Recommendation**: Use a passphrase, especially on laptops or shared machines.
### 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)
---
## 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/azure-devops-user-settings.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/azure-devops-add-ssh-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
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**
![Azure DevOps - SSH Key Added Successfully](./images/azure-devops-ssh-key-success.png)
*Your SSH key has been successfully added and is ready to use*
**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: Configure SSH (Optional but Recommended)
Create or edit your SSH configuration file to specify which key to use with Azure DevOps.
### Create/Edit SSH Config File
**Linux/Mac:**
```bash
mkdir -p ~/.ssh
nano ~/.ssh/config
```
**Windows PowerShell:**
```powershell
if (!(Test-Path "$HOME\.ssh")) { New-Item -ItemType Directory -Path "$HOME\.ssh" }
notepad $HOME\.ssh\config
```
### Add Azure DevOps Host Configuration
Add these lines to your `~/.ssh/config` file:
```
Host ssh.dev.azure.com
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
```
**For Windows users**, use backslashes in the path:
```
Host ssh.dev.azure.com
IdentityFile C:\Users\YourName\.ssh\id_rsa
IdentitiesOnly yes
```
**What this does:**
- `Host ssh.dev.azure.com` - Applies these settings only to Azure DevOps
- `IdentityFile` - Specifies which private key to use (your RSA key)
- `IdentitiesOnly yes` - Prevents SSH from trying other keys
### Save the Configuration
Save and close the file:
- **Nano**: Press `Ctrl+X`, then `Y`, then `Enter`
- **Notepad**: Click File → Save, then close
---
## Step 4: Test SSH Connection
Verify that your SSH key is working correctly.
### Test Command
Run this command to test your connection:
```bash
ssh -T git@ssh.dev.azure.com
```
### Expected Output
**First-time connection** will show a host key verification prompt:
```
The authenticity of host 'ssh.dev.azure.com (20.42.134.1)' can't be established.
RSA key fingerprint is SHA256:ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og.
Are you sure you want to continue connecting (yes/no)?
```
Type `yes` and press Enter to add Azure DevOps to your known hosts.
**Successful authentication** will show:
```
remote: Shell access is not supported.
shell request failed on channel 0
```
![Azure DevOps - Successful SSH Test](./images/azure-devops-ssh-test-success.png)
*Successful SSH test output showing authenticated connection*
**This is normal!** Azure DevOps doesn't provide shell access, but this message confirms your SSH key authentication worked.
### Troubleshooting Connection Issues
If the connection fails, see the [Troubleshooting section](#troubleshooting) below.
---
## Step 5: 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.
---
## Troubleshooting
### Permission Denied (publickey)
**Error:**
```
git@ssh.dev.azure.com: Permission denied (publickey).
fatal: Could not read from remote repository.
```
**Causes and solutions:**
1. **SSH key not added to Azure DevOps**
- Go back to [Step 2](#step-2-add-ssh-public-key-to-azure-devops) and verify your public key is uploaded
- Check you copied the **entire** public key (from `ssh-rsa` to your email)
2. **Wrong private key being used**
- Verify your SSH config file points to the correct key
- Test with: `ssh -vT git@ssh.dev.azure.com` (verbose output shows which keys are tried)
3. **SSH agent not running** (if you used a passphrase)
- Start the SSH agent:
```bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
```
### Connection Timeout
**Error:**
```
ssh: connect to host ssh.dev.azure.com port 22: Connection timed out
```
**Causes and solutions:**
1. **Firewall blocking SSH port (22)**
- Check if your organization's firewall blocks port 22
- Try using HTTPS as a fallback
2. **Network restrictions**
- Try from a different network (mobile hotspot, home network)
- Contact your IT department about SSH access
3. **Proxy configuration**
- If behind a corporate proxy, you may need to configure SSH to use it
- Add to `~/.ssh/config`:
```
Host ssh.dev.azure.com
ProxyCommand nc -X connect -x proxy.company.com:3128 %h %p
```
### Host Key Verification Failed
**Error:**
```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
```
**Causes and solutions:**
1. **Azure DevOps updated their host keys** (rare but happens)
- Check [Azure DevOps SSH key fingerprints](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate#verify-the-host-key-fingerprint)
- If fingerprint matches, remove old key and re-add:
```bash
ssh-keygen -R ssh.dev.azure.com
```
2. **Man-in-the-middle attack** (security risk!)
- If fingerprint doesn't match Microsoft's published keys, **DO NOT PROCEED**
- Contact your security team
### SSH Key Not Working After Creation
**Symptoms:**
- Created key successfully
- Added to Azure DevOps
- Still getting "Permission denied"
**Solutions:**
1. **Check file permissions** (Linux/Mac only)
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
```
2. **Verify key format**
- Ensure you copied the **public key** (.pub file) to Azure DevOps, not the private key
- Public key starts with `ssh-rsa`
3. **Test with verbose output**
```bash
ssh -vvv git@ssh.dev.azure.com
```
- Look for lines like "Offering public key" to see which keys are tried
- Check for "Authentication succeeded" message
---
## Security Best Practices
Follow these security guidelines to keep your SSH keys safe:
### Use Passphrase Protection
**Always use a passphrase for your SSH keys**, especially on:
- Laptops (risk of theft)
- Shared machines
- Devices that leave your office/home
**How to add a passphrase to an existing key:**
```bash
ssh-keygen -p -f ~/.ssh/id_rsa
```
### Never Share Your Private Key
**Critical security rule:**
- **NEVER** share your private key (`~/.ssh/id_rsa`)
- **NEVER** commit private keys to Git repositories
- **NEVER** send private keys via email or chat
**Only share:**
- Public key (`~/.ssh/id_rsa.pub`) - This is safe and intended to be shared
### Use Different Keys for Different Purposes
Consider creating separate SSH keys for:
- Work projects
- Personal projects
- Different organizations
**Benefits:**
- Limit blast radius if one key is compromised
- Easier to revoke access to specific services
- Better audit trail
**Example: Create a work-specific key:**
```bash
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_work -C "work.email@company.com"
```
Then add to `~/.ssh/config`:
```
Host ssh.dev.azure.com-work
HostName ssh.dev.azure.com
IdentityFile ~/.ssh/id_rsa_work
```
### Rotate Keys Periodically
**Recommended schedule:**
- Personal projects: Annually
- Work projects: Every 6 months
- High-security projects: Every 3 months
**How to rotate:**
1. Generate new SSH key pair
2. Add new public key to Azure DevOps
3. Test the new key works
4. Remove old public key from Azure DevOps
5. Delete old private key from your machine
### Revoke Compromised Keys Immediately
If your private key is exposed:
1. **Immediately** remove the public key from Azure DevOps
- User Settings → SSH Public Keys → Click the key → Delete
2. Generate a new key pair
3. Update all repositories to use the new key
### Protect Your Private Key File
Ensure correct file permissions:
**Linux/Mac:**
```bash
chmod 600 ~/.ssh/id_rsa
```
**Windows:**
```powershell
icacls "$HOME\.ssh\id_rsa" /inheritance:r /grant:r "$($env:USERNAME):F"
```
### Use SSH Agent Forwarding Carefully
SSH agent forwarding (`-A` flag) can be convenient but risky:
- Only use with trusted servers
- Prefer ProxyJump instead when possible
### Enable Two-Factor Authentication (2FA)
While SSH keys are secure, enable 2FA on your Azure DevOps account for additional security:
1. Azure DevOps → User Settings → Security → Two-factor authentication
2. Use an authenticator app (Microsoft Authenticator, Google Authenticator)
---
## 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 rsa -b 4096 -C "your.email@example.com"
# 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
```
---
## Important Note: RSA and Modern SSH Key Algorithms
**Why This Guide Uses RSA:**
This guide exclusively uses RSA keys because **Azure DevOps currently only supports RSA SSH keys**. As of January 2026, Azure DevOps does not support modern SSH key algorithms like Ed25519, ECDSA, or other newer formats.
**About RSA Security:**
RSA is an older cryptographic algorithm that has been the industry standard for decades. While RSA with 4096-bit keys (as used in this guide) is still considered secure for most use cases, it has some limitations compared to modern alternatives:
**RSA Drawbacks:**
- **Larger key sizes**: RSA requires 4096 bits for strong security, resulting in larger keys
- **Slower performance**: Key generation and signature operations are slower than modern algorithms
- **Older cryptographic foundation**: Based on mathematical principles from the 1970s
- **More CPU-intensive**: Authentication operations require more computational resources
**Modern Alternatives (Not Supported by Azure DevOps):**
If Azure DevOps supported modern algorithms, we would recommend:
**Ed25519:**
- **Faster**: Significantly faster key generation and authentication
- **Smaller keys**: 256-bit keys (much smaller than RSA 4096-bit)
- **Modern cryptography**: Based on elliptic curve cryptography (ECC) with strong security guarantees
- **Better performance**: Less CPU usage, faster operations
- **Widely supported**: GitHub, GitLab, Bitbucket, and most modern Git platforms support Ed25519
**ECDSA:**
- Also based on elliptic curve cryptography
- Faster than RSA but slightly slower than Ed25519
- Supported by many platforms
**Current State:**
RSA with 4096-bit keys remains secure and is acceptable for Git authentication, despite being outdated compared to modern algorithms. The Azure DevOps team has not provided a timeline for supporting Ed25519 or other modern key types.
**For Other Platforms:**
If you're using GitHub, GitLab, Bitbucket, or other Git hosting services, we strongly recommend using Ed25519 instead of RSA:
```bash
# For platforms that support Ed25519 (GitHub, GitLab, Bitbucket, etc.)
ssh-keygen -t ed25519 -C "your.email@example.com"
```
**References:**
- [Ed25519 Wikipedia](https://en.wikipedia.org/wiki/EdDSA#Ed25519)
- [SSH Key Algorithm Comparison](https://security.stackexchange.com/questions/5096/rsa-vs-dsa-for-ssh-authentication-keys)
- [Azure DevOps SSH Documentation](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
---
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.

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.

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.
@@ -285,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
@@ -336,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
- Pairing students
- Monitoring progress
- Troubleshooting common issues
**Setup Steps:**
**Option 2: Azure DevOps / GitHub / GitLab**
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
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
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)
**Both options work - Gitea gives you more control and is free for any number of students.**
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 SSH and authentication issues
**Alternative: GitHub / GitLab / Bitbucket**
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
---
@@ -380,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)
@@ -406,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.
@@ -459,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.