Compare commits
7 Commits
9e03a9624a
...
7b638d27de
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7b638d27de | ||
|
|
ea5cbccc75 | ||
|
|
76c4182186 | ||
|
|
5b4c544fee | ||
|
|
74a23dbbca | ||
|
|
25077cd589 | ||
|
|
8b3ba808d1 |
File diff suppressed because it is too large
Load Diff
1
01-essentials/03-branching-and-merging/challenge
Submodule
1
01-essentials/03-branching-and-merging/challenge
Submodule
Submodule 01-essentials/03-branching-and-merging/challenge added at 8ae52cc25c
@@ -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
|
||||
|
||||
@@ -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 ""
|
||||
|
||||
@@ -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 "`nYou've successfully practiced:" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Creating branches" -ForegroundColor White
|
||||
Write-Host " ✓ Making commits on branches" -ForegroundColor White
|
||||
Write-Host " ✓ Merging branches together" -ForegroundColor White
|
||||
Write-Host " ✓ Resolving merge conflicts" -ForegroundColor White
|
||||
Write-Host "`nReady for the next module!" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "`nCheckpoint '$Checkpoint' complete!" -ForegroundColor Cyan
|
||||
|
||||
switch ($Checkpoint) {
|
||||
'start' {
|
||||
Write-Host "Next: Move to the merging checkpoint" -ForegroundColor White
|
||||
Write-Host " ..\reset.ps1 merge OR continue to merge feature-login" -ForegroundColor Yellow
|
||||
}
|
||||
'merge' {
|
||||
Write-Host "Next: Move to the conflict resolution checkpoint" -ForegroundColor White
|
||||
Write-Host " ..\reset.ps1 merge-conflict" -ForegroundColor Yellow
|
||||
}
|
||||
'merge-conflict' {
|
||||
Write-Host "Module complete! Ready for the next module!" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host ""
|
||||
exit 0
|
||||
} else {
|
||||
Write-Host "[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
|
||||
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
|
||||
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Quick guide:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Create a branch: git switch -c my-feature" -ForegroundColor White
|
||||
Write-Host " 2. Make changes and commit them" -ForegroundColor White
|
||||
Write-Host " 3. Switch to 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
|
||||
}
|
||||
|
||||
487
01-essentials/04-merge-conflict/README.md
Normal file
487
01-essentials/04-merge-conflict/README.md
Normal 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!
|
||||
1
01-essentials/04-merge-conflict/challenge
Submodule
1
01-essentials/04-merge-conflict/challenge
Submodule
Submodule 01-essentials/04-merge-conflict/challenge added at 676b08e650
23
01-essentials/04-merge-conflict/reset.ps1
Normal file
23
01-essentials/04-merge-conflict/reset.ps1
Normal 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 ""
|
||||
}
|
||||
125
01-essentials/04-merge-conflict/setup.ps1
Normal file
125
01-essentials/04-merge-conflict/setup.ps1
Normal 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 ""
|
||||
208
01-essentials/04-merge-conflict/verify.ps1
Normal file
208
01-essentials/04-merge-conflict/verify.ps1
Normal 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
|
||||
}
|
||||
@@ -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
254
01-essentials/09-multiplayer/01_FACILITATOR.md
Normal file
254
01-essentials/09-multiplayer/01_FACILITATOR.md
Normal 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
|
||||
1303
01-essentials/09-multiplayer/02_README.md
Normal file
1303
01-essentials/09-multiplayer/02_README.md
Normal file
File diff suppressed because it is too large
Load Diff
395
01-essentials/09-multiplayer/03_TASKS.md
Normal file
395
01-essentials/09-multiplayer/03_TASKS.md
Normal 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
628
AZURE-DEVOPS-SSH-SETUP.md
Normal 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**
|
||||
|
||||

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

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

|
||||
*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
|
||||
```
|
||||
|
||||

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

|
||||
*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
146
BEST-PRACTICES.md
Normal 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
123
COMMIT-MESSAGES.md
Normal 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.
|
||||
74
README.md
74
README.md
@@ -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
|
||||
**Setup Steps:**
|
||||
|
||||
1. **Create Azure DevOps Organization** (if you don't have one):
|
||||
- Sign up at [dev.azure.com](https://dev.azure.com) with a Microsoft account
|
||||
- Create a new organization for your workshop
|
||||
|
||||
2. **Set up SSH authentication** (recommended for all users):
|
||||
- See [AZURE-DEVOPS-SSH-SETUP.md](AZURE-DEVOPS-SSH-SETUP.md) for complete SSH key setup instructions
|
||||
- SSH provides secure, passwordless authentication (industry standard)
|
||||
|
||||
3. **Configure workshop repository and users**:
|
||||
- See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for detailed workshop preparation:
|
||||
- Adding student accounts to Azure DevOps
|
||||
- Creating The Great Print Project repository
|
||||
- Configuring branch policies
|
||||
- Pairing students
|
||||
- Monitoring progress
|
||||
- Troubleshooting common issues
|
||||
- Troubleshooting SSH and authentication issues
|
||||
|
||||
**Option 2: Azure DevOps / GitHub / GitLab**
|
||||
**Alternative: GitHub / GitLab / Bitbucket**
|
||||
|
||||
You can also use existing cloud Git platforms:
|
||||
- Create organization/group for the workshop
|
||||
- Set up repository with starter code (see facilitator guide)
|
||||
- Create user accounts for students
|
||||
- Configure permissions
|
||||
|
||||
**Both options work - Gitea gives you more control and is free for any number of students.**
|
||||
While this workshop uses Azure DevOps, the skills learned apply to any Git platform:
|
||||
- The workflow is identical across all platforms
|
||||
- SSH authentication works the same way everywhere
|
||||
- Pull request concepts transfer directly
|
||||
- Students can apply these skills to any Git hosting service
|
||||
|
||||
---
|
||||
|
||||
@@ -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
124
WHAT-IS-GIT.md
Normal 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.
|
||||
Reference in New Issue
Block a user