Compare commits
8 Commits
575e083f33
...
advanced-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2e36690ea | ||
|
|
356b6268ba | ||
|
|
bd69774191 | ||
|
|
fcaf97f60b | ||
|
|
2a5eb137f6 | ||
|
|
b0d2d43c8b | ||
|
|
cdd695b250 | ||
|
|
0474a6de0e |
@@ -18,10 +18,9 @@ Welcome to Module 06, 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 commits safely while preserving surrounding changes
|
1. Revert commits safely while preserving surrounding changes
|
||||||
2. Revert old commits in the middle of history
|
2. Understand how revert creates new commits instead of erasing history
|
||||||
3. Understand how revert preserves commits before and after the target
|
3. Revert multiple commits at once
|
||||||
4. Revert multiple commits at once
|
4. Know when to use revert vs. other undo strategies
|
||||||
5. Know when to use revert vs. other undo strategies
|
|
||||||
|
|
||||||
## Prerequisites
|
## Prerequisites
|
||||||
|
|
||||||
@@ -38,9 +37,8 @@ Run the setup script to create the challenge environment:
|
|||||||
.\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
|
||||||
- `middle-revert` - Reverting a commit in the middle of history
|
|
||||||
- `multi-revert` - Multiple commit reversion
|
- `multi-revert` - Multiple commit reversion
|
||||||
|
|
||||||
## Challenge 1: Reverting a Regular Commit
|
## Challenge 1: Reverting a Regular Commit
|
||||||
@@ -119,110 +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 Commit in the Middle
|
## Challenge 2: Reverting Multiple Commits
|
||||||
|
|
||||||
### Scenario
|
|
||||||
|
|
||||||
You're working on improving your calculator application. Several commits were made in sequence:
|
|
||||||
1. Added input validation (good)
|
|
||||||
2. Added output formatter (BAD - has bugs!)
|
|
||||||
3. Added configuration module (good - but came after the bad commit)
|
|
||||||
|
|
||||||
The formatter has critical bugs and needs to be removed, but you want to keep both the validation module (added before) and the configuration module (added after).
|
|
||||||
|
|
||||||
**This demonstrates an important Git principle:** Revert works on ANY commit in history, not just recent ones!
|
|
||||||
|
|
||||||
### Understanding History Preservation
|
|
||||||
|
|
||||||
Here's what the commit history looks like:
|
|
||||||
|
|
||||||
```
|
|
||||||
A (initial) → B (validation) → C (formatter BAD) → D (config)
|
|
||||||
↑
|
|
||||||
We want to remove THIS
|
|
||||||
```
|
|
||||||
|
|
||||||
When you revert commit C:
|
|
||||||
|
|
||||||
```
|
|
||||||
A (initial) → B (validation) → C (formatter BAD) → D (config) → E (revert C)
|
|
||||||
↑
|
|
||||||
Removes formatter, keeps validation & config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key insight:** The revert creates a NEW commit (E) that undoes commit C, but leaves B and D completely intact!
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
1. **Switch to the middle-revert branch:**
|
|
||||||
```pwsh
|
|
||||||
git switch middle-revert
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **View the commit history:**
|
|
||||||
```pwsh
|
|
||||||
git log --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see three commits after the initial:
|
|
||||||
- "Add input validation module"
|
|
||||||
- "Add broken formatter - needs to be reverted!"
|
|
||||||
- "Add configuration module"
|
|
||||||
|
|
||||||
3. **Find the broken formatter commit:**
|
|
||||||
- Look for the message: "Add broken formatter - needs to be reverted!"
|
|
||||||
- Note the commit hash (the 7-character code)
|
|
||||||
- Write it down
|
|
||||||
|
|
||||||
4. **Revert that middle commit** (replace `<commit-hash>` with actual hash):
|
|
||||||
```pwsh
|
|
||||||
git revert <commit-hash>
|
|
||||||
```
|
|
||||||
|
|
||||||
5. **Visual Studio Code will open** with the revert commit message:
|
|
||||||
- The default message is fine
|
|
||||||
- Close the editor window to accept it
|
|
||||||
- Git will create the revert commit
|
|
||||||
|
|
||||||
6. **Check the result:**
|
|
||||||
```pwsh
|
|
||||||
# View files - formatter.py should be gone
|
|
||||||
ls
|
|
||||||
# You should see validation.py and config.py but NOT formatter.py
|
|
||||||
|
|
||||||
# View the history
|
|
||||||
git log --oneline
|
|
||||||
# You should see the new revert commit at the top
|
|
||||||
```
|
|
||||||
|
|
||||||
### What to Observe
|
|
||||||
|
|
||||||
After reverting, notice:
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
# Check which files exist
|
|
||||||
ls
|
|
||||||
|
|
||||||
# You should see:
|
|
||||||
# - calculator.py (from initial commit)
|
|
||||||
# - validation.py (from commit BEFORE bad one) ✅
|
|
||||||
# - config.py (from commit AFTER bad one) ✅
|
|
||||||
# - formatter.py is GONE (reverted) ❌
|
|
||||||
```
|
|
||||||
|
|
||||||
**Important:** Git successfully removed the bad formatter while keeping everything else!
|
|
||||||
|
|
||||||
### Why This Matters
|
|
||||||
|
|
||||||
This scenario demonstrates revert's power in real-world situations:
|
|
||||||
- You discover a bug in code committed days or weeks ago
|
|
||||||
- Many commits have been made since then
|
|
||||||
- You can't just delete the old commit (that would break history)
|
|
||||||
- Revert lets you surgically remove just the bad commit
|
|
||||||
|
|
||||||
**Revert is your "undo button" for shared history!**
|
|
||||||
|
|
||||||
## Challenge 3: Reverting Multiple Commits
|
|
||||||
|
|
||||||
### Scenario
|
### Scenario
|
||||||
|
|
||||||
@@ -303,7 +198,7 @@ This is useful when reverting multiple commits and you want one combined revert
|
|||||||
|
|
||||||
## Verification
|
## Verification
|
||||||
|
|
||||||
After completing all three challenges, verify your solutions:
|
After completing both challenges, verify your solutions:
|
||||||
|
|
||||||
```pwsh
|
```pwsh
|
||||||
cd .. # Return to module directory (if you're in challenge/)
|
cd .. # Return to module directory (if you're in challenge/)
|
||||||
@@ -320,7 +215,6 @@ 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
|
||||||
- ✅ Revert works on commits in the middle of history
|
|
||||||
|
|
||||||
## Command Reference
|
## Command Reference
|
||||||
|
|
||||||
@@ -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
|
||||||
@@ -450,7 +344,7 @@ git revert --abort
|
|||||||
### 1. Using Reset on Pushed Commits
|
### 1. Using Reset on Pushed Commits
|
||||||
|
|
||||||
```pwsh
|
```pwsh
|
||||||
# ❌ NEVER do this with pushed commits
|
# ❌ NEVER do this with pushed commits. Or at least try your best to avoid it.
|
||||||
git reset --hard HEAD~3
|
git reset --hard HEAD~3
|
||||||
|
|
||||||
# ✅ Do this instead
|
# ✅ Do this instead
|
||||||
@@ -504,100 +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
|
|
||||||
|
|
||||||
### "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:**
|
|
||||||
```pwsh
|
|
||||||
# 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.
|
|
||||||
|
|
||||||
### "Reverting Old Commit Breaks Something"
|
|
||||||
|
|
||||||
**Problem:** After reverting an old commit, something else stops working.
|
|
||||||
|
|
||||||
**Why:** The old commit might have been a dependency for later commits.
|
|
||||||
|
|
||||||
**Solution:**
|
|
||||||
1. Check what changed: `git diff HEAD~1 HEAD`
|
|
||||||
2. Either fix the issue with a new commit, or
|
|
||||||
3. Revert the revert if needed: `git revert <revert-commit-hash>`
|
|
||||||
|
|
||||||
## Advanced: Revert Internals
|
|
||||||
|
|
||||||
Understanding what revert does under the hood:
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
# 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
|
|
||||||
- ✅ Revert works on any commit in history, even old ones
|
|
||||||
- ✅ Commits before and after the reverted commit are preserved
|
|
||||||
- ✅ 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
|
|
||||||
|
|||||||
@@ -4,10 +4,9 @@
|
|||||||
Sets up the Module 06 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 at the end
|
- regular-revert: Basic revert of a single bad commit
|
||||||
- middle-revert: Reverting a bad commit in the middle of history
|
|
||||||
- multi-revert: Reverting multiple commits at once
|
- multi-revert: Reverting multiple commits at once
|
||||||
#>
|
#>
|
||||||
|
|
||||||
@@ -104,68 +103,9 @@ 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: Revert in Middle of History
|
# SCENARIO 2: Multi Revert (Multiple Bad Commits)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
Write-Host "`nScenario 2: Creating middle-revert scenario..." -ForegroundColor Cyan
|
Write-Host "`nScenario 2: Creating multi-revert branch..." -ForegroundColor Cyan
|
||||||
|
|
||||||
# Switch back to main
|
|
||||||
git switch $mainBranch | Out-Null
|
|
||||||
|
|
||||||
# Create middle-revert branch
|
|
||||||
git switch -c middle-revert | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add validation module
|
|
||||||
$validationContent = @"
|
|
||||||
# validation.py - Input validation
|
|
||||||
|
|
||||||
def validate_number(value):
|
|
||||||
"""Check if value is a valid number."""
|
|
||||||
try:
|
|
||||||
float(value)
|
|
||||||
return True
|
|
||||||
except ValueError:
|
|
||||||
return False
|
|
||||||
"@
|
|
||||||
Set-Content -Path "validation.py" -Value $validationContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add input validation module" | Out-Null
|
|
||||||
|
|
||||||
# BAD commit: Add broken formatter
|
|
||||||
$formatterContent = @"
|
|
||||||
# formatter.py - Output formatting
|
|
||||||
|
|
||||||
def format_result(result):
|
|
||||||
"""BROKEN: Doesn't handle None or errors properly!"""
|
|
||||||
return f"Result: {result.upper()}" # This crashes if result is not a string!
|
|
||||||
"@
|
|
||||||
Set-Content -Path "formatter.py" -Value $formatterContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add broken formatter - needs to be reverted!" | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add configuration (depends on validation, not formatter)
|
|
||||||
$configContent = @"
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
from validation import validate_number
|
|
||||||
|
|
||||||
DEFAULT_PRECISION = 2
|
|
||||||
|
|
||||||
def set_precision(value):
|
|
||||||
"""Set calculation precision."""
|
|
||||||
if validate_number(value):
|
|
||||||
return int(value)
|
|
||||||
return DEFAULT_PRECISION
|
|
||||||
"@
|
|
||||||
Set-Content -Path "config.py" -Value $configContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add configuration module" | Out-Null
|
|
||||||
|
|
||||||
Write-Host "[CREATED] middle-revert branch with bad commit in the middle" -ForegroundColor Green
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 3: Multi Revert (Multiple Bad Commits)
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`nScenario 3: Creating multi-revert branch..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Switch back to main
|
# Switch back to main
|
||||||
git switch $mainBranch | Out-Null
|
git switch $mainBranch | Out-Null
|
||||||
@@ -247,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 bad commit at the end" -ForegroundColor White
|
Write-Host " 1. regular-revert - Revert a single bad commit" -ForegroundColor White
|
||||||
Write-Host " 2. middle-revert - Revert a bad commit in the middle of history" -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
|
||||||
|
|||||||
@@ -4,9 +4,8 @@
|
|||||||
Verifies the Module 06 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
|
||||||
- middle-revert: Commit in middle of history reverted
|
|
||||||
- multi-revert: Multiple commits reverted
|
- multi-revert: Multiple commits reverted
|
||||||
#>
|
#>
|
||||||
|
|
||||||
@@ -87,57 +86,9 @@ if ($LASTEXITCODE -ne 0) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# SCENARIO 2: Middle Revert Verification
|
# SCENARIO 2: Multi Revert Verification
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
Write-Host "`n=== Scenario 2: Middle Revert ===" -ForegroundColor Cyan
|
Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
git switch middle-revert 2>&1 | Out-Null
|
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Host "[FAIL] middle-revert branch not found" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
# Check that a revert commit exists
|
|
||||||
$revertCommit = git log --oneline --grep="Revert" 2>$null
|
|
||||||
if ($revertCommit) {
|
|
||||||
Write-Host "[PASS] Revert commit found" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] No revert commit found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use: git revert <commit-hash>" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that formatter.py is removed (reverted)
|
|
||||||
if (-not (Test-Path "formatter.py")) {
|
|
||||||
Write-Host "[PASS] Broken formatter.py successfully reverted (file removed)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] formatter.py still exists (should be reverted)" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] The bad commit should be reverted, removing formatter.py" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that validation.py still exists (good commit before bad one)
|
|
||||||
if (Test-Path "validation.py") {
|
|
||||||
Write-Host "[PASS] validation.py preserved (good commit before bad one)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] validation.py missing (should still exist)" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that config.py still exists (good commit after bad one)
|
|
||||||
if (Test-Path "config.py") {
|
|
||||||
Write-Host "[PASS] config.py preserved (good commit after bad one)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] config.py missing (should still exist)" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Only revert the bad commit, not the good ones after it" -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
|
||||||
|
|
||||||
@@ -209,9 +160,8 @@ if ($allChecksPassed) {
|
|||||||
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 commits safely without erasing history" -ForegroundColor White
|
Write-Host " ✓ Reverting commits safely without erasing history" -ForegroundColor White
|
||||||
Write-Host " ✓ Reverting old commits in the middle of history" -ForegroundColor White
|
|
||||||
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
|
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
|
||||||
Write-Host " ✓ Preserving commits before and after the reverted one" -ForegroundColor White
|
Write-Host " ✓ Preserving all other commits while undoing specific changes" -ForegroundColor White
|
||||||
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
|
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
# Module 11: Stash
|
# Module 07: Git Stash - Temporary Storage
|
||||||
|
|
||||||
## Learning Objectives
|
## Learning Objectives
|
||||||
|
|
||||||
@@ -64,12 +64,112 @@ stash@{2} <- Oldest stash
|
|||||||
|
|
||||||
You can have multiple stashes and apply any of them.
|
You can have multiple stashes and apply any of them.
|
||||||
|
|
||||||
## Useful Commands
|
## Setup
|
||||||
|
|
||||||
```bash
|
Run the setup script to create the challenge environment:
|
||||||
# Stash current changes
|
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `challenge/` directory where you're working on a login feature with uncommitted changes, and a critical bug needs fixing on main.
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
### The Scenario
|
||||||
|
|
||||||
|
You're working on a login feature on the `feature-login` branch. Your work is incomplete (has TODOs), so it's not ready to commit.
|
||||||
|
|
||||||
|
Suddenly, your teammate reports a **critical security bug** in production! You need to:
|
||||||
|
1. Temporarily save your incomplete work
|
||||||
|
2. Switch to the main branch
|
||||||
|
3. Fix the urgent bug
|
||||||
|
4. Return to your feature and continue working
|
||||||
|
|
||||||
|
### Step-by-Step Instructions
|
||||||
|
|
||||||
|
1. **Navigate to the challenge directory:**
|
||||||
|
```pwsh
|
||||||
|
cd challenge
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check your current status:**
|
||||||
|
```pwsh
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
You should see modified `login.py` (uncommitted changes)
|
||||||
|
|
||||||
|
3. **Stash your work with a message:**
|
||||||
|
```pwsh
|
||||||
|
git stash save "WIP: login feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify working directory is clean:**
|
||||||
|
```pwsh
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
Should say "nothing to commit, working tree clean"
|
||||||
|
|
||||||
|
5. **Switch to main branch:**
|
||||||
|
```pwsh
|
||||||
|
git switch main
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Open app.py and find the bug:**
|
||||||
|
```pwsh
|
||||||
|
cat app.py
|
||||||
|
```
|
||||||
|
Look for the comment "# BUG: This allows unauthenticated access!"
|
||||||
|
|
||||||
|
7. **Fix the bug** by editing app.py:
|
||||||
|
- Remove the buggy comment line
|
||||||
|
- You can leave the implementation as-is or improve it
|
||||||
|
- The important thing is removing the comment that says "allows unauthenticated access"
|
||||||
|
|
||||||
|
8. **Commit the fix:**
|
||||||
|
```pwsh
|
||||||
|
git add app.py
|
||||||
|
git commit -m "Fix critical security bug"
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Switch back to your feature branch:**
|
||||||
|
```pwsh
|
||||||
|
git switch feature-login
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Restore your stashed work:**
|
||||||
|
```pwsh
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
This applies the stash and removes it from the stash stack
|
||||||
|
|
||||||
|
11. **Complete the TODOs in login.py:**
|
||||||
|
- Open login.py in your editor
|
||||||
|
- Complete the login method (verify password and return session)
|
||||||
|
- Add a logout method
|
||||||
|
- Remove all TODO comments
|
||||||
|
|
||||||
|
12. **Commit your completed feature:**
|
||||||
|
```pwsh
|
||||||
|
git add login.py
|
||||||
|
git commit -m "Complete login feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
13. **Verify your solution:**
|
||||||
|
```pwsh
|
||||||
|
..\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Stash Commands
|
||||||
|
|
||||||
|
### Basic Operations
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Stash current changes with a message
|
||||||
|
git stash save "description"
|
||||||
|
|
||||||
|
# Stash without a message (not recommended)
|
||||||
git stash
|
git stash
|
||||||
git stash save "description" # With a descriptive message
|
|
||||||
|
|
||||||
# Stash including untracked files
|
# Stash including untracked files
|
||||||
git stash -u
|
git stash -u
|
||||||
@@ -77,7 +177,17 @@ git stash -u
|
|||||||
# List all stashes
|
# List all stashes
|
||||||
git stash list
|
git stash list
|
||||||
|
|
||||||
# Apply most recent stash and remove it from stack
|
# Show what's in the most recent stash
|
||||||
|
git stash show
|
||||||
|
|
||||||
|
# Show full diff of stash
|
||||||
|
git stash show -p
|
||||||
|
```
|
||||||
|
|
||||||
|
### Applying Stashes
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Apply most recent stash and remove it (RECOMMENDED)
|
||||||
git stash pop
|
git stash pop
|
||||||
|
|
||||||
# Apply most recent stash but keep it in stack
|
# Apply most recent stash but keep it in stack
|
||||||
@@ -86,114 +196,192 @@ git stash apply
|
|||||||
# Apply a specific stash
|
# Apply a specific stash
|
||||||
git stash apply stash@{1}
|
git stash apply stash@{1}
|
||||||
|
|
||||||
# Show what's in a stash
|
# Apply a specific stash by number
|
||||||
git stash show
|
git stash apply 1
|
||||||
git stash show -p # Show full diff
|
```
|
||||||
|
|
||||||
# Drop (delete) a stash
|
### Managing Stashes
|
||||||
git stash drop stash@{0}
|
|
||||||
|
```pwsh
|
||||||
|
# Drop (delete) the most recent stash
|
||||||
|
git stash drop
|
||||||
|
|
||||||
|
# Drop a specific stash
|
||||||
|
git stash drop stash@{1}
|
||||||
|
|
||||||
# Clear all stashes
|
# Clear all stashes
|
||||||
git stash clear
|
git stash clear
|
||||||
|
|
||||||
# Create a branch from a stash
|
# Create a new branch from a stash
|
||||||
git stash branch new-branch-name
|
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
|
## Verification
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
Run the verification script to check your solution:
|
||||||
|
|
||||||
```bash
|
```pwsh
|
||||||
|
..\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from the module directory:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
.\verify.ps1
|
.\verify.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
The verification will check that:
|
The verification will check that:
|
||||||
- The bug fix commit exists on main
|
- ✅ The bug fix commit exists on main
|
||||||
- Your feature is completed on the feature branch
|
- ✅ Your feature is completed on the feature-login branch
|
||||||
- Changes were properly stashed and restored
|
- ✅ All TODOs are removed from login.py
|
||||||
- No uncommitted changes remain
|
- ✅ No uncommitted changes remain
|
||||||
|
|
||||||
## Challenge Steps
|
## Troubleshooting
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
### "Cannot switch branches - you have uncommitted changes"
|
||||||
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
|
**Problem:** Git won't let you switch branches with uncommitted changes.
|
||||||
|
|
||||||
- Always use `git stash save "message"` to describe what you're stashing
|
**Solution:**
|
||||||
- Use `git stash list` to see all your stashes
|
```pwsh
|
||||||
- `git stash pop` applies and removes the stash (use this most often)
|
# Stash your changes first
|
||||||
- `git stash apply` keeps the stash (useful if you want to apply it to multiple branches)
|
git stash save "work in progress"
|
||||||
- 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
|
# Now you can switch
|
||||||
|
git switch other-branch
|
||||||
|
|
||||||
### Scenario 1: Quick Branch Switch
|
# When you come back, restore your work
|
||||||
```bash
|
git switch original-branch
|
||||||
# Working on feature, need to switch to main
|
|
||||||
git stash
|
|
||||||
git switch main
|
|
||||||
# Do work on main
|
|
||||||
git switch feature
|
|
||||||
git stash pop
|
git stash pop
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scenario 2: Pull with Local Changes
|
### "I don't remember what's in my stash"
|
||||||
```bash
|
|
||||||
# You have local changes but need to pull
|
**Problem:** You stashed something but forgot what it was.
|
||||||
git stash
|
|
||||||
git pull
|
**Solution:**
|
||||||
git stash pop
|
```pwsh
|
||||||
# Resolve any conflicts
|
# List all stashes
|
||||||
|
git stash list
|
||||||
|
|
||||||
|
# Show summary of what changed
|
||||||
|
git stash show stash@{0}
|
||||||
|
|
||||||
|
# Show full diff
|
||||||
|
git stash show -p stash@{0}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Scenario 3: Experimental Changes
|
### "Stash conflicts when I apply"
|
||||||
```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
|
**Problem:** Applying a stash causes merge conflicts.
|
||||||
```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
|
**Solution:**
|
||||||
|
1. Git marks conflicts in your files with `<<<<<<<` markers
|
||||||
If applying a stash causes conflicts:
|
2. Open the files and resolve conflicts manually
|
||||||
1. Git will mark the conflicts in your files
|
|
||||||
2. Resolve conflicts manually (like merge conflicts)
|
|
||||||
3. Stage the resolved files: `git add <file>`
|
3. Stage the resolved files: `git add <file>`
|
||||||
4. The stash is automatically dropped after successful pop
|
4. If you used `pop`, the stash is automatically dropped
|
||||||
5. If you used `apply`, manually drop it: `git stash drop`
|
5. If you used `apply`, manually drop it: `git stash drop`
|
||||||
|
|
||||||
## What You'll Learn
|
### "I accidentally cleared my stash"
|
||||||
|
|
||||||
Git stash is an essential tool for managing context switches in your daily workflow. It lets you maintain a clean working directory while preserving incomplete work, making it easy to handle interruptions, urgent fixes, and quick branch switches. Mastering stash makes you more efficient and helps avoid the temptation to make "WIP" commits just to switch branches. Think of stash as your temporary workspace that follows you around.
|
**Problem:** You deleted a stash you still needed.
|
||||||
|
|
||||||
|
**Unfortunately:** Stashes are hard to recover once deleted. Lessons learned:
|
||||||
|
- Use `git stash pop` instead of `git stash drop` when you're unsure
|
||||||
|
- Use `git stash show -p` to preview before dropping
|
||||||
|
- Consider committing work instead of stashing for important changes
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
💡 **Always add a message** - `git stash save "your message"` helps you remember what you stashed
|
||||||
|
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
|
||||||
|
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
|
||||||
|
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
|
||||||
|
💡 **Stashes are local** - They don't get pushed to remote repositories
|
||||||
|
💡 **Clean working directory** - Always verify with `git status` after stashing
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Quick Branch Switch
|
||||||
|
```pwsh
|
||||||
|
# You're working on feature-A
|
||||||
|
git stash save "feature A progress"
|
||||||
|
git switch hotfix-branch
|
||||||
|
# Fix the issue, commit
|
||||||
|
git switch feature-A
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull with Local Changes
|
||||||
|
```pwsh
|
||||||
|
# You have uncommitted changes
|
||||||
|
git stash save "local changes"
|
||||||
|
git pull
|
||||||
|
git stash pop
|
||||||
|
# Resolve conflicts if any
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Clean State
|
||||||
|
```pwsh
|
||||||
|
# Stash changes to test on clean code
|
||||||
|
git stash save "testing clean state"
|
||||||
|
# Run tests
|
||||||
|
git stash pop # Restore your changes
|
||||||
|
```
|
||||||
|
|
||||||
|
## What You've Learned
|
||||||
|
|
||||||
|
After completing this module, you understand:
|
||||||
|
|
||||||
|
- ✅ Stash temporarily saves uncommitted changes
|
||||||
|
- ✅ Stash lets you switch contexts without committing
|
||||||
|
- ✅ `git stash pop` applies and removes the stash
|
||||||
|
- ✅ `git stash apply` applies but keeps the stash
|
||||||
|
- ✅ Stashes are local and not pushed to remote
|
||||||
|
- ✅ Stash is essential for handling interruptions and urgent fixes
|
||||||
|
|
||||||
|
**Key Takeaway:** Stash is your "temporary clipboard" for incomplete work. It helps you stay productive when you need to context-switch without making messy "WIP" commits.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to continue? You've now mastered the essential Git commands for daily development:
|
||||||
|
- Committing and history
|
||||||
|
- Branching and merging
|
||||||
|
- Cherry-picking specific changes
|
||||||
|
- Safely reverting commits
|
||||||
|
- Temporarily stashing work
|
||||||
|
|
||||||
|
Move on to **Module 08: Multiplayer Git** to practice collaborating with others!
|
||||||
|
|
||||||
|
To start over:
|
||||||
|
```pwsh
|
||||||
|
.\reset.ps1
|
||||||
|
```
|
||||||
|
|||||||
@@ -158,10 +158,10 @@ 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 $mainBranch: git checkout $mainBranch" -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
|
||||||
|
|||||||
@@ -73,7 +73,15 @@ git checkout $mainBranch 2>$null | Out-Null
|
|||||||
|
|
||||||
# Check for bug fix commit
|
# Check for bug fix commit
|
||||||
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||||
if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
|
$hasSecurityFix = $false
|
||||||
|
foreach ($commit in $mainCommits) {
|
||||||
|
if ($commit -match "security|Fix.*bug") {
|
||||||
|
$hasSecurityFix = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $hasSecurityFix) {
|
||||||
Write-Host "[FAIL] No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
|
Write-Host "[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
|
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
|
||||||
@@ -128,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
|
||||||
|
|||||||
@@ -232,23 +232,8 @@ 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
|
- **Walk the room** - help students who get stuck
|
||||||
- **Point students to 03_TASKS.md** - Simple explanations of clone, push, pull, and fetch for beginners
|
- **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
|
|
||||||
|
|||||||
264
01-essentials/08-multiplayer/02_AZURE-DEVOPS-SSH-SETUP.md
Normal file
264
01-essentials/08-multiplayer/02_AZURE-DEVOPS-SSH-SETUP.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Azure DevOps SSH Setup - Best Practices Guide
|
||||||
|
|
||||||
|
This guide provides comprehensive instructions for setting up SSH authentication with Azure DevOps. SSH is the recommended authentication method for secure Git operations.
|
||||||
|
|
||||||
|
## Why SSH is Best Practice
|
||||||
|
|
||||||
|
SSH (Secure Shell) keys provide a secure way to authenticate with Azure DevOps without exposing passwords or tokens. Here's why SSH is the security best practice:
|
||||||
|
|
||||||
|
**Security Benefits:**
|
||||||
|
- **No Password Exposure**: Your credentials never travel over the network
|
||||||
|
- **Strong Encryption**: Uses RSA cryptographic algorithms
|
||||||
|
- **No Credential Prompts**: Seamless authentication after initial setup
|
||||||
|
- **Revocable**: Individual keys can be removed without changing passwords
|
||||||
|
- **Auditable**: Track which key was used for each operation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before starting, ensure you have:
|
||||||
|
|
||||||
|
- **Git 2.23 or higher** installed
|
||||||
|
```powershell
|
||||||
|
git --version
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Azure DevOps account** with access to your organization/project
|
||||||
|
- If you don't have one, create a free account at [dev.azure.com](https://dev.azure.com)
|
||||||
|
|
||||||
|
- **PowerShell 7+ or Bash terminal** for running commands
|
||||||
|
```powershell
|
||||||
|
pwsh --version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Generate SSH Key Pair
|
||||||
|
|
||||||
|
SSH authentication uses a key pair: a private key (stays on your computer) and a public key (uploaded to Azure DevOps).
|
||||||
|
|
||||||
|
### Generate RSA Key
|
||||||
|
|
||||||
|
Open your terminal and run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ssh-keygen -t rsa
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note about RSA:** Azure DevOps currently only supports RSA SSH keys. While newer algorithms like Ed25519 offer better security and performance, they are not yet supported by Azure DevOps. See the note at the end of this guide for more information.
|
||||||
|
|
||||||
|
### Save Location
|
||||||
|
|
||||||
|
When prompted for the file location, press `Enter` to accept the default:
|
||||||
|
|
||||||
|
```
|
||||||
|
Enter file in which to save the key (/Users/yourname/.ssh/id_rsa):
|
||||||
|
```
|
||||||
|
|
||||||
|
**Default locations:**
|
||||||
|
- **Windows**: `C:\Users\YourName\.ssh\id_rsa` and `C:\Users\YourName\.ssh\id_rsa.pub`
|
||||||
|
|
||||||
|
### Passphrase (Optional but Recommended)
|
||||||
|
|
||||||
|
You'll be prompted to enter a passphrase, just press `Enter` no password is needed:
|
||||||
|
|
||||||
|
```
|
||||||
|
Enter passphrase (empty for no passphrase):
|
||||||
|
Enter same passphrase again:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Key Generation
|
||||||
|
|
||||||
|
Check that your keys were created:
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
**Windows PowerShell:**
|
||||||
|
```powershell
|
||||||
|
dir $HOME\.ssh\
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see two files:
|
||||||
|
- `id_rsa` - Private key (NEVER share this)
|
||||||
|
- `id_rsa.pub` - Public key (safe to share for upload to Azure DevOps)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Add SSH Public Key to Azure DevOps
|
||||||
|
|
||||||
|
Now you'll upload your public key to Azure DevOps.
|
||||||
|
|
||||||
|
### Navigate to SSH Public Keys Settings
|
||||||
|
|
||||||
|
1. Sign in to Azure DevOps at [https://dev.azure.com](https://dev.azure.com)
|
||||||
|
2. Click your **profile icon** in the top-right corner
|
||||||
|
3. Select **User settings** from the dropdown menu
|
||||||
|
4. Click **SSH Public Keys**
|
||||||
|
|
||||||
|

|
||||||
|
*Navigate to your user settings by clicking the profile icon in the top-right corner*
|
||||||
|
|
||||||
|
### Add New SSH Key
|
||||||
|
|
||||||
|
5. Click the **+ New Key** button
|
||||||
|
|
||||||
|

|
||||||
|
*Click '+ New Key' to begin adding your SSH public key*
|
||||||
|
|
||||||
|
### Copy Your Public Key
|
||||||
|
|
||||||
|
Open your terminal and display your public key:
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
```bash
|
||||||
|
cat ~/.ssh/id_rsa.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows PowerShell:**
|
||||||
|
```powershell
|
||||||
|
type $HOME\.ssh\id_rsa.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
**Windows Command Prompt:**
|
||||||
|
```cmd
|
||||||
|
type %USERPROFILE%\.ssh\id_rsa.pub
|
||||||
|
```
|
||||||
|
|
||||||
|
The output will look like this:
|
||||||
|
```
|
||||||
|
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2YbXnrSK5TTflZSwUv9KUedvI4p3JJ4dHgwp/SeJGqMNWnOMDbzQQzYT7E39w9Q8ItrdWsK4vRLGY2B1rQ+BpS6nn4KhTanMXLTaUFDlg6I1Yn5S3cTTe8dMAoa14j3CZfoSoRRgK8E+ktNb0o0nBMuZJlLkgEtPIz28fwU1vcHoSK7jFp5KL0pjf37RYZeHkbpI7hdCG2qHtdrC35gzdirYPJOekErF5VFRrLZaIRSSsX0V4XzwY2k1hxM037o/h6qcTLWfi5ugbyrdscL8BmhdGNH4Giwqd1k3MwSyiswRuAuclYv27oKnFVBRT+n649px4g3Vqa8dh014wM2HDjMGENIkHx0hcV9BWdfBfTSCJengmosGW+wQfmaNUo4WpAbwZD73ALNsoLg5Yl1tB6ZZ5mHwLRY3LG2BbQZMZRCELUyvbh8ZsRksNN/2zcS44RIQdObV8/4hcLse30+NQ7GRaMnJeAMRz4Rpzbb02y3w0wNQFp/evj1nN4WTz6l8= your@email.com
|
||||||
|
```
|
||||||
|
|
||||||
|
**Copy the entire output** (from `ssh-rsa` to your email address).
|
||||||
|
|
||||||
|
|
||||||
|
### Paste and Name Your Key
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
6. In the Azure DevOps dialog:
|
||||||
|
- **Name**: Give your key a descriptive name (e.g., "Workshop Laptop 2026", "Home Desktop", "Work MacBook")
|
||||||
|
- **Public Key Data**: Paste the entire public key you just copied
|
||||||
|
7. Click **Save**
|
||||||
|
|
||||||
|
**Naming tip**: Use names that help you identify which machine uses each key. This makes it easier to revoke keys later if needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Using SSH with Git
|
||||||
|
|
||||||
|
Now that SSH is configured, you can use it for all Git operations.
|
||||||
|
|
||||||
|
### Clone a Repository with SSH
|
||||||
|
|
||||||
|
To clone a repository using SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example** (replace placeholders with your actual values):
|
||||||
|
```bash
|
||||||
|
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
|
||||||
|
```
|
||||||
|
|
||||||
|
**How to find your SSH URL:**
|
||||||
|
1. Navigate to your repository in Azure DevOps
|
||||||
|
2. Click **Clone** in the top-right
|
||||||
|
3. Select **SSH** from the dropdown
|
||||||
|
4. Copy the SSH URL
|
||||||
|
|
||||||
|

|
||||||
|
*Select SSH from the clone dialog to get your repository's SSH URL*
|
||||||
|
|
||||||
|
### Convert Existing HTTPS Repository to SSH
|
||||||
|
|
||||||
|
If you already cloned a repository using HTTPS, you can switch it to SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/your/repository
|
||||||
|
git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify the change:**
|
||||||
|
```bash
|
||||||
|
git remote -v
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see SSH URLs:
|
||||||
|
```
|
||||||
|
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (fetch)
|
||||||
|
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (push)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Daily Git Operations
|
||||||
|
|
||||||
|
All standard Git commands now work seamlessly with SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest changes
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Push your commits
|
||||||
|
git push
|
||||||
|
|
||||||
|
# Fetch from remote
|
||||||
|
git fetch
|
||||||
|
|
||||||
|
# Push a new branch
|
||||||
|
git push -u origin feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
**No more credential prompts!** SSH authentication happens automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- **Azure DevOps SSH Documentation**: [https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
|
||||||
|
- **SSH Key Best Practices**: [https://security.stackexchange.com/questions/tagged/ssh-keys](https://security.stackexchange.com/questions/tagged/ssh-keys)
|
||||||
|
- **Git with SSH**: [https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate RSA key
|
||||||
|
ssh-keygen -t
|
||||||
|
|
||||||
|
# Display public key (Linux/Mac)
|
||||||
|
cat ~/.ssh/id_rsa.pub
|
||||||
|
|
||||||
|
# Display public key (Windows)
|
||||||
|
type $HOME\.ssh\id_rsa.pub
|
||||||
|
|
||||||
|
# Test SSH connection
|
||||||
|
ssh -T git@ssh.dev.azure.com
|
||||||
|
|
||||||
|
# Clone with SSH
|
||||||
|
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
||||||
|
|
||||||
|
# Convert HTTPS to SSH
|
||||||
|
git remote set-url origin git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
||||||
|
|
||||||
|
# Check remote URL
|
||||||
|
git remote -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSH URL Format
|
||||||
|
|
||||||
|
```
|
||||||
|
git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.
|
||||||
File diff suppressed because it is too large
Load Diff
167
01-essentials/08-multiplayer/03_README.md
Normal file
167
01-essentials/08-multiplayer/03_README.md
Normal 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
|
||||||
|
```
|
||||||
@@ -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.
|
|
||||||
BIN
01-essentials/08-multiplayer/images/01_settings.png
Normal file
BIN
01-essentials/08-multiplayer/images/01_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
01-essentials/08-multiplayer/images/02_ssh_option.png
Normal file
BIN
01-essentials/08-multiplayer/images/02_ssh_option.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
BIN
01-essentials/08-multiplayer/images/03_add_new_key.png
Normal file
BIN
01-essentials/08-multiplayer/images/03_add_new_key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
01-essentials/08-multiplayer/images/04_copy_paste_key.png
Normal file
BIN
01-essentials/08-multiplayer/images/04_copy_paste_key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
11
01-essentials/08-multiplayer/numbers.txt
Normal file
11
01-essentials/08-multiplayer/numbers.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
@@ -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**
|
|
||||||
|
|
||||||

|
|
||||||
*Navigate to your user settings by clicking the profile icon in the top-right corner*
|
|
||||||
|
|
||||||
### Add New SSH Key
|
|
||||||
|
|
||||||
5. Click the **+ New Key** button
|
|
||||||
|
|
||||||

|
|
||||||
*Click '+ New Key' to begin adding your SSH public key*
|
|
||||||
|
|
||||||
### Copy Your Public Key
|
|
||||||
|
|
||||||
Open your terminal and display your public key:
|
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
```bash
|
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows PowerShell:**
|
|
||||||
```powershell
|
|
||||||
type $HOME\.ssh\id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows Command Prompt:**
|
|
||||||
```cmd
|
|
||||||
type %USERPROFILE%\.ssh\id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
The output will look like this:
|
|
||||||
```
|
|
||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2YbXnrSK5TTflZSwUv9KUedvI4p3JJ4dHgwp/SeJGqMNWnOMDbzQQzYT7E39w9Q8ItrdWsK4vRLGY2B1rQ+BpS6nn4KhTanMXLTaUFDlg6I1Yn5S3cTTe8dMAoa14j3CZfoSoRRgK8E+ktNb0o0nBMuZJlLkgEtPIz28fwU1vcHoSK7jFp5KL0pjf37RYZeHkbpI7hdCG2qHtdrC35gzdirYPJOekErF5VFRrLZaIRSSsX0V4XzwY2k1hxM037o/h6qcTLWfi5ugbyrdscL8BmhdGNH4Giwqd1k3MwSyiswRuAuclYv27oKnFVBRT+n649px4g3Vqa8dh014wM2HDjMGENIkHx0hcV9BWdfBfTSCJengmosGW+wQfmaNUo4WpAbwZD73ALNsoLg5Yl1tB6ZZ5mHwLRY3LG2BbQZMZRCELUyvbh8ZsRksNN/2zcS44RIQdObV8/4hcLse30+NQ7GRaMnJeAMRz4Rpzbb02y3w0wNQFp/evj1nN4WTz6l8= your@email.com
|
|
||||||
```
|
|
||||||
|
|
||||||
**Copy the entire output** (from `ssh-rsa` to your email address).
|
|
||||||
|
|
||||||
### Paste and Name Your Key
|
|
||||||
|
|
||||||
6. In the Azure DevOps dialog:
|
|
||||||
- **Name**: Give your key a descriptive name (e.g., "Workshop Laptop 2026", "Home Desktop", "Work MacBook")
|
|
||||||
- **Public Key Data**: Paste the entire public key you just copied
|
|
||||||
7. Click **Save**
|
|
||||||
|
|
||||||

|
|
||||||
*Your SSH key has been successfully added and is ready to use*
|
|
||||||
|
|
||||||
**Naming tip**: Use names that help you identify which machine uses each key. This makes it easier to revoke keys later if needed.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Configure SSH (Optional but Recommended)
|
|
||||||
|
|
||||||
Create or edit your SSH configuration file to specify which key to use with Azure DevOps.
|
|
||||||
|
|
||||||
### Create/Edit SSH Config File
|
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
```bash
|
|
||||||
mkdir -p ~/.ssh
|
|
||||||
nano ~/.ssh/config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows PowerShell:**
|
|
||||||
```powershell
|
|
||||||
if (!(Test-Path "$HOME\.ssh")) { New-Item -ItemType Directory -Path "$HOME\.ssh" }
|
|
||||||
notepad $HOME\.ssh\config
|
|
||||||
```
|
|
||||||
|
|
||||||
### Add Azure DevOps Host Configuration
|
|
||||||
|
|
||||||
Add these lines to your `~/.ssh/config` file:
|
|
||||||
|
|
||||||
```
|
|
||||||
Host ssh.dev.azure.com
|
|
||||||
IdentityFile ~/.ssh/id_rsa
|
|
||||||
IdentitiesOnly yes
|
|
||||||
```
|
|
||||||
|
|
||||||
**For Windows users**, use backslashes in the path:
|
|
||||||
```
|
|
||||||
Host ssh.dev.azure.com
|
|
||||||
IdentityFile C:\Users\YourName\.ssh\id_rsa
|
|
||||||
IdentitiesOnly yes
|
|
||||||
```
|
|
||||||
|
|
||||||
**What this does:**
|
|
||||||
- `Host ssh.dev.azure.com` - Applies these settings only to Azure DevOps
|
|
||||||
- `IdentityFile` - Specifies which private key to use (your RSA key)
|
|
||||||
- `IdentitiesOnly yes` - Prevents SSH from trying other keys
|
|
||||||
|
|
||||||
### Save the Configuration
|
|
||||||
|
|
||||||
Save and close the file:
|
|
||||||
- **Nano**: Press `Ctrl+X`, then `Y`, then `Enter`
|
|
||||||
- **Notepad**: Click File → Save, then close
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Test SSH Connection
|
|
||||||
|
|
||||||
Verify that your SSH key is working correctly.
|
|
||||||
|
|
||||||
### Test Command
|
|
||||||
|
|
||||||
Run this command to test your connection:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ssh -T git@ssh.dev.azure.com
|
|
||||||
```
|
|
||||||
|
|
||||||
### Expected Output
|
|
||||||
|
|
||||||
**First-time connection** will show a host key verification prompt:
|
|
||||||
|
|
||||||
```
|
|
||||||
The authenticity of host 'ssh.dev.azure.com (20.42.134.1)' can't be established.
|
|
||||||
RSA key fingerprint is SHA256:ohD8VZEXGWo6Ez8GSEJQ9WpafgLFsOfLOtGGQCQo6Og.
|
|
||||||
Are you sure you want to continue connecting (yes/no)?
|
|
||||||
```
|
|
||||||
|
|
||||||
Type `yes` and press Enter to add Azure DevOps to your known hosts.
|
|
||||||
|
|
||||||
**Successful authentication** will show:
|
|
||||||
|
|
||||||
```
|
|
||||||
remote: Shell access is not supported.
|
|
||||||
shell request failed on channel 0
|
|
||||||
```
|
|
||||||
|
|
||||||

|
|
||||||
*Successful SSH test output showing authenticated connection*
|
|
||||||
|
|
||||||
**This is normal!** Azure DevOps doesn't provide shell access, but this message confirms your SSH key authentication worked.
|
|
||||||
|
|
||||||
### Troubleshooting Connection Issues
|
|
||||||
|
|
||||||
If the connection fails, see the [Troubleshooting section](#troubleshooting) below.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Using SSH with Git
|
|
||||||
|
|
||||||
Now that SSH is configured, you can use it for all Git operations.
|
|
||||||
|
|
||||||
### Clone a Repository with SSH
|
|
||||||
|
|
||||||
To clone a repository using SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example** (replace placeholders with your actual values):
|
|
||||||
```bash
|
|
||||||
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
|
|
||||||
```
|
|
||||||
|
|
||||||
**How to find your SSH URL:**
|
|
||||||
1. Navigate to your repository in Azure DevOps
|
|
||||||
2. Click **Clone** in the top-right
|
|
||||||
3. Select **SSH** from the dropdown
|
|
||||||
4. Copy the SSH URL
|
|
||||||
|
|
||||||

|
|
||||||
*Select SSH from the clone dialog to get your repository's SSH URL*
|
|
||||||
|
|
||||||
### Convert Existing HTTPS Repository to SSH
|
|
||||||
|
|
||||||
If you already cloned a repository using HTTPS, you can switch it to SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd /path/to/your/repository
|
|
||||||
git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Verify the change:**
|
|
||||||
```bash
|
|
||||||
git remote -v
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see SSH URLs:
|
|
||||||
```
|
|
||||||
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (fetch)
|
|
||||||
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (push)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Daily Git Operations
|
|
||||||
|
|
||||||
All standard Git commands now work seamlessly with SSH:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Pull latest changes
|
|
||||||
git pull
|
|
||||||
|
|
||||||
# Push your commits
|
|
||||||
git push
|
|
||||||
|
|
||||||
# Fetch from remote
|
|
||||||
git fetch
|
|
||||||
|
|
||||||
# Push a new branch
|
|
||||||
git push -u origin feature-branch
|
|
||||||
```
|
|
||||||
|
|
||||||
**No more credential prompts!** SSH authentication happens automatically.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Permission Denied (publickey)
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
git@ssh.dev.azure.com: Permission denied (publickey).
|
|
||||||
fatal: Could not read from remote repository.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Causes and solutions:**
|
|
||||||
|
|
||||||
1. **SSH key not added to Azure DevOps**
|
|
||||||
- Go back to [Step 2](#step-2-add-ssh-public-key-to-azure-devops) and verify your public key is uploaded
|
|
||||||
- Check you copied the **entire** public key (from `ssh-rsa` to your email)
|
|
||||||
|
|
||||||
2. **Wrong private key being used**
|
|
||||||
- Verify your SSH config file points to the correct key
|
|
||||||
- Test with: `ssh -vT git@ssh.dev.azure.com` (verbose output shows which keys are tried)
|
|
||||||
|
|
||||||
3. **SSH agent not running** (if you used a passphrase)
|
|
||||||
- Start the SSH agent:
|
|
||||||
```bash
|
|
||||||
eval "$(ssh-agent -s)"
|
|
||||||
ssh-add ~/.ssh/id_rsa
|
|
||||||
```
|
|
||||||
|
|
||||||
### Connection Timeout
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
ssh: connect to host ssh.dev.azure.com port 22: Connection timed out
|
|
||||||
```
|
|
||||||
|
|
||||||
**Causes and solutions:**
|
|
||||||
|
|
||||||
1. **Firewall blocking SSH port (22)**
|
|
||||||
- Check if your organization's firewall blocks port 22
|
|
||||||
- Try using HTTPS as a fallback
|
|
||||||
|
|
||||||
2. **Network restrictions**
|
|
||||||
- Try from a different network (mobile hotspot, home network)
|
|
||||||
- Contact your IT department about SSH access
|
|
||||||
|
|
||||||
3. **Proxy configuration**
|
|
||||||
- If behind a corporate proxy, you may need to configure SSH to use it
|
|
||||||
- Add to `~/.ssh/config`:
|
|
||||||
```
|
|
||||||
Host ssh.dev.azure.com
|
|
||||||
ProxyCommand nc -X connect -x proxy.company.com:3128 %h %p
|
|
||||||
```
|
|
||||||
|
|
||||||
### Host Key Verification Failed
|
|
||||||
|
|
||||||
**Error:**
|
|
||||||
```
|
|
||||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
||||||
@ WARNING: REMOTE HOST IDENTIFICATION HAS CHANGED! @
|
|
||||||
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
|
|
||||||
IT IS POSSIBLE THAT SOMEONE IS DOING SOMETHING NASTY!
|
|
||||||
```
|
|
||||||
|
|
||||||
**Causes and solutions:**
|
|
||||||
|
|
||||||
1. **Azure DevOps updated their host keys** (rare but happens)
|
|
||||||
- Check [Azure DevOps SSH key fingerprints](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate#verify-the-host-key-fingerprint)
|
|
||||||
- If fingerprint matches, remove old key and re-add:
|
|
||||||
```bash
|
|
||||||
ssh-keygen -R ssh.dev.azure.com
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Man-in-the-middle attack** (security risk!)
|
|
||||||
- If fingerprint doesn't match Microsoft's published keys, **DO NOT PROCEED**
|
|
||||||
- Contact your security team
|
|
||||||
|
|
||||||
### SSH Key Not Working After Creation
|
|
||||||
|
|
||||||
**Symptoms:**
|
|
||||||
- Created key successfully
|
|
||||||
- Added to Azure DevOps
|
|
||||||
- Still getting "Permission denied"
|
|
||||||
|
|
||||||
**Solutions:**
|
|
||||||
|
|
||||||
1. **Check file permissions** (Linux/Mac only)
|
|
||||||
```bash
|
|
||||||
chmod 700 ~/.ssh
|
|
||||||
chmod 600 ~/.ssh/id_rsa
|
|
||||||
chmod 644 ~/.ssh/id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Verify key format**
|
|
||||||
- Ensure you copied the **public key** (.pub file) to Azure DevOps, not the private key
|
|
||||||
- Public key starts with `ssh-rsa`
|
|
||||||
|
|
||||||
3. **Test with verbose output**
|
|
||||||
```bash
|
|
||||||
ssh -vvv git@ssh.dev.azure.com
|
|
||||||
```
|
|
||||||
- Look for lines like "Offering public key" to see which keys are tried
|
|
||||||
- Check for "Authentication succeeded" message
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Security Best Practices
|
|
||||||
|
|
||||||
Follow these security guidelines to keep your SSH keys safe:
|
|
||||||
|
|
||||||
### Use Passphrase Protection
|
|
||||||
|
|
||||||
**Always use a passphrase for your SSH keys**, especially on:
|
|
||||||
- Laptops (risk of theft)
|
|
||||||
- Shared machines
|
|
||||||
- Devices that leave your office/home
|
|
||||||
|
|
||||||
**How to add a passphrase to an existing key:**
|
|
||||||
```bash
|
|
||||||
ssh-keygen -p -f ~/.ssh/id_rsa
|
|
||||||
```
|
|
||||||
|
|
||||||
### Never Share Your Private Key
|
|
||||||
|
|
||||||
**Critical security rule:**
|
|
||||||
- **NEVER** share your private key (`~/.ssh/id_rsa`)
|
|
||||||
- **NEVER** commit private keys to Git repositories
|
|
||||||
- **NEVER** send private keys via email or chat
|
|
||||||
|
|
||||||
**Only share:**
|
|
||||||
- Public key (`~/.ssh/id_rsa.pub`) - This is safe and intended to be shared
|
|
||||||
|
|
||||||
### Use Different Keys for Different Purposes
|
|
||||||
|
|
||||||
Consider creating separate SSH keys for:
|
|
||||||
- Work projects
|
|
||||||
- Personal projects
|
|
||||||
- Different organizations
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- Limit blast radius if one key is compromised
|
|
||||||
- Easier to revoke access to specific services
|
|
||||||
- Better audit trail
|
|
||||||
|
|
||||||
**Example: Create a work-specific key:**
|
|
||||||
```bash
|
|
||||||
ssh-keygen -t rsa -b 4096 -f ~/.ssh/id_rsa_work -C "work.email@company.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
Then add to `~/.ssh/config`:
|
|
||||||
```
|
|
||||||
Host ssh.dev.azure.com-work
|
|
||||||
HostName ssh.dev.azure.com
|
|
||||||
IdentityFile ~/.ssh/id_rsa_work
|
|
||||||
```
|
|
||||||
|
|
||||||
### Rotate Keys Periodically
|
|
||||||
|
|
||||||
**Recommended schedule:**
|
|
||||||
- Personal projects: Annually
|
|
||||||
- Work projects: Every 6 months
|
|
||||||
- High-security projects: Every 3 months
|
|
||||||
|
|
||||||
**How to rotate:**
|
|
||||||
1. Generate new SSH key pair
|
|
||||||
2. Add new public key to Azure DevOps
|
|
||||||
3. Test the new key works
|
|
||||||
4. Remove old public key from Azure DevOps
|
|
||||||
5. Delete old private key from your machine
|
|
||||||
|
|
||||||
### Revoke Compromised Keys Immediately
|
|
||||||
|
|
||||||
If your private key is exposed:
|
|
||||||
1. **Immediately** remove the public key from Azure DevOps
|
|
||||||
- User Settings → SSH Public Keys → Click the key → Delete
|
|
||||||
2. Generate a new key pair
|
|
||||||
3. Update all repositories to use the new key
|
|
||||||
|
|
||||||
### Protect Your Private Key File
|
|
||||||
|
|
||||||
Ensure correct file permissions:
|
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
```bash
|
|
||||||
chmod 600 ~/.ssh/id_rsa
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows:**
|
|
||||||
```powershell
|
|
||||||
icacls "$HOME\.ssh\id_rsa" /inheritance:r /grant:r "$($env:USERNAME):F"
|
|
||||||
```
|
|
||||||
|
|
||||||
### Use SSH Agent Forwarding Carefully
|
|
||||||
|
|
||||||
SSH agent forwarding (`-A` flag) can be convenient but risky:
|
|
||||||
- Only use with trusted servers
|
|
||||||
- Prefer ProxyJump instead when possible
|
|
||||||
|
|
||||||
### Enable Two-Factor Authentication (2FA)
|
|
||||||
|
|
||||||
While SSH keys are secure, enable 2FA on your Azure DevOps account for additional security:
|
|
||||||
1. Azure DevOps → User Settings → Security → Two-factor authentication
|
|
||||||
2. Use an authenticator app (Microsoft Authenticator, Google Authenticator)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Additional Resources
|
|
||||||
|
|
||||||
- **Azure DevOps SSH Documentation**: [https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
|
|
||||||
- **SSH Key Best Practices**: [https://security.stackexchange.com/questions/tagged/ssh-keys](https://security.stackexchange.com/questions/tagged/ssh-keys)
|
|
||||||
- **Git with SSH**: [https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference
|
|
||||||
|
|
||||||
### Common Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Generate RSA key
|
|
||||||
ssh-keygen -t rsa -b 4096 -C "your.email@example.com"
|
|
||||||
|
|
||||||
# Display public key (Linux/Mac)
|
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
|
|
||||||
# Display public key (Windows)
|
|
||||||
type $HOME\.ssh\id_rsa.pub
|
|
||||||
|
|
||||||
# Test SSH connection
|
|
||||||
ssh -T git@ssh.dev.azure.com
|
|
||||||
|
|
||||||
# Clone with SSH
|
|
||||||
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
|
||||||
|
|
||||||
# Convert HTTPS to SSH
|
|
||||||
git remote set-url origin git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
|
||||||
|
|
||||||
# Check remote URL
|
|
||||||
git remote -v
|
|
||||||
```
|
|
||||||
|
|
||||||
### SSH URL Format
|
|
||||||
|
|
||||||
```
|
|
||||||
git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```
|
|
||||||
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Important Note: RSA and Modern SSH Key Algorithms
|
|
||||||
|
|
||||||
**Why This Guide Uses RSA:**
|
|
||||||
|
|
||||||
This guide exclusively uses RSA keys because **Azure DevOps currently only supports RSA SSH keys**. As of January 2026, Azure DevOps does not support modern SSH key algorithms like Ed25519, ECDSA, or other newer formats.
|
|
||||||
|
|
||||||
**About RSA Security:**
|
|
||||||
|
|
||||||
RSA is an older cryptographic algorithm that has been the industry standard for decades. While RSA with 4096-bit keys (as used in this guide) is still considered secure for most use cases, it has some limitations compared to modern alternatives:
|
|
||||||
|
|
||||||
**RSA Drawbacks:**
|
|
||||||
- **Larger key sizes**: RSA requires 4096 bits for strong security, resulting in larger keys
|
|
||||||
- **Slower performance**: Key generation and signature operations are slower than modern algorithms
|
|
||||||
- **Older cryptographic foundation**: Based on mathematical principles from the 1970s
|
|
||||||
- **More CPU-intensive**: Authentication operations require more computational resources
|
|
||||||
|
|
||||||
**Modern Alternatives (Not Supported by Azure DevOps):**
|
|
||||||
|
|
||||||
If Azure DevOps supported modern algorithms, we would recommend:
|
|
||||||
|
|
||||||
**Ed25519:**
|
|
||||||
- **Faster**: Significantly faster key generation and authentication
|
|
||||||
- **Smaller keys**: 256-bit keys (much smaller than RSA 4096-bit)
|
|
||||||
- **Modern cryptography**: Based on elliptic curve cryptography (ECC) with strong security guarantees
|
|
||||||
- **Better performance**: Less CPU usage, faster operations
|
|
||||||
- **Widely supported**: GitHub, GitLab, Bitbucket, and most modern Git platforms support Ed25519
|
|
||||||
|
|
||||||
**ECDSA:**
|
|
||||||
- Also based on elliptic curve cryptography
|
|
||||||
- Faster than RSA but slightly slower than Ed25519
|
|
||||||
- Supported by many platforms
|
|
||||||
|
|
||||||
**Current State:**
|
|
||||||
|
|
||||||
RSA with 4096-bit keys remains secure and is acceptable for Git authentication, despite being outdated compared to modern algorithms. The Azure DevOps team has not provided a timeline for supporting Ed25519 or other modern key types.
|
|
||||||
|
|
||||||
**For Other Platforms:**
|
|
||||||
|
|
||||||
If you're using GitHub, GitLab, Bitbucket, or other Git hosting services, we strongly recommend using Ed25519 instead of RSA:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# For platforms that support Ed25519 (GitHub, GitLab, Bitbucket, etc.)
|
|
||||||
ssh-keygen -t ed25519 -C "your.email@example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
**References:**
|
|
||||||
- [Ed25519 Wikipedia](https://en.wikipedia.org/wiki/EdDSA#Ed25519)
|
|
||||||
- [SSH Key Algorithm Comparison](https://security.stackexchange.com/questions/5096/rsa-vs-dsa-for-ssh-authentication-keys)
|
|
||||||
- [Azure DevOps SSH Documentation](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.
|
|
||||||
Reference in New Issue
Block a user