25 Commits

Author SHA1 Message Date
Bjarke Sporring
6cf0418ebd fix: remove references to git reset 2026-01-16 10:18:46 +01:00
Bjarke Sporring
a8e9507f89 fix: missing images for ssh setup 2026-01-15 18:30:52 +01:00
Bjarke Sporring
a895abdd03 feat: drastically simplify tasks for multiplayer 2026-01-15 17:30:03 +01:00
Bjarke Sporring
356b6268ba refactor: remove advanced for now 2026-01-15 17:27:35 +01:00
Bjarke Sporring
bd69774191 feat: drastically simplify the multiplayer module 2026-01-15 17:25:33 +01:00
Bjarke Sporring
fcaf97f60b refactor: simplify the multiplayer part 2026-01-15 17:03:51 +01:00
Bjarke Sporring
2a5eb137f6 fix: stash challenge 2026-01-15 16:42:45 +01:00
Bjarke Sporring
b0d2d43c8b fix: stash issues 2026-01-15 16:37:57 +01:00
Bjarke Sporring
cdd695b250 fix: cleanup revert module 2026-01-15 16:29:06 +01:00
Bjarke Sporring
0474a6de0e fix: remove revert-middle challenge 2026-01-15 16:23:32 +01:00
Bjarke Sporring
575e083f33 refactor: rewrite the merge-revert section
It is an advanced and difficult revert to accomplish and should probably
be done through a reset instead, which means that we're modifying
history which is dangerous and so should be handled by someone who
understands these dangers.
2026-01-15 16:11:24 +01:00
Bjarke Sporring
aa24c50b45 fix: revert conflict 2026-01-15 15:50:54 +01:00
Bjarke Sporring
939bd397f1 refactor: move 08 down a step 2026-01-15 15:42:05 +01:00
Bjarke Sporring
3130874981 fix: cleanup cherry-picking tasks and tips 2026-01-15 15:41:21 +01:00
Bjarke Sporring
a34b7d155f refactor: move reset to advanced 2026-01-15 15:22:45 +01:00
Bjarke Sporring
1c20a06c21 fix: cherry-pick conflict 2026-01-15 15:20:32 +01:00
Bjarke Sporring
988ce3bc92 fix: remove subproject "challenge" folders 2026-01-15 14:31:30 +01:00
Bjarke Sporring
9fbdd941fa feat: check for mainbranch 2026-01-15 14:31:00 +01:00
Bjarke Sporring
40341d21a7 fix: add suggestion for a new file for branching and merging 2026-01-15 14:30:08 +01:00
Bjarke Sporring
8a82253fc2 fix: add better explanation of merges 2026-01-15 14:22:11 +01:00
Bjarke Sporring
f0522e14bc feat: setup merge and diff for git 2026-01-15 14:07:25 +01:00
Bjarke Sporring
0183a06134 feat: set git defaultBranch to main 2026-01-15 13:55:55 +01:00
Bjarke Sporring
c3d9d3337c refactor: add better instructions for branching and merging 2026-01-15 13:47:30 +01:00
Bjarke Sporring
ced65740a3 fix: naming of main branch write host 2026-01-15 13:16:34 +01:00
Bjarke Sporring
bf07cb1868 refactor: check for main branch 2026-01-15 13:14:38 +01:00
62 changed files with 1747 additions and 8431 deletions

View File

@@ -14,7 +14,7 @@ By the end of this module, you will:
Create the challenge environment: Create the challenge environment:
```bash ```pwsh
.\setup.ps1 .\setup.ps1
``` ```
@@ -39,7 +39,7 @@ This creates a repository with a realistic project history showing multiple merg
First, explore the existing history to see what merging looks like: First, explore the existing history to see what merging looks like:
```bash ```pwsh
cd challenge cd challenge
git log --oneline --graph --all git log --oneline --graph --all
``` ```
@@ -55,7 +55,7 @@ You'll see a visual graph showing:
- Find the merge commits (they have two parent lines converging) - Find the merge commits (they have two parent lines converging)
**Explore the branches:** **Explore the branches:**
```bash ```pwsh
# See all branches # See all branches
git branch --all git branch --all
@@ -66,7 +66,7 @@ git ls-tree --name-only main
``` ```
**View specific merges:** **View specific merges:**
```bash ```pwsh
# See all merge commits # See all merge commits
git log --merges --oneline git log --merges --oneline
@@ -78,64 +78,38 @@ git show <merge-commit-hash>
Now practice creating your own branch: Now practice creating your own branch:
```bash 1. Create a new branch with the name `my-feature` using `git switch -c my-feature`
# Create and switch to a new branch 2. Check which branch you're on with `git branch`. The `*` shows which branch you're currently on.
git switch -c my-feature
# Verify you're on the new branch
git branch
```
The `*` shows which branch you're currently on.
### Part 3: Make Commits on Your Branch ### Part 3: Make Commits on Your Branch
Add some changes to your branch: 1. Create a new file called `DOCS.md` and add some content. What doesn't matter, just something.
2. Add and commit the `DOCS.md` file (use the commands from `01-basics` module).
```bash 3. Add some more content to the `DOCS.md` file, add and commit the changes.
# Create a new file or modify an existing one 4. Now check how the tree is looking with `git log --oneline --graph --all`
echo "# My Feature" > my-feature.md
# Stage and commit
git add .
git commit -m "Add my feature"
# Make another commit
echo "More details" >> my-feature.md
git add .
git commit -m "Expand feature documentation"
```
**Important:** Changes on your branch don't affect main! **Important:** Changes on your branch don't affect main!
```bash 1. Change back to the `main` branch `git switch main`
# Switch to main 2. Check the changes you committed before. You'll notice that they're gone!
git switch main 3. Now edit a file or create a new file (perhaps GUIDE.md, content of the file doesn't matter) and add it and commit it on your `main` branch (hint: `git add .`, `git commit -m`)
- This way we create diverging branches. The `main` branch has changes as well as your new `my-feature` branch.
# Notice your my-feature.md doesn't exist here - Run `git log --oneline --graph --all` to see how the tree is looking
ls 4. Switch back to your branch `git switch my-feature`
5. The changes from the `main` branch are now gone. Check the changes you committed before. You'll notice they're back!
# Switch back to your branch
git switch my-feature
# Now it exists again!
ls
```
### Part 4: Merge Your Branch ### Part 4: Merge Your Branch
Bring your work into main: Bring your work into main:
```bash 1. Go back to the main branch `git switch main`
# Switch to the branch you want to merge INTO 2. Run a `git log --oneline --graph --all` to see that the `HEAD` is on your `main` branch
git switch main 3. It might be wise to first ensure that we're using Visual Studio Code to handle merge messages. If you haven't already set `git config --global core.editor "code --wait"` this sets Visual Studio Code to be the default editor for anything Git related.
4. Let's merge the recently created branch `git merge my-feature`
# Merge your feature branch 5. Visual Studio Code should open with a commit message. In order to solidify the commit simply close the window. That tells Git that the commit message has been written and the change should be committed.
git merge my-feature 6. Now run `git log --oneline --graph --all` and see your changes merge into the `main` branch!
7. Now let's clean up a bit, run `git branch -d my-feature` to remove the recently merged branch.
# View the result - If you hadn't merged the branch first this command would fail as Git will warn you that you have changes not merged into the `main` branch
git log --oneline --graph --all
```
You should see your feature branch merged into main! You should see your feature branch merged into main!
@@ -143,7 +117,7 @@ You should see your feature branch merged into main!
Create additional branches to practice: Create additional branches to practice:
```bash ```pwsh
# Create another feature # Create another feature
git switch -c another-feature git switch -c another-feature
@@ -156,7 +130,7 @@ git merge another-feature
``` ```
**Verify your work:** **Verify your work:**
```bash ```pwsh
# From the module directory (not inside challenge/) # From the module directory (not inside challenge/)
.\verify.ps1 .\verify.ps1
``` ```
@@ -182,7 +156,7 @@ my-feature: D---E
`HEAD` points to your current branch. It's Git's way of saying "you are here." `HEAD` points to your current branch. It's Git's way of saying "you are here."
```bash ```pwsh
# HEAD points to main # HEAD points to main
git switch main git switch main
@@ -226,7 +200,7 @@ If main hasn't changed, Git just moves the pointer forward. No merge commit need
### Branching ### Branching
```bash ```pwsh
# List all branches (* shows current branch) # List all branches (* shows current branch)
git branch git branch
@@ -251,7 +225,7 @@ git branch -D feature-name
### Merging ### Merging
```bash ```pwsh
# Merge a branch into your current branch # Merge a branch into your current branch
git merge branch-name git merge branch-name
@@ -264,7 +238,7 @@ git merge --no-ff branch-name
### Viewing History ### Viewing History
```bash ```pwsh
# Visual branch graph # Visual branch graph
git log --oneline --graph --all git log --oneline --graph --all
@@ -285,7 +259,7 @@ git branch --no-merged main
### Creating a Feature ### Creating a Feature
```bash ```pwsh
# Start from main # Start from main
git switch main git switch main
@@ -305,7 +279,7 @@ git commit -m "Improve awesome feature"
### Merging a Feature ### Merging a Feature
```bash ```pwsh
# Switch to main # Switch to main
git switch main git switch main
@@ -318,7 +292,7 @@ git branch -d feature-awesome
### Keeping Main Updated While Working ### Keeping Main Updated While Working
```bash ```pwsh
# You're on feature-awesome # You're on feature-awesome
git switch feature-awesome git switch feature-awesome
@@ -333,47 +307,9 @@ git merge main
git pull origin main git pull origin main
``` ```
## Troubleshooting
### "I'm on the wrong branch!"
```bash
# Switch to the correct branch
git switch correct-branch
# Check current branch anytime
git branch
```
### "I made commits on the wrong branch!"
Don't panic! You can move commits to another branch:
```bash
# Create the correct branch from current state
git branch correct-branch
# Switch to wrong branch and remove the commits
git switch wrong-branch
git reset --hard HEAD~2 # Remove last 2 commits (adjust number)
# Switch to correct branch - commits are there!
git switch correct-branch
```
### "The merge created unexpected results!"
```bash
# Undo the merge
git merge --abort
# Or if already committed:
git reset --hard HEAD~1
```
### "I want to see what changed in a merge!" ### "I want to see what changed in a merge!"
```bash ```pwsh
# Show the merge commit # Show the merge commit
git show <merge-commit-hash> git show <merge-commit-hash>
@@ -384,35 +320,30 @@ git diff main..feature-branch
## Tips for Success ## Tips for Success
💡 **Branch often** - Branches are cheap! Create one for each feature or experiment. 💡 **Branch often** - Branches are cheap! Create one for each feature or experiment.
💡 **Commit before switching** - Always commit (or stash) changes before switching branches. 💡 **Commit before switching** - Always commit (or stash) changes before switching branches.
💡 **Keep branches focused** - One feature per branch makes merging easier. 💡 **Keep branches focused** - One feature per branch makes merging easier.
💡 **Delete merged branches** - Clean up with `git branch -d branch-name` after merging. 💡 **Delete merged branches** - Clean up with `git branch -d branch-name` after merging.
💡 **Use descriptive names** - `feature-login` is better than `stuff` or `branch1`. 💡 **Use descriptive names** - `feature-login` is better than `stuff` or `branch1`.
💡 **Visualize often** - Run `git log --oneline --graph --all` to understand your history. 💡 **Visualize often** - Run `git log --oneline --graph --all` to understand your history.
## What You've Learned ## What You've Learned
After completing this module, you understand: After completing this module, you understand:
- Branches create independent lines of development - Branches create independent lines of development
- `git switch -c` creates a new branch - `git switch -c` creates a new branch
- Changes in one branch don't affect others - Changes in one branch don't affect others
- `git merge` combines branches - `git merge` combines branches
- Merge commits have two parent commits - Merge commits have two parent commits
- `git log --graph` visualizes branch history - `git log --graph` visualizes branch history
- Branches are pointers, not copies of files - Branches are pointers, not copies of files
## Next Steps ## Next Steps
Ready to continue? The next module covers **merge conflicts** - what happens when Git can't automatically merge changes. Ready to continue? The next module covers **merge conflicts** - what happens when Git can't automatically merge changes.
To start over: To start over:
```bash ```pwsh
.\reset.ps1 .\reset.ps1
.\setup.ps1 .\setup.ps1
``` ```

Submodule 01-essentials/03-branching-and-merging/challenge deleted from 8ae52cc25c

View File

@@ -31,6 +31,19 @@ git init | Out-Null
git config user.name "Workshop Student" git config user.name "Workshop Student"
git config user.email "student@example.com" git config user.email "student@example.com"
# Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after that
$mainBranch = git branch --show-current
if (-not $mainBranch) {
# Fallback: Get default branch name from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use "main"
$mainBranch = "main"
}
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# ============================================================================ # ============================================================================
# Create a realistic project history with multiple merged branches # Create a realistic project history with multiple merged branches
# ============================================================================ # ============================================================================
@@ -96,8 +109,8 @@ Set-Content -Path "login.py" -Value $loginContent
git add . git add .
git commit -m "Add password validation" | Out-Null git commit -m "Add password validation" | Out-Null
# Switch back to main and make more commits # Switch back to main branch
git switch main | Out-Null git switch $mainBranch | Out-Null
$appContent = @" $appContent = @"
# app.py - Application entry point # app.py - Application entry point
@@ -116,8 +129,8 @@ Set-Content -Path "app.py" -Value $appContent
git add . git add .
git commit -m "Add app.py entry point" | Out-Null git commit -m "Add app.py entry point" | Out-Null
# Merge feature-login into main # Merge feature-login into $mainBranch
Write-Host "Merging feature-login into main..." -ForegroundColor Green Write-Host "Merging feature-login into $mainBranch..." -ForegroundColor Green
git merge feature-login --no-edit | Out-Null git merge feature-login --no-edit | Out-Null
# ============================================================================ # ============================================================================
@@ -163,8 +176,8 @@ Set-Content -Path "api.py" -Value $apiContent
git add . git add .
git commit -m "Add delete endpoint to API" | Out-Null git commit -m "Add delete endpoint to API" | Out-Null
# Switch back to main and add documentation # Switch back to main branch and add documentation
git switch main | Out-Null git switch $mainBranch | Out-Null
$readmeContent = @" $readmeContent = @"
# My Application # My Application
@@ -184,8 +197,8 @@ Set-Content -Path "README.md" -Value $readmeContent
git add . git add .
git commit -m "Update README with setup instructions" | Out-Null git commit -m "Update README with setup instructions" | Out-Null
# Merge feature-api into main # Merge feature-api into $mainBranch
Write-Host "Merging feature-api into main..." -ForegroundColor Green Write-Host "Merging feature-api into $mainBranch..." -ForegroundColor Green
git merge feature-api --no-edit | Out-Null git merge feature-api --no-edit | Out-Null
# ============================================================================ # ============================================================================
@@ -241,9 +254,23 @@ Set-Content -Path "database.py" -Value $dbContent
git add . git add .
git commit -m "Add disconnect method" | Out-Null git commit -m "Add disconnect method" | Out-Null
# Switch to main and merge # Switch to main branch and add another commit (to create divergent history)
git switch main | Out-Null git switch $mainBranch | Out-Null
Write-Host "Merging feature-database into main..." -ForegroundColor Green
$configContent = @"
{
"app": {
"port": 3000,
"debug": false
}
}
"@
Set-Content -Path "config.json" -Value $configContent
git add .
git commit -m "Add configuration file" | Out-Null
# Merge feature-database (will be three-way merge since main diverged)
Write-Host "Merging feature-database into $mainBranch..." -ForegroundColor Green
git merge feature-database --no-edit | Out-Null git merge feature-database --no-edit | Out-Null
# Final update on main # Final update on main
@@ -278,7 +305,7 @@ Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
Write-Host "`nThe repository contains a realistic project history:" -ForegroundColor Yellow Write-Host "`nThe repository contains a realistic project history:" -ForegroundColor Yellow
Write-Host " - Multiple feature branches (login, api, database)" -ForegroundColor White Write-Host " - Multiple feature branches (login, api, database)" -ForegroundColor White
Write-Host " - All branches have been merged into main" -ForegroundColor White Write-Host " - All branches have been merged into $mainBranch" -ForegroundColor White
Write-Host " - View the history: git log --oneline --graph --all" -ForegroundColor White Write-Host " - View the history: git log --oneline --graph --all" -ForegroundColor White
Write-Host "`nNext steps:" -ForegroundColor Cyan Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White

View File

@@ -59,9 +59,33 @@ if (-not (Test-Path ".git")) {
Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundColor Cyan Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundColor Cyan
# ============================================================================ # ============================================================================
# Count initial setup commits (should be 13 commits from setup) # Detect the main branch name (could be main, master, etc.)
# ============================================================================ # ============================================================================
$initialCommitCount = 13 # Try to get the default branch from remote origin first
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
if (-not $mainBranch) {
# Fallback: try to detect from local branches
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
# Get the default branch from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use the first branch
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# ============================================================================
# Count initial setup commits (should be 15 commits from setup)
# ============================================================================
$initialCommitCount = 15
# ============================================================================ # ============================================================================
# Check for new commits beyond setup # Check for new commits beyond setup
@@ -83,7 +107,7 @@ if ($totalCommits -gt $initialCommitCount) {
# Check for branches (excluding the example branches) # Check for branches (excluding the example branches)
# ============================================================================ # ============================================================================
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') } $allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
$exampleBranches = @('main', 'feature-login', 'feature-api', 'feature-database') $exampleBranches = @($mainBranch, 'feature-login', 'feature-api', 'feature-database')
$studentBranches = $allBranches | Where-Object { $_ -notin $exampleBranches } $studentBranches = $allBranches | Where-Object { $_ -notin $exampleBranches }
if ($studentBranches.Count -gt 0) { if ($studentBranches.Count -gt 0) {
@@ -115,11 +139,11 @@ if ($totalMerges -gt $setupMerges) {
# Check current branch # Check current branch
# ============================================================================ # ============================================================================
$currentBranch = git branch --show-current 2>$null $currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq "main") { if ($currentBranch -eq $mainBranch) {
Write-Pass "Currently on main branch" Write-Pass "Currently on $mainBranch branch"
} else { } else {
Write-Info "Currently on '$currentBranch' branch" Write-Info "Currently on '$currentBranch' branch"
Write-Hint "Typically you merge feature branches INTO main" Write-Hint "Typically you merge feature branches INTO $mainBranch"
} }
Pop-Location Pop-Location
@@ -145,7 +169,7 @@ if ($script:allChecksPassed) {
Write-Host "Quick guide:" -ForegroundColor Cyan Write-Host "Quick guide:" -ForegroundColor Cyan
Write-Host " 1. Create a branch: git switch -c my-feature" -ForegroundColor White 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 " 2. Make changes and commit them" -ForegroundColor White
Write-Host " 3. Switch to main: git switch main" -ForegroundColor White Write-Host " 3. Switch to $mainBranch : git switch $mainBranch" -ForegroundColor White
Write-Host " 4. Merge your branch: git merge my-feature" -ForegroundColor White Write-Host " 4. Merge your branch: git merge my-feature" -ForegroundColor White
Write-Host " 5. Run this verify script again" -ForegroundColor White Write-Host " 5. Run this verify script again" -ForegroundColor White
Write-Host "" Write-Host ""

Submodule 01-essentials/04-merge-conflict/challenge deleted from 676b08e650

View File

@@ -30,6 +30,19 @@ git init | Out-Null
git config user.name "Workshop Student" git config user.name "Workshop Student"
git config user.email "student@example.com" git config user.email "student@example.com"
# Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after that
$mainBranch = git branch --show-current
if (-not $mainBranch) {
# Fallback: Get default branch name from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use "main"
$mainBranch = "main"
}
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# ============================================================================ # ============================================================================
# Create base project # Create base project
# ============================================================================ # ============================================================================
@@ -87,7 +100,7 @@ git commit -m "Add timeout configuration" | Out-Null
# Branch 2: add-debug (adds debug setting - CONFLICTS with timeout!) # Branch 2: add-debug (adds debug setting - CONFLICTS with timeout!)
# ============================================================================ # ============================================================================
Write-Host "Creating add-debug branch..." -ForegroundColor Cyan Write-Host "Creating add-debug branch..." -ForegroundColor Cyan
git switch main | Out-Null git switch $mainBranch | Out-Null
git switch -c add-debug | Out-Null git switch -c add-debug | Out-Null
$debugConfig = @" $debugConfig = @"
@@ -104,8 +117,8 @@ Set-Content -Path "config.json" -Value $debugConfig
git add . git add .
git commit -m "Add debug mode configuration" | Out-Null git commit -m "Add debug mode configuration" | Out-Null
# Switch back to main # Switch back to main branch
git switch main | Out-Null git switch $mainBranch | Out-Null
# Return to module directory # Return to module directory
Set-Location .. Set-Location ..
@@ -113,7 +126,7 @@ Set-Location ..
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
Write-Host "`nThe repository contains:" -ForegroundColor Yellow Write-Host "`nThe repository contains:" -ForegroundColor Yellow
Write-Host " - main branch: base configuration" -ForegroundColor White Write-Host " - $mainBranch branch: base configuration" -ForegroundColor White
Write-Host " - add-timeout branch: adds timeout setting" -ForegroundColor White Write-Host " - add-timeout branch: adds timeout setting" -ForegroundColor White
Write-Host " - add-debug branch: adds debug 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 "`nBoth branches modify the same part of config.json!" -ForegroundColor Red

View File

@@ -59,15 +59,39 @@ if (-not (Test-Path ".git")) {
Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cyan Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cyan
# ============================================================================
# Detect the main branch name (could be main, master, etc.)
# ============================================================================
# Try to get the default branch from remote origin first
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
if (-not $mainBranch) {
# Fallback: try to detect from local branches
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
if ($allBranches -contains "main") {
$mainBranch = "main"
} elseif ($allBranches -contains "master") {
$mainBranch = "master"
} else {
# Get the default branch from git config
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) {
# Ultimate fallback: use the first branch
$mainBranch = $allBranches | Select-Object -First 1
if (-not $mainBranch) { $mainBranch = "main" }
}
}
}
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
# ============================================================================ # ============================================================================
# Check current branch # Check current branch
# ============================================================================ # ============================================================================
$currentBranch = git branch --show-current 2>$null $currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq "main") { if ($currentBranch -eq $mainBranch) {
Write-Pass "Currently on main branch" Write-Pass "Currently on $mainBranch branch"
} else { } else {
Write-Fail "Should be on main branch (currently on: $currentBranch)" Write-Fail "Should be on $mainBranch branch (currently on: $currentBranch)"
Write-Hint "Switch to main with: git switch main" Write-Hint "Switch to $mainBranch with: git switch $mainBranch"
} }
# ============================================================================ # ============================================================================
@@ -196,7 +220,7 @@ if ($script:allChecksPassed) {
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
Write-Host "" Write-Host ""
Write-Host "Quick guide:" -ForegroundColor Cyan Write-Host "Quick guide:" -ForegroundColor Cyan
Write-Host " 1. Make sure you're on main: git switch main" -ForegroundColor White Write-Host " 1. Make sure you're on $mainBranch : git switch $mainBranch" -ForegroundColor White
Write-Host " 2. Merge first branch: git merge add-timeout" -ForegroundColor White Write-Host " 2. Merge first branch: git merge add-timeout" -ForegroundColor White
Write-Host " 3. Merge second branch: git merge add-debug" -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 " 4. Resolve conflict: edit config.json, remove markers, keep both settings" -ForegroundColor White

View File

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

View File

@@ -26,7 +26,8 @@ git init | Out-Null
git config user.name "Workshop User" | Out-Null git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null git config user.email "user@workshop.local" | Out-Null
# Create initial commits on main branch # Detect the default branch name (could be main, master, etc.)
# First commit creates the branch, so we detect it after the first commit below
$app = @" $app = @"
class App: class App:
def __init__(self): def __init__(self):
@@ -40,6 +41,14 @@ Set-Content -Path "app.py" -Value $app
git add app.py git add app.py
git commit -m "Initial app implementation" | Out-Null git commit -m "Initial app implementation" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
$readme = @" $readme = @"
# Application # Application
@@ -119,40 +128,29 @@ git add app.py
git commit -m "Add beta features framework" | Out-Null git commit -m "Add beta features framework" | Out-Null
# Commit 4: Performance bug fix (SHOULD be cherry-picked) # Commit 4: Performance bug fix (SHOULD be cherry-picked)
$appWithPerformance = @" # This commit adds caching to the existing file to fix performance
class App: # It should apply cleanly to main since it doesn't depend on experimental features
$performanceCode = @"
class DataCache:
def __init__(self): def __init__(self):
self.version = '1.0.0'
self.experimental_mode = False
self.beta_features = []
self.cache = {} self.cache = {}
def start(self): def get(self, key):
print('App started')
if self.experimental_mode:
self.enable_experimental_features()
def enable_experimental_features(self):
print('Experimental features enabled')
def add_beta_feature(self, feature):
self.beta_features.append(feature)
def get_data(self, key):
# Use cache to improve performance # Use cache to improve performance
if key in self.cache: if key in self.cache:
return self.cache[key] return self.cache[key]
data = self.fetch_data(key) return None
self.cache[key] = data
return data
def fetch_data(self, key): def set(self, key, value):
# Simulate data fetching self.cache[key] = value
return {'key': key, 'value': 'data'}
def clear(self):
self.cache.clear()
"@ "@
Set-Content -Path "app.py" -Value $appWithPerformance Set-Content -Path "cache.py" -Value $performanceCode
git add app.py git add cache.py
git commit -m "Fix performance issue with data caching" | Out-Null git commit -m "Fix performance issue with data caching" | Out-Null
# Return to module directory # Return to module directory
@@ -164,12 +162,13 @@ Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan
Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow
Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
Write-Host "`nYour task:" -ForegroundColor Yellow Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White
Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White
Write-Host "4. Switch to main branch: git checkout main" -ForegroundColor White Write-Host "4. Switch to $mainBranch branch: git checkout $mainBranch" -ForegroundColor White
Write-Host "5. Cherry-pick ONLY the bug fix commits to main" -ForegroundColor White Write-Host "5. Cherry-pick ONLY the bug fix commits to $mainBranch" -ForegroundColor White
Write-Host "6. Do NOT bring the experimental features to main" -ForegroundColor White Write-Host "6. Do NOT bring the experimental features to $mainBranch" -ForegroundColor White
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

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

View File

@@ -1,8 +1,8 @@
# Module 05: Git Revert - Safe Undoing # Module 06: Git Revert - Safe Undoing
## About This Module ## About This Module
Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history. Welcome to Module 06, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
**Why revert is important:** **Why revert is important:**
- ✅ Safe for shared/pushed commits - ✅ Safe for shared/pushed commits
@@ -17,31 +17,28 @@ Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo c
By completing this module, you will: By completing this module, you will:
1. Revert regular commits safely while preserving surrounding changes 1. Revert commits safely while preserving surrounding changes
2. Revert merge commits using the `-m` flag 2. Understand how revert creates new commits instead of erasing history
3. Understand merge commit parent numbering 3. Revert multiple commits at once
4. Handle the re-merge problem that occurs after reverting merges 4. Know when to use revert vs. other undo strategies
5. Revert multiple commits at once
6. Know when to use revert vs. other undo strategies
## Prerequisites ## Prerequisites
Before starting this module, you should be comfortable with: Before starting this module, you should be comfortable with:
- Creating commits (`git commit`) - Creating commits (`git commit`)
- Viewing commit history (`git log`) - Viewing commit history (`git log`)
- Understanding branches and merging (Module 03) - Understanding branches (Module 03)
## Setup ## Setup
Run the setup script to create the challenge environment: Run the setup script to create the challenge environment:
```powershell ```pwsh
./setup.ps1 .\setup.ps1
``` ```
This creates a `challenge/` directory with three branches demonstrating different revert scenarios: This creates a `challenge/` directory with two branches demonstrating different revert scenarios:
- `regular-revert` - Basic commit reversion - `regular-revert` - Basic commit reversion
- `merge-revert` - Merge commit reversion
- `multi-revert` - Multiple commit reversion - `multi-revert` - Multiple commit reversion
## Challenge 1: Reverting a Regular Commit ## Challenge 1: Reverting a Regular Commit
@@ -52,44 +49,57 @@ You're working on a calculator application. A developer added a `divide` functio
### Your Task ### Your Task
1. Navigate to the challenge directory: 1. **Navigate to the challenge directory:**
```bash ```pwsh
cd challenge cd challenge
``` ```
2. You should be on the `regular-revert` branch. View the commit history: 2. **Check which branch you're on** (you should be on `regular-revert`):
```bash ```pwsh
git branch
```
The `*` should be next to `regular-revert`.
3. **View the commit history:**
```pwsh
git log --oneline git log --oneline
``` ```
3. Find the commit with the broken divide function (message: "Add broken divide function - needs to be reverted!") 4. **Find the commit with message:** "Add broken divide function - needs to be reverted!"
- Note the commit hash (the 7-character code at the start, like `a1b2c3d`)
- Write it down or copy it
4. Revert that specific commit: 5. **Revert that specific commit** (replace `<commit-hash>` with the actual hash):
```bash ```pwsh
git revert <commit-hash> git revert <commit-hash>
``` ```
5. Git will open your editor for the revert commit message. The default message is fine—save and close. 6. **Visual Studio Code will open** with the revert commit message:
- The default message is fine (it says "Revert 'Add broken divide function...'")
- Close the editor window to accept the commit message
- Git will create the revert commit
### What to Observe ### What to Observe
After reverting, check: After reverting, check your work:
```bash ```pwsh
# View the new revert commit # View the new revert commit in history
git log --oneline git log --oneline
# Check that divide function is gone # Check that divide.py file is gone (reverted)
cat calculator.py | grep "def divide" # Should return nothing ls
# You should see calculator.py but NOT divide.py
# Check that modulo function still exists (it came after the bad commit) # Check that modulo function still exists in calculator.py (it came after the bad commit)
cat calculator.py | grep "def modulo" # Should find it cat calculator.py
# You should see def modulo
# Check that multiply function still exists (it came before the bad commit) # Check that multiply function still exists (it came before the bad commit)
cat calculator.py | grep "def multiply" # Should find it # (You already see it when you cat the file above)
``` ```
**Key insight:** Revert creates a new commit that undoes the changes from the target commit, but leaves all other commits intact. **Key insight:** Revert creates a NEW commit that undoes the changes from the target commit, but leaves all other commits intact.
### Understanding the Timeline ### Understanding the Timeline
@@ -107,134 +117,7 @@ main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → rev
The revert commit adds a new point in history that undoes the divide changes. The revert commit adds a new point in history that undoes the divide changes.
## Challenge 2: Reverting a Merge Commit ## Challenge 2: Reverting Multiple Commits
### Scenario
Your team merged a `feature-auth` branch that added authentication functionality. After deployment, you discovered the authentication system has critical security issues. You need to revert the entire merge while the security team redesigns the feature.
**This is different from reverting a regular commit!** Merge commits have **two parents**, so you must tell Git which parent to keep.
### Understanding Merge Commit Parents
When you merge a feature branch into main:
```
feature-auth (parent 2)
C---D
/ \
A---B-----M ← Merge commit (has TWO parents)
parent 1 (main)
```
The merge commit `M` has:
- **Parent 1**: The branch you merged INTO (main)
- **Parent 2**: The branch you merged FROM (feature-auth)
When reverting a merge, you must specify which parent to keep using the `-m` flag:
- `-m 1` means "keep parent 1" (main) - **Most common**
- `-m 2` means "keep parent 2" (feature-auth) - Rare
**In practice:** You almost always use `-m 1` to keep the main branch and undo the feature branch changes.
### Your Task
1. Switch to the merge-revert branch:
```bash
git switch merge-revert
```
2. View the commit history and find the merge commit:
```bash
git log --oneline --graph
```
Look for: "Merge feature-auth branch"
3. Revert the merge commit using `-m 1`:
```bash
git revert -m 1 <merge-commit-hash>
```
**Explanation:**
- `-m 1` tells Git to keep parent 1 (main branch)
- This undoes all changes from the feature-auth branch
- Creates a new "revert merge" commit
4. Save the default commit message and check the result:
```bash
# Verify auth.py is gone
ls auth.py # Should not exist
# Verify calculator.py no longer imports auth
cat calculator.py | grep "from auth" # Should return nothing
```
### What Happens Without -m?
If you try to revert a merge commit without the `-m` flag:
```bash
git revert <merge-commit-hash>
# Error: commit <hash> is a merge but no -m option was given
```
Git doesn't know which parent you want to keep, so it refuses to proceed.
### The Re-Merge Problem
**Important gotcha:** After reverting a merge, you **cannot simply re-merge** the same branch!
Here's why:
```
Initial merge:
A---B---M (merged feature-auth)
All changes from feature-auth are now in main
After revert:
A---B---M---R (reverted merge)
Changes removed, but Git remembers they were merged
Attempting to re-merge:
A---B---M---R---M2 (try to merge feature-auth again)
Git thinks: "I already merged these commits,
nothing new to add!" (Empty merge)
```
**Solutions if you need to re-merge:**
1. **Revert the revert** (recommended):
```bash
git revert <revert-commit-hash>
```
This brings back all the feature-auth changes.
2. **Cherry-pick new commits** from the feature branch:
```bash
git cherry-pick <new-commits>
```
3. **Merge with --no-ff** and resolve conflicts manually (advanced).
### When to Revert Merges
Revert merge commits when:
- ✅ Feature causes production issues
- ✅ Need to temporarily remove a feature
- ✅ Discovered critical bugs after merging
- ✅ Security issues require immediate rollback
Don't revert merges when:
- ❌ You just need to fix a small bug (fix it with a new commit instead)
- ❌ You plan to re-merge the same branch soon (use reset if local, or revert-the-revert later)
## Challenge 3: Reverting Multiple Commits
### Scenario ### Scenario
@@ -242,51 +125,55 @@ Two separate commits added broken mathematical functions (`square_root` and `log
### Your Task ### Your Task
1. Switch to the multi-revert branch: 1. **Switch to the multi-revert branch:**
```bash ```pwsh
git switch multi-revert git switch multi-revert
``` ```
2. View the commit history: 2. **View the commit history:**
```bash ```pwsh
git log --oneline git log --oneline
``` ```
Find the two commits: Find the two bad commits:
- "Add broken square_root - REVERT THIS!" - "Add broken square_root - REVERT THIS!"
- "Add broken logarithm - REVERT THIS TOO!" - "Add broken logarithm - REVERT THIS TOO!"
Note both commit hashes (write them down)
3. Revert both commits in one command: 3. **Revert both commits in one command** (replace with actual hashes):
```bash ```pwsh
git revert <commit-hash-1> <commit-hash-2> git revert <commit-hash-1> <commit-hash-2>
``` ```
**Important:** List commits from **oldest to newest** for cleanest history. **Important:** List commits from **oldest to newest** for cleanest history (square_root first, then logarithm).
Alternatively, revert them one at a time: **Alternatively**, revert them one at a time:
```bash ```pwsh
git revert <commit-hash-1> git revert <commit-hash-1>
git revert <commit-hash-2> git revert <commit-hash-2>
``` ```
4. Git will prompt for a commit message for each revert. Accept the defaults. 4. **Visual Studio Code will open TWICE** (once for each revert):
- Close the editor each time to accept the default commit message
- Git will create two revert commits
5. Verify the result: 5. **Verify the result:**
```bash ```pwsh
# Check that both bad functions are gone # View files - sqrt.py and logarithm.py should be gone
cat calculator.py | grep "def square_root" # Should return nothing ls
cat calculator.py | grep "def logarithm" # Should return nothing # You should see calculator.py but NOT sqrt.py or logarithm.py
# Check that good functions remain # Check that good functions remain in calculator.py
cat calculator.py | grep "def power" # Should find it cat calculator.py
cat calculator.py | grep "def absolute" # Should find it # You should see def power and def absolute
``` ```
### Multi-Revert Strategies ### Multi-Revert Strategies
**Reverting a range of commits:** **Reverting a range of commits:**
```bash ```pwsh
# Revert commits from A to B (inclusive) # Revert commits from A to B (inclusive)
git revert A^..B git revert A^..B
@@ -296,7 +183,7 @@ git revert HEAD~3..HEAD
**Reverting without auto-commit:** **Reverting without auto-commit:**
```bash ```pwsh
# Stage revert changes without committing # Stage revert changes without committing
git revert --no-commit <commit-hash> git revert --no-commit <commit-hash>
@@ -311,25 +198,29 @@ This is useful when reverting multiple commits and you want one combined revert
## Verification ## Verification
Verify your solutions by running the verification script: After completing both challenges, verify your solutions:
```bash ```pwsh
cd .. # Return to module directory cd .. # Return to module directory (if you're in challenge/)
./verify.ps1 .\verify.ps1
```
Or from inside the challenge directory:
```pwsh
..\verify.ps1
``` ```
The script checks that: The script checks that:
- ✅ Revert commits were created (not destructive deletion) - ✅ Revert commits were created (not destructive deletion)
- ✅ Bad code is removed - ✅ Bad code is removed
- ✅ Good code before and after is preserved - ✅ Good code before and after is preserved
- ✅ Merge commits still exist in history
- ✅ Proper use of `-m` flag for merge reverts
## Command Reference ## Command Reference
### Basic Revert ### Basic Revert
```bash ```pwsh
# Revert a specific commit # Revert a specific commit
git revert <commit-hash> git revert <commit-hash>
@@ -340,19 +231,22 @@ git revert HEAD
git revert HEAD~1 git revert HEAD~1
``` ```
### Merge Commit Revert ### Reverting Old Commits
```bash ```pwsh
# Revert a merge commit (keep parent 1) # Revert a specific commit from any point in history
git revert -m 1 <merge-commit-hash> git revert <commit-hash>
# Revert a merge commit (keep parent 2) - rare # Revert a commit from 5 commits ago
git revert -m 2 <merge-commit-hash> git revert HEAD~5
# View what a commit changed before reverting
git show <commit-hash>
``` ```
### Multiple Commits ### Multiple Commits
```bash ```pwsh
# Revert multiple specific commits # Revert multiple specific commits
git revert <hash1> <hash2> <hash3> git revert <hash1> <hash2> <hash3>
@@ -365,7 +259,7 @@ git revert HEAD~3..HEAD
### Revert Options ### Revert Options
```bash ```pwsh
# Revert but don't commit automatically # Revert but don't commit automatically
git revert --no-commit <commit-hash> git revert --no-commit <commit-hash>
@@ -399,10 +293,10 @@ Use `git revert` when:
Consider alternatives when: Consider alternatives when:
- ❌ **Commits are still local** - Use `git reset` instead (Module 06) - ❌ **Commits are still local** - Use `git reset` instead (advanced module)
- ❌ **Just want to edit a commit** - Use `git commit --amend` - ❌ **Just want to edit a commit** - Use `git commit --amend`
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup - ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup, but more dangerous, stick to revert if in doubt
- ❌ **Need to combine commits** - Use interactive rebase - ❌ **Need to combine commits** - Use interactive rebase IF nothing has been pushed to cloud
- ❌ **Reverting creates complex conflicts** - Might need manual fix forward - ❌ **Reverting creates complex conflicts** - Might need manual fix forward
## Revert vs. Reset vs. Rebase ## Revert vs. Reset vs. Rebase
@@ -419,7 +313,7 @@ Consider alternatives when:
Sometimes reverting causes conflicts if subsequent changes touched the same code: Sometimes reverting causes conflicts if subsequent changes touched the same code:
```bash ```pwsh
# Start revert # Start revert
git revert <commit-hash> git revert <commit-hash>
@@ -432,59 +326,36 @@ git revert <commit-hash>
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers) 1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
2. Stage resolved files: 2. Stage resolved files:
```bash ```pwsh
git add <resolved-files> git add <resolved-files>
``` ```
3. Continue the revert: 3. Continue the revert:
```bash ```pwsh
git revert --continue git revert --continue
``` ```
Or abort if you change your mind: Or abort if you change your mind:
```bash ```pwsh
git revert --abort git revert --abort
``` ```
## Common Mistakes ## Common Mistakes
### 1. Forgetting -m for Merge Commits ### 1. Using Reset on Pushed Commits
```bash ```pwsh
# ❌ Wrong - will fail # ❌ NEVER do this with pushed commits. Or at least try your best to avoid it.
git revert <merge-commit>
# ✅ Correct
git revert -m 1 <merge-commit>
```
### 2. Trying to Re-Merge After Revert
```bash
# After reverting a merge:
git revert -m 1 <merge-commit>
# ❌ This won't work as expected
git merge feature-branch # Empty merge!
# ✅ Do this instead
git revert <the-revert-commit> # Revert the revert
```
### 3. Using Reset on Pushed Commits
```bash
# ❌ NEVER do this with pushed commits
git reset --hard HEAD~3 git reset --hard HEAD~3
# ✅ Do this instead # ✅ Do this instead
git revert HEAD~3..HEAD git revert HEAD~3..HEAD
``` ```
### 4. Reverting Commits in Wrong Order ### 2. Reverting Commits in Wrong Order
When reverting multiple related commits, revert from newest to oldest: When reverting multiple related commits, revert from newest to oldest:
```bash ```pwsh
# If you have: A → B → C (and C depends on B) # If you have: A → B → C (and C depends on B)
# ✅ Correct order # ✅ Correct order
@@ -499,7 +370,7 @@ git revert C
## Best Practices ## Best Practices
1. **Write clear revert messages:** 1. **Write clear revert messages:**
```bash ```pwsh
git revert <hash> -m "Revert authentication - security issue #1234" git revert <hash> -m "Revert authentication - security issue #1234"
``` ```
@@ -527,112 +398,3 @@ git revert C
- Revert the minimum necessary - Revert the minimum necessary
- Don't bundle multiple unrelated reverts - Don't bundle multiple unrelated reverts
- One problem = one revert commit - One problem = one revert commit
## Troubleshooting
### "Commit is a merge but no -m option was given"
**Problem:** Trying to revert a merge commit without `-m`.
**Solution:**
```bash
git revert -m 1 <merge-commit-hash>
```
### "Empty Revert / No Changes"
**Problem:** Revert doesn't seem to do anything.
**Possible causes:**
- Commit was already reverted
- Subsequent commits already undid the changes
- Wrong commit hash
**Solution:**
```bash
# Check what the commit actually changed
git show <commit-hash>
# Check if already reverted
git log --grep="Revert"
```
### "Conflicts During Revert"
**Problem:** Revert causes merge conflicts.
**Why:** Subsequent commits modified the same code.
**Solution:**
1. Manually resolve conflicts in affected files
2. `git add <resolved-files>`
3. `git revert --continue`
Or consider fixing forward with a new commit instead of reverting.
### "Can't Re-Merge After Reverting Merge"
**Problem:** After reverting a merge, re-merging the branch brings no changes.
**Solution:** Revert the revert commit:
```bash
# Find the revert commit
git log --oneline
# Revert the revert (brings changes back)
git revert <revert-commit-hash>
```
## Advanced: Revert Internals
Understanding what revert does under the hood:
```bash
# Revert creates a new commit with inverse changes
git revert <commit-hash>
# This is equivalent to:
git diff <commit-hash>^..<commit-hash> > changes.patch
patch -R < changes.patch # Apply in reverse
git add .
git commit -m "Revert '<original message>'"
```
**Key insight:** Revert computes the diff of the target commit, inverts it, and applies it as a new commit.
## Going Further
Now that you understand revert, you're ready for:
- **Module 06: Git Reset** - Learn the dangerous but powerful local history rewriting
- **Module 07: Git Stash** - Temporarily set aside uncommitted changes
- **Module 08: Multiplayer Git** - Collaborate with advanced workflows
## Summary
You've learned:
- ✅ `git revert` creates new commits that undo previous changes
- ✅ Revert is safe for shared/pushed commits
- ✅ Merge commits require `-m 1` or `-m 2` flag
- ✅ Parent 1 = branch merged into, Parent 2 = branch merged from
- ✅ Can't simply re-merge after reverting a merge
- ✅ Multiple commits can be reverted in one command
- ✅ Revert preserves complete history for audit trails
**The Golden Rule of Revert:** Use revert for any commit that might be shared with others.
## Next Steps
1. Complete all three challenge scenarios
2. Run `./verify.ps1` to check your solutions
3. Experiment with reverting different commits
4. Move on to Module 06: Git Reset (dangerous but powerful!)
---
**Need Help?**
- Review the command reference above
- Check the troubleshooting section
- Re-run `./setup.ps1` to start fresh
- Practice reverting in different orders to understand the behavior

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env pwsh #!/usr/bin/env pwsh
<# <#
.SYNOPSIS .SYNOPSIS
Resets the Module 05 challenge environment to start fresh. Resets the Module 06 challenge environment to start fresh.
.DESCRIPTION .DESCRIPTION
This script removes the challenge directory and re-runs setup.ps1 This script removes the challenge directory and re-runs setup.ps1
to create a fresh challenge environment. to create a fresh challenge environment.
#> #>
Write-Host "`n=== Resetting Module 05: Git Revert Challenge ===" -ForegroundColor Cyan Write-Host "`n=== Resetting Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
# Check if challenge directory exists # Check if challenge directory exists
if (Test-Path "challenge") { if (Test-Path "challenge") {

View File

@@ -1,17 +1,16 @@
#!/usr/bin/env pwsh #!/usr/bin/env pwsh
<# <#
.SYNOPSIS .SYNOPSIS
Sets up the Module 05 challenge environment for learning git revert. Sets up the Module 06 challenge environment for learning git revert.
.DESCRIPTION .DESCRIPTION
This script creates a challenge directory with three branches demonstrating This script creates a challenge directory with two branches demonstrating
different revert scenarios: different revert scenarios:
- regular-revert: Basic revert of a single bad commit - regular-revert: Basic revert of a single bad commit
- merge-revert: Reverting a merge commit with -m flag
- multi-revert: Reverting multiple commits at once - multi-revert: Reverting multiple commits at once
#> #>
Write-Host "`n=== Setting up Module 05: Git Revert Challenge ===" -ForegroundColor Cyan Write-Host "`n=== Setting up Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists # Remove existing challenge directory if it exists
if (Test-Path "challenge") { if (Test-Path "challenge") {
@@ -32,6 +31,9 @@ git init | Out-Null
git config user.name "Workshop Student" git config user.name "Workshop Student"
git config user.email "student@example.com" git config user.email "student@example.com"
# Detect the default branch name after first commit (created below)
# Will be detected after the initial commit in SCENARIO 1
# ============================================================================ # ============================================================================
# SCENARIO 1: Regular Revert (Basic) # SCENARIO 1: Regular Revert (Basic)
# ============================================================================ # ============================================================================
@@ -53,176 +55,60 @@ Set-Content -Path "calculator.py" -Value $calcContent
git add . git add .
git commit -m "Initial calculator implementation" | Out-Null git commit -m "Initial calculator implementation" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
# Create regular-revert branch # Create regular-revert branch
git switch -c regular-revert | Out-Null git switch -c regular-revert | Out-Null
# Good commit: Add multiply # Good commit: Add multiply using append
$calcContent = @" $multiplyFunc = @"
# calculator.py - Simple calculator
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def multiply(a, b): def multiply(a, b):
"""Multiply two numbers.""" """Multiply two numbers."""
return a * b return a * b
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Add-Content -Path "calculator.py" -Value $multiplyFunc
git add . git add .
git commit -m "Add multiply function" | Out-Null git commit -m "Add multiply function" | Out-Null
# BAD commit: Add broken divide function # BAD commit: Add broken divide function using separate file
$calcContent = @" $divideContent = @"
# calculator.py - Simple calculator # divide.py - Division functionality
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
def divide(a, b): def divide(a, b):
"""Divide a by b - BROKEN: doesn't handle division by zero!""" """Divide a by b - BROKEN: doesn't handle division by zero!"""
return a / b # This will crash if b is 0! return a / b # This will crash if b is 0!
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Set-Content -Path "divide.py" -Value $divideContent
git add . git add .
git commit -m "Add broken divide function - needs to be reverted!" | Out-Null git commit -m "Add broken divide function - needs to be reverted!" | Out-Null
# Good commit: Add modulo (after bad commit) # Good commit: Add modulo (after bad commit) using append
$calcContent = @" $moduloFunc = @"
# calculator.py - Simple calculator
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def multiply(a, b):
"""Multiply two numbers."""
return a * b
def divide(a, b):
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
return a / b # This will crash if b is 0!
def modulo(a, b): def modulo(a, b):
"""Return remainder of a divided by b.""" """Return remainder of a divided by b."""
return a % b return a % b
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Add-Content -Path "calculator.py" -Value $moduloFunc
git add . git add .
git commit -m "Add modulo function" | Out-Null git commit -m "Add modulo function" | Out-Null
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
# ============================================================================ # ============================================================================
# SCENARIO 2: Merge Revert (Merge Commit with -m flag) # SCENARIO 2: Multi Revert (Multiple Bad Commits)
# ============================================================================ # ============================================================================
Write-Host "`nScenario 2: Creating merge-revert scenario..." -ForegroundColor Cyan Write-Host "`nScenario 2: Creating multi-revert branch..." -ForegroundColor Cyan
# Switch back to main # Switch back to main
git switch main | Out-Null git switch $mainBranch | Out-Null
# Create merge-revert branch
git switch -c merge-revert | Out-Null
# Create a feature branch to merge
git switch -c feature-auth | Out-Null
# Add auth functionality
$authContent = @"
# auth.py - Authentication module
def login(username, password):
\"\"\"Login user.\"\"\"
print(f"Logging in {username}...")
return True
def logout(username):
\"\"\"Logout user.\"\"\"
print(f"Logging out {username}...")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Add authentication module" | Out-Null
# Add password validation
$authContent = @"
# auth.py - Authentication module
def validate_password(password):
\"\"\"Validate password strength.\"\"\"
return len(password) >= 8
def login(username, password):
\"\"\"Login user.\"\"\"
if not validate_password(password):
print("Password too weak!")
return False
print(f"Logging in {username}...")
return True
def logout(username):
\"\"\"Logout user.\"\"\"
print(f"Logging out {username}...")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Add password validation" | Out-Null
# Integrate auth into calculator (part of the feature branch)
$calcContent = @"
# calculator.py - Simple calculator
from auth import login
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def secure_divide(a, b, username):
"""Secure divide - requires authentication."""
if login(username, "password123"):
return a / b
return None
"@
Set-Content -Path "calculator.py" -Value $calcContent
git add .
git commit -m "Integrate auth into calculator" | Out-Null
# Switch back to merge-revert and merge feature-auth
git switch merge-revert | Out-Null
git merge feature-auth --no-ff -m "Merge feature-auth branch" | Out-Null
Write-Host "[CREATED] merge-revert branch with merge commit to revert" -ForegroundColor Green
# ============================================================================
# SCENARIO 3: Multi Revert (Multiple Bad Commits)
# ============================================================================
Write-Host "`nScenario 3: Creating multi-revert branch..." -ForegroundColor Cyan
# Switch back to main
git switch main | Out-Null
# Create multi-revert branch # Create multi-revert branch
git switch -c multi-revert | Out-Null git switch -c multi-revert | Out-Null
@@ -243,109 +129,50 @@ Set-Content -Path "calculator.py" -Value $calcContent
git add . git add .
git commit -m "Reset to basic calculator" | Out-Null git commit -m "Reset to basic calculator" | Out-Null
# Good commit: Add power function # Good commit: Add power function using append
$calcContent = @" $powerFunc = @"
# calculator.py - Simple calculator
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def power(a, b): def power(a, b):
"""Raise a to the power of b.""" """Raise a to the power of b."""
return a ** b return a ** b
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Add-Content -Path "calculator.py" -Value $powerFunc
git add . git add .
git commit -m "Add power function" | Out-Null git commit -m "Add power function" | Out-Null
# BAD commit 1: Add broken square_root # BAD commit 1: Add broken square_root in separate file
$calcContent = @" $sqrtContent = @"
# calculator.py - Simple calculator # sqrt.py - Square root functionality
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def power(a, b):
"""Raise a to the power of b."""
return a ** b
def square_root(a): def square_root(a):
"""BROKEN: Returns wrong result for negative numbers!""" """BROKEN: Returns wrong result for negative numbers!"""
return a ** 0.5 # This returns NaN for negative numbers! return a ** 0.5 # This returns NaN for negative numbers!
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Set-Content -Path "sqrt.py" -Value $sqrtContent
git add . git add .
git commit -m "Add broken square_root - REVERT THIS!" | Out-Null git commit -m "Add broken square_root - REVERT THIS!" | Out-Null
# BAD commit 2: Add broken logarithm # BAD commit 2: Add broken logarithm in separate file
$calcContent = @" $logContent = @"
# calculator.py - Simple calculator # logarithm.py - Logarithm functionality
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def power(a, b):
"""Raise a to the power of b."""
return a ** b
def square_root(a):
"""BROKEN: Returns wrong result for negative numbers!"""
return a ** 0.5 # This returns NaN for negative numbers!
def logarithm(a): def logarithm(a):
"""BROKEN: Doesn't handle zero or negative numbers!""" """BROKEN: Doesn't handle zero or negative numbers!"""
import math import math
return math.log(a) # This crashes for a <= 0! return math.log(a) # This crashes for a <= 0!
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Set-Content -Path "logarithm.py" -Value $logContent
git add . git add .
git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null
# Good commit: Add absolute value (after bad commits) # Good commit: Add absolute value (after bad commits) using append
$calcContent = @" $absoluteFunc = @"
# calculator.py - Simple calculator
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def power(a, b):
"""Raise a to the power of b."""
return a ** b
def square_root(a):
"""BROKEN: Returns wrong result for negative numbers!"""
return a ** 0.5 # This returns NaN for negative numbers!
def logarithm(a):
"""BROKEN: Doesn't handle zero or negative numbers!"""
import math
return math.log(a) # This crashes for a <= 0!
def absolute(a): def absolute(a):
"""Return absolute value of a.""" """Return absolute value of a."""
return abs(a) return abs(a)
"@ "@
Set-Content -Path "calculator.py" -Value $calcContent Add-Content -Path "calculator.py" -Value $absoluteFunc
git add . git add .
git commit -m "Add absolute value function" | Out-Null git commit -m "Add absolute value function" | Out-Null
@@ -360,10 +187,9 @@ git switch regular-revert | Out-Null
Set-Location .. Set-Location ..
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nThree revert scenarios have been created:" -ForegroundColor Cyan Write-Host "`nTwo revert scenarios have been created:" -ForegroundColor Cyan
Write-Host " 1. regular-revert - Revert a single bad commit (basic)" -ForegroundColor White Write-Host " 1. regular-revert - Revert a single bad commit" -ForegroundColor White
Write-Host " 2. merge-revert - Revert a merge commit with -m flag" -ForegroundColor White Write-Host " 2. multi-revert - Revert multiple bad commits" -ForegroundColor White
Write-Host " 3. multi-revert - Revert multiple bad commits" -ForegroundColor White
Write-Host "`nYou are currently on the 'regular-revert' branch." -ForegroundColor Cyan Write-Host "`nYou are currently on the 'regular-revert' branch." -ForegroundColor Cyan
Write-Host "`nNext steps:" -ForegroundColor Cyan Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White Write-Host " 1. cd challenge" -ForegroundColor White

View File

@@ -1,16 +1,15 @@
#!/usr/bin/env pwsh #!/usr/bin/env pwsh
<# <#
.SYNOPSIS .SYNOPSIS
Verifies the Module 05 challenge solutions. Verifies the Module 06 challenge solutions.
.DESCRIPTION .DESCRIPTION
Checks that all three revert scenarios have been completed correctly: Checks that both revert scenarios have been completed correctly:
- regular-revert: Single commit reverted - regular-revert: Single commit reverted
- merge-revert: Merge commit reverted with -m flag
- multi-revert: Multiple commits reverted - multi-revert: Multiple commits reverted
#> #>
Write-Host "`n=== Verifying Module 05: Git Revert Solutions ===" -ForegroundColor Cyan Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
$allChecksPassed = $true $allChecksPassed = $true
$originalDir = Get-Location $originalDir = Get-Location
@@ -51,19 +50,19 @@ if ($LASTEXITCODE -ne 0) {
$allChecksPassed = $false $allChecksPassed = $false
} }
# Check that calculator.py exists # Check that divide.py is removed (was reverted)
if (-not (Test-Path "divide.py")) {
Write-Host "[PASS] Broken divide.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[FAIL] divide.py still exists (should be reverted)" -ForegroundColor Red
Write-Host "[HINT] The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that calculator.py exists and has correct content
if (Test-Path "calculator.py") { if (Test-Path "calculator.py") {
$calcContent = Get-Content "calculator.py" -Raw $calcContent = Get-Content "calculator.py" -Raw
# Check that divide function is NOT in the code (was reverted)
if ($calcContent -notmatch "def divide") {
Write-Host "[PASS] Broken divide function successfully reverted" -ForegroundColor Green
} else {
Write-Host "[FAIL] divide function still exists (should be reverted)" -ForegroundColor Red
Write-Host "[HINT] The bad commit should be reverted, removing the divide function" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that modulo function still exists (should be preserved) # Check that modulo function still exists (should be preserved)
if ($calcContent -match "def modulo") { if ($calcContent -match "def modulo") {
Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green
@@ -87,60 +86,9 @@ if ($LASTEXITCODE -ne 0) {
} }
# ============================================================================ # ============================================================================
# SCENARIO 2: Merge Revert Verification # SCENARIO 2: Multi Revert Verification
# ============================================================================ # ============================================================================
Write-Host "`n=== Scenario 2: Merge Revert ===" -ForegroundColor Cyan Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
git switch merge-revert 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAIL] merge-revert branch not found" -ForegroundColor Red
$allChecksPassed = $false
} else {
# Check that a revert commit for the merge exists
$revertMerge = git log --oneline --grep="Revert.*Merge" 2>$null
if ($revertMerge) {
Write-Host "[PASS] Merge revert commit found" -ForegroundColor Green
} else {
Write-Host "[FAIL] No merge revert commit found" -ForegroundColor Red
Write-Host "[HINT] Use: git revert -m 1 <merge-commit-hash>" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that the original merge commit still exists (revert doesn't erase it)
$mergeCommit = git log --merges --oneline --grep="Merge feature-auth" 2>$null
if ($mergeCommit) {
Write-Host "[PASS] Original merge commit still in history (not erased)" -ForegroundColor Green
} else {
Write-Host "[INFO] Original merge commit not found (this is OK if you used a different approach)" -ForegroundColor Yellow
}
# Check that auth.py no longer exists or its effects are reverted
if (-not (Test-Path "auth.py")) {
Write-Host "[PASS] auth.py removed (merge reverted successfully)" -ForegroundColor Green
} else {
Write-Host "[INFO] auth.py still exists (check if merge was fully reverted)" -ForegroundColor Yellow
}
# Check that calculator.py exists
if (Test-Path "calculator.py") {
$calcContent = Get-Content "calculator.py" -Raw
# After reverting the merge, calculator shouldn't import auth
if ($calcContent -notmatch "from auth import") {
Write-Host "[PASS] Auth integration reverted from calculator.py" -ForegroundColor Green
} else {
Write-Host "[FAIL] calculator.py still imports auth (merge not fully reverted)" -ForegroundColor Red
Write-Host "[HINT] Reverting the merge should remove the auth integration" -ForegroundColor Yellow
$allChecksPassed = $false
}
}
}
# ============================================================================
# SCENARIO 3: Multi Revert Verification
# ============================================================================
Write-Host "`n=== Scenario 3: Multi Revert ===" -ForegroundColor Cyan
git switch multi-revert 2>&1 | Out-Null git switch multi-revert 2>&1 | Out-Null
@@ -160,26 +108,26 @@ if ($LASTEXITCODE -ne 0) {
$allChecksPassed = $false $allChecksPassed = $false
} }
# Check that sqrt.py is removed (reverted)
if (-not (Test-Path "sqrt.py")) {
Write-Host "[PASS] Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[FAIL] sqrt.py still exists (should be reverted)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that logarithm.py is removed (reverted)
if (-not (Test-Path "logarithm.py")) {
Write-Host "[PASS] Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[FAIL] logarithm.py still exists (should be reverted)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check calculator.py content # Check calculator.py content
if (Test-Path "calculator.py") { if (Test-Path "calculator.py") {
$calcContent = Get-Content "calculator.py" -Raw $calcContent = Get-Content "calculator.py" -Raw
# Check that square_root is NOT in code (reverted)
if ($calcContent -notmatch "def square_root") {
Write-Host "[PASS] Broken square_root function reverted" -ForegroundColor Green
} else {
Write-Host "[FAIL] square_root function still exists (should be reverted)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that logarithm is NOT in code (reverted)
if ($calcContent -notmatch "def logarithm") {
Write-Host "[PASS] Broken logarithm function reverted" -ForegroundColor Green
} else {
Write-Host "[FAIL] logarithm function still exists (should be reverted)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that power function still exists (good commit before bad ones) # Check that power function still exists (good commit before bad ones)
if ($calcContent -match "def power") { if ($calcContent -match "def power") {
Write-Host "[PASS] power function preserved" -ForegroundColor Green Write-Host "[PASS] power function preserved" -ForegroundColor Green
@@ -211,11 +159,10 @@ if ($allChecksPassed) {
Write-Host "=========================================" -ForegroundColor Green Write-Host "=========================================" -ForegroundColor Green
Write-Host "`nYou've mastered git revert!" -ForegroundColor Cyan Write-Host "`nYou've mastered git revert!" -ForegroundColor Cyan
Write-Host "You now understand:" -ForegroundColor Cyan Write-Host "You now understand:" -ForegroundColor Cyan
Write-Host " ✓ Reverting regular commits safely" -ForegroundColor White Write-Host " ✓ Reverting commits safely without erasing history" -ForegroundColor White
Write-Host " ✓ Reverting merge commits with -m flag" -ForegroundColor White
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
Write-Host " ✓ Preserving history while undoing changes" -ForegroundColor White Write-Host " ✓ Preserving all other commits while undoing specific changes" -ForegroundColor White
Write-Host "`nReady for Module 06: Git Reset!" -ForegroundColor Green Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
Write-Host "" Write-Host ""
exit 0 exit 0
} else { } else {

View File

@@ -1,717 +0,0 @@
# Module 06: Git Reset - Dangerous History Rewriting
## ⚠️ CRITICAL SAFETY WARNING ⚠️
**Git reset is DESTRUCTIVE and DANGEROUS when misused!**
Before using `git reset`, always ask yourself:
```
Have I pushed these commits to a remote repository?
├─ YES → ❌ DO NOT USE RESET!
│ Use git revert instead (Module 05)
│ Rewriting pushed history breaks collaboration!
└─ NO → ✅ Proceed with reset (local cleanup only)
Choose your mode carefully:
--soft (safest), --mixed (moderate), --hard (DANGEROUS)
```
**The Golden Rule:** NEVER reset commits that have been pushed/shared.
## About This Module
Welcome to Module 06, where you'll learn the powerful but dangerous `git reset` command. Unlike `git revert` (Module 05) which safely creates new commits, **reset erases commits from history**.
**Why reset exists:**
- ✅ Clean up messy local commit history before pushing
- ✅ Undo commits you haven't shared yet
- ✅ Unstage files from the staging area
- ✅ Recover from mistakes (with reflog)
**Why reset is dangerous:**
- ⚠️ Erases commits permanently (without reflog)
- ⚠️ Breaks repositories if used on pushed commits
- ⚠️ Can lose work if used incorrectly
- ⚠️ Confuses teammates if they have your commits
**Key principle:** Reset is for polishing LOCAL history before sharing.
## Learning Objectives
By completing this module, you will:
1. Understand the three reset modes: --soft, --mixed, --hard
2. Reset commits while keeping changes staged (--soft)
3. Reset commits and unstage changes (--mixed)
4. Reset commits and discard everything (--hard)
5. Know when reset is appropriate (local only!)
6. Understand when to use revert instead
7. Use reflog to recover from mistakes
## Prerequisites
Before starting this module, you should:
- Be comfortable with commits and staging (`git add`, `git commit`)
- Understand `git revert` from Module 05
- **Know the difference between local and pushed commits!**
## Setup
Run the setup script to create the challenge environment:
```powershell
./setup.ps1
```
This creates a `challenge/` directory with three branches demonstrating different reset modes:
- `soft-reset` - Reset with --soft (keep changes staged)
- `mixed-reset` - Reset with --mixed (unstage changes)
- `hard-reset` - Reset with --hard (discard everything)
**Remember:** These are all LOCAL commits that have NEVER been pushed!
## Understanding Reset Modes
Git reset has three modes that control what happens to your changes:
| Mode | Commits | Staging Area | Working Directory |
|------|---------|--------------|-------------------|
| **--soft** | ✂️ Removed | ✅ Kept (staged) | ✅ Kept |
| **--mixed** (default) | ✂️ Removed | ✂️ Cleared | ✅ Kept (unstaged) |
| **--hard** | ✂️ Removed | ✂️ Cleared | ✂️ **LOST!** |
**Visual explanation:**
```
Before reset (3 commits):
A → B → C → HEAD
After git reset --soft HEAD~1:
A → B → HEAD
C's changes are staged
After git reset --mixed HEAD~1 (or just git reset HEAD~1):
A → B → HEAD
C's changes are unstaged (in working directory)
After git reset --hard HEAD~1:
A → B → HEAD
C's changes are GONE (discarded completely!)
```
## Challenge 1: Soft Reset (Safest)
### Scenario
You committed "feature C" but immediately realized the implementation is wrong. You want to undo the commit but keep the changes staged so you can edit and re-commit them properly.
**Use case:** Fixing the last commit's message or contents.
### Your Task
1. Navigate to the challenge directory:
```bash
cd challenge
```
2. You should be on the `soft-reset` branch. View the commits:
```bash
git log --oneline
```
You should see:
- "Add feature C - needs better implementation!"
- "Add feature B"
- "Add feature A"
- "Initial project setup"
3. View the current state:
```bash
git status
# Should be clean
```
4. Reset the last commit with --soft:
```bash
git reset --soft HEAD~1
```
5. Check what happened:
```bash
# Commit is gone
git log --oneline
# Should only show 3 commits now (feature C commit removed)
# Changes are still staged
git status
# Should show "Changes to be committed"
# View the staged changes
git diff --cached
# Should show feature C code ready to be re-committed
```
### What to Observe
After `--soft` reset:
- ✅ Commit removed from history
- ✅ Changes remain in staging area
- ✅ Working directory unchanged
- ✅ Ready to edit and re-commit
**When to use --soft:**
- Fix the last commit message (though `commit --amend` is simpler)
- Combine multiple commits into one
- Re-do a commit with better changes
## Challenge 2: Mixed Reset (Default, Moderate)
### Scenario
You committed two experimental features that aren't ready. You want to remove both commits and have the changes back in your working directory (unstaged) so you can review and selectively re-commit them.
**Use case:** Undoing commits and starting over with more careful staging.
### Your Task
1. Switch to the mixed-reset branch:
```bash
git switch mixed-reset
```
2. View the commits:
```bash
git log --oneline
```
You should see:
- "Add debug mode - REMOVE THIS TOO!"
- "Add experimental feature X - REMOVE THIS!"
- "Add logging system"
- "Add application lifecycle"
3. Reset the last TWO commits (default is --mixed):
```bash
git reset HEAD~2
# This is equivalent to: git reset --mixed HEAD~2
```
4. Check what happened:
```bash
# Commits are gone
git log --oneline
# Should only show 2 commits (lifecycle + logging)
# NO staged changes
git diff --cached
# Should be empty
# Changes are in working directory (unstaged)
git status
# Should show "Changes not staged for commit"
# View the unstaged changes
git diff
# Should show experimental and debug code
```
### What to Observe
After `--mixed` reset (the default):
- ✅ Commits removed from history
- ✅ Staging area cleared
- ✅ Changes moved to working directory (unstaged)
- ✅ Can selectively stage and re-commit parts
**When to use --mixed (default):**
- Undo commits and start over with clean staging
- Split one large commit into multiple smaller ones
- Review changes before re-committing
- Most common reset mode for cleanup
## Challenge 3: Hard Reset (MOST DANGEROUS!)
### ⚠️ EXTREME CAUTION REQUIRED ⚠️
**This will PERMANENTLY DELETE your work!**
Only use `--hard` when you're absolutely sure you want to throw away changes.
### Scenario
You committed completely broken code that you want to discard entirely. There's no salvaging it—you just want it gone.
**Use case:** Throwing away failed experiments or completely wrong code.
### Your Task
1. Switch to the hard-reset branch:
```bash
git switch hard-reset
```
2. View the commits and the broken code:
```bash
git log --oneline
# Shows "Add broken helper D - DISCARD COMPLETELY!"
cat utils.py
# Shows the broken helper_d function
```
3. Reset the last commit with --hard:
```bash
git reset --hard HEAD~1
```
**WARNING:** This will permanently discard all changes from that commit!
4. Check what happened:
```bash
# Commit is gone
git log --oneline
# Should only show 2 commits
# NO staged changes
git diff --cached
# Empty
# NO unstaged changes
git diff
# Empty
# Working directory clean
git status
# "nothing to commit, working tree clean"
# File doesn't have broken code
cat utils.py
# helper_d is completely gone
```
### What to Observe
After `--hard` reset:
- ✅ Commit removed from history
- ✅ Staging area cleared
- ✅ Working directory reset to match
- ⚠️ All changes from that commit PERMANENTLY DELETED
**When to use --hard:**
- Discarding failed experiments completely
- Throwing away work you don't want (CAREFUL!)
- Cleaning up after mistakes (use reflog to recover if needed)
- Resetting to a known good state
**⚠️ WARNING:** Files in the discarded commit are NOT gone forever—they're still in reflog for about 90 days. See "Recovery with Reflog" section below.
## Understanding HEAD~N Syntax
When resetting, you specify where to reset to:
```bash
# Reset to the commit before HEAD
git reset HEAD~1
# Reset to 2 commits before HEAD
git reset HEAD~2
# Reset to 3 commits before HEAD
git reset HEAD~3
# Reset to a specific commit hash
git reset abc123
# Reset to a branch
git reset main
```
**Visualization:**
```
HEAD~3 HEAD~2 HEAD~1 HEAD
↓ ↓ ↓ ↓
A → B → C → D → E
Current commit
```
- `git reset HEAD~1` moves HEAD from E to D
- `git reset HEAD~2` moves HEAD from E to C
- `git reset abc123` moves HEAD to that specific commit
## Verification
Verify your solutions by running the verification script:
```bash
cd .. # Return to module directory
./verify.ps1
```
The script checks that:
- ✅ Commits were reset (count decreased)
- ✅ --soft: Changes remain staged
- ✅ --mixed: Changes are unstaged
- ✅ --hard: Everything is clean
## Recovery with Reflog
**Good news:** Even `--hard` reset doesn't immediately destroy commits!
Git keeps a "reflog" (reference log) of where HEAD has been for about 90 days. You can use this to recover "lost" commits.
### How to Recover from a Reset
1. View the reflog:
```bash
git reflog
```
Output example:
```
abc123 HEAD@{0}: reset: moving to HEAD~1
def456 HEAD@{1}: commit: Add broken helper D
...
```
2. Find the commit you want to recover (def456 in this example)
3. Reset back to it:
```bash
git reset def456
# Or use the reflog reference:
git reset HEAD@{1}
```
4. Your "lost" commit is back!
### Reflog Safety Net
**Important:**
- Reflog entries expire after ~90 days (configurable)
- Reflog is LOCAL to your repository (not shared)
- `git gc` can clean up old reflog entries
- If you really lose a commit, check reflog first!
**Pro tip:** Before doing dangerous operations, note your current commit hash:
```bash
git log --oneline | head -1
# abc123 Current work
```
## When to Use Git Reset
Use `git reset` when:
- ✅ **Commits are LOCAL only** (never pushed)
- ✅ **Cleaning up messy history** before sharing
- ✅ **Undoing recent commits** you don't want
- ✅ **Combining commits** into one clean commit
- ✅ **Unstaging files** (mixed mode)
- ✅ **Polishing commit history** before pull request
**Golden Rule:** Only reset commits that are local to your machine!
## When NOT to Use Git Reset
DO NOT use `git reset` when:
- ❌ **Commits are pushed/shared** with others
- ❌ **Teammates have your commits** (breaks their repos)
- ❌ **In public repositories** (use revert instead)
- ❌ **Unsure if pushed** (check `git log origin/main`)
- ❌ **On main/master branch** after push
- ❌ **Need audit trail** of changes
**Use git revert instead** (Module 05) for pushed commits!
## Decision Tree: Reset vs Revert
```
Need to undo a commit?
├─ Have you pushed this commit?
│ │
│ ├─ YES → Use git revert (Module 05)
│ │ Safe for shared history
│ │ Preserves complete audit trail
│ │
│ └─ NO → Can use git reset (local only)
│ │
│ ├─ Want to keep changes?
│ │ │
│ │ ├─ Keep staged → git reset --soft
│ │ └─ Keep unstaged → git reset --mixed
│ │
│ └─ Discard everything? → git reset --hard
│ (CAREFUL!)
```
## Reset vs Revert vs Rebase
| Command | History | Safety | Use Case |
|---------|---------|--------|----------|
| **reset** | Erases | ⚠️ Dangerous | Local cleanup before push |
| **revert** | Preserves | ✅ Safe | Undo pushed commits |
| **rebase** | Rewrites | ⚠️ Dangerous | Polish history before push |
**This module teaches reset.** You learned revert in Module 05.
## Command Reference
### Basic Reset
```bash
# Reset last commit, keep changes staged
git reset --soft HEAD~1
# Reset last commit, unstage changes (default)
git reset HEAD~1
git reset --mixed HEAD~1 # Same as above
# Reset last commit, discard everything (DANGEROUS!)
git reset --hard HEAD~1
# Reset multiple commits
git reset --soft HEAD~3 # Last 3 commits
# Reset to specific commit
git reset --soft abc123
```
### Unstaging Files
```bash
# Unstage a specific file (common use of reset)
git reset HEAD filename.txt
# Unstage all files
git reset HEAD .
# This is the same as:
git restore --staged filename.txt # Modern syntax
```
### Reflog and Recovery
```bash
# View reflog
git reflog
# Recover from reset
git reset --hard HEAD@{1}
git reset --hard abc123
```
### Check Before Reset
```bash
# Check if commits are pushed
git log origin/main..HEAD
# If output is empty, commits are pushed (DO NOT RESET)
# If output shows commits, they're local (safe to reset)
# Another way to check
git log --oneline --graph --all
# Look for origin/main marker
```
## Common Mistakes
### 1. Resetting Pushed Commits
```bash
# ❌ NEVER do this if you've pushed!
git push
# ... time passes ...
git reset --hard HEAD~3 # BREAKS teammate repos!
# ✅ Do this instead
git revert HEAD~3..HEAD # Safe for shared history
```
### 2. Using --hard Without Thinking
```bash
# ❌ Dangerous - loses work!
git reset --hard HEAD~1
# ✅ Better - keep changes to review
git reset --mixed HEAD~1
# Now you can review changes and decide
```
### 3. Resetting Without Checking If Pushed
```bash
# ❌ Risky - are these commits pushed?
git reset HEAD~5
# ✅ Check first
git log origin/main..HEAD # Local commits only
git reset HEAD~5 # Now safe if output showed commits
```
### 4. Forgetting Reflog Exists
```bash
# ❌ Panic after accidental --hard reset
# "I lost my work!"
# ✅ Check reflog first!
git reflog # Find the "lost" commit
git reset --hard HEAD@{1} # Recover it
```
## Best Practices
1. **Always check if commits are pushed before reset:**
```bash
git log origin/main..HEAD
```
2. **Prefer --mixed over --hard:**
- You can always discard changes later
- Hard to recover if you use --hard by mistake
3. **Commit often locally, reset before push:**
- Make many small local commits
- Reset/squash into clean commits before pushing
4. **Use descriptive commit messages even for local commits:**
- Helps when reviewing before reset
- Useful when checking reflog
5. **Know your escape hatch:**
```bash
git reflog # Your safety net!
```
6. **Communicate with team:**
- NEVER reset shared branches (main, develop, etc.)
- Only reset your personal feature branches
- Only before pushing!
## Troubleshooting
### "I accidentally reset with --hard and lost work!"
**Solution:** Check reflog:
```bash
git reflog
# Find the commit before your reset
git reset --hard HEAD@{1} # Or the commit hash
```
**Prevention:** Always use --mixed first, then discard if really needed.
### "I reset but teammates still have my commits"
**Problem:** You reset and pushed with --force after they pulled.
**Impact:** Their repository is now broken/inconsistent.
**Solution:** Communicate! They need to:
```bash
git fetch
git reset --hard origin/main # Or whatever branch
```
**Prevention:** NEVER reset pushed commits!
### "Reset didn't do what I expected"
**Issue:** Wrong mode or wrong HEAD~N count.
**Solution:** Check current state:
```bash
git status
git diff
git diff --cached
git log --oneline
```
Undo the reset:
```bash
git reflog
git reset HEAD@{1} # Go back to before your reset
```
### "Can't reset - 'fatal: ambiguous argument HEAD~1'"
**Issue:** No commits to reset (probably first commit).
**Solution:** You can't reset before the first commit. If you want to remove the first commit entirely:
```bash
rm -rf .git # Nuclear option - deletes entire repo
git init # Start over
```
## Advanced: Reset Internals
Understanding what reset does under the hood:
```bash
# Reset moves the branch pointer
# Before:
main → A → B → C (HEAD)
# After git reset --soft HEAD~1:
main → A → B (HEAD)
C still exists in reflog, just not in branch history
# The commit object C is still in .git/objects
# It's just unreachable from any branch
```
**Key insight:** Reset moves the HEAD and branch pointers backward. The commits still exist temporarily in reflog until garbage collection.
## Going Further
Now that you understand reset, you're ready for:
- **Module 07: Git Stash** - Temporarily save uncommitted work
- **Module 08: Multiplayer Git** - Collaborate with complex workflows
- **Interactive Rebase** - Advanced history polishing (beyond this workshop)
## Summary
You've learned:
- ✅ `git reset` rewrites history by moving HEAD backward
- ✅ `--soft` keeps changes staged (safest)
- ✅ `--mixed` (default) unstages changes
- ✅ `--hard` discards everything (most dangerous)
- ✅ NEVER reset pushed/shared commits
- ✅ Use reflog to recover from mistakes
- ✅ Check if commits are pushed before resetting
- ✅ Use revert (Module 05) for shared commits
**The Critical Rule:** Reset is for LOCAL commits ONLY. Once you push, use revert!
## Next Steps
1. Complete all three challenge scenarios
2. Run `./verify.ps1` to check your solutions
3. Practice checking if commits are pushed before reset
4. Move on to Module 07: Git Stash
---
**⚠️ FINAL REMINDER ⚠️**
**Before any `git reset` command, ask yourself:**
> "Have I pushed these commits?"
If YES → Use `git revert` instead!
If NO → Proceed carefully, choose the right mode.
**When in doubt, use --mixed instead of --hard!**

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 06 challenge environment to start fresh.
.DESCRIPTION
This script removes the challenge directory and re-runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "`n=== Resetting Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
# Check if challenge directory exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
Write-Host "[OK] Challenge directory removed" -ForegroundColor Green
} else {
Write-Host "[INFO] No existing challenge directory found" -ForegroundColor Yellow
}
# Run setup to create fresh environment
Write-Host "`nRunning setup to create fresh challenge environment..." -ForegroundColor Cyan
& "$PSScriptRoot/setup.ps1"

View File

@@ -1,348 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 06 challenge environment for learning git reset.
.DESCRIPTION
This script creates a challenge directory with three branches demonstrating
different reset scenarios:
- soft-reset: Reset with --soft (keeps changes staged)
- mixed-reset: Reset with --mixed (unstages changes)
- hard-reset: Reset with --hard (discards everything) + reflog recovery
#>
Write-Host "`n=== Setting up Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
Write-Host "⚠️ WARNING: Git reset is DANGEROUS - use with extreme caution! ⚠️" -ForegroundColor Red
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Configure git for this repository
git config user.name "Workshop Student"
git config user.email "student@example.com"
# ============================================================================
# Create initial commit (shared by all scenarios)
# ============================================================================
$readmeContent = @"
# Git Reset Practice
This repository contains practice scenarios for learning git reset.
"@
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Initial commit" | Out-Null
# ============================================================================
# SCENARIO 1: Soft Reset (--soft)
# ============================================================================
Write-Host "`nScenario 1: Creating soft-reset branch..." -ForegroundColor Cyan
# Create soft-reset branch from initial commit
git switch -c soft-reset | Out-Null
# Build up scenario 1 commits
$projectContent = @"
# project.py - Main project file
def initialize():
"""Initialize the project."""
print("Project initialized")
def main():
initialize()
print("Running application...")
"@
Set-Content -Path "project.py" -Value $projectContent
git add .
git commit -m "Initial project setup" | Out-Null
# Good commit: Add feature A
$projectContent = @"
# project.py - Main project file
def initialize():
"""Initialize the project."""
print("Project initialized")
def feature_a():
"""Feature A implementation."""
print("Feature A is working")
def main():
initialize()
feature_a()
print("Running application...")
"@
Set-Content -Path "project.py" -Value $projectContent
git add .
git commit -m "Add feature A" | Out-Null
# Good commit: Add feature B
$projectContent = @"
# project.py - Main project file
def initialize():
"""Initialize the project."""
print("Project initialized")
def feature_a():
"""Feature A implementation."""
print("Feature A is working")
def feature_b():
"""Feature B implementation."""
print("Feature B is working")
def main():
initialize()
feature_a()
feature_b()
print("Running application...")
"@
Set-Content -Path "project.py" -Value $projectContent
git add .
git commit -m "Add feature B" | Out-Null
# BAD commit: Add feature C (wrong implementation)
$projectContent = @"
# project.py - Main project file
def initialize():
"""Initialize the project."""
print("Project initialized")
def feature_a():
"""Feature A implementation."""
print("Feature A is working")
def feature_b():
"""Feature B implementation."""
print("Feature B is working")
def feature_c():
"""Feature C implementation - WRONG!"""
print("Feature C has bugs!") # This needs to be re-implemented
def main():
initialize()
feature_a()
feature_b()
feature_c()
print("Running application...")
"@
Set-Content -Path "project.py" -Value $projectContent
git add .
git commit -m "Add feature C - needs better implementation!" | Out-Null
Write-Host "[CREATED] soft-reset branch with commit to reset --soft" -ForegroundColor Green
# ============================================================================
# SCENARIO 2: Mixed Reset (--mixed, default)
# ============================================================================
Write-Host "`nScenario 2: Creating mixed-reset branch..." -ForegroundColor Cyan
# Switch back to initial commit and create mixed-reset branch
git switch main | Out-Null
git switch -c mixed-reset | Out-Null
# Build up scenario 2 commits
$appContent = @"
# app.py - Application entry point
def start():
"""Start the application."""
print("Application started")
def stop():
"""Stop the application."""
print("Application stopped")
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add application lifecycle" | Out-Null
# Good commit: Add logging
$appContent = @"
# app.py - Application entry point
def log(message):
"""Log a message."""
print(f"[LOG] {message}")
def start():
"""Start the application."""
log("Application started")
def stop():
"""Stop the application."""
log("Application stopped")
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add logging system" | Out-Null
# BAD commit 1: Add experimental feature X
$appContent = @"
# app.py - Application entry point
def log(message):
"""Log a message."""
print(f"[LOG] {message}")
def experimental_feature_x():
"""Experimental feature - NOT READY!"""
log("Feature X is experimental and buggy")
def start():
"""Start the application."""
log("Application started")
experimental_feature_x()
def stop():
"""Stop the application."""
log("Application stopped")
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add experimental feature X - REMOVE THIS!" | Out-Null
# BAD commit 2: Add debug mode (also not ready)
$appContent = @"
# app.py - Application entry point
DEBUG_MODE = True # Should not be committed!
def log(message):
"""Log a message."""
print(f"[LOG] {message}")
def experimental_feature_x():
"""Experimental feature - NOT READY!"""
log("Feature X is experimental and buggy")
def start():
"""Start the application."""
if DEBUG_MODE:
log("DEBUG MODE ACTIVE!")
log("Application started")
experimental_feature_x()
def stop():
"""Stop the application."""
log("Application stopped")
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add debug mode - REMOVE THIS TOO!" | Out-Null
Write-Host "[CREATED] mixed-reset branch with commits to reset --mixed" -ForegroundColor Green
# ============================================================================
# SCENARIO 3: Hard Reset (--hard) + Reflog Recovery
# ============================================================================
Write-Host "`nScenario 3: Creating hard-reset branch..." -ForegroundColor Cyan
# Switch back to main and create hard-reset branch
git switch main | Out-Null
git switch -c hard-reset | Out-Null
# Reset to basic state
$utilsContent = @"
# utils.py - Utility functions
def helper_a():
"""Helper function A."""
return "Helper A"
def helper_b():
"""Helper function B."""
return "Helper B"
"@
Set-Content -Path "utils.py" -Value $utilsContent
git add .
git commit -m "Add utility helpers" | Out-Null
# Good commit: Add helper C
$utilsContent = @"
# utils.py - Utility functions
def helper_a():
"""Helper function A."""
return "Helper A"
def helper_b():
"""Helper function B."""
return "Helper B"
def helper_c():
"""Helper function C."""
return "Helper C"
"@
Set-Content -Path "utils.py" -Value $utilsContent
git add .
git commit -m "Add helper C" | Out-Null
# BAD commit: Add broken helper D (completely wrong)
$utilsContent = @"
# utils.py - Utility functions
def helper_a():
"""Helper function A."""
return "Helper A"
def helper_b():
"""Helper function B."""
return "Helper B"
def helper_c():
"""Helper function C."""
return "Helper C"
def helper_d():
"""COMPLETELY BROKEN - throw away!"""
# This is all wrong and needs to be discarded
broken_code = "This doesn't even make sense"
return broken_code.nonexistent_method() # Will crash!
"@
Set-Content -Path "utils.py" -Value $utilsContent
git add .
git commit -m "Add broken helper D - DISCARD COMPLETELY!" | Out-Null
Write-Host "[CREATED] hard-reset branch with commit to reset --hard" -ForegroundColor Green
# ============================================================================
# Return to soft-reset to start
# ============================================================================
git switch soft-reset | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n=== Setup Complete! ===`n" -ForegroundColor Green
Write-Host "Three reset scenarios have been created:" -ForegroundColor Cyan
Write-Host " 1. soft-reset - Reset --soft (keep changes staged)" -ForegroundColor White
Write-Host " 2. mixed-reset - Reset --mixed (unstage changes)" -ForegroundColor White
Write-Host " 3. hard-reset - Reset --hard (discard everything) + reflog recovery" -ForegroundColor White
Write-Host "`n⚠️ CRITICAL SAFETY REMINDER ⚠️" -ForegroundColor Red
Write-Host "NEVER use git reset on commits that have been PUSHED!" -ForegroundColor Red
Write-Host "These scenarios are LOCAL ONLY for practice." -ForegroundColor Yellow
Write-Host "`nYou are currently on the 'soft-reset' branch." -ForegroundColor Cyan
Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. Read the README.md for detailed instructions" -ForegroundColor White
Write-Host " 3. Complete each reset challenge" -ForegroundColor White
Write-Host " 4. Run '..\\verify.ps1' to check your solutions" -ForegroundColor White
Write-Host ""

View File

@@ -1,231 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 06 challenge solutions.
.DESCRIPTION
Checks that all three reset scenarios have been completed correctly:
- soft-reset: Commit reset but changes remain staged
- mixed-reset: Commits reset and changes unstaged
- hard-reset: Everything reset and discarded
#>
Write-Host "`n=== Verifying Module 06: Git Reset Solutions ===" -ForegroundColor Cyan
Write-Host "⚠️ Remember: NEVER reset pushed commits! ⚠️" -ForegroundColor Red
$allChecksPassed = $true
$originalDir = Get-Location
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Run setup.ps1 first." -ForegroundColor Red
Set-Location $originalDir
exit 1
}
# ============================================================================
# SCENARIO 1: Soft Reset Verification
# ============================================================================
Write-Host "`n=== Scenario 1: Soft Reset ===`n" -ForegroundColor Cyan
git switch soft-reset 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAIL] soft-reset branch not found" -ForegroundColor Red
$allChecksPassed = $false
} else {
# Count commits (should be 4: Initial + project setup + feature A + feature B)
$commitCount = [int](git rev-list --count HEAD 2>$null)
if ($commitCount -eq 4) {
Write-Host "[PASS] Commit count is 4 (feature C commit was reset)" -ForegroundColor Green
} else {
Write-Host "[FAIL] Expected 4 commits, found $commitCount" -ForegroundColor Red
Write-Host "[HINT] Use: git reset --soft HEAD~1" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check if changes are staged
$stagedChanges = git diff --cached --name-only 2>$null
if ($stagedChanges) {
Write-Host "[PASS] Changes are staged (feature C code in staging area)" -ForegroundColor Green
# Verify the staged changes contain feature C code
$stagedContent = git diff --cached 2>$null
if ($stagedContent -match "feature_c") {
Write-Host "[PASS] Staged changes contain feature C code" -ForegroundColor Green
} else {
Write-Host "[INFO] Staged changes don't seem to contain feature C" -ForegroundColor Yellow
}
} else {
Write-Host "[FAIL] No staged changes found" -ForegroundColor Red
Write-Host "[HINT] After --soft reset, changes should remain staged" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check working directory has no unstaged changes to tracked files
$unstagedChanges = git diff --name-only 2>$null
if (-not $unstagedChanges) {
Write-Host "[PASS] No unstaged changes (all changes are staged)" -ForegroundColor Green
} else {
Write-Host "[INFO] Found unstaged changes (expected only staged changes)" -ForegroundColor Yellow
}
}
# ============================================================================
# SCENARIO 2: Mixed Reset Verification
# ============================================================================
Write-Host "`n=== Scenario 2: Mixed Reset ===`n" -ForegroundColor Cyan
git switch mixed-reset 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAIL] mixed-reset branch not found" -ForegroundColor Red
$allChecksPassed = $false
} else {
# Count commits (should be 3: Initial + lifecycle + logging, bad commits removed)
$commitCount = [int](git rev-list --count HEAD 2>$null)
if ($commitCount -eq 3) {
Write-Host "[PASS] Commit count is 3 (both bad commits were reset)" -ForegroundColor Green
} elseif ($commitCount -eq 4) {
Write-Host "[INFO] Commit count is 4 (one commit reset, need to reset one more)" -ForegroundColor Yellow
Write-Host "[HINT] Use: git reset HEAD~1 (or git reset --mixed HEAD~1)" -ForegroundColor Yellow
$allChecksPassed = $false
} else {
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
Write-Host "[HINT] Use: git reset --mixed HEAD~2 to remove both bad commits" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that there are NO staged changes
$stagedChanges = git diff --cached --name-only 2>$null
if (-not $stagedChanges) {
Write-Host "[PASS] No staged changes (--mixed unstages everything)" -ForegroundColor Green
} else {
Write-Host "[FAIL] Found staged changes (--mixed should unstage)" -ForegroundColor Red
Write-Host "[HINT] After --mixed reset, changes should be unstaged" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that there ARE unstaged changes in working directory
$unstagedChanges = git diff --name-only 2>$null
if ($unstagedChanges) {
Write-Host "[PASS] Unstaged changes present in working directory" -ForegroundColor Green
# Verify unstaged changes contain the experimental/debug code
$workingContent = git diff 2>$null
if ($workingContent -match "experimental|DEBUG") {
Write-Host "[PASS] Unstaged changes contain the reset code" -ForegroundColor Green
} else {
Write-Host "[INFO] Unstaged changes don't contain expected code" -ForegroundColor Yellow
}
} else {
Write-Host "[FAIL] No unstaged changes found" -ForegroundColor Red
Write-Host "[HINT] After --mixed reset, changes should be in working directory (unstaged)" -ForegroundColor Yellow
$allChecksPassed = $false
}
}
# ============================================================================
# SCENARIO 3: Hard Reset Verification
# ============================================================================
Write-Host "`n=== Scenario 3: Hard Reset ===`n" -ForegroundColor Cyan
git switch hard-reset 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAIL] hard-reset branch not found" -ForegroundColor Red
$allChecksPassed = $false
} else {
# Count commits (should be 3: Initial + utilities + helper C, bad commit removed)
$commitCount = [int](git rev-list --count HEAD 2>$null)
if ($commitCount -eq 3) {
Write-Host "[PASS] Commit count is 3 (broken commit was reset)" -ForegroundColor Green
} else {
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
Write-Host "[HINT] Use: git reset --hard HEAD~1" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that there are NO staged changes
$stagedChanges = git diff --cached --name-only 2>$null
if (-not $stagedChanges) {
Write-Host "[PASS] No staged changes (--hard discards everything)" -ForegroundColor Green
} else {
Write-Host "[FAIL] Found staged changes (--hard should discard all)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that there are NO unstaged changes
$unstagedChanges = git diff --name-only 2>$null
if (-not $unstagedChanges) {
Write-Host "[PASS] No unstaged changes (--hard discards everything)" -ForegroundColor Green
} else {
Write-Host "[FAIL] Found unstaged changes (--hard should discard all)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check working directory is clean
$statusOutput = git status --porcelain 2>$null
if (-not $statusOutput) {
Write-Host "[PASS] Working directory is completely clean" -ForegroundColor Green
} else {
Write-Host "[INFO] Working directory has some changes" -ForegroundColor Yellow
}
# Verify the file doesn't have the broken code
if (Test-Path "utils.py") {
$utilsContent = Get-Content "utils.py" -Raw
if ($utilsContent -notmatch "helper_d") {
Write-Host "[PASS] Broken helper_d function is gone" -ForegroundColor Green
} else {
Write-Host "[FAIL] Broken helper_d still exists (wasn't reset)" -ForegroundColor Red
$allChecksPassed = $false
}
if ($utilsContent -match "helper_c") {
Write-Host "[PASS] Good helper_c function is preserved" -ForegroundColor Green
} else {
Write-Host "[FAIL] Good helper_c function missing" -ForegroundColor Red
$allChecksPassed = $false
}
}
}
Set-Location $originalDir
# Final summary
Write-Host ""
if ($allChecksPassed) {
Write-Host "==========================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! ALL SCENARIOS PASSED!" -ForegroundColor Green
Write-Host "==========================================" -ForegroundColor Green
Write-Host "`nYou've mastered git reset!" -ForegroundColor Cyan
Write-Host "You now understand:" -ForegroundColor Cyan
Write-Host " ✓ Resetting commits with --soft (keep staged)" -ForegroundColor White
Write-Host " ✓ Resetting commits with --mixed (unstage)" -ForegroundColor White
Write-Host " ✓ Resetting commits with --hard (discard all)" -ForegroundColor White
Write-Host " ✓ The DANGER of reset on shared history" -ForegroundColor White
Write-Host "`n⚠️ CRITICAL REMINDER ⚠️" -ForegroundColor Red
Write-Host "NEVER use 'git reset' on commits you've already PUSHED!" -ForegroundColor Red
Write-Host "Always use 'git revert' (Module 05) for shared commits!" -ForegroundColor Yellow
Write-Host "`nReady for Module 07: Git Stash!" -ForegroundColor Green
Write-Host ""
exit 0
} else {
Write-Host "[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host "[REMINDER] Reset is ONLY for local, un-pushed commits!" -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

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

View File

@@ -25,6 +25,9 @@ git init | Out-Null
git config user.name "Workshop User" | Out-Null git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null git config user.email "user@workshop.local" | Out-Null
# Detect the default branch name after first commit (created below)
# Will be detected after the initial commit
# Create initial application on main # Create initial application on main
$app = @" $app = @"
class Application: class Application:
@@ -45,6 +48,14 @@ Set-Content -Path "app.py" -Value $app
git add app.py git add app.py
git commit -m "Initial application" | Out-Null git commit -m "Initial application" | Out-Null
# Detect the main branch name after first commit
$mainBranch = git branch --show-current
if (-not $mainBranch) {
$mainBranch = git config --get init.defaultBranch
if (-not $mainBranch) { $mainBranch = "main" }
}
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
$readme = @" $readme = @"
# MyApp # MyApp
@@ -78,7 +89,7 @@ git add login.py
git commit -m "Start login service implementation" | Out-Null git commit -m "Start login service implementation" | Out-Null
# Add a critical bug to main branch (simulating a bug that was introduced) # Add a critical bug to main branch (simulating a bug that was introduced)
git checkout main | Out-Null git checkout $mainBranch | Out-Null
$appWithBug = @" $appWithBug = @"
class Application: class Application:
@@ -140,16 +151,17 @@ Write-Host "========================================" -ForegroundColor Green
Write-Host "`nSituation:" -ForegroundColor Cyan Write-Host "`nSituation:" -ForegroundColor Cyan
Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White
Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow
Write-Host "`nUrgent: A critical security bug was found in production (main branch)!" -ForegroundColor Red Write-Host "`nUrgent: A critical security bug was found in production ($mainBranch branch)!" -ForegroundColor Red
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
Write-Host "`nYour task:" -ForegroundColor Yellow Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White
Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White
Write-Host "4. Switch to main: git checkout main" -ForegroundColor White Write-Host "4. Switch to ${mainBranch}: git switch $mainBranch" -ForegroundColor White
Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White
Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White
Write-Host "7. Switch back: git checkout feature-login" -ForegroundColor White Write-Host "7. Switch back: git switch feature-login" -ForegroundColor White
Write-Host "8. Restore your work: git stash pop" -ForegroundColor White Write-Host "8. Restore your work: git stash pop" -ForegroundColor White
Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White
Write-Host "10. Commit your completed feature" -ForegroundColor White Write-Host "10. Commit your completed feature" -ForegroundColor White

View File

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

View File

@@ -232,23 +232,6 @@ To reuse the repository:
## Tips ## Tips
- **Keep groups small** (4-8 people) for more interaction - **Keep groups small** (2 people per repository) for more interaction
- **Encourage communication** - the exercise works best when people talk - **Encourage communication** - the exercise works best when people talk
- **Let conflicts happen** - they're the best learning opportunity - **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

View File

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

View File

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

View File

@@ -0,0 +1,148 @@
# Multiplayer Git
Work with others using branches and pull requests.
## Goal
Learn to collaborate on a shared repository using:
- **Branches** - work independently without breaking main
- **Pull Requests** - review and merge changes safely
## The Workflow
```
1. Create branch → 2. Make changes → 3. Push branch
6. Delete branch ← 5. Merge PR ← 4. Create PR
```
This is how professional teams work together on code.
---
## Step 1: Clone the Repository
Get the repository URL from your facilitator, then:
```powershell
git clone <repository-url>
code <repository-name>
```
---
## Step 2: Create a Branch
Never work directly on `main`. Create your own branch:
```powershell
git switch -c <branch>
```
This creates a new branch and switches to it.
---
## Step 3: Make Changes
1. Open `numbers.txt` in VS Code
2. Move one number to its correct position
3. Save the file (`Ctrl+S`)
---
## Step 4: Commit and Push
```powershell
git add .
git commit -m "fix: move 7 to correct position"
git push feature-2
```
Your branch is now on Azure DevOps.
---
## Step 5: Create a Pull Request
1. Go to Azure DevOps in your browser
2. Navigate to **Repos****Pull Requests**
3. Click **New Pull Request**
4. Set:
- **Source branch:** `feature/<your-name>`
- **Target branch:** `main`
5. Add a title describing your change
6. Click **Create**
---
## Step 6: Review and Merge
1. Review the changes shown in the PR
2. If everything looks good, click **Complete**
3. Select **Complete merge**
4. Your changes are now in `main`
---
## Step 7: Update Your Local Main
After merging, update your local copy:
```powershell
git switch main
git pull
```
---
## Step 8: Repeat
1. Create a new branch for your next change
2. Make changes, commit, push
3. Create another PR
4. Continue until all numbers are sorted
---
## Quick Reference
| Command | What It Does |
|---------|--------------|
| `git switch -c <name>` | Create and switch to new branch |
| `git push -u origin <branch>` | Push branch to Azure DevOps |
| `git switch main` | Switch to main branch |
| `git pull` | Get latest changes from remote |
---
## Common Issues
### "I accidentally committed to main"
Switch to a new branch and push from there:
```powershell
git switch -c feature/<your-name>
git push -u origin feature/<your-name>
```
### "My PR has conflicts"
1. Update your branch with latest main:
```powershell
git switch main
git pull
git switch feature/<your-name>
git merge main
```
2. Resolve conflicts in VS Code
3. Commit and push again
### "I need to make more changes to my PR"
Just commit and push to the same branch - the PR updates automatically:
```powershell
git add .
git commit -m "fix: address review feedback"
git push
```

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 165 KiB

View File

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

View File

@@ -1,199 +0,0 @@
# Module 11: Stash
## Learning Objectives
By the end of this module, you will:
- Understand what git stash is and when to use it
- Temporarily save work without committing
- Switch between branches without losing uncommitted changes
- Manage multiple stashes
- Apply and remove stashed changes
- Understand the difference between stash pop and stash apply
## Challenge Description
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
Your task is to:
1. Stash your incomplete work
2. Switch to the main branch
3. Fix the urgent bug and commit it
4. Return to your feature branch
5. Restore your stashed changes
6. Complete your feature and commit it
## Key Concepts
### What is Git Stash?
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
### When to Use Stash
Use stash when you need to:
- Switch branches but have uncommitted changes
- Pull updates from remote but have local modifications
- Quickly test something on a clean working directory
- Save work temporarily without creating a commit
- Context-switch between tasks
### Stash vs Commit
**Stash:**
- Temporary storage
- Not part of project history
- Can be applied to different branches
- Easy to discard if not needed
- Local only (not pushed to remote)
**Commit:**
- Permanent part of history
- Creates a snapshot in the project timeline
- Associated with a specific branch
- Should be meaningful and complete
- Can be pushed to remote
### The Stash Stack
Git stash works like a stack (LIFO - Last In, First Out):
```
stash@{0} <- Most recent stash (top of stack)
stash@{1}
stash@{2} <- Oldest stash
```
You can have multiple stashes and apply any of them.
## Useful Commands
```bash
# Stash current changes
git stash
git stash save "description" # With a descriptive message
# Stash including untracked files
git stash -u
# List all stashes
git stash list
# Apply most recent stash and remove it from stack
git stash pop
# Apply most recent stash but keep it in stack
git stash apply
# Apply a specific stash
git stash apply stash@{1}
# Show what's in a stash
git stash show
git stash show -p # Show full diff
# Drop (delete) a stash
git stash drop stash@{0}
# Clear all stashes
git stash clear
# Create a branch from a stash
git stash branch new-branch-name
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- The bug fix commit exists on main
- Your feature is completed on the feature branch
- Changes were properly stashed and restored
- No uncommitted changes remain
## Challenge Steps
1. Navigate to the challenge directory
2. You're on feature-login with uncommitted changes
3. Check status: `git status` (you'll see modified files)
4. Stash your changes: `git stash save "WIP: login feature"`
5. Verify working directory is clean: `git status`
6. Switch to main: `git switch main`
7. View the bug in app.js and fix it (remove the incorrect line)
8. Commit the fix: `git add app.js && git commit -m "Fix critical security bug"`
9. Switch back to feature: `git switch feature-login`
10. Restore your work: `git stash pop`
11. Complete the feature (the TODOs in login.js)
12. Commit your completed feature
13. Run verification
## Tips
- Always use `git stash save "message"` to describe what you're stashing
- Use `git stash list` to see all your stashes
- `git stash pop` applies and removes the stash (use this most often)
- `git stash apply` keeps the stash (useful if you want to apply it to multiple branches)
- Stashes are local - they don't get pushed to remote repositories
- You can stash even if you have changes to different files
- Stash before pulling to avoid merge conflicts
- Use `git stash show -p` to preview what's in a stash before applying
## Common Stash Scenarios
### Scenario 1: Quick Branch Switch
```bash
# Working on feature, need to switch to main
git stash
git switch main
# Do work on main
git switch feature
git stash pop
```
### Scenario 2: Pull with Local Changes
```bash
# You have local changes but need to pull
git stash
git pull
git stash pop
# Resolve any conflicts
```
### Scenario 3: Experimental Changes
```bash
# Try something experimental
git stash # Save current work
# Make experimental changes
# Decide you don't like it
git restore . # Discard experiment
git stash pop # Restore original work
```
### Scenario 4: Apply to Multiple Branches
```bash
# Same fix needed on multiple branches
git stash
git switch branch1
git stash apply
git commit -am "Apply fix"
git switch branch2
git stash apply
git commit -am "Apply fix"
git stash drop # Clean up when done
```
## Stash Conflicts
If applying a stash causes conflicts:
1. Git will mark the conflicts in your files
2. Resolve conflicts manually (like merge conflicts)
3. Stage the resolved files: `git add <file>`
4. The stash is automatically dropped after successful pop
5. If you used `apply`, manually drop it: `git stash drop`
## What You'll Learn
Git stash is an essential tool for managing context switches in your daily workflow. It lets you maintain a clean working directory while preserving incomplete work, making it easy to handle interruptions, urgent fixes, and quick branch switches. Mastering stash makes you more efficient and helps avoid the temptation to make "WIP" commits just to switch branches. Think of stash as your temporary workspace that follows you around.

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,130 +0,0 @@
# Module 07: Rebasing
## Learning Objectives
By the end of this module, you will:
- Understand what rebasing is and how it works
- Know the difference between merge and rebase
- Perform a rebase to integrate changes from one branch to another
- Understand when to use rebase vs merge
- Know the golden rule of rebasing
## Challenge Description
You have a repository with a `main` branch and a `feature` branch. While you were working on the feature branch, new commits were added to `main`. You want to incorporate those changes into your feature branch while maintaining a clean, linear history.
Your task is to:
1. Review the current commit history
2. Rebase the `feature` branch onto `main`
3. Verify that the history is now linear
## Key Concepts
### What is Rebasing?
Rebasing is the process of moving or combining a sequence of commits to a new base commit. Instead of creating a merge commit like `git merge` does, rebasing rewrites the commit history by replaying your commits on top of another branch.
### Rebase vs Merge
**Merge:**
```
A---B---C feature
/ \
D---E---F---G main
```
Creates a merge commit (G) that ties the histories together.
**Rebase:**
```
A'--B'--C' feature
/
D---E---F main
```
Replays commits A, B, C on top of F, creating new commits A', B', C' with the same changes but different commit hashes.
### Benefits of Rebasing
- **Cleaner history**: Linear history is easier to read and understand
- **Simpler log**: No merge commits cluttering the history
- **Easier bisecting**: Finding bugs with `git bisect` is simpler with linear history
### The Golden Rule of Rebasing
**Never rebase commits that have been pushed to a public/shared repository.**
Why? Because rebasing rewrites history by creating new commits. If others have based work on the original commits, rebasing will cause serious problems for collaborators.
**Safe to rebase:**
- Local commits not yet pushed
- Feature branches you're working on alone
- Cleaning up your work before creating a pull request
**Never rebase:**
- Commits already pushed to a shared branch (like `main` or `develop`)
- Commits that others might have based work on
## Useful Commands
```bash
# Rebase current branch onto another branch
git rebase <branch-name>
# View commit history as a graph
git log --oneline --graph --all
# If conflicts occur during rebase:
# 1. Resolve conflicts in files
# 2. Stage the resolved files
git add <file>
# 3. Continue the rebase
git rebase --continue
# Abort a rebase if something goes wrong
git rebase --abort
# Check which branch you're on
git branch
# Switch to a branch
git switch <branch-name>
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- You're on the feature branch
- The rebase was completed successfully
- The history is linear (no merge commits)
- All commits from both branches are present
## Tips
- Always check your commit graph with `git log --oneline --graph --all` before and after rebasing
- If you encounter conflicts during rebase, resolve them just like merge conflicts
- Use `git rebase --abort` if you want to cancel the rebase and start over
- Rebasing rewrites history, so the commit hashes will change
- Only rebase local commits that haven't been shared with others
## When to Use Rebase vs Merge
**Use Rebase when:**
- You want a clean, linear history
- Working on a local feature branch that hasn't been shared
- Updating your feature branch with the latest changes from main
- You want to clean up commits before submitting a pull request
**Use Merge when:**
- Working on a shared/public branch
- You want to preserve the complete history including when branches diverged
- You're merging a completed feature into main
- You want to be safe and avoid rewriting history
## What You'll Learn
Rebasing is a powerful tool for maintaining a clean project history. While merging is safer and preserves exact history, rebasing creates a more readable linear timeline. Understanding both techniques and knowing when to use each is essential for effective Git workflow management.

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the rebasing challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
}
# Run setup script
Write-Host "Running setup script...`n" -ForegroundColor Cyan
& ".\setup.ps1"

View File

@@ -1,123 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the rebasing challenge environment.
.DESCRIPTION
Creates a Git repository with diverged branches to practice rebasing.
The feature branch needs to be rebased onto main.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Create initial file and commit
$readme = @"
# My Project
A sample project for learning Git rebasing.
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Initial commit" | Out-Null
# Create and switch to feature branch
git checkout -b feature | Out-Null
# Add commits on feature branch
$feature1 = @"
# My Project
A sample project for learning Git rebasing.
## Features
- Feature A: User authentication
"@
Set-Content -Path "README.md" -Value $feature1
git add README.md
git commit -m "Add feature A" | Out-Null
$feature2 = @"
# My Project
A sample project for learning Git rebasing.
## Features
- Feature A: User authentication
- Feature B: Data validation
"@
Set-Content -Path "README.md" -Value $feature2
git add README.md
git commit -m "Add feature B" | Out-Null
# Switch back to main and add commits (simulating other work happening on main)
git checkout main | Out-Null
$main1 = @"
# My Project
A sample project for learning Git rebasing.
## Installation
Run \`npm install\` to install dependencies.
"@
Set-Content -Path "README.md" -Value $main1
git add README.md
git commit -m "Add installation instructions" | Out-Null
$main2 = @"
# My Project
A sample project for learning Git rebasing.
## Installation
Run \`npm install\` to install dependencies.
## Configuration
Copy \`config.example.json\` to \`config.json\` and update settings.
"@
Set-Content -Path "README.md" -Value $main2
git add README.md
git commit -m "Add configuration instructions" | Out-Null
# Switch back to feature branch
git checkout feature | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou are now on the 'feature' branch." -ForegroundColor Cyan
Write-Host "The 'main' branch has new commits that aren't in your feature branch." -ForegroundColor Cyan
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. View the commit history: git log --oneline --graph --all" -ForegroundColor White
Write-Host "3. Rebase the 'feature' branch onto 'main'" -ForegroundColor White
Write-Host "4. View the history again to see the linear result" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -1,154 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the rebasing challenge solution.
.DESCRIPTION
Checks that the user successfully rebased the feature branch onto main,
resulting in a clean, linear history.
#>
Set-Location "challenge" -ErrorAction SilentlyContinue
# Check if challenge directory exists
if (-not (Test-Path "../verify.ps1")) {
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
exit 1
}
if (-not (Test-Path ".")) {
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
Set-Location ..
exit 1
}
Write-Host "Verifying your solution..." -ForegroundColor Cyan
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check current branch
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -ne "feature") {
Write-Host "[FAIL] You should be on the 'feature' branch." -ForegroundColor Red
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
Write-Host "Hint: Use 'git checkout feature' to switch to feature branch" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check if there's an ongoing rebase
if (Test-Path ".git/rebase-merge") {
Write-Host "[FAIL] Rebase is not complete. There may be unresolved conflicts." -ForegroundColor Red
Write-Host "Hint: Resolve any conflicts, then use:" -ForegroundColor Yellow
Write-Host " git add <file>" -ForegroundColor White
Write-Host " git rebase --continue" -ForegroundColor White
Write-Host "Or abort with: git rebase --abort" -ForegroundColor White
Set-Location ..
exit 1
}
# Get all commits on feature branch
$featureCommits = git log --oneline feature 2>$null
if (-not $featureCommits) {
Write-Host "[FAIL] No commits found on feature branch." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check commit count (should be 5: 1 initial + 2 main + 2 feature)
$commitCount = (git rev-list --count feature 2>$null)
if ($commitCount -ne 5) {
Write-Host "[FAIL] Expected 5 commits on feature branch, found $commitCount" -ForegroundColor Red
Write-Host "Hint: The rebased feature branch should contain:" -ForegroundColor Yellow
Write-Host " - 1 initial commit" -ForegroundColor White
Write-Host " - 2 commits from main" -ForegroundColor White
Write-Host " - 2 commits from feature" -ForegroundColor White
Set-Location ..
exit 1
}
# Get commit messages in reverse chronological order (newest first)
$commits = git log --pretty=format:"%s" feature 2>$null
# Convert to array and reverse to get chronological order (oldest first)
$commitArray = $commits -split "`n"
[array]::Reverse($commitArray)
# Check that commits are in the expected order after rebase
$expectedOrder = @(
"Initial commit",
"Add installation instructions",
"Add configuration instructions",
"Add feature A",
"Add feature B"
)
$orderCorrect = $true
for ($i = 0; $i -lt $expectedOrder.Length; $i++) {
if ($commitArray[$i] -ne $expectedOrder[$i]) {
$orderCorrect = $false
break
}
}
if (-not $orderCorrect) {
Write-Host "[FAIL] Commit order is incorrect after rebase." -ForegroundColor Red
Write-Host "Expected order (oldest to newest):" -ForegroundColor Yellow
$expectedOrder | ForEach-Object { Write-Host " $_" -ForegroundColor White }
Write-Host "`nActual order:" -ForegroundColor Yellow
$commitArray | ForEach-Object { Write-Host " $_" -ForegroundColor White }
Write-Host "`nHint: The feature commits should come AFTER the main commits" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that main branch still has only 3 commits (initial + 2 main commits)
$mainCommitCount = (git rev-list --count main 2>$null)
if ($mainCommitCount -ne 3) {
Write-Host "[FAIL] Main branch should have 3 commits, found $mainCommitCount" -ForegroundColor Red
Write-Host "Hint: You should rebase feature onto main, not the other way around" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that there are no merge commits (parent count should be 1 for all commits)
$mergeCommits = git log --merges --oneline feature 2>$null
if ($mergeCommits) {
Write-Host "[FAIL] Found merge commits in history. Rebasing should create a linear history." -ForegroundColor Red
Write-Host "Hint: Use 'git rebase main' instead of 'git merge main'" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Verify that feature branch contains all commits from main
$mainCommits = git log --pretty=format:"%s" main 2>$null
$featureCommitMessages = git log --pretty=format:"%s" feature 2>$null
foreach ($mainCommit in ($mainCommits -split "`n")) {
if ($featureCommitMessages -notcontains $mainCommit) {
Write-Host "[FAIL] Feature branch is missing commits from main." -ForegroundColor Red
Write-Host "Missing: $mainCommit" -ForegroundColor Yellow
Set-Location ..
exit 1
}
}
# Success!
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
Write-Host "- Rebased the feature branch onto main" -ForegroundColor White
Write-Host "- Created a clean, linear commit history" -ForegroundColor White
Write-Host "- Preserved all commits from both branches" -ForegroundColor White
Write-Host "`nYour feature branch now has a linear history!" -ForegroundColor Green
Write-Host "Run 'git log --oneline --graph --all' to see the result.`n" -ForegroundColor Cyan
Set-Location ..
exit 0

View File

@@ -1,159 +0,0 @@
# Module 08: Interactive Rebase
## Learning Objectives
By the end of this module, you will:
- Understand what interactive rebase is and when to use it
- Learn the different interactive rebase commands (pick, reword, squash, fixup, drop)
- Clean up commit history by squashing related commits
- Reword commit messages to be more descriptive
- Use reset and commit techniques to achieve similar results
## Challenge Description
You have a feature branch with messy commit history - multiple "WIP" commits, typos in commit messages, and commits that should be combined. Before submitting a pull request, you want to clean up this history.
Your task is to:
1. Review the current commit history with its messy commits
2. Combine the four feature commits into a single, well-described commit
3. Keep the initial commit unchanged
4. End up with a clean, professional commit history
## Key Concepts
### What is Interactive Rebase?
Interactive rebase (`git rebase -i`) is a powerful tool that lets you modify commit history. Unlike regular rebase which simply moves commits, interactive rebase lets you:
- Reorder commits
- Edit commit messages (reword)
- Combine multiple commits (squash/fixup)
- Split commits into smaller ones
- Delete commits entirely
- Edit the contents of commits
### Interactive Rebase Commands
When you run `git rebase -i HEAD~3`, Git opens an editor with a list of commits and commands:
```
pick abc1234 First commit
pick def5678 Second commit
pick ghi9012 Third commit
```
You can change `pick` to other commands:
- **pick**: Keep the commit as-is
- **reword**: Keep the commit but edit its message
- **edit**: Pause the rebase to amend the commit
- **squash**: Combine this commit with the previous one, keeping both messages
- **fixup**: Like squash, but discard this commit's message
- **drop**: Remove the commit entirely
### When to Use Interactive Rebase
Interactive rebase is perfect for:
- Cleaning up work-in-progress commits before creating a pull request
- Fixing typos in commit messages
- Combining related commits into logical units
- Removing debug commits or experimental code
- Creating a clean, professional history
**Remember**: Only rebase commits that haven't been pushed to a shared branch!
## Alternative Approach: Reset and Recommit
Since interactive rebase requires an interactive editor, this challenge uses an alternative approach that achieves the same result:
1. **Reset soft**: Move HEAD back while keeping changes staged
```bash
git reset --soft HEAD~4
```
This keeps all changes from the last 4 commits but "uncommits" them.
2. **Create a new commit**: Commit all changes with a clean message
```bash
git commit -m "Your new clean commit message"
```
This technique is useful when you want to combine multiple commits into one without using interactive rebase.
## Useful Commands
```bash
# View commit history
git log --oneline
# See the last N commits in detail
git log -n 5
# Reset to N commits back, keeping changes staged
git reset --soft HEAD~N
# Reset to N commits back, keeping changes unstaged
git reset --mixed HEAD~N
# Reset to N commits back, discarding all changes (DANGEROUS!)
git reset --hard HEAD~N
# Create a new commit
git commit -m "message"
# Amend the last commit
git commit --amend -m "new message"
# Interactive rebase (requires interactive editor)
git rebase -i HEAD~N
# Check current status
git status
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- You have exactly 2 commits total (initial + your combined feature commit)
- The feature commit contains all the changes from the original 4 feature commits
- The commit message is clean and descriptive
- All expected files are present
## Challenge Steps
1. Navigate to the challenge directory
2. View the current messy commit history: `git log --oneline`
3. Use `git reset --soft HEAD~4` to uncommit the last 4 commits while keeping changes
4. Check status with `git status` - you should see all changes staged
5. Create a new commit with a clean message: `git commit -m "Add user profile feature with validation"`
6. Verify with `git log --oneline` - you should now have clean history
7. Run the verification script
## Tips
- Always check your history with `git log --oneline` before and after
- `git reset --soft` is safer than `--hard` because it keeps your changes
- You can use `git reset --soft HEAD~N` where N is the number of commits to undo
- If you make a mistake, run `.\reset.ps1` to start over
- In real projects with interactive rebase, you would use `git rebase -i HEAD~N` and modify the commit list in the editor
## What is Git Reset?
`git reset` moves the current branch pointer to a different commit:
- **--soft**: Moves HEAD but keeps changes staged
- **--mixed** (default): Moves HEAD and unstages changes, but keeps them in working directory
- **--hard**: Moves HEAD and discards all changes (dangerous!)
Reset is useful for:
- Undoing commits while keeping changes (`--soft`)
- Unstaging files (`--mixed`)
- Discarding commits entirely (`--hard`)
## What You'll Learn
Cleaning up commit history is an important skill for professional development. Whether you use interactive rebase or reset-and-recommit, the goal is the same: create a clear, logical history that makes your code review easier and your project history more understandable. Messy WIP commits are fine during development, but cleaning them up before merging shows attention to detail and makes your codebase more maintainable.

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the interactive rebase challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
}
# Run setup script
Write-Host "Running setup script...`n" -ForegroundColor Cyan
& ".\setup.ps1"

View File

@@ -1,133 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the interactive rebase challenge environment.
.DESCRIPTION
Creates a Git repository with messy commit history that needs to be
cleaned up using reset and recommit techniques.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Create initial commit
$readme = @"
# User Management System
A simple user management application.
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Initial commit" | Out-Null
# Create messy commits that should be squashed
# Commit 1: WIP user profile
$userProfile = @"
class UserProfile:
def __init__(self, name, email):
self.name = name
self.email = email
"@
Set-Content -Path "user_profile.py" -Value $userProfile
git add user_profile.py
git commit -m "WIP: user profile" | Out-Null
# Commit 2: Add validation (typo in message)
$userProfileWithValidation = @"
class UserProfile:
def __init__(self, name, email):
self.name = name
self.email = email
def validate(self):
if not self.name or not self.email:
raise ValueError('Name and email are required')
return True
"@
Set-Content -Path "user_profile.py" -Value $userProfileWithValidation
git add user_profile.py
git commit -m "add validaton" | Out-Null # Intentional typo
# Commit 3: Fix validation
$userProfileFixed = @"
class UserProfile:
def __init__(self, name, email):
self.name = name
self.email = email
def validate(self):
if not self.name or not self.email:
raise ValueError('Name and email are required')
if '@' not in self.email:
raise ValueError('Invalid email format')
return True
"@
Set-Content -Path "user_profile.py" -Value $userProfileFixed
git add user_profile.py
git commit -m "fix validation bug" | Out-Null
# Commit 4: Add tests (another WIP commit)
$tests = @"
import unittest
from user_profile import UserProfile
class TestUserProfile(unittest.TestCase):
def test_validate_correct_user_data(self):
user = UserProfile('John', 'john@example.com')
self.assertTrue(user.validate())
def test_reject_missing_name(self):
user = UserProfile('', 'john@example.com')
with self.assertRaises(ValueError):
user.validate()
def test_reject_invalid_email(self):
user = UserProfile('John', 'invalid-email')
with self.assertRaises(ValueError):
user.validate()
if __name__ == '__main__':
unittest.main()
"@
Set-Content -Path "test_user_profile.py" -Value $tests
git add test_user_profile.py
git commit -m "WIP tests" | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have a repository with messy commit history:" -ForegroundColor Cyan
Write-Host "- WIP commits" -ForegroundColor Yellow
Write-Host "- Typos in commit messages" -ForegroundColor Yellow
Write-Host "- Multiple commits that should be combined" -ForegroundColor Yellow
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. View the messy history: git log --oneline" -ForegroundColor White
Write-Host "3. Use 'git reset --soft HEAD~4' to uncommit the last 4 commits" -ForegroundColor White
Write-Host "4. Create a single clean commit with a descriptive message" -ForegroundColor White
Write-Host " Example: git commit -m 'Add user profile feature with validation'" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -1,172 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the interactive rebase challenge solution.
.DESCRIPTION
Checks that the user successfully cleaned up the commit history
by combining multiple messy commits into a single clean commit.
#>
Set-Location "challenge" -ErrorAction SilentlyContinue
# Check if challenge directory exists
if (-not (Test-Path "../verify.ps1")) {
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
exit 1
}
if (-not (Test-Path ".")) {
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
Set-Location ..
exit 1
}
Write-Host "Verifying your solution..." -ForegroundColor Cyan
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check commit count (should be exactly 2: initial + combined feature commit)
$commitCount = (git rev-list --count HEAD 2>$null)
if ($commitCount -ne 2) {
Write-Host "[FAIL] Expected exactly 2 commits, found $commitCount" -ForegroundColor Red
if ($commitCount -gt 2) {
Write-Host "Hint: You have too many commits. Use 'git reset --soft HEAD~N' to combine them." -ForegroundColor Yellow
Write-Host " Where N is the number of commits to undo (should be 4 in this case)." -ForegroundColor Yellow
} else {
Write-Host "Hint: You may have reset too far back. Run ../reset.ps1 to start over." -ForegroundColor Yellow
}
Set-Location ..
exit 1
}
# Get commit messages
$commits = git log --pretty=format:"%s" 2>$null
$commitArray = $commits -split "`n"
# Check that first commit is still the initial commit
if ($commitArray[1] -ne "Initial commit") {
Write-Host "[FAIL] The initial commit should remain unchanged." -ForegroundColor Red
Write-Host "Expected first commit: 'Initial commit'" -ForegroundColor Yellow
Write-Host "Found: '$($commitArray[1])'" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that the second commit message is clean (not WIP, not with typos)
$featureCommit = $commitArray[0]
if ($featureCommit -match "WIP|wip") {
Write-Host "[FAIL] Commit message still contains 'WIP'." -ForegroundColor Red
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
Write-Host "Hint: Create a clean, descriptive commit message." -ForegroundColor Yellow
Set-Location ..
exit 1
}
if ($featureCommit -match "validaton") {
Write-Host "[FAIL] Commit message contains a typo ('validaton')." -ForegroundColor Red
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
Write-Host "Hint: Use a properly spelled commit message." -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that commit message is not empty and has reasonable length
if ($featureCommit.Length -lt 10) {
Write-Host "[FAIL] Commit message is too short. Be more descriptive." -ForegroundColor Red
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that required files exist
if (-not (Test-Path "user_profile.py")) {
Write-Host "[FAIL] user_profile.py not found." -ForegroundColor Red
Set-Location ..
exit 1
}
if (-not (Test-Path "test_user_profile.py")) {
Write-Host "[FAIL] test_user_profile.py not found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that user_profile.py contains all expected features
$userProfileContent = Get-Content "user_profile.py" -Raw
# Should have the class
if ($userProfileContent -notmatch "class UserProfile") {
Write-Host "[FAIL] user_profile.py should contain UserProfile class." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should have validation method
if ($userProfileContent -notmatch "def validate\(") {
Write-Host "[FAIL] user_profile.py should contain validate() method." -ForegroundColor Red
Set-Location ..
exit 1
}
# Should have email format validation (the final fix from commit 3)
if ($userProfileContent -notmatch "'@'.*in.*email|email.*in.*'@'") {
Write-Host "[FAIL] user_profile.py should contain email format validation." -ForegroundColor Red
Write-Host "Hint: Make sure all changes from all 4 commits are included." -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check that test file has content
$testContent = Get-Content "test_user_profile.py" -Raw
if ($testContent -notmatch "class.*TestUserProfile") {
Write-Host "[FAIL] test_user_profile.py should contain TestUserProfile tests." -ForegroundColor Red
Set-Location ..
exit 1
}
# Check that we have at least 3 test cases
$testMatches = ([regex]::Matches($testContent, "def test_")).Count
if ($testMatches -lt 3) {
Write-Host "[FAIL] test_user_profile.py should contain at least 3 test cases." -ForegroundColor Red
Set-Location ..
exit 1
}
# Verify that the latest commit contains changes to both files
$filesInLastCommit = git diff-tree --no-commit-id --name-only -r HEAD 2>$null
if ($filesInLastCommit -notcontains "user_profile.py") {
Write-Host "[FAIL] The feature commit should include user_profile.py" -ForegroundColor Red
Set-Location ..
exit 1
}
if ($filesInLastCommit -notcontains "test_user_profile.py") {
Write-Host "[FAIL] The feature commit should include test_user_profile.py" -ForegroundColor Red
Set-Location ..
exit 1
}
# Success!
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
Write-Host "- Combined 4 messy commits into 1 clean commit" -ForegroundColor White
Write-Host "- Created a descriptive commit message" -ForegroundColor White
Write-Host "- Preserved all code changes" -ForegroundColor White
Write-Host "- Cleaned up the commit history" -ForegroundColor White
Write-Host "`nYour commit history is now clean and professional!" -ForegroundColor Green
Write-Host "Run 'git log --oneline' to see the result.`n" -ForegroundColor Cyan
Write-Host "Key takeaway: Clean commit history makes code review easier" -ForegroundColor Yellow
Write-Host "and your project more maintainable.`n" -ForegroundColor Yellow
Set-Location ..
exit 0

View File

@@ -1,208 +0,0 @@
# Module 13: Worktrees
## Learning Objectives
By the end of this module, you will:
- Understand what Git worktrees are and when to use them
- Create and manage multiple working directories for the same repository
- Work on multiple branches simultaneously
- Understand the benefits of worktrees over stashing or cloning
- Remove and clean up worktrees
## Challenge Description
You're working on a feature when an urgent bug report comes in. Instead of stashing your work or creating a separate clone, you'll use Git worktrees to work on both the feature and the bugfix simultaneously in different directories.
Your task is to:
1. Create a worktree for the bugfix on a separate branch
2. Fix the bug in the worktree
3. Commit and verify the fix
4. Continue working on your feature in the main working directory
5. Clean up the worktree when done
## Key Concepts
### What are Git Worktrees?
A worktree is an additional working directory attached to the same repository. Each worktree can have a different branch checked out, allowing you to work on multiple branches simultaneously without switching.
### Traditional Workflow vs Worktrees
**Traditional (switching branches):**
```
main-repo/
- Switch to bugfix branch
- Fix bug
- Switch back to feature branch
- Continue feature work
- (Requires stashing or committing incomplete work)
```
**With Worktrees:**
```
main-repo/ <- feature branch
worktrees/bugfix/ <- bugfix branch
Work in both simultaneously!
```
### Why Use Worktrees?
**Advantages:**
- Work on multiple branches at the same time
- No need to stash or commit incomplete work
- Each worktree has its own working directory and index
- Share the same Git history (one `.git` directory)
- Faster than cloning the entire repository
- Perfect for code reviews, comparisons, or parallel development
**Use Cases:**
- Urgent bug fixes while working on a feature
- Code reviews (checkout PR in separate worktree)
- Comparing implementations side by side
- Running tests on one branch while coding on another
- Building different versions simultaneously
## Useful Commands
```bash
# List all worktrees
git worktree list
# Add a new worktree
git worktree add <path> <branch>
git worktree add ../bugfix bugfix-branch
# Create new branch in worktree
git worktree add <path> -b <new-branch>
git worktree add ../feature-new -b feature-new
# Remove a worktree
git worktree remove <path>
git worktree remove ../bugfix
# Prune stale worktree information
git worktree prune
# Move a worktree
git worktree move <old-path> <new-path>
# Lock a worktree (prevent deletion)
git worktree lock <path>
git worktree unlock <path>
```
## Verification
Run the verification script to check your solution:
```bash
.\verify.ps1
```
The verification will check that:
- You created a worktree for the bugfix
- The bug was fixed and committed in the worktree
- Your feature work continued in the main directory
- Both branches have the expected changes
## Challenge Steps
1. Navigate to the challenge directory
2. You're in main-repo with a feature branch checked out
3. View current worktrees: `git worktree list`
4. Create a worktree for bugfix: `git worktree add ../bugfix-worktree -b bugfix`
5. Navigate to the worktree: `cd ../bugfix-worktree`
6. Fix the bug in calculator.js (fix the divide by zero check)
7. Commit the fix: `git add . && git commit -m "Fix divide by zero bug"`
8. Go back to main repo: `cd ../main-repo`
9. Continue working on your feature
10. Add a new method to calculator.js
11. Commit your feature
12. List worktrees: `git worktree list`
13. Remove the worktree: `git worktree remove ../bugfix-worktree`
14. Run verification
## Tips
- Worktree paths are typically siblings of your main repo (use `../worktree-name`)
- Each worktree must have a different branch checked out
- Can't checkout the same branch in multiple worktrees
- The main `.git` directory is shared, so commits in any worktree are visible everywhere
- Worktrees are listed in `.git/worktrees/`
- Use `git worktree remove` to clean up, or just delete the directory and run `git worktree prune`
- Worktrees persist across restarts until explicitly removed
## Common Worktree Workflows
### Urgent Bugfix
```bash
# Currently on feature branch with uncommitted changes
git worktree add ../hotfix -b hotfix
cd ../hotfix
# Fix the bug
git add .
git commit -m "Fix critical bug"
git push origin hotfix
cd ../main-repo
# Continue working on feature
```
### Code Review
```bash
# Review a pull request without switching branches
git fetch origin pull/123/head:pr-123
git worktree add ../review-pr-123 pr-123
cd ../review-pr-123
# Review code, test it
# Run: npm test, npm start, etc.
cd ../main-repo
git worktree remove ../review-pr-123
```
### Parallel Development
```bash
# Work on two features simultaneously
git worktree add ../feature-a -b feature-a
git worktree add ../feature-b -b feature-b
# Terminal 1
cd feature-a && code .
# Terminal 2
cd feature-b && code .
```
### Build Comparison
```bash
# Compare builds between branches
git worktree add ../release-build release-v2.0
cd ../release-build
npm run build
# Test production build
# Meanwhile, continue development in main repo
```
## Worktree vs Other Approaches
### vs Stashing
- **Stash**: Temporary, one at a time, requires branch switching
- **Worktree**: Persistent, multiple simultaneously, no switching
### vs Cloning
- **Clone**: Full copy, separate `.git`, uses more disk space
- **Worktree**: Shared `.git`, less disk space, instant sync
### vs Branch Switching
- **Switching**: Requires clean working directory, one branch at a time
- **Worktree**: Keep dirty working directory, multiple branches active
## What You'll Learn
Git worktrees are a powerful but underutilized feature that can significantly improve your workflow. They eliminate the need for constant branch switching, stashing, or maintaining multiple clones. Whether you're handling urgent fixes, reviewing code, or comparing implementations, worktrees provide a clean and efficient solution. Once you understand worktrees, you'll find many situations where they're the perfect tool for the job.

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the worktrees challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
}
# Run setup script
Write-Host "Running setup script...`n" -ForegroundColor Cyan
& ".\setup.ps1"

View File

@@ -1,123 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the worktrees challenge environment.
.DESCRIPTION
Creates a Git repository with a feature in progress, ready for
demonstrating the use of worktrees for parallel work.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Create main repository
New-Item -ItemType Directory -Path "main-repo" | Out-Null
Set-Location "main-repo"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Create initial calculator with a bug
$calculator = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
# BUG: No division by zero check!
def divide(self, a, b):
return a / b
"@
Set-Content -Path "calculator.py" -Value $calculator
git add calculator.py
git commit -m "Initial calculator implementation" | Out-Null
$readme = @"
# Calculator Project
A simple calculator with basic operations.
## Features
- Addition
- Subtraction
- Multiplication
- Division (has a bug!)
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Add README" | Out-Null
# Create feature branch and start working on it
git checkout -b feature-advanced-math | Out-Null
# Add work in progress on feature branch
$calculatorWithFeature = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
# BUG: No division by zero check!
def divide(self, a, b):
return a / b
# New feature: power function (work in progress)
def power(self, a, b):
return a ** b
# TODO: Add square root function
# TODO: Add logarithm function
"@
Set-Content -Path "calculator.py" -Value $calculatorWithFeature
git add calculator.py
git commit -m "Add power function (WIP: more math functions coming)" | Out-Null
# Return to challenge directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nSituation:" -ForegroundColor Cyan
Write-Host "You're working on the 'feature-advanced-math' branch" -ForegroundColor White
Write-Host "You have plans to add more math functions (see TODOs)" -ForegroundColor White
Write-Host "`nUrgent: A critical bug was discovered in the divide function!" -ForegroundColor Red
Write-Host "It doesn't check for division by zero." -ForegroundColor Red
Write-Host "`nInstead of stashing your feature work, use a worktree:" -ForegroundColor Yellow
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to main-repo: cd challenge/main-repo" -ForegroundColor White
Write-Host "2. Create a worktree: git worktree add ../bugfix-worktree -b bugfix" -ForegroundColor White
Write-Host "3. Go to worktree: cd ../bugfix-worktree" -ForegroundColor White
Write-Host "4. Fix the bug in calculator.py:" -ForegroundColor White
Write-Host " Add a check: if (b === 0) throw new Error('Division by zero');" -ForegroundColor White
Write-Host "5. Commit the fix: git add . && git commit -m 'Fix divide by zero bug'" -ForegroundColor White
Write-Host "6. Return to main-repo: cd ../main-repo" -ForegroundColor White
Write-Host "7. Complete your feature: Add square root method to calculator.py" -ForegroundColor White
Write-Host "8. Commit: git add . && git commit -m 'Add square root function'" -ForegroundColor White
Write-Host "9. Clean up worktree: git worktree remove ../bugfix-worktree" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -1,165 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the worktrees challenge solution.
.DESCRIPTION
Checks that the user successfully used worktrees to fix a bug
while continuing work on a feature.
#>
Set-Location "challenge" -ErrorAction SilentlyContinue
# Check if challenge directory exists
if (-not (Test-Path "../verify.ps1")) {
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
exit 1
}
if (-not (Test-Path ".")) {
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
Set-Location ..
exit 1
}
Write-Host "Verifying your solution..." -ForegroundColor Cyan
# Check if main-repo exists
if (-not (Test-Path "main-repo")) {
Write-Host "[FAIL] main-repo directory not found." -ForegroundColor Red
Set-Location ..
exit 1
}
Set-Location "main-repo"
# Check if it's a git repository
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] main-repo is not a git repository." -ForegroundColor Red
Set-Location ../..
exit 1
}
# Check if bugfix branch exists
$branches = git branch --all 2>$null
if ($branches -notmatch "bugfix") {
Write-Host "[FAIL] bugfix branch not found." -ForegroundColor Red
Write-Host "Hint: Create a worktree with: git worktree add ../bugfix-worktree -b bugfix" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
Write-Host "[PASS] Bugfix branch exists!" -ForegroundColor Green
# Check bugfix branch for the fix
git checkout bugfix 2>$null | Out-Null
if (-not (Test-Path "calculator.py")) {
Write-Host "[FAIL] calculator.py not found on bugfix branch." -ForegroundColor Red
Set-Location ../..
exit 1
}
$bugfixCalc = Get-Content "calculator.py" -Raw
# Check if division by zero check was added
if ($bugfixCalc -notmatch "b === 0|b == 0|division by zero|divide by zero") {
Write-Host "[FAIL] Division by zero check not found in bugfix branch." -ForegroundColor Red
Write-Host "Hint: Add a check in the divide method to prevent division by zero" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
# Check for commit on bugfix branch
$bugfixCommits = git log --pretty=format:"%s" bugfix 2>$null
if ($bugfixCommits -notmatch "bug|fix|division|divide") {
Write-Host "[FAIL] No bugfix commit found on bugfix branch." -ForegroundColor Red
Write-Host "Hint: Commit your fix with a descriptive message" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
Write-Host "[PASS] Bug fixed on bugfix branch!" -ForegroundColor Green
# Check feature branch for continued work
git checkout feature-advanced-math 2>$null | Out-Null
$featureCalc = Get-Content "calculator.py" -Raw
# Check if square root function was added
if ($featureCalc -notmatch "sqrt|squareRoot") {
Write-Host "[FAIL] Square root function not found on feature branch." -ForegroundColor Red
Write-Host "Hint: Add the square root method to calculator.py on feature-advanced-math branch" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
# Check that feature work was committed
$featureCommits = git log --pretty=format:"%s" feature-advanced-math 2>$null
$featureCommitArray = $featureCommits -split "`n"
# Should have at least 3 commits: initial + README + power + sqrt
if ($featureCommitArray.Count -lt 4) {
Write-Host "[FAIL] Not enough commits on feature branch." -ForegroundColor Red
Write-Host "Expected: initial, README, power, and square root commits" -ForegroundColor Yellow
Write-Host "Found $($featureCommitArray.Count) commits" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
# Check if the latest feature commit is about square root
if ($featureCommitArray[0] -notmatch "sqrt|square|root") {
Write-Host "[FAIL] Latest commit on feature branch should be about square root." -ForegroundColor Red
Write-Host "Latest commit: $($featureCommitArray[0])" -ForegroundColor Yellow
Set-Location ../..
exit 1
}
Write-Host "[PASS] Feature work completed!" -ForegroundColor Green
# Check if worktree was cleaned up (bugfix-worktree should not exist or be removed)
Set-Location ..
$worktreeStillExists = Test-Path "bugfix-worktree"
if ($worktreeStillExists) {
Write-Host "[WARNING] bugfix-worktree directory still exists." -ForegroundColor Yellow
Write-Host "Hint: Clean up with: git worktree remove ../bugfix-worktree" -ForegroundColor Yellow
# Don't fail on this, just warn
}
# Check worktree list
Set-Location "main-repo"
$worktrees = git worktree list 2>$null
# Verify that the concept was understood (they should have created the worktree at some point)
# We can check this by looking for the bugfix branch existence
if ($branches -notmatch "bugfix") {
Write-Host "[FAIL] No evidence of worktree usage." -ForegroundColor Red
Set-Location ../..
exit 1
}
# Success!
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
Write-Host "- Created a worktree for the bugfix" -ForegroundColor White
Write-Host "- Fixed the division by zero bug" -ForegroundColor White
Write-Host "- Committed the fix on the bugfix branch" -ForegroundColor White
Write-Host "- Continued feature work in parallel" -ForegroundColor White
Write-Host "- Added the square root function" -ForegroundColor White
Write-Host "- Committed the feature work" -ForegroundColor White
if (-not $worktreeStillExists) {
Write-Host "- Cleaned up the worktree" -ForegroundColor White
}
Write-Host "`nYou now understand Git worktrees!" -ForegroundColor Green
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
Write-Host "Worktrees let you work on multiple branches simultaneously" -ForegroundColor White
Write-Host "without stashing, switching, or cloning the repository.`n" -ForegroundColor White
Set-Location ../..
exit 0

View File

@@ -1,237 +0,0 @@
# Module 14: Bisect - Finding Bugs with Binary Search
## Learning Objectives
By the end of this module, you will:
- Understand what git bisect is and when to use it
- Use binary search to find the commit that introduced a bug
- Mark commits as good or bad during bisection
- Automate bisect with test scripts
- Understand the efficiency of binary search for debugging
## Challenge Description
A bug has appeared in your calculator application, but you don't know which commit introduced it. The project has many commits, and manually checking each one would take too long. You'll use `git bisect` to efficiently find the culprit commit using binary search.
Your task is to:
1. Start a bisect session
2. Mark the current commit as bad (bug exists)
3. Mark an old commit as good (bug didn't exist)
4. Test commits and mark them good or bad
5. Let Git find the first bad commit
6. Identify what change introduced the bug
## Key Concepts
### What is Git Bisect?
Git bisect uses binary search to find the commit that introduced a bug. Instead of checking every commit linearly, it cuts the search space in half with each test, making it extremely efficient.
### Binary Search Efficiency
**Linear Search (manual checking):**
- 100 commits = up to 100 tests
- 1000 commits = up to 1000 tests
**Binary Search (bisect):**
- 100 commits = ~7 tests
- 1000 commits = ~10 tests
Formula: log₂(n) tests needed for n commits
### How Bisect Works
```
Commits: A---B---C---D---E---F---G---H
✓ ✓ ✓ ? ? ? ? ✗
1. Start: Mark H (bad) and A (good)
2. Git checks middle: E
3. You test E: bad ✗
Commits: A---B---C---D---E
✓ ✓ ✓ ? ✗
4. Git checks middle: C
5. You test C: good ✓
Commits: C---D---E
✓ ? ✗
6. Git checks: D
7. You test D: bad ✗
Result: D is the first bad commit!
```
### When to Use Bisect
Use bisect when:
- You know a bug exists now but didn't exist in the past
- You have many commits to check
- You can reliably test for the bug
- You want to find exactly when something broke
## Useful Commands
```powershell
# Start bisect session
git bisect start
# Mark current commit as bad
git bisect bad
# Mark a commit as good
git bisect good <commit-hash>
git bisect good HEAD~10
# After testing current commit
git bisect good # This commit is fine
git bisect bad # This commit has the bug
# Skip a commit (if you can't test it)
git bisect skip
# End bisect session and return to original state
git bisect reset
# Visualize bisect process
git bisect visualize
git bisect view
# Automate with a test script
git bisect run <test-script>
git bisect run npm test
git bisect run ./test.sh
# Show bisect log
git bisect log
```
## Verification
Run the verification script to check your solution:
```powershell
.\verify.ps1
```
The verification will check that:
- You completed a bisect session
- You identified the correct commit that introduced the bug
- You understand which change caused the problem
## Challenge Steps
1. Navigate to the challenge directory
2. View the bug: run the calculator and see it fails
3. Start bisect: `git bisect start`
4. Mark current as bad: `git bisect bad`
5. Mark old commit as good: `git bisect good HEAD~10`
6. Git will checkout a middle commit
7. Test the current commit (run the test or check manually)
8. Mark it: `git bisect good` or `git bisect bad`
9. Repeat testing until Git identifies the bad commit
10. Note the commit hash and message
11. End bisect: `git bisect reset`
12. Check the identified commit: `git show <bad-commit-hash>`
13. Create a file named `bug-commit.txt` with the bad commit hash
14. Run verification
## Tips
- Always start with a known good commit (far enough back)
- Keep a clear way to test each commit (script or manual steps)
- Use `git bisect log` to see your progress
- `git bisect reset` returns you to your original state
- You can bisect on any criteria, not just bugs (performance, features, etc.)
- Automate with `git bisect run` for faster results
- Each bisect step cuts remaining commits in half
- Skip commits you can't build/test with `git bisect skip`
## Manual vs Automated Bisect
### Manual Bisect
```powershell
git bisect start
git bisect bad
git bisect good HEAD~20
# For each commit Git checks out:
npm test
git bisect good # or bad
git bisect reset
```
### Automated Bisect
```powershell
git bisect start
git bisect bad
git bisect good HEAD~20
git bisect run npm test
# Git automatically tests each commit
git bisect reset
```
The test script should exit with:
- 0 for good (test passes)
- 1-127 (except 125) for bad (test fails)
- 125 for skip (can't test this commit)
## Bisect Workflow Example
### Finding a Performance Regression
```powershell
# App is slow now, was fast 50 commits ago
git bisect start
git bisect bad
git bisect good HEAD~50
# Create test script
@'
$output = npm start 2>&1 | Select-String "Started in"
if ($output -match "Started in (\d+)") {
if ([int]$Matches[1] -gt 5000) { exit 1 } # Slow
else { exit 0 } # Fast
}
exit 1
'@ | Out-File -FilePath test.ps1
git bisect run pwsh test.ps1
# Git finds the commit that made it slow
```
### Finding When a Feature Broke
```powershell
git bisect start
git bisect bad
git bisect good v1.0.0 # Last known good version
# For each commit
npm test -- user-login.test.js
git bisect good # or bad
# Found! Commit abc123 broke login
git show abc123
```
## Common Bisect Pitfalls
### Pitfall 1: Testing Incorrectly
- Make sure your test is consistent
- Automate when possible to avoid human error
- Use the same test for every commit
### Pitfall 2: Wrong Good Commit
- If the "good" commit actually has the bug, bisect will fail
- Choose a commit you're confident was working
### Pitfall 3: Multiple Bugs
- Bisect finds one commit at a time
- If multiple bugs exist, they might confuse the search
- Fix found bugs and bisect again for others
## What You'll Learn
Git bisect is a powerful debugging tool that turns a tedious manual search into an efficient automated process. By leveraging binary search, you can quickly pinpoint problematic commits even in repositories with thousands of commits. This is invaluable for debugging regressions, performance issues, or any situation where something that worked before is now broken. Mastering bisect makes you a more effective debugger and shows deep Git proficiency.

View File

@@ -1,22 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the bisect challenge environment.
.DESCRIPTION
Removes the existing challenge directory and runs setup.ps1
to create a fresh challenge environment.
#>
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Remove-Item -Path "challenge" -Recurse -Force
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
}
# Run setup script
Write-Host "Running setup script...`n" -ForegroundColor Cyan
& ".\setup.ps1"

View File

@@ -1,311 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the bisect challenge environment.
.DESCRIPTION
Creates a Git repository with multiple commits where a bug is
introduced in one of them. Students use bisect to find it.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Commit 1: Initial calculator
$calc1 = @"
class Calculator:
def add(self, a, b):
return a + b
"@
Set-Content -Path "calculator.py" -Value $calc1
git add calculator.py
git commit -m "Initial calculator with add function" | Out-Null
# Commit 2: Add subtract
$calc2 = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
"@
Set-Content -Path "calculator.py" -Value $calc2
git add calculator.py
git commit -m "Add subtract function" | Out-Null
# Commit 3: Add multiply
$calc3 = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
"@
Set-Content -Path "calculator.py" -Value $calc3
git add calculator.py
git commit -m "Add multiply function" | Out-Null
# Commit 4: Add divide
$calc4 = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
"@
Set-Content -Path "calculator.py" -Value $calc4
git add calculator.py
git commit -m "Add divide function" | Out-Null
# Commit 5: Add modulo
$calc5 = @"
class Calculator:
def add(self, a, b):
return a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
"@
Set-Content -Path "calculator.py" -Value $calc5
git add calculator.py
git commit -m "Add modulo function" | Out-Null
# Commit 6: BUG - Introduce error in add function
$calc6 = @"
class Calculator:
def add(self, a, b):
return a - b # BUG: Should be a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
"@
Set-Content -Path "calculator.py" -Value $calc6
git add calculator.py
git commit -m "Refactor add function for clarity" | Out-Null
# Commit 7: Add power function (bug still exists)
$calc7 = @"
class Calculator:
def add(self, a, b):
return a - b # BUG: Should be a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
def power(self, a, b):
return a ** b
"@
Set-Content -Path "calculator.py" -Value $calc7
git add calculator.py
git commit -m "Add power function" | Out-Null
# Commit 8: Add square root
$calc8 = @"
import math
class Calculator:
def add(self, a, b):
return a - b # BUG: Should be a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
def power(self, a, b):
return a ** b
def sqrt(self, a):
return math.sqrt(a)
"@
Set-Content -Path "calculator.py" -Value $calc8
git add calculator.py
git commit -m "Add square root function" | Out-Null
# Commit 9: Add absolute value
$calc9 = @"
import math
class Calculator:
def add(self, a, b):
return a - b # BUG: Should be a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
def power(self, a, b):
return a ** b
def sqrt(self, a):
return math.sqrt(a)
def abs(self, a):
return abs(a)
"@
Set-Content -Path "calculator.py" -Value $calc9
git add calculator.py
git commit -m "Add absolute value function" | Out-Null
# Commit 10: Add max function
$calc10 = @"
import math
class Calculator:
def add(self, a, b):
return a - b # BUG: Should be a + b
def subtract(self, a, b):
return a - b
def multiply(self, a, b):
return a * b
def divide(self, a, b):
if b == 0:
raise ValueError('Division by zero')
return a / b
def modulo(self, a, b):
return a % b
def power(self, a, b):
return a ** b
def sqrt(self, a):
return math.sqrt(a)
def abs(self, a):
return abs(a)
def max(self, a, b):
return a if a > b else b
"@
Set-Content -Path "calculator.py" -Value $calc10
git add calculator.py
git commit -m "Add max function" | Out-Null
# Create a test file
$test = @"
import sys
from calculator import Calculator
calc = Calculator()
# Test addition (this will fail due to bug)
result = calc.add(5, 3)
if result != 8:
print(f'FAIL: add(5, 3) returned {result}, expected 8')
sys.exit(1)
print('PASS: All tests passed')
sys.exit(0)
"@
Set-Content -Path "test.py" -Value $test
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nSituation:" -ForegroundColor Cyan
Write-Host "The calculator has a bug - addition doesn't work correctly!" -ForegroundColor Red
Write-Host "calc.add(5, 3) returns 2 instead of 8" -ForegroundColor Red
Write-Host "`nThe bug was introduced somewhere in the last 10 commits." -ForegroundColor Yellow
Write-Host "Manually checking each commit would be tedious." -ForegroundColor Yellow
Write-Host "Use git bisect to find it efficiently!" -ForegroundColor Green
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. Test the bug: python test.py (it will fail)" -ForegroundColor White
Write-Host "3. Start bisect: git bisect start" -ForegroundColor White
Write-Host "4. Mark current as bad: git bisect bad" -ForegroundColor White
Write-Host "5. Mark old commit as good: git bisect good HEAD~10" -ForegroundColor White
Write-Host "6. Git will checkout a commit - test it: python test.py" -ForegroundColor White
Write-Host "7. Mark result: git bisect good (if test passes) or git bisect bad (if it fails)" -ForegroundColor White
Write-Host "8. Repeat until Git finds the first bad commit" -ForegroundColor White
Write-Host "9. Note the commit hash" -ForegroundColor White
Write-Host "10. End bisect: git bisect reset" -ForegroundColor White
Write-Host "11. Create bug-commit.txt with the bad commit hash" -ForegroundColor White
Write-Host "`nHint: The bug is in commit 6 ('Refactor add function for clarity')" -ForegroundColor Cyan
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

@@ -1,133 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the bisect challenge solution.
.DESCRIPTION
Checks that the user successfully used git bisect to find
the commit that introduced the bug.
#>
Set-Location "challenge" -ErrorAction SilentlyContinue
# Check if challenge directory exists
if (-not (Test-Path "../verify.ps1")) {
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
exit 1
}
if (-not (Test-Path ".")) {
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
Set-Location ..
exit 1
}
Write-Host "Verifying your solution..." -ForegroundColor Cyan
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
Set-Location ..
exit 1
}
# Make sure we're not in a bisect session
$bisectHead = Test-Path ".git/BISECT_HEAD"
if ($bisectHead) {
Write-Host "[FAIL] You're still in a bisect session." -ForegroundColor Red
Write-Host "Hint: End the bisect with: git bisect reset" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check if bug-commit.txt exists
if (-not (Test-Path "bug-commit.txt")) {
Write-Host "[FAIL] bug-commit.txt not found." -ForegroundColor Red
Write-Host "Hint: After finding the bad commit, create a file with its hash:" -ForegroundColor Yellow
Write-Host " echo 'commit-hash' > bug-commit.txt" -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Read the commit hash from the file
$userCommit = (Get-Content "bug-commit.txt" -Raw).Trim()
if (-not $userCommit) {
Write-Host "[FAIL] bug-commit.txt is empty." -ForegroundColor Red
Set-Location ..
exit 1
}
# Get all commit hashes
$allCommits = git log --pretty=format:"%H" --reverse 2>$null
$commitArray = $allCommits -split "`n"
# The bug was introduced in commit 6 (index 5 in 0-based array)
# This is the "Refactor add function for clarity" commit
$badCommitMessage = git log --pretty=format:"%s" --grep="Refactor add function" 2>$null
$actualBadCommit = git log --pretty=format:"%H" --grep="Refactor add function" 2>$null
if (-not $actualBadCommit) {
Write-Host "[FAIL] Could not find the expected bad commit." -ForegroundColor Red
Write-Host "Something may be wrong with the repository setup." -ForegroundColor Yellow
Set-Location ..
exit 1
}
# Check if user found the correct commit
if ($userCommit -ne $actualBadCommit) {
# Maybe they provided a short hash
if ($actualBadCommit -like "$userCommit*") {
Write-Host "[PASS] Correct commit identified (using short hash)!" -ForegroundColor Green
} else {
Write-Host "[FAIL] Incorrect commit identified." -ForegroundColor Red
Write-Host "You identified: $userCommit" -ForegroundColor Yellow
Write-Host "Expected: $actualBadCommit" -ForegroundColor Yellow
Write-Host "Expected commit message: 'Refactor add function for clarity'" -ForegroundColor Yellow
Write-Host "`nHint: Use git bisect to find where the add function broke:" -ForegroundColor Yellow
Write-Host " git bisect start" -ForegroundColor White
Write-Host " git bisect bad" -ForegroundColor White
Write-Host " git bisect good HEAD~10" -ForegroundColor White
Write-Host " # Then test with: node test.py" -ForegroundColor White
Write-Host " git bisect good # or bad" -ForegroundColor White
Set-Location ..
exit 1
}
} else {
Write-Host "[PASS] Correct commit identified!" -ForegroundColor Green
}
# Verify the commit actually has the bug
git checkout $actualBadCommit 2>$null | Out-Null
$calcContent = Get-Content "calculator.py" -Raw
if ($calcContent -notmatch "add\(a, b\)[\s\S]*?return a - b") {
Write-Host "[WARNING] The identified commit doesn't seem to have the expected bug." -ForegroundColor Yellow
}
# Return to latest commit
git checkout $(git branch --show-current 2>$null) 2>$null | Out-Null
if (-not $?) {
git checkout main 2>$null | Out-Null
}
# Success!
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
Write-Host "- Used git bisect to perform a binary search" -ForegroundColor White
Write-Host "- Tested commits systematically" -ForegroundColor White
Write-Host "- Identified the exact commit that introduced the bug" -ForegroundColor White
Write-Host "- Found: '$badCommitMessage'" -ForegroundColor White
Write-Host "`nThe bug was in commit 6 out of 10 commits." -ForegroundColor Yellow
Write-Host "Manual checking: up to 10 tests" -ForegroundColor Yellow
Write-Host "With bisect: only ~4 tests needed!" -ForegroundColor Green
Write-Host "`nYou now understand git bisect!" -ForegroundColor Green
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
Write-Host "Bisect uses binary search to efficiently find bugs," -ForegroundColor White
Write-Host "saving massive amounts of time in large codebases.`n" -ForegroundColor White
Set-Location ..
exit 0

View File

@@ -1,169 +0,0 @@
# Module 05: Git Blame - Code Archaeology
## Learning Objectives
In this module, you will:
- Use `git blame` to find who made specific changes
- Understand blame output format and information
- Track down problematic code changes
- Learn when and why to use `git blame`
- Investigate code history to understand context
## Challenge
### Setup
Run the setup script to create your challenge environment:
```powershell
.\setup.ps1
```
This will create a `challenge/` directory with a Git repository that has a security issue - someone committed hardcoded credentials!
### Your Task
Your team has discovered a security vulnerability: hardcoded credentials were added to the codebase. Your job is to investigate who made this change and document your findings.
The setup script will create an `investigation.md` file in the challenge directory with questions for you to answer. Use `git blame` and other Git commands to track down the responsible developer.
**Scenario:**
- Someone added hardcoded login credentials (`username: "admin"`, `password: "admin123"`) to `app.py`
- This is a critical security issue
- You need to identify who made this change so the team can discuss it with them
**Suggested Approach:**
1. Navigate to the challenge directory: `cd challenge`
2. Open `investigation.md` to see the questions
3. Examine `app.py` to find the suspicious line
4. Use `git blame` to find who wrote that line
5. Use `git blame -e` to see email addresses
6. Use `git show` to see the full commit details
7. Document your findings in `investigation.md`
> **Important Notes:**
> - `git blame` shows who last modified each line
> - Each line shows: commit hash, author, date, line number, and content
> - Use `-e` flag to show email addresses
> - Use `-L` to focus on specific line ranges
## Key Concepts
- **Git Blame**: Shows the revision and author who last modified each line of a file
- **Code Archaeology**: Using Git history to understand when and why code changed
- **Author Attribution**: Identifying who wrote specific code for context, not punishment
- **Commit Context**: Understanding the full story behind a change
## Understanding Git Blame Output
When you run `git blame app.py`, you'll see output like this:
```
a1b2c3d4 (John Doe 2024-01-15 10:30:45 +0000 1) # app.py - Main application
a1b2c3d4 (John Doe 2024-01-15 10:30:45 +0000 2)
e5f6g7h8 (Jane Smith 2024-01-16 14:20:10 +0000 3) from auth import login
e5f6g7h8 (Jane Smith 2024-01-16 14:20:10 +0000 4)
i9j0k1l2 (Bob Wilson 2024-01-17 09:15:30 +0000 5) def main():
i9j0k1l2 (Bob Wilson 2024-01-17 09:15:30 +0000 6) login("admin", "admin123")
```
### Breaking It Down
Each line shows:
1. **Commit Hash** (`a1b2c3d4`) - The commit that last changed this line
2. **Author Name** (`John Doe`) - Who made the change
3. **Date/Time** (`2024-01-15 10:30:45 +0000`) - When it was changed
4. **Line Number** (`1`) - The line number in the current file
5. **Line Content** (`# app.py - Main application`) - The actual code
### Useful Git Blame Options
```bash
git blame <file> # Basic blame output
git blame -e <file> # Show email addresses instead of names
git blame -L 10,20 <file> # Only show lines 10-20
git blame -L 10,+5 <file> # Show 5 lines starting from line 10
git blame -w <file> # Ignore whitespace changes
git blame <commit> <file> # Blame as of specific commit
```
### Following Up After Blame
Once you find the commit hash:
```bash
git show <commit-hash> # See the full commit details
git log -p <commit-hash> # See commit with diff
git show <commit-hash> --stat # See which files were changed
```
## When to Use Git Blame
**Good reasons to use `git blame`:**
- 🔍 Understanding why code was written a certain way
- 📚 Finding context for a piece of code
- 🐛 Identifying when a bug was introduced
- 💡 Discovering the thought process behind a decision
- 👥 Finding who to ask about specific code
**Not for blaming:**
- ❌ Finding someone to blame for mistakes
- ❌ Tracking "productivity" or code ownership
- ❌ Punishing developers for old code
**Remember:** Code archaeology is about understanding, not blaming!
## Useful Commands
### Investigation Commands
```bash
# Find who changed each line
git blame <file>
git blame -e <file> # With email addresses
# Focus on specific lines
git blame -L 10,20 <file> # Lines 10-20
git blame -L :function_name <file> # Specific function (Git 2.20+)
# See historical blame
git blame <commit>^ <file> # Blame before a specific commit
# Combine with grep
git blame <file> | grep "pattern" # Find who wrote lines matching pattern
```
### Context Commands
```bash
# See full commit details
git show <commit-hash>
git log -1 <commit-hash> # Just the commit message
# See all commits by author
git log --author="name"
# See what else changed in that commit
git show <commit-hash> --stat
```
## Verification
Once you've completed your investigation in `investigation.md`, verify your solution:
```powershell
.\verify.ps1
```
The verification script will check that you've identified the correct developer.
## Need to Start Over?
If you want to reset the challenge and start fresh:
```powershell
.\reset.ps1
```
This will remove the challenge directory and run the setup script again, giving you a clean slate.

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 05 challenge environment.
.DESCRIPTION
This script removes the challenge directory and re-runs the setup script
to give you a fresh start.
#>
Write-Host "`n=== Resetting Module 05 Challenge ===" -ForegroundColor Cyan
# Remove challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
Write-Host "Challenge directory removed." -ForegroundColor Green
} else {
Write-Host "No challenge directory found to remove." -ForegroundColor Yellow
}
# Run setup script
Write-Host "`nRunning setup script..." -ForegroundColor Cyan
& "./setup.ps1"

View File

@@ -1,323 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 05 challenge environment for git blame investigation.
.DESCRIPTION
This script creates a challenge directory with a Git repository that
contains a security vulnerability (hardcoded credentials) for students
to investigate using git blame.
#>
Write-Host "`n=== Setting up Module 05 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Commit 1: Initial project structure (by Alice)
Write-Host "Creating initial project structure..." -ForegroundColor Green
git config user.name "Alice Johnson"
git config user.email "alice@example.com"
$appContent = @"
# app.py - Main application file
def main():
print("Welcome to My App!")
# Application initialization code here
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Initial project structure" | Out-Null
# Commit 2: Add authentication module (by Bob)
Write-Host "Adding authentication module..." -ForegroundColor Green
git config user.name "Bob Chen"
git config user.email "bob@example.com"
$authContent = @"
# auth.py - Authentication module
def login(username, password):
# Authenticate user
print(f"Logging in user: {username}")
return True
def logout(username):
# Log out user
print(f"Logging out user: {username}")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
$appContent = @"
# app.py - Main application file
from auth import login, logout
def main():
print("Welcome to My App!")
# Application initialization code here
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add authentication module" | Out-Null
# Commit 3: Add database connection (by Carol)
Write-Host "Adding database connection..." -ForegroundColor Green
git config user.name "Carol Martinez"
git config user.email "carol@example.com"
$databaseContent = @"
# database.py - Database connection module
def connect():
# Connect to database
print("Connecting to database...")
return True
def disconnect():
# Disconnect from database
print("Disconnecting from database...")
return True
"@
Set-Content -Path "database.py" -Value $databaseContent
$appContent = @"
# app.py - Main application file
from auth import login, logout
from database import connect, disconnect
def main():
print("Welcome to My App!")
connect()
# Application initialization code here
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add database connection" | Out-Null
# Commit 4: Add hardcoded credentials (THE SECURITY ISSUE - by Suspicious Developer)
Write-Host "Adding suspicious change..." -ForegroundColor Green
git config user.name "Suspicious Developer"
git config user.email "guilty@email.com"
$appContent = @"
# app.py - Main application file
from auth import login, logout
from database import connect, disconnect
def main():
print("Welcome to My App!")
connect()
# Quick fix for testing - TODO: Remove before production!
if login("admin", "admin123"):
print("Admin logged in successfully")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Add quick test login for debugging" | Out-Null
# Commit 5: Add logging (by David - innocent commit after the security issue)
Write-Host "Adding logging module..." -ForegroundColor Green
git config user.name "David Lee"
git config user.email "david@example.com"
$loggingContent = @"
# logging_config.py - Logging configuration
import logging
def setup_logging():
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
)
return logging.getLogger(__name__)
"@
Set-Content -Path "logging_config.py" -Value $loggingContent
git add .
git commit -m "Add logging configuration" | Out-Null
# Reset git config
git config user.name "Workshop Student"
git config user.email "student@example.com"
# Create investigation.md template
Write-Host "Creating investigation template..." -ForegroundColor Green
$investigationTemplate = @"
# Security Investigation Report
## Incident Overview
A security vulnerability has been discovered in the codebase: hardcoded credentials in `app.py`.
**Your task:** Use git blame and related Git commands to investigate this security issue and document your findings.
---
## Question 1: What line number contains the hardcoded password?
Look at `app.py` and find the line with `"admin123"`.
**Your Answer:**
<!-- Write the line number here -->
---
## Question 2: Who added the hardcoded credentials?
Use `git blame` to find the email address of the developer who wrote the line with the hardcoded credentials.
**Suggested commands:**
``````bash
# View blame with email addresses
git blame -e app.py
# Or focus on specific lines (if you know the line range)
git blame -L 8,10 app.py
# Look for the line containing login("admin", "admin123")
``````
**Your Answer (provide the email address):**
<!-- Write the email address here -->
---
## Question 3: What was the commit message for the change that introduced the hardcoded credentials?
Once you've found the commit hash from git blame, use `git show` or `git log` to see the full commit message.
**Suggested commands:**
``````bash
# After finding the commit hash from git blame
git show <commit-hash>
git log -1 <commit-hash>
``````
**Your Answer:**
<!-- Write the commit message here -->
---
## Question 4: How many files were modified in the commit that added the hardcoded credentials?
Use `git show` with the `--stat` flag to see which files were changed.
**Suggested commands:**
``````bash
git show <commit-hash> --stat
git show <commit-hash> --name-only
``````
**Your Answer:**
<!-- Write the number or list the files here -->
---
## Question 5: When was this security vulnerability introduced?
Use the timestamp from git blame to determine when the vulnerable code was committed.
**Your Answer (date and time):**
<!-- Write the date/time here -->
---
## Recommendations
Based on your investigation, what actions should the team take?
**Your Recommendations:**
<!-- Write your recommendations here, for example:
- Remove hardcoded credentials
- Implement proper environment variables
- Add pre-commit hooks to prevent secrets
- Review with the developer who made the change
-->
---
## Quick Reference - Investigation Commands
**Finding Who Changed What:**
``````bash
git blame <file> # Show who last modified each line
git blame -e <file> # Show with email addresses
git blame -L 10,20 <file> # Blame specific line range
``````
**Getting Commit Details:**
``````bash
git show <commit-hash> # See full commit details
git show <commit-hash> --stat # See files changed
git log -1 <commit-hash> # See commit message only
git log -p <commit-hash> # See commit with diff
``````
**Searching History:**
``````bash
git log --all --grep="keyword" # Search commit messages
git log --author="name" # See commits by author
git log --since="2 weeks ago" # Recent commits
``````
---
When you're done with your investigation, run ``..\verify.ps1`` to check your answers!
"@
Set-Content -Path "investigation.md" -Value $investigationTemplate
# Return to module directory
Set-Location ..
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nYour investigation environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
Write-Host "`nScenario: Someone committed hardcoded credentials to app.py!" -ForegroundColor Yellow
Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. Open 'investigation.md' to see the investigation questions" -ForegroundColor White
Write-Host " 3. Use 'git blame -e app.py' to start your investigation" -ForegroundColor White
Write-Host " 4. Fill in your findings in 'investigation.md'" -ForegroundColor White
Write-Host " 5. Run '..\verify.ps1' to check your investigation" -ForegroundColor White
Write-Host ""

View File

@@ -1,114 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 05 challenge solution.
.DESCRIPTION
This script checks that:
- The challenge directory exists
- A Git repository exists
- investigation.md exists with correct findings about the security issue
#>
Write-Host "`n=== Verifying Module 05 Solution ===" -ForegroundColor Cyan
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
Set-Location ..
exit 1
}
# Check if investigation.md exists
if (-not (Test-Path "investigation.md")) {
Write-Host "[FAIL] investigation.md not found. Did you run setup.ps1?" -ForegroundColor Red
Write-Host "[HINT] The setup script should have created investigation.md for you" -ForegroundColor Yellow
$allChecksPassed = $false
} else {
Write-Host "[PASS] investigation.md exists" -ForegroundColor Green
# Read the investigation file
$investigation = Get-Content "investigation.md" -Raw
$investigationLower = $investigation.ToLower()
# Check 1: Line number (line 8 contains the hardcoded password)
if ($investigationLower -match "8") {
Write-Host "[PASS] Correct line number identified" -ForegroundColor Green
} else {
Write-Host "[FAIL] Line number not found or incorrect" -ForegroundColor Red
Write-Host "[HINT] Look at app.py to find which line contains 'admin123'" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 2: Email address (guilty@email.com)
if ($investigationLower -match "guilty@email\.com") {
Write-Host "[PASS] Correct email address found using git blame!" -ForegroundColor Green
} else {
Write-Host "[FAIL] Developer's email address not found" -ForegroundColor Red
Write-Host "[HINT] Use 'git blame -e app.py' to see who changed each line with email addresses" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 3: Commit message (contains "test" or "debug" or "quick")
if ($investigationLower -match "test|debug|quick") {
Write-Host "[PASS] Commit message identified" -ForegroundColor Green
} else {
Write-Host "[FAIL] Commit message not found" -ForegroundColor Red
Write-Host "[HINT] Use 'git show <commit-hash>' to see the commit message" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 4: Number of files (1 file - only app.py)
if ($investigationLower -match "1|one|app\.py") {
Write-Host "[PASS] Number of files modified identified" -ForegroundColor Green
} else {
Write-Host "[FAIL] Number of files modified not found" -ForegroundColor Red
Write-Host "[HINT] Use 'git show <commit-hash> --stat' to see which files were changed" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 5: Some mention of timestamp/date (flexible check)
# We're just checking they attempted to answer this
if ($investigationLower -match "202|date|time|\d{4}-\d{2}-\d{2}") {
Write-Host "[PASS] Timestamp/date documented" -ForegroundColor Green
} else {
Write-Host "[FAIL] Timestamp/date not documented" -ForegroundColor Red
Write-Host "[HINT] The git blame output shows the date and time of each change" -ForegroundColor Yellow
$allChecksPassed = $false
}
}
Set-Location ..
# Final summary
if ($allChecksPassed) {
Write-Host "`n" -NoNewline
Write-Host "=====================================" -ForegroundColor Green
Write-Host " INVESTIGATION COMPLETE!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Green
Write-Host "`nExcellent detective work! You've successfully used git blame to track down the security issue." -ForegroundColor Cyan
Write-Host "`nYou now know how to:" -ForegroundColor Cyan
Write-Host " - Use git blame to find who modified each line" -ForegroundColor White
Write-Host " - Read and interpret git blame output" -ForegroundColor White
Write-Host " - Use git blame with -e flag to show email addresses" -ForegroundColor White
Write-Host " - Find commit details after identifying changes with blame" -ForegroundColor White
Write-Host " - Conduct code archaeology to understand code history" -ForegroundColor White
Write-Host "`nRemember: git blame is for understanding, not blaming!" -ForegroundColor Yellow
Write-Host "`nReady for the next module!" -ForegroundColor Green
Write-Host ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host ""
exit 1
}

View File

@@ -1,448 +0,0 @@
# Module 06: Merge Strategies - Fast-Forward vs Three-Way
## Learning Objectives
In this module, you will:
- Understand the difference between fast-forward and three-way merges
- Learn when Git automatically chooses each strategy
- Force specific merge behavior with `--no-ff` and `--ff-only` flags
- Understand the trade-offs between linear and branched history
- Make informed decisions about merge strategies for different workflows
## Prerequisites
Before starting this module, you should have completed:
- Module 03: Branching Basics
- Module 04: Merging Branches
## Challenge
### Setup
Run the setup script to create your challenge environment:
```powershell
.\setup.ps1
```
This will create a `challenge/` directory with scenarios for both fast-forward and three-way merges.
### Your Task
You'll experiment with both types of merges and learn how to control Git's merge behavior.
**Part 1: Fast-Forward Merge**
1. Merge `feature-fast-forward` into main
2. Observe Git's "Fast-forward" message
3. Examine the linear history
**Part 2: Three-Way Merge**
4. Merge `feature-divergent` into main
5. Observe Git creates a merge commit
6. Examine the branched history
**Part 3: Force Merge Commit**
7. Merge `feature-optional` into main using `--no-ff`
8. Compare with the fast-forward from Part 1
**Steps:**
1. Navigate to the challenge directory: `cd challenge`
2. View all branches: `git branch -a`
3. View the current graph: `git log --oneline --graph --all`
4. Merge feature-fast-forward: `git merge feature-fast-forward`
5. Check the log: `git log --oneline --graph`
6. Merge feature-divergent: `git merge feature-divergent`
7. Check the log: `git log --oneline --graph --all`
8. Merge feature-optional with --no-ff: `git merge --no-ff feature-optional`
9. Compare the results: `git log --oneline --graph --all`
> **Key Questions to Consider:**
> - Which merges created merge commits?
> - Which merge kept a linear history?
> - How does `--no-ff` change Git's behavior?
> - When would you prefer each approach?
## Understanding Merge Strategies
### Fast-Forward Merge
A **fast-forward merge** happens when the target branch (e.g., `main`) hasn't changed since the feature branch was created. Git simply "fast-forwards" the branch pointer to the latest commit on the feature branch.
**Before the merge:**
```
main: A---B
\
feature: C---D
```
In this scenario:
- Commit B is where `feature` branched off from `main`
- Commits C and D are new commits on the `feature` branch
- `main` has NO new commits since the branch split
- The history is **linear** (straight line from A to D)
**After `git merge feature` (on main):**
```
main: A---B---C---D
feature
```
**What happened:**
- Git moved the `main` pointer forward to commit D
- NO merge commit was created
- The history remains linear (a straight line)
- Both `main` and `feature` now point to the same commit (D)
**Command:**
```bash
git switch main
git merge feature-fast-forward
```
**Output you'll see:**
```
Updating abc123..def456
Fast-forward
new-feature.py | 10 ++++++++++
1 file changed, 10 insertions(+)
```
**Notice the "Fast-forward" message!**
**Characteristics:**
- ✅ Keeps history linear and clean
- ✅ Simpler to read in `git log`
- ✅ No extra merge commit
- ❌ Loses visibility that work was done on a branch
- ❌ Harder to revert entire features at once
---
### Three-Way Merge
A **three-way merge** happens when BOTH branches have new commits since they diverged. Git must combine changes from both branches, which creates a special merge commit.
**Before the merge:**
```
main: A---B---C---E
\
feature: D---F
```
In this scenario:
- Commit B is where `feature` branched off from `main`
- Commits C and E are new commits on `main`
- Commits D and F are new commits on `feature`
- The branches have **diverged** (both have unique commits)
**After `git merge feature` (on main):**
```
main: A---B---C---E---M
\ /
feature: D---F---/
```
**What happened:**
- Git created a new **merge commit** (M)
- Commit M has TWO parent commits: E (from main) and F (from feature)
- The merge commit combines changes from both branches
- The history shows the branches converging
**Why it's called "three-way":**
Git uses THREE commits to perform the merge:
1. **Commit B** - The common ancestor (where branches split)
2. **Commit E** - The latest commit on `main`
3. **Commit F** - The latest commit on `feature`
Git compares all three to figure out what changed on each branch and how to combine them.
**Command:**
```bash
git switch main
git merge feature-divergent
```
**Output you'll see:**
```
Merge made by the 'ort' strategy.
feature-code.py | 5 +++++
1 file changed, 5 insertions(+)
```
**Notice it says "Merge made by the 'ort' strategy" instead of "Fast-forward"!**
**Characteristics:**
- ✅ Preserves feature branch history
- ✅ Shows when features were merged
- ✅ Easier to revert entire features (revert the merge commit)
- ✅ Clear visualization in git log --graph
- ❌ Creates more commits (merge commits)
- ❌ History can become complex with many merges
---
### When Does Each Type Happen?
| Situation | Merge Type | Merge Commit? | Git's Behavior |
|-----------|------------|---------------|----------------|
| Target branch (main) has NO new commits | Fast-Forward | ❌ No | Automatic |
| Target branch (main) HAS new commits | Three-Way | ✅ Yes | Automatic |
| You use `--no-ff` flag | Three-Way | ✅ Yes | Forced |
| You use `--ff-only` flag | Fast-Forward | ❌ No | Fails if not possible |
**Git chooses automatically** based on the branch state, but you can override this behavior!
---
## Controlling Merge Behavior
### Force a Merge Commit with `--no-ff`
Even if a fast-forward is possible, you can force Git to create a merge commit:
```bash
git merge --no-ff feature-optional
```
**Before (fast-forward would be possible):**
```
main: A---B
\
feature: C---D
```
**After `git merge --no-ff feature` (on main):**
```
main: A---B-------M
\ /
feature: C---D
```
Notice that even though main didn't change, a merge commit (M) was created!
**When to use `--no-ff`:**
- ✅ When you want to preserve the feature branch in history
- ✅ For important features that might need to be reverted
- ✅ In team workflows where you want to see when features were merged
- ✅ When following a branching model like Git Flow
**Example:**
```bash
# You've finished a major feature on feature-auth
git switch main
git merge --no-ff feature-auth -m "Merge feature-auth: Add user authentication system"
```
This creates a clear marker in history showing when and what was merged.
---
### Require Fast-Forward with `--ff-only`
You can make Git fail the merge if a fast-forward isn't possible:
```bash
git merge --ff-only feature-branch
```
**What happens:**
- ✅ If fast-forward is possible, Git merges
- ❌ If branches have diverged, Git refuses to merge
**Output when it fails:**
```
fatal: Not possible to fast-forward, aborting.
```
**When to use `--ff-only`:**
- ✅ When you want to keep history strictly linear
- ✅ To ensure you rebase before merging
- ✅ In workflows that prohibit merge commits
**Example workflow:**
```bash
# Try to merge
git merge --ff-only feature-branch
# If it fails, rebase first
git switch feature-branch
git rebase main
git switch main
git merge --ff-only feature-branch # Now it works!
```
---
## Visualizing the Difference
### Linear History (Fast-Forward)
```bash
git log --oneline --graph
```
```
* d1e2f3g (HEAD -> main, feature) Add feature C
* a4b5c6d Add feature B
* 7e8f9g0 Add feature A
* 1a2b3c4 Initial commit
```
Notice the straight line! No branching visible.
---
### Branched History (Three-Way Merge)
```bash
git log --oneline --graph --all
```
```
* m1e2r3g (HEAD -> main) Merge branch 'feature'
|\
| * f4e5a6t (feature) Add feature implementation
| * u7r8e9s Feature setup
* | a1b2c3d Update documentation on main
|/
* 0i1n2i3t Initial commit
```
Notice the branching pattern! You can see:
- Where the branch split (`|/`)
- Commits on each branch (`|`)
- Where branches merged (`* merge commit`)
---
## Decision Guide: Which Strategy to Use?
### Use Fast-Forward When:
- ✅ Working on personal projects
- ✅ Want simplest, cleanest history
- ✅ Small changes or bug fixes
- ✅ Don't need to track feature branches
- ✅ History readability is top priority
### Use Three-Way Merge (or force with --no-ff) When:
- ✅ Working in teams
- ✅ Want to preserve feature context
- ✅ Need to revert features as units
- ✅ Following Git Flow or similar workflow
- ✅ Important to see when features were integrated
### Force Fast-Forward (--ff-only) When:
- ✅ Enforcing rebase workflow
- ✅ Maintaining strictly linear history
- ✅ Integration branch requires clean history
---
## Comparison Table
| Aspect | Fast-Forward | Three-Way |
|--------|-------------|-----------|
| **When it happens** | Target branch unchanged | Both branches have new commits |
| **Merge commit created?** | ❌ No | ✅ Yes |
| **History appearance** | Linear | Branched |
| **Command output** | "Fast-forward" | "Merge made by..." |
| **Git log --graph** | Straight line | Fork and merge pattern |
| **Can revert entire feature?** | ❌ No (must revert each commit) | ✅ Yes (revert merge commit) |
| **Force it** | `--ff-only` | `--no-ff` |
| **Best for** | Solo work, small changes | Team work, features |
---
## Useful Commands
### Merging with Strategy Control
```bash
git merge <branch> # Let Git decide automatically
git merge --no-ff <branch> # Force a merge commit
git merge --ff-only <branch> # Only merge if fast-forward possible
git merge --abort # Cancel a merge in progress
```
### Viewing Different Merge Types
```bash
# See all merges
git log --merges # Only merge commits
git log --no-merges # Hide merge commits
# Visualize history
git log --oneline --graph # See branch structure
git log --oneline --graph --all # Include all branches
git log --first-parent # Follow only main branch line
# Check if merge would be fast-forward
git merge-base main feature # Find common ancestor
git log main..feature # See commits unique to feature
```
### Configuring Default Behavior
```bash
# Disable fast-forward by default
git config merge.ff false # Always create merge commits
# Only allow fast-forward
git config merge.ff only # Refuse non-fast-forward merges
# Reset to default
git config --unset merge.ff
```
---
## Common Patterns in the Wild
### GitHub Flow (Simple)
- Use fast-forward when possible
- Short-lived feature branches
- Merge to main frequently
### Git Flow (Structured)
- Always use `--no-ff` for features
- Preserve branch history
- Complex release management
### Rebase Workflow
- Always use `--ff-only`
- Rebase before merging
- Strictly linear history
---
## Verification
Once you've completed all three merges, verify your solution:
```powershell
.\verify.ps1
```
The verification script will check that you've experienced both types of merges and used the `--no-ff` flag.
## Need to Start Over?
If you want to reset the challenge and start fresh:
```powershell
.\reset.ps1
```
This will remove the challenge directory and run the setup script again, giving you a clean slate.
## What's Next?
Now that you understand merge strategies, you can make informed decisions about your workflow. Consider:
- **For personal projects:** Fast-forward merges keep history simple
- **For team projects:** Three-way merges preserve context
- **For open source:** Follow the project's contribution guidelines
The best strategy depends on your team's needs and workflow!

View File

@@ -1,24 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 06 challenge environment.
.DESCRIPTION
This script removes the challenge directory and re-runs the setup script
to give you a fresh start.
#>
Write-Host "`n=== Resetting Module 06 Challenge ===" -ForegroundColor Cyan
# Remove challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
Write-Host "Challenge directory removed." -ForegroundColor Green
} else {
Write-Host "No challenge directory found to remove." -ForegroundColor Yellow
}
# Run setup script
Write-Host "`nRunning setup script..." -ForegroundColor Cyan
& "./setup.ps1"

View File

@@ -1,221 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 06 challenge environment for learning merge strategies.
.DESCRIPTION
This script creates a challenge directory with a Git repository that
contains scenarios for both fast-forward and three-way merges, allowing
students to compare different merge strategies.
#>
Write-Host "`n=== Setting up Module 06 Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Recurse -Force "challenge"
}
# Create fresh challenge directory
Write-Host "Creating challenge directory..." -ForegroundColor Green
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize Git repository
Write-Host "Initializing Git repository..." -ForegroundColor Green
git init | Out-Null
# Configure git for this repository
git config user.name "Workshop Student"
git config user.email "student@example.com"
# ========================================
# Scenario 1: Fast-Forward Merge Setup
# ========================================
Write-Host "Setting up fast-forward merge scenario..." -ForegroundColor Green
# Commit 1: Initial structure on main
$appContent = @"
# app.py - Main application
def main():
print("Application started")
pass
if __name__ == "__main__":
main()
"@
Set-Content -Path "app.py" -Value $appContent
git add .
git commit -m "Initial application structure" | Out-Null
# Create feature-fast-forward branch (main won't change after this)
git switch -c feature-fast-forward | Out-Null
# Commit on feature-fast-forward
$utilsContent = @"
# utils.py - Utility functions
def format_string(text):
"""Format a string to title case."""
return text.title()
def validate_input(text):
"""Validate user input."""
return text and len(text) > 0
"@
Set-Content -Path "utils.py" -Value $utilsContent
git add .
git commit -m "Add utility functions" | Out-Null
# Second commit on feature-fast-forward
$utilsContent = @"
# utils.py - Utility functions
def format_string(text):
"""Format a string to title case."""
return text.title()
def validate_input(text):
"""Validate user input."""
return text and len(text) > 0
def sanitize_input(text):
"""Remove dangerous characters from input."""
return text.replace("<", "").replace(">", "")
"@
Set-Content -Path "utils.py" -Value $utilsContent
git add .
git commit -m "Add input sanitization" | Out-Null
# ========================================
# Scenario 2: Three-Way Merge Setup
# ========================================
Write-Host "Setting up three-way merge scenario..." -ForegroundColor Green
# Switch back to main
git switch main | Out-Null
# Create feature-divergent branch
git switch -c feature-divergent | Out-Null
# Commit on feature-divergent
$authContent = @"
# auth.py - Authentication module
def authenticate(username, password):
"""Authenticate a user."""
print(f"Authenticating: {username}")
# TODO: Implement actual authentication
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Add authentication module" | Out-Null
# Second commit on feature-divergent
$authContent = @"
# auth.py - Authentication module
def authenticate(username, password):
"""Authenticate a user."""
print(f"Authenticating: {username}")
# TODO: Implement actual authentication
return True
def check_permissions(user, resource):
"""Check if user has permission for resource."""
print(f"Checking permissions for {user}")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Add permission checking" | Out-Null
# Switch back to main and make a commit (creates divergence)
git switch main | Out-Null
$readmeContent = @"
# My Application
A Python application with utilities and authentication.
## Features
- String formatting and validation
- User authentication
- Permission management
## Setup
1. Install Python 3.8+
2. Run: python app.py
"@
Set-Content -Path "README.md" -Value $readmeContent
git add .
git commit -m "Add README documentation" | Out-Null
# ========================================
# Scenario 3: Optional Fast-Forward for --no-ff
# ========================================
Write-Host "Setting up --no-ff demonstration scenario..." -ForegroundColor Green
# Create feature-optional branch (main won't change after this)
git switch -c feature-optional | Out-Null
# Commit on feature-optional
$configContent = @"
# config.py - Configuration settings
DEBUG_MODE = False
LOG_LEVEL = "INFO"
DATABASE_URL = "sqlite:///app.db"
"@
Set-Content -Path "config.py" -Value $configContent
git add .
git commit -m "Add configuration module" | 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 "`nYou have THREE scenarios set up:" -ForegroundColor Yellow
Write-Host "`n Scenario 1: Fast-Forward Merge" -ForegroundColor White
Write-Host " Branch: feature-fast-forward" -ForegroundColor Cyan
Write-Host " Status: main has NOT changed since branch was created" -ForegroundColor Cyan
Write-Host " Result: Will fast-forward (no merge commit)" -ForegroundColor Green
Write-Host "`n Scenario 2: Three-Way Merge" -ForegroundColor White
Write-Host " Branch: feature-divergent" -ForegroundColor Cyan
Write-Host " Status: BOTH main and branch have new commits" -ForegroundColor Cyan
Write-Host " Result: Will create merge commit" -ForegroundColor Green
Write-Host "`n Scenario 3: Force Merge Commit" -ForegroundColor White
Write-Host " Branch: feature-optional" -ForegroundColor Cyan
Write-Host " Status: Could fast-forward, but we'll use --no-ff" -ForegroundColor Cyan
Write-Host " Result: Will create merge commit even though fast-forward is possible" -ForegroundColor Green
Write-Host "`nNext steps:" -ForegroundColor Cyan
Write-Host " 1. cd challenge" -ForegroundColor White
Write-Host " 2. View initial state: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 3. Merge fast-forward: git merge feature-fast-forward" -ForegroundColor White
Write-Host " 4. View result: git log --oneline --graph" -ForegroundColor White
Write-Host " 5. Merge divergent: git merge feature-divergent" -ForegroundColor White
Write-Host " 6. View result: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 7. Merge with --no-ff: git merge --no-ff feature-optional" -ForegroundColor White
Write-Host " 8. View final result: git log --oneline --graph --all" -ForegroundColor White
Write-Host " 9. Compare all three merges!" -ForegroundColor White
Write-Host " 10. Run '..\verify.ps1' to check your solution" -ForegroundColor White
Write-Host ""

View File

@@ -1,140 +0,0 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 06 challenge solution.
.DESCRIPTION
This script checks that:
- The challenge directory exists
- A Git repository exists
- All three feature branches have been merged
- Appropriate merge strategies were used
- Student understands the difference between fast-forward and three-way merges
#>
Write-Host "`n=== Verifying Module 06 Solution ===" -ForegroundColor Cyan
$allChecksPassed = $true
# Check if challenge directory exists
if (-not (Test-Path "challenge")) {
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
exit 1
}
Set-Location "challenge"
# Check if git repository exists
if (-not (Test-Path ".git")) {
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
Set-Location ..
exit 1
}
# Check current branch is main
$currentBranch = git branch --show-current 2>$null
if ($currentBranch -eq "main") {
Write-Host "[PASS] Currently on main branch" -ForegroundColor Green
} else {
Write-Host "[FAIL] Not on main branch (currently on: $currentBranch)" -ForegroundColor Red
Write-Host "[HINT] Switch to main with: git switch main" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 1: Fast-Forward Merge - utils.py should exist
if (Test-Path "utils.py") {
Write-Host "[PASS] utils.py exists (feature-fast-forward merged)" -ForegroundColor Green
} else {
Write-Host "[FAIL] utils.py not found" -ForegroundColor Red
Write-Host "[HINT] Merge feature-fast-forward: git merge feature-fast-forward" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 2: Three-Way Merge - auth.py should exist
if (Test-Path "auth.py") {
Write-Host "[PASS] auth.py exists (feature-divergent merged)" -ForegroundColor Green
} else {
Write-Host "[FAIL] auth.py not found" -ForegroundColor Red
Write-Host "[HINT] Merge feature-divergent: git merge feature-divergent" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check 3: --no-ff Merge - config.py should exist
if (Test-Path "config.py") {
Write-Host "[PASS] config.py exists (feature-optional merged)" -ForegroundColor Green
} else {
Write-Host "[FAIL] config.py not found" -ForegroundColor Red
Write-Host "[HINT] Merge feature-optional: git merge --no-ff feature-optional" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check for merge commits
$mergeCommits = git log --merges --oneline 2>$null
$mergeCount = ($mergeCommits | Measure-Object -Line).Lines
if ($mergeCount -ge 2) {
Write-Host "[PASS] Found $mergeCount merge commit(s)" -ForegroundColor Green
Write-Host "[INFO] Expected: 1 from three-way merge + 1 from --no-ff = 2 total" -ForegroundColor Cyan
} else {
Write-Host "[FAIL] Only found $mergeCount merge commit(s), expected at least 2" -ForegroundColor Red
Write-Host "[HINT] Make sure to:" -ForegroundColor Yellow
Write-Host " 1. Merge feature-divergent (creates merge commit)" -ForegroundColor Yellow
Write-Host " 2. Merge feature-optional with --no-ff flag (forces merge commit)" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Provide detailed merge analysis
Write-Host "`n--- Merge Analysis ---" -ForegroundColor Cyan
# Count total commits
$totalCommits = git rev-list --count HEAD 2>$null
Write-Host "[INFO] Total commits on main: $totalCommits" -ForegroundColor Cyan
# List merge commits
if ($mergeCommits) {
Write-Host "[INFO] Merge commits found:" -ForegroundColor Cyan
$mergeCommits | ForEach-Object {
Write-Host " $_" -ForegroundColor White
}
}
# Check if branches still exist
$branches = git branch 2>$null
Write-Host "[INFO] Existing branches:" -ForegroundColor Cyan
$branches | ForEach-Object {
Write-Host " $_" -ForegroundColor White
}
Set-Location ..
# Final summary
if ($allChecksPassed) {
Write-Host "`n" -NoNewline
Write-Host "=====================================" -ForegroundColor Green
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
Write-Host "=====================================" -ForegroundColor Green
Write-Host "`nYou've mastered Git merge strategies!" -ForegroundColor Cyan
Write-Host "`nYou now understand:" -ForegroundColor Cyan
Write-Host " - Fast-forward merges (linear history, no merge commit)" -ForegroundColor White
Write-Host " - Three-way merges (divergent branches, creates merge commit)" -ForegroundColor White
Write-Host " - How to force merge commits with --no-ff flag" -ForegroundColor White
Write-Host " - When to use each merge strategy" -ForegroundColor White
Write-Host " - Trade-offs between linear and branched history" -ForegroundColor White
Write-Host "`nKey Takeaways:" -ForegroundColor Yellow
Write-Host " - Git chooses the strategy automatically based on branch state" -ForegroundColor Cyan
Write-Host " - Use --no-ff to preserve feature branch history" -ForegroundColor Cyan
Write-Host " - Use --ff-only to enforce linear history" -ForegroundColor Cyan
Write-Host " - Different workflows prefer different strategies" -ForegroundColor Cyan
Write-Host "`nNow you can make informed decisions about merge strategies for your projects!" -ForegroundColor Green
Write-Host ""
} else {
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
Write-Host ""
Write-Host "Quick Reference:" -ForegroundColor Cyan
Write-Host " git merge feature-fast-forward # Fast-forward merge" -ForegroundColor White
Write-Host " git merge feature-divergent # Three-way merge" -ForegroundColor White
Write-Host " git merge --no-ff feature-optional # Force merge commit" -ForegroundColor White
Write-Host ""
exit 1
}

View File

@@ -1,628 +0,0 @@
# Azure DevOps SSH Setup - Best Practices Guide
This guide provides comprehensive instructions for setting up SSH authentication with Azure DevOps. SSH is the recommended authentication method for secure Git operations.
## Why SSH is Best Practice
SSH (Secure Shell) keys provide a secure way to authenticate with Azure DevOps without exposing passwords or tokens. Here's why SSH is the security best practice:
**Security Benefits:**
- **No Password Exposure**: Your credentials never travel over the network
- **Strong Encryption**: Uses RSA cryptographic algorithms
- **No Credential Prompts**: Seamless authentication after initial setup
- **Better for Automation**: Scripts and CI/CD pipelines benefit from passwordless authentication
- **Revocable**: Individual keys can be removed without changing passwords
- **Auditable**: Track which key was used for each operation
**Comparison with HTTPS/PAT:**
- HTTPS with Personal Access Tokens (PAT) requires storing tokens, which can be accidentally committed to repositories
- SSH keys separate your authentication (private key stays on your machine) from the service
- SSH connections are faster after initial setup (no token validation on every request)
---
## Prerequisites
Before starting, ensure you have:
- **Git 2.23 or higher** installed
```powershell
git --version
```
- **Azure DevOps account** with access to your organization/project
- If you don't have one, create a free account at [dev.azure.com](https://dev.azure.com)
- **PowerShell 7+ or Bash terminal** for running commands
```powershell
pwsh --version
```
---
## Step 1: Generate SSH Key Pair
SSH authentication uses a key pair: a private key (stays on your computer) and a public key (uploaded to Azure DevOps).
### Generate RSA Key
Open your terminal and run:
```powershell
ssh-keygen -t rsa -b 4096 -C "your.email@example.com"
```
**Important notes:**
- Replace `your.email@example.com` with your actual email address
- The `-C` flag adds a comment to help identify the key later
- The `-b 4096` flag specifies a 4096-bit key size for enhanced security
**Note about RSA:** Azure DevOps currently only supports RSA SSH keys. While newer algorithms like Ed25519 offer better security and performance, they are not yet supported by Azure DevOps. See the note at the end of this guide for more information.
### Save Location
When prompted for the file location, press `Enter` to accept the default:
```
Enter file in which to save the key (/Users/yourname/.ssh/id_rsa):
```
**Default locations:**
- **Linux/Mac**: `~/.ssh/id_rsa`
- **Windows**: `C:\Users\YourName\.ssh\id_rsa`
### Passphrase (Optional but Recommended)
You'll be prompted to enter a passphrase, just press `Enter` no password is needed:
```
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
```
**Passphrase pros and cons:**
- **With passphrase**: Extra security layer - even if someone steals your private key, they can't use it without the passphrase
- **Without passphrase**: More convenient - no prompt when pushing/pulling (but less secure if your machine is compromised)
**Recommendation**: Use a passphrase, especially on laptops or shared machines.
### Verify Key Generation
Check that your keys were created:
**Linux/Mac:**
**Windows PowerShell:**
```powershell
dir $HOME\.ssh\
```
You should see two files:
- `id_rsa` - Private key (NEVER share this)
- `id_rsa.pub` - Public key (safe to share)
---
## Step 2: Add SSH Public Key to Azure DevOps
Now you'll upload your public key to Azure DevOps.
### Navigate to SSH Public Keys Settings
1. Sign in to Azure DevOps at [https://dev.azure.com](https://dev.azure.com)
2. Click your **profile icon** in the top-right corner
3. Select **User settings** from the dropdown menu
4. Click **SSH Public Keys**
![Azure DevOps - User Settings Menu](./images/azure-devops-user-settings.png)
*Navigate to your user settings by clicking the profile icon in the top-right corner*
### Add New SSH Key
5. Click the **+ New Key** button
![Azure DevOps - Add SSH Public Key Dialog](./images/azure-devops-add-ssh-key.png)
*Click '+ New Key' to begin adding your SSH public key*
### Copy Your Public Key
Open your terminal and display your public key:
**Linux/Mac:**
```bash
cat ~/.ssh/id_rsa.pub
```
**Windows PowerShell:**
```powershell
type $HOME\.ssh\id_rsa.pub
```
**Windows Command Prompt:**
```cmd
type %USERPROFILE%\.ssh\id_rsa.pub
```
The output will look like this:
```
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2YbXnrSK5TTflZSwUv9KUedvI4p3JJ4dHgwp/SeJGqMNWnOMDbzQQzYT7E39w9Q8ItrdWsK4vRLGY2B1rQ+BpS6nn4KhTanMXLTaUFDlg6I1Yn5S3cTTe8dMAoa14j3CZfoSoRRgK8E+ktNb0o0nBMuZJlLkgEtPIz28fwU1vcHoSK7jFp5KL0pjf37RYZeHkbpI7hdCG2qHtdrC35gzdirYPJOekErF5VFRrLZaIRSSsX0V4XzwY2k1hxM037o/h6qcTLWfi5ugbyrdscL8BmhdGNH4Giwqd1k3MwSyiswRuAuclYv27oKnFVBRT+n649px4g3Vqa8dh014wM2HDjMGENIkHx0hcV9BWdfBfTSCJengmosGW+wQfmaNUo4WpAbwZD73ALNsoLg5Yl1tB6ZZ5mHwLRY3LG2BbQZMZRCELUyvbh8ZsRksNN/2zcS44RIQdObV8/4hcLse30+NQ7GRaMnJeAMRz4Rpzbb02y3w0wNQFp/evj1nN4WTz6l8= your@email.com
```
**Copy the entire output** (from `ssh-rsa` to your email address).
### Paste and Name Your Key
6. In the Azure DevOps dialog:
- **Name**: Give your key a descriptive name (e.g., "Workshop Laptop 2026", "Home Desktop", "Work MacBook")
- **Public Key Data**: Paste the entire public key you just copied
7. Click **Save**
![Azure DevOps - SSH Key Added Successfully](./images/azure-devops-ssh-key-success.png)
*Your SSH key has been successfully added and is ready to use*
**Naming tip**: Use names that help you identify which machine uses each key. This makes it easier to revoke keys later if needed.
---
## Step 3: Configure SSH (Optional but Recommended)
Create or edit your SSH configuration file to specify which key to use with Azure DevOps.
### Create/Edit SSH Config File
**Linux/Mac:**
```bash
mkdir -p ~/.ssh
nano ~/.ssh/config
```
**Windows PowerShell:**
```powershell
if (!(Test-Path "$HOME\.ssh")) { New-Item -ItemType Directory -Path "$HOME\.ssh" }
notepad $HOME\.ssh\config
```
### Add Azure DevOps Host Configuration
Add these lines to your `~/.ssh/config` file:
```
Host ssh.dev.azure.com
IdentityFile ~/.ssh/id_rsa
IdentitiesOnly yes
```
**For Windows users**, use backslashes in the path:
```
Host ssh.dev.azure.com
IdentityFile C:\Users\YourName\.ssh\id_rsa
IdentitiesOnly yes
```
**What this does:**
- `Host ssh.dev.azure.com` - Applies these settings only to Azure DevOps
- `IdentityFile` - Specifies which private key to use (your RSA key)
- `IdentitiesOnly yes` - Prevents SSH from trying other keys
### Save the Configuration
Save and close the file:
- **Nano**: Press `Ctrl+X`, then `Y`, then `Enter`
- **Notepad**: Click File → Save, then close
---
## Step 4: Test SSH Connection
Verify that your SSH key is working correctly.
### Test Command
Run this command to test your connection:
```bash
ssh -T git@ssh.dev.azure.com
```
### Expected Output
**First-time connection** will show a host key verification prompt:
```
The authenticity of host 'ssh.dev.azure.com (20.42.134.1)' can't be established.
RSA key fingerprint is SHA256:ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og.
Are you sure you want to continue connecting (yes/no)?
```
Type `yes` and press Enter to add Azure DevOps to your known hosts.
**Successful authentication** will show:
```
remote: Shell access is not supported.
shell request failed on channel 0
```
![Azure DevOps - Successful SSH Test](./images/azure-devops-ssh-test-success.png)
*Successful SSH test output showing authenticated connection*
**This is normal!** Azure DevOps doesn't provide shell access, but this message confirms your SSH key authentication worked.
### Troubleshooting Connection Issues
If the connection fails, see the [Troubleshooting section](#troubleshooting) below.
---
## Step 5: Using SSH with Git
Now that SSH is configured, you can use it for all Git operations.
### Clone a Repository with SSH
To clone a repository using SSH:
```bash
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Example** (replace placeholders with your actual values):
```bash
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
```
**How to find your SSH URL:**
1. Navigate to your repository in Azure DevOps
2. Click **Clone** in the top-right
3. Select **SSH** from the dropdown
4. Copy the SSH URL
![Azure DevOps - Get SSH Clone URL](./images/azure-devops-clone-ssh.png)
*Select SSH from the clone dialog to get your repository's SSH URL*
### Convert Existing HTTPS Repository to SSH
If you already cloned a repository using HTTPS, you can switch it to SSH:
```bash
cd /path/to/your/repository
git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Verify the change:**
```bash
git remote -v
```
You should see SSH URLs:
```
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (fetch)
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (push)
```
### Daily Git Operations
All standard Git commands now work seamlessly with SSH:
```bash
# Pull latest changes
git pull
# Push your commits
git push
# Fetch from remote
git fetch
# Push a new branch
git push -u origin feature-branch
```
**No more credential prompts!** SSH authentication happens automatically.
---
## Troubleshooting
### Permission Denied (publickey)
**Error:**
```
git@ssh.dev.azure.com: Permission denied (publickey).
fatal: Could not read from remote repository.
```
**Causes and solutions:**
1. **SSH key not added to Azure DevOps**
- Go back to [Step 2](#step-2-add-ssh-public-key-to-azure-devops) and verify your public key is uploaded
- Check you copied the **entire** public key (from `ssh-rsa` to your email)
2. **Wrong private key being used**
- Verify your SSH config file points to the correct key
- Test with: `ssh -vT git@ssh.dev.azure.com` (verbose output shows which keys are tried)
3. **SSH agent not running** (if you used a passphrase)
- Start the SSH agent:
```bash
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/id_rsa
```
### Connection Timeout
**Error:**
```
ssh: connect to host ssh.dev.azure.com port 22: Connection timed out
```
**Causes and solutions:**
1. **Firewall blocking SSH port (22)**
- Check if your organization's firewall blocks port 22
- Try using HTTPS as a fallback
2. **Network restrictions**
- Try from a different network (mobile hotspot, home network)
- Contact your IT department about SSH access
3. **Proxy configuration**
- If behind a corporate proxy, you may need to configure SSH to use it
- Add to `~/.ssh/config`:
```
Host ssh.dev.azure.com
ProxyCommand nc -X connect -x proxy.company.com:3128 %h %p
```
### Host Key Verification Failed
**Error:**
```
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
```
**Causes and solutions:**
1. **Azure DevOps updated their host keys** (rare but happens)
- Check [Azure DevOps SSH key fingerprints](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate#verify-the-host-key-fingerprint)
- If fingerprint matches, remove old key and re-add:
```bash
ssh-keygen -R ssh.dev.azure.com
```
2. **Man-in-the-middle attack** (security risk!)
- If fingerprint doesn't match Microsoft's published keys, **DO NOT PROCEED**
- Contact your security team
### SSH Key Not Working After Creation
**Symptoms:**
- Created key successfully
- Added to Azure DevOps
- Still getting "Permission denied"
**Solutions:**
1. **Check file permissions** (Linux/Mac only)
```bash
chmod 700 ~/.ssh
chmod 600 ~/.ssh/id_rsa
chmod 644 ~/.ssh/id_rsa.pub
```
2. **Verify key format**
- Ensure you copied the **public key** (.pub file) to Azure DevOps, not the private key
- Public key starts with `ssh-rsa`
3. **Test with verbose output**
```bash
ssh -vvv git@ssh.dev.azure.com
```
- Look for lines like "Offering public key" to see which keys are tried
- Check for "Authentication succeeded" message
---
## Security Best Practices
Follow these security guidelines to keep your SSH keys safe:
### Use Passphrase Protection
**Always use a passphrase for your SSH keys**, especially on:
- Laptops (risk of theft)
- Shared machines
- Devices that leave your office/home
**How to add a passphrase to an existing key:**
```bash
ssh-keygen -p -f ~/.ssh/id_rsa
```
### Never Share Your Private Key
**Critical security rule:**
- **NEVER** share your private key (`~/.ssh/id_rsa`)
- **NEVER** commit private keys to Git repositories
- **NEVER** send private keys via email or chat
**Only share:**
- Public key (`~/.ssh/id_rsa.pub`) - This is safe and intended to be shared
### Use Different Keys for Different Purposes
Consider creating separate SSH keys for:
- Work projects
- Personal projects
- Different organizations
**Benefits:**
- Limit blast radius if one key is compromised
- Easier to revoke access to specific services
- Better audit trail
**Example: Create a work-specific key:**
```bash
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_work -C "work.email@company.com"
```
Then add to `~/.ssh/config`:
```
Host ssh.dev.azure.com-work
HostName ssh.dev.azure.com
IdentityFile ~/.ssh/id_rsa_work
```
### Rotate Keys Periodically
**Recommended schedule:**
- Personal projects: Annually
- Work projects: Every 6 months
- High-security projects: Every 3 months
**How to rotate:**
1. Generate new SSH key pair
2. Add new public key to Azure DevOps
3. Test the new key works
4. Remove old public key from Azure DevOps
5. Delete old private key from your machine
### Revoke Compromised Keys Immediately
If your private key is exposed:
1. **Immediately** remove the public key from Azure DevOps
- User Settings → SSH Public Keys → Click the key → Delete
2. Generate a new key pair
3. Update all repositories to use the new key
### Protect Your Private Key File
Ensure correct file permissions:
**Linux/Mac:**
```bash
chmod 600 ~/.ssh/id_rsa
```
**Windows:**
```powershell
icacls "$HOME\.ssh\id_rsa" /inheritance:r /grant:r "$($env:USERNAME):F"
```
### Use SSH Agent Forwarding Carefully
SSH agent forwarding (`-A` flag) can be convenient but risky:
- Only use with trusted servers
- Prefer ProxyJump instead when possible
### Enable Two-Factor Authentication (2FA)
While SSH keys are secure, enable 2FA on your Azure DevOps account for additional security:
1. Azure DevOps → User Settings → Security → Two-factor authentication
2. Use an authenticator app (Microsoft Authenticator, Google Authenticator)
---
## Additional Resources
- **Azure DevOps SSH Documentation**: [https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
- **SSH Key Best Practices**: [https://security.stackexchange.com/questions/tagged/ssh-keys](https://security.stackexchange.com/questions/tagged/ssh-keys)
- **Git with SSH**: [https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key)
---
## Quick Reference
### Common Commands
```bash
# Generate RSA key
ssh-keygen -t rsa -b 4096 -C "your.email@example.com"
# Display public key (Linux/Mac)
cat ~/.ssh/id_rsa.pub
# Display public key (Windows)
type $HOME\.ssh\id_rsa.pub
# Test SSH connection
ssh -T git@ssh.dev.azure.com
# Clone with SSH
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
# Convert HTTPS to SSH
git remote set-url origin git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
# Check remote URL
git remote -v
```
### SSH URL Format
```
git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
```
**Example:**
```
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
```
---
## Important Note: RSA and Modern SSH Key Algorithms
**Why This Guide Uses RSA:**
This guide exclusively uses RSA keys because **Azure DevOps currently only supports RSA SSH keys**. As of January 2026, Azure DevOps does not support modern SSH key algorithms like Ed25519, ECDSA, or other newer formats.
**About RSA Security:**
RSA is an older cryptographic algorithm that has been the industry standard for decades. While RSA with 4096-bit keys (as used in this guide) is still considered secure for most use cases, it has some limitations compared to modern alternatives:
**RSA Drawbacks:**
- **Larger key sizes**: RSA requires 4096 bits for strong security, resulting in larger keys
- **Slower performance**: Key generation and signature operations are slower than modern algorithms
- **Older cryptographic foundation**: Based on mathematical principles from the 1970s
- **More CPU-intensive**: Authentication operations require more computational resources
**Modern Alternatives (Not Supported by Azure DevOps):**
If Azure DevOps supported modern algorithms, we would recommend:
**Ed25519:**
- **Faster**: Significantly faster key generation and authentication
- **Smaller keys**: 256-bit keys (much smaller than RSA 4096-bit)
- **Modern cryptography**: Based on elliptic curve cryptography (ECC) with strong security guarantees
- **Better performance**: Less CPU usage, faster operations
- **Widely supported**: GitHub, GitLab, Bitbucket, and most modern Git platforms support Ed25519
**ECDSA:**
- Also based on elliptic curve cryptography
- Faster than RSA but slightly slower than Ed25519
- Supported by many platforms
**Current State:**
RSA with 4096-bit keys remains secure and is acceptable for Git authentication, despite being outdated compared to modern algorithms. The Azure DevOps team has not provided a timeline for supporting Ed25519 or other modern key types.
**For Other Platforms:**
If you're using GitHub, GitLab, Bitbucket, or other Git hosting services, we strongly recommend using Ed25519 instead of RSA:
```bash
# For platforms that support Ed25519 (GitHub, GitLab, Bitbucket, etc.)
ssh-keygen -t ed25519 -C "your.email@example.com"
```
**References:**
- [Ed25519 Wikipedia](https://en.wikipedia.org/wiki/EdDSA#Ed25519)
- [SSH Key Algorithm Comparison](https://security.stackexchange.com/questions/5096/rsa-vs-dsa-for-ssh-authentication-keys)
- [Azure DevOps SSH Documentation](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
---
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.

View File

@@ -635,6 +635,20 @@ if ($userChoice -ne "CloneOnly") {
-CheckCommand "git" ` -CheckCommand "git" `
-MinVersion "2.23" ` -MinVersion "2.23" `
-AdditionalArgs "-e" -AdditionalArgs "-e"
Write-Host " Setting the init.defaultBranch to be 'main'"
git config --global init.defaultBranch main
Write-Host " Setting the default editor to code, to handle merge messages"
git config --global core.editor "code --wait"
Write-Host " Setting vscode at the default code editor for merge conflicts"
git config --global merge.tool vscode
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
Write-Host " Setting vscode as the default code editor for diffs"
git config --global diff.tool vscode
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
# Verify Git version specifically # Verify Git version specifically
if ($results.Git) { if ($results.Git) {