Compare commits
23 Commits
7b638d27de
...
advanced-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2e36690ea | ||
|
|
356b6268ba | ||
|
|
bd69774191 | ||
|
|
fcaf97f60b | ||
|
|
2a5eb137f6 | ||
|
|
b0d2d43c8b | ||
|
|
cdd695b250 | ||
|
|
0474a6de0e | ||
|
|
575e083f33 | ||
|
|
aa24c50b45 | ||
|
|
939bd397f1 | ||
|
|
3130874981 | ||
|
|
a34b7d155f | ||
|
|
1c20a06c21 | ||
|
|
988ce3bc92 | ||
|
|
9fbdd941fa | ||
|
|
40341d21a7 | ||
|
|
8a82253fc2 | ||
|
|
f0522e14bc | ||
|
|
0183a06134 | ||
|
|
c3d9d3337c | ||
|
|
ced65740a3 | ||
|
|
bf07cb1868 |
@@ -14,7 +14,7 @@ By the end of this module, you will:
|
||||
|
||||
Create the challenge environment:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
.\setup.ps1
|
||||
```
|
||||
|
||||
@@ -39,7 +39,7 @@ This creates a repository with a realistic project history showing multiple merg
|
||||
|
||||
First, explore the existing history to see what merging looks like:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
cd challenge
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
@@ -55,7 +55,7 @@ You'll see a visual graph showing:
|
||||
- Find the merge commits (they have two parent lines converging)
|
||||
|
||||
**Explore the branches:**
|
||||
```bash
|
||||
```pwsh
|
||||
# See all branches
|
||||
git branch --all
|
||||
|
||||
@@ -66,7 +66,7 @@ git ls-tree --name-only main
|
||||
```
|
||||
|
||||
**View specific merges:**
|
||||
```bash
|
||||
```pwsh
|
||||
# See all merge commits
|
||||
git log --merges --oneline
|
||||
|
||||
@@ -78,64 +78,38 @@ git show <merge-commit-hash>
|
||||
|
||||
Now practice creating your own branch:
|
||||
|
||||
```bash
|
||||
# Create and switch to a new branch
|
||||
git switch -c my-feature
|
||||
|
||||
# Verify you're on the new branch
|
||||
git branch
|
||||
```
|
||||
|
||||
The `*` shows which branch you're currently on.
|
||||
1. Create a new branch with the name `my-feature` using `git switch -c my-feature`
|
||||
2. Check which branch you're on with `git branch`. The `*` shows which branch you're currently on.
|
||||
|
||||
### Part 3: Make Commits on Your Branch
|
||||
|
||||
Add some changes to your branch:
|
||||
|
||||
```bash
|
||||
# Create a new file or modify an existing one
|
||||
echo "# My Feature" > my-feature.md
|
||||
|
||||
# Stage and commit
|
||||
git add .
|
||||
git commit -m "Add my feature"
|
||||
|
||||
# Make another commit
|
||||
echo "More details" >> my-feature.md
|
||||
git add .
|
||||
git commit -m "Expand feature documentation"
|
||||
```
|
||||
1. Create a new file called `DOCS.md` and add some content. What doesn't matter, just something.
|
||||
2. Add and commit the `DOCS.md` file (use the commands from `01-basics` module).
|
||||
3. Add some more content to the `DOCS.md` file, add and commit the changes.
|
||||
4. Now check how the tree is looking with `git log --oneline --graph --all`
|
||||
|
||||
**Important:** Changes on your branch don't affect main!
|
||||
|
||||
```bash
|
||||
# Switch to main
|
||||
git switch main
|
||||
|
||||
# Notice your my-feature.md doesn't exist here
|
||||
ls
|
||||
|
||||
# Switch back to your branch
|
||||
git switch my-feature
|
||||
|
||||
# Now it exists again!
|
||||
ls
|
||||
```
|
||||
1. Change back to the `main` branch `git switch main`
|
||||
2. Check the changes you committed before. You'll notice that they're gone!
|
||||
3. Now edit a file or create a new file (perhaps GUIDE.md, content of the file doesn't matter) and add it and commit it on your `main` branch (hint: `git add .`, `git commit -m`)
|
||||
- This way we create diverging branches. The `main` branch has changes as well as your new `my-feature` branch.
|
||||
- Run `git log --oneline --graph --all` to see how the tree is looking
|
||||
4. Switch back to your branch `git switch my-feature`
|
||||
5. The changes from the `main` branch are now gone. Check the changes you committed before. You'll notice they're back!
|
||||
|
||||
### Part 4: Merge Your Branch
|
||||
|
||||
Bring your work into main:
|
||||
|
||||
```bash
|
||||
# Switch to the branch you want to merge INTO
|
||||
git switch main
|
||||
|
||||
# Merge your feature branch
|
||||
git merge my-feature
|
||||
|
||||
# View the result
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
1. Go back to the main branch `git switch main`
|
||||
2. Run a `git log --oneline --graph --all` to see that the `HEAD` is on your `main` branch
|
||||
3. It might be wise to first ensure that we're using Visual Studio Code to handle merge messages. If you haven't already set `git config --global core.editor "code --wait"` this sets Visual Studio Code to be the default editor for anything Git related.
|
||||
4. Let's merge the recently created branch `git merge my-feature`
|
||||
5. Visual Studio Code should open with a commit message. In order to solidify the commit simply close the window. That tells Git that the commit message has been written and the change should be committed.
|
||||
6. Now run `git log --oneline --graph --all` and see your changes merge into the `main` branch!
|
||||
7. Now let's clean up a bit, run `git branch -d my-feature` to remove the recently merged branch.
|
||||
- If you hadn't merged the branch first this command would fail as Git will warn you that you have changes not merged into the `main` branch
|
||||
|
||||
You should see your feature branch merged into main!
|
||||
|
||||
@@ -143,7 +117,7 @@ You should see your feature branch merged into main!
|
||||
|
||||
Create additional branches to practice:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Create another feature
|
||||
git switch -c another-feature
|
||||
|
||||
@@ -156,7 +130,7 @@ git merge another-feature
|
||||
```
|
||||
|
||||
**Verify your work:**
|
||||
```bash
|
||||
```pwsh
|
||||
# From the module directory (not inside challenge/)
|
||||
.\verify.ps1
|
||||
```
|
||||
@@ -182,7 +156,7 @@ my-feature: D---E
|
||||
|
||||
`HEAD` points to your current branch. It's Git's way of saying "you are here."
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# HEAD points to main
|
||||
git switch main
|
||||
|
||||
@@ -226,7 +200,7 @@ If main hasn't changed, Git just moves the pointer forward. No merge commit need
|
||||
|
||||
### Branching
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# List all branches (* shows current branch)
|
||||
git branch
|
||||
|
||||
@@ -251,7 +225,7 @@ git branch -D feature-name
|
||||
|
||||
### Merging
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Merge a branch into your current branch
|
||||
git merge branch-name
|
||||
|
||||
@@ -264,7 +238,7 @@ git merge --no-ff branch-name
|
||||
|
||||
### Viewing History
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Visual branch graph
|
||||
git log --oneline --graph --all
|
||||
|
||||
@@ -285,7 +259,7 @@ git branch --no-merged main
|
||||
|
||||
### Creating a Feature
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Start from main
|
||||
git switch main
|
||||
|
||||
@@ -305,7 +279,7 @@ git commit -m "Improve awesome feature"
|
||||
|
||||
### Merging a Feature
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Switch to main
|
||||
git switch main
|
||||
|
||||
@@ -318,7 +292,7 @@ git branch -d feature-awesome
|
||||
|
||||
### Keeping Main Updated While Working
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# You're on feature-awesome
|
||||
git switch feature-awesome
|
||||
|
||||
@@ -337,7 +311,7 @@ git pull origin main
|
||||
|
||||
### "I'm on the wrong branch!"
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Switch to the correct branch
|
||||
git switch correct-branch
|
||||
|
||||
@@ -349,7 +323,7 @@ git branch
|
||||
|
||||
Don't panic! You can move commits to another branch:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Create the correct branch from current state
|
||||
git branch correct-branch
|
||||
|
||||
@@ -363,7 +337,7 @@ git switch correct-branch
|
||||
|
||||
### "The merge created unexpected results!"
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Undo the merge
|
||||
git merge --abort
|
||||
|
||||
@@ -373,7 +347,7 @@ git reset --hard HEAD~1
|
||||
|
||||
### "I want to see what changed in a merge!"
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Show the merge commit
|
||||
git show <merge-commit-hash>
|
||||
|
||||
@@ -384,15 +358,10 @@ git diff main..feature-branch
|
||||
## Tips for Success
|
||||
|
||||
💡 **Branch often** - Branches are cheap! Create one for each feature or experiment.
|
||||
|
||||
💡 **Commit before switching** - Always commit (or stash) changes before switching branches.
|
||||
|
||||
💡 **Keep branches focused** - One feature per branch makes merging easier.
|
||||
|
||||
💡 **Delete merged branches** - Clean up with `git branch -d branch-name` after merging.
|
||||
|
||||
💡 **Use descriptive names** - `feature-login` is better than `stuff` or `branch1`.
|
||||
|
||||
💡 **Visualize often** - Run `git log --oneline --graph --all` to understand your history.
|
||||
|
||||
## What You've Learned
|
||||
@@ -412,7 +381,7 @@ After completing this module, you understand:
|
||||
Ready to continue? The next module covers **merge conflicts** - what happens when Git can't automatically merge changes.
|
||||
|
||||
To start over:
|
||||
```bash
|
||||
```pwsh
|
||||
.\reset.ps1
|
||||
.\setup.ps1
|
||||
```
|
||||
|
||||
Submodule 01-essentials/03-branching-and-merging/challenge deleted from 8ae52cc25c
@@ -31,6 +31,19 @@ git init | Out-Null
|
||||
git config user.name "Workshop Student"
|
||||
git config user.email "student@example.com"
|
||||
|
||||
# Detect the default branch name (could be main, master, etc.)
|
||||
# First commit creates the branch, so we detect it after that
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
# Fallback: Get default branch name from git config
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
# Ultimate fallback: use "main"
|
||||
$mainBranch = "main"
|
||||
}
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
# ============================================================================
|
||||
# Create a realistic project history with multiple merged branches
|
||||
# ============================================================================
|
||||
@@ -96,8 +109,8 @@ Set-Content -Path "login.py" -Value $loginContent
|
||||
git add .
|
||||
git commit -m "Add password validation" | Out-Null
|
||||
|
||||
# Switch back to main and make more commits
|
||||
git switch main | Out-Null
|
||||
# Switch back to main branch
|
||||
git switch $mainBranch | Out-Null
|
||||
|
||||
$appContent = @"
|
||||
# app.py - Application entry point
|
||||
@@ -116,8 +129,8 @@ Set-Content -Path "app.py" -Value $appContent
|
||||
git add .
|
||||
git commit -m "Add app.py entry point" | Out-Null
|
||||
|
||||
# Merge feature-login into main
|
||||
Write-Host "Merging feature-login into main..." -ForegroundColor Green
|
||||
# Merge feature-login into $mainBranch
|
||||
Write-Host "Merging feature-login into $mainBranch..." -ForegroundColor Green
|
||||
git merge feature-login --no-edit | Out-Null
|
||||
|
||||
# ============================================================================
|
||||
@@ -163,8 +176,8 @@ Set-Content -Path "api.py" -Value $apiContent
|
||||
git add .
|
||||
git commit -m "Add delete endpoint to API" | Out-Null
|
||||
|
||||
# Switch back to main and add documentation
|
||||
git switch main | Out-Null
|
||||
# Switch back to main branch and add documentation
|
||||
git switch $mainBranch | Out-Null
|
||||
|
||||
$readmeContent = @"
|
||||
# My Application
|
||||
@@ -184,8 +197,8 @@ Set-Content -Path "README.md" -Value $readmeContent
|
||||
git add .
|
||||
git commit -m "Update README with setup instructions" | Out-Null
|
||||
|
||||
# Merge feature-api into main
|
||||
Write-Host "Merging feature-api into main..." -ForegroundColor Green
|
||||
# Merge feature-api into $mainBranch
|
||||
Write-Host "Merging feature-api into $mainBranch..." -ForegroundColor Green
|
||||
git merge feature-api --no-edit | Out-Null
|
||||
|
||||
# ============================================================================
|
||||
@@ -241,9 +254,23 @@ Set-Content -Path "database.py" -Value $dbContent
|
||||
git add .
|
||||
git commit -m "Add disconnect method" | Out-Null
|
||||
|
||||
# Switch to main and merge
|
||||
git switch main | Out-Null
|
||||
Write-Host "Merging feature-database into main..." -ForegroundColor Green
|
||||
# Switch to main branch and add another commit (to create divergent history)
|
||||
git switch $mainBranch | Out-Null
|
||||
|
||||
$configContent = @"
|
||||
{
|
||||
"app": {
|
||||
"port": 3000,
|
||||
"debug": false
|
||||
}
|
||||
}
|
||||
"@
|
||||
Set-Content -Path "config.json" -Value $configContent
|
||||
git add .
|
||||
git commit -m "Add configuration file" | Out-Null
|
||||
|
||||
# Merge feature-database (will be three-way merge since main diverged)
|
||||
Write-Host "Merging feature-database into $mainBranch..." -ForegroundColor Green
|
||||
git merge feature-database --no-edit | Out-Null
|
||||
|
||||
# Final update on main
|
||||
@@ -278,7 +305,7 @@ Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
||||
Write-Host "`nThe repository contains a realistic project history:" -ForegroundColor Yellow
|
||||
Write-Host " - Multiple feature branches (login, api, database)" -ForegroundColor White
|
||||
Write-Host " - All branches have been merged into main" -ForegroundColor White
|
||||
Write-Host " - All branches have been merged into $mainBranch" -ForegroundColor White
|
||||
Write-Host " - View the history: git log --oneline --graph --all" -ForegroundColor White
|
||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White
|
||||
|
||||
@@ -59,9 +59,33 @@ if (-not (Test-Path ".git")) {
|
||||
Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundColor Cyan
|
||||
|
||||
# ============================================================================
|
||||
# Count initial setup commits (should be 13 commits from setup)
|
||||
# Detect the main branch name (could be main, master, etc.)
|
||||
# ============================================================================
|
||||
$initialCommitCount = 13
|
||||
# Try to get the default branch from remote origin first
|
||||
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
||||
if (-not $mainBranch) {
|
||||
# Fallback: try to detect from local branches
|
||||
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||
if ($allBranches -contains "main") {
|
||||
$mainBranch = "main"
|
||||
} elseif ($allBranches -contains "master") {
|
||||
$mainBranch = "master"
|
||||
} else {
|
||||
# Get the default branch from git config
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
# Ultimate fallback: use the first branch
|
||||
$mainBranch = $allBranches | Select-Object -First 1
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
|
||||
# ============================================================================
|
||||
# Count initial setup commits (should be 15 commits from setup)
|
||||
# ============================================================================
|
||||
$initialCommitCount = 15
|
||||
|
||||
# ============================================================================
|
||||
# Check for new commits beyond setup
|
||||
@@ -83,7 +107,7 @@ if ($totalCommits -gt $initialCommitCount) {
|
||||
# Check for branches (excluding the example branches)
|
||||
# ============================================================================
|
||||
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||
$exampleBranches = @('main', 'feature-login', 'feature-api', 'feature-database')
|
||||
$exampleBranches = @($mainBranch, 'feature-login', 'feature-api', 'feature-database')
|
||||
$studentBranches = $allBranches | Where-Object { $_ -notin $exampleBranches }
|
||||
|
||||
if ($studentBranches.Count -gt 0) {
|
||||
@@ -115,11 +139,11 @@ if ($totalMerges -gt $setupMerges) {
|
||||
# Check current branch
|
||||
# ============================================================================
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -eq "main") {
|
||||
Write-Pass "Currently on main branch"
|
||||
if ($currentBranch -eq $mainBranch) {
|
||||
Write-Pass "Currently on $mainBranch branch"
|
||||
} else {
|
||||
Write-Info "Currently on '$currentBranch' branch"
|
||||
Write-Hint "Typically you merge feature branches INTO main"
|
||||
Write-Hint "Typically you merge feature branches INTO $mainBranch"
|
||||
}
|
||||
|
||||
Pop-Location
|
||||
@@ -145,7 +169,7 @@ if ($script:allChecksPassed) {
|
||||
Write-Host "Quick guide:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Create a branch: git switch -c my-feature" -ForegroundColor White
|
||||
Write-Host " 2. Make changes and commit them" -ForegroundColor White
|
||||
Write-Host " 3. Switch to main: git switch main" -ForegroundColor White
|
||||
Write-Host " 3. Switch to $mainBranch : git switch $mainBranch" -ForegroundColor White
|
||||
Write-Host " 4. Merge your branch: git merge my-feature" -ForegroundColor White
|
||||
Write-Host " 5. Run this verify script again" -ForegroundColor White
|
||||
Write-Host ""
|
||||
|
||||
Submodule 01-essentials/04-merge-conflict/challenge deleted from 676b08e650
@@ -30,6 +30,19 @@ git init | Out-Null
|
||||
git config user.name "Workshop Student"
|
||||
git config user.email "student@example.com"
|
||||
|
||||
# Detect the default branch name (could be main, master, etc.)
|
||||
# First commit creates the branch, so we detect it after that
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
# Fallback: Get default branch name from git config
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
# Ultimate fallback: use "main"
|
||||
$mainBranch = "main"
|
||||
}
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
# ============================================================================
|
||||
# Create base project
|
||||
# ============================================================================
|
||||
@@ -87,7 +100,7 @@ git commit -m "Add timeout configuration" | Out-Null
|
||||
# Branch 2: add-debug (adds debug setting - CONFLICTS with timeout!)
|
||||
# ============================================================================
|
||||
Write-Host "Creating add-debug branch..." -ForegroundColor Cyan
|
||||
git switch main | Out-Null
|
||||
git switch $mainBranch | Out-Null
|
||||
git switch -c add-debug | Out-Null
|
||||
|
||||
$debugConfig = @"
|
||||
@@ -104,8 +117,8 @@ Set-Content -Path "config.json" -Value $debugConfig
|
||||
git add .
|
||||
git commit -m "Add debug mode configuration" | Out-Null
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
# Switch back to main branch
|
||||
git switch $mainBranch | Out-Null
|
||||
|
||||
# Return to module directory
|
||||
Set-Location ..
|
||||
@@ -113,7 +126,7 @@ Set-Location ..
|
||||
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
||||
Write-Host "`nThe repository contains:" -ForegroundColor Yellow
|
||||
Write-Host " - main branch: base configuration" -ForegroundColor White
|
||||
Write-Host " - $mainBranch branch: base configuration" -ForegroundColor White
|
||||
Write-Host " - add-timeout branch: adds timeout setting" -ForegroundColor White
|
||||
Write-Host " - add-debug branch: adds debug setting" -ForegroundColor White
|
||||
Write-Host "`nBoth branches modify the same part of config.json!" -ForegroundColor Red
|
||||
|
||||
@@ -59,15 +59,39 @@ if (-not (Test-Path ".git")) {
|
||||
|
||||
Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cyan
|
||||
|
||||
# ============================================================================
|
||||
# Detect the main branch name (could be main, master, etc.)
|
||||
# ============================================================================
|
||||
# Try to get the default branch from remote origin first
|
||||
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
||||
if (-not $mainBranch) {
|
||||
# Fallback: try to detect from local branches
|
||||
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||
if ($allBranches -contains "main") {
|
||||
$mainBranch = "main"
|
||||
} elseif ($allBranches -contains "master") {
|
||||
$mainBranch = "master"
|
||||
} else {
|
||||
# Get the default branch from git config
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
# Ultimate fallback: use the first branch
|
||||
$mainBranch = $allBranches | Select-Object -First 1
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
}
|
||||
}
|
||||
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
|
||||
# ============================================================================
|
||||
# Check current branch
|
||||
# ============================================================================
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -eq "main") {
|
||||
Write-Pass "Currently on main branch"
|
||||
if ($currentBranch -eq $mainBranch) {
|
||||
Write-Pass "Currently on $mainBranch branch"
|
||||
} else {
|
||||
Write-Fail "Should be on main branch (currently on: $currentBranch)"
|
||||
Write-Hint "Switch to main with: git switch main"
|
||||
Write-Fail "Should be on $mainBranch branch (currently on: $currentBranch)"
|
||||
Write-Hint "Switch to $mainBranch with: git switch $mainBranch"
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
@@ -196,7 +220,7 @@ if ($script:allChecksPassed) {
|
||||
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
|
||||
Write-Host ""
|
||||
Write-Host "Quick guide:" -ForegroundColor Cyan
|
||||
Write-Host " 1. Make sure you're on main: git switch main" -ForegroundColor White
|
||||
Write-Host " 1. Make sure you're on $mainBranch : git switch $mainBranch" -ForegroundColor White
|
||||
Write-Host " 2. Merge first branch: git merge add-timeout" -ForegroundColor White
|
||||
Write-Host " 3. Merge second branch: git merge add-debug" -ForegroundColor White
|
||||
Write-Host " 4. Resolve conflict: edit config.json, remove markers, keep both settings" -ForegroundColor White
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# Module 09: Cherry-Pick
|
||||
# Module 05: Cherry-Pick
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
@@ -6,155 +6,358 @@ By the end of this module, you will:
|
||||
- Understand what cherry-picking is and how it works
|
||||
- Know when to use cherry-pick vs merge or rebase
|
||||
- Apply specific commits from one branch to another
|
||||
- Handle cherry-pick conflicts if they occur
|
||||
- Understand common use cases for cherry-picking
|
||||
- Learn how cherry-pick creates new commits with different hashes
|
||||
|
||||
## Challenge Description
|
||||
## Setup
|
||||
|
||||
You have a `development` branch with several commits. Some of these commits are bug fixes that need to be applied to the `main` branch immediately, but other commits are experimental features that shouldn't be merged yet.
|
||||
Create the challenge environment:
|
||||
|
||||
Your task is to:
|
||||
1. Review the commits on the development branch
|
||||
2. Identify which commits are bug fixes
|
||||
3. Cherry-pick only the bug fix commits to the main branch
|
||||
4. Verify that main has the bug fixes but not the experimental features
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### What is Cherry-Pick?
|
||||
|
||||
Cherry-pick allows you to apply a specific commit from one branch to another. Instead of merging an entire branch, you can selectively choose individual commits.
|
||||
|
||||
```
|
||||
A---B---C---D development
|
||||
/
|
||||
E---F main
|
||||
```pwsh
|
||||
.\setup.ps1
|
||||
```
|
||||
|
||||
After cherry-picking commit C:
|
||||
```
|
||||
A---B---C---D development
|
||||
/
|
||||
E---F---C' main
|
||||
This creates a repository with a `development` branch containing both bug fixes and experimental features.
|
||||
|
||||
## Overview
|
||||
|
||||
**Cherry-pick** allows you to copy specific commits from one branch to another. Unlike merging (which brings ALL commits), cherry-pick lets you be surgical about exactly which changes you want to apply.
|
||||
|
||||
Think of it like picking cherries from a tree - you select only the ripe ones you want, leaving the rest behind.
|
||||
|
||||
### Why Use Cherry-Pick?
|
||||
|
||||
- **Selective deployment** - Apply critical bug fixes without merging unfinished features
|
||||
- **Hotfixes** - Quickly move a fix from development to production
|
||||
- **Backporting** - Apply fixes to older release branches
|
||||
- **Wrong branch** - Move commits you accidentally made on the wrong branch
|
||||
- **Duplicate commits** - Apply the same fix across multiple branches
|
||||
|
||||
## Your Task
|
||||
|
||||
### The Scenario
|
||||
|
||||
You're working on a project where:
|
||||
- The `main` branch is stable and in production
|
||||
- The `development` branch has new features being tested
|
||||
- Development has critical bug fixes that need to go to production NOW
|
||||
- But development also has experimental features that aren't ready yet
|
||||
|
||||
You need to cherry-pick ONLY the bug fixes to main, leaving the experimental features behind.
|
||||
|
||||
### Part 1: Explore the Development Branch
|
||||
|
||||
First, see what commits are on the development branch:
|
||||
|
||||
```pwsh
|
||||
cd challenge
|
||||
|
||||
# View all commits on development branch
|
||||
git log --oneline development
|
||||
|
||||
# View the full commit graph
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
Note that C' is a new commit with the same changes as C but a different commit hash.
|
||||
**Study the commits:**
|
||||
- Look for commits with "Fix" in the message (these are bug fixes)
|
||||
- Look for commits with "experimental" or "beta" (these should stay on development)
|
||||
- Note the commit hashes (the 7-character codes like `abc1234`)
|
||||
|
||||
### Cherry-Pick vs Merge vs Rebase
|
||||
|
||||
- **Merge**: Brings all commits from another branch and creates a merge commit
|
||||
- **Rebase**: Replays all commits from your branch on top of another branch
|
||||
- **Cherry-Pick**: Applies one or more specific commits to your current branch
|
||||
|
||||
### When to Use Cherry-Pick
|
||||
|
||||
Cherry-pick is useful when you:
|
||||
- Need a bug fix from a feature branch but can't merge the whole branch yet
|
||||
- Want to apply a specific commit to a release branch
|
||||
- Need to backport a fix to an older version
|
||||
- Made a commit on the wrong branch and need to move it
|
||||
- Want to duplicate a commit across multiple branches
|
||||
|
||||
### Cherry-Pick Creates New Commits
|
||||
|
||||
Important: Cherry-picked commits are new commits with different hashes. The original commit remains on the source branch, and a copy is created on the target branch with the same changes but a different commit ID.
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# View commits on another branch
|
||||
git log <branch-name> --oneline
|
||||
|
||||
# View a specific commit's details
|
||||
**Inspect specific commits:**
|
||||
```pwsh
|
||||
# See what files a commit changed
|
||||
git show <commit-hash>
|
||||
|
||||
# Example:
|
||||
# git show abc1234
|
||||
```
|
||||
|
||||
You should see:
|
||||
- 2 commits that fix bugs (security and performance)
|
||||
- 2 commits that add experimental features
|
||||
|
||||
### Part 2: Switch to Main Branch
|
||||
|
||||
Before cherry-picking, you need to be on the target branch (main):
|
||||
|
||||
```pwsh
|
||||
# Switch to main branch
|
||||
git switch main
|
||||
|
||||
# Verify you're on main
|
||||
git branch
|
||||
```
|
||||
|
||||
The `*` should be next to `main`.
|
||||
|
||||
**Check what's currently on main:**
|
||||
```pwsh
|
||||
# See main's commits
|
||||
git log --oneline
|
||||
|
||||
# See what files exist
|
||||
ls
|
||||
```
|
||||
|
||||
Main should only have the initial app and README - no bug fixes yet, no experimental features.
|
||||
|
||||
### Part 3: Cherry-Pick the Bug Fixes
|
||||
|
||||
Now copy the bug fix commits from development to main:
|
||||
|
||||
1. Find the security fix commit hash by looking at your earlier `git log --oneline --graph --all`
|
||||
- Look for a commit message like "Fix security vulnerability in input validation"
|
||||
- Note its hash (first 7 characters)
|
||||
|
||||
2. Cherry-pick the security fix:
|
||||
```pwsh
|
||||
git cherry-pick <security-fix-hash>
|
||||
|
||||
# Example if the hash is abc1234:
|
||||
# git cherry-pick abc1234
|
||||
```
|
||||
3. Verify it worked: Check that security.py, with `ls` or check your file explorer in VSCode, now exists and check that the commit has been added to the main branch with `git log --oneline --graph --all`
|
||||
4. Find the performance fix commit hash
|
||||
- Look for "Fix performance issue with data caching"
|
||||
- Note its hash
|
||||
|
||||
5. Cherry-pick the performance fix:
|
||||
```pwsh
|
||||
git cherry-pick <performance-fix-hash>
|
||||
```
|
||||
|
||||
6. Verify both fixes are now on main:
|
||||
```pwsh
|
||||
# You should see both security.py and cache.py
|
||||
ls
|
||||
|
||||
# View the graph showing both branches
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
### Part 4: Verify Your Solution
|
||||
|
||||
Check that you completed the challenge correctly:
|
||||
|
||||
```pwsh
|
||||
# From inside the module directory
|
||||
.\verify.ps1
|
||||
```
|
||||
|
||||
The verification checks:
|
||||
- ✅ You're on the main branch
|
||||
- ✅ Security fix is applied to main
|
||||
- ✅ Performance fix is applied to main
|
||||
- ✅ Experimental features are NOT on main
|
||||
- ✅ Development branch still has all commits
|
||||
|
||||
## Understanding Cherry-Pick
|
||||
|
||||
### What Actually Happens?
|
||||
|
||||
When you cherry-pick a commit, Git:
|
||||
1. Looks at what changed in that specific commit
|
||||
2. Applies those same changes to your current branch
|
||||
3. Creates a NEW commit with those changes
|
||||
|
||||
```
|
||||
Before cherry-pick:
|
||||
|
||||
development: A---B---C---D
|
||||
/
|
||||
main: E---F
|
||||
|
||||
After: git switch main && git cherry-pick C
|
||||
|
||||
development: A---B---C---D
|
||||
/
|
||||
main: E---F---C'
|
||||
```
|
||||
|
||||
Notice:
|
||||
- `C'` is a NEW commit (different hash than original `C`)
|
||||
- Original `C` still exists on development
|
||||
- Main now has the changes from C, but not B or D
|
||||
|
||||
### Cherry-Pick vs Merge
|
||||
|
||||
**Merge brings everything:**
|
||||
```pwsh
|
||||
git switch main
|
||||
git merge development
|
||||
# Result: A, B, C, and D all come to main
|
||||
```
|
||||
|
||||
**Cherry-pick is selective:**
|
||||
```pwsh
|
||||
git switch main
|
||||
git cherry-pick C
|
||||
# Result: Only C comes to main (as C')
|
||||
```
|
||||
|
||||
### Important: New Commits, New Hashes
|
||||
|
||||
Cherry-picked commits are COPIES, not moves:
|
||||
- Original commit stays on source branch
|
||||
- New commit created on target branch
|
||||
- Different commit hash (because different parent)
|
||||
- Same changes, same message, different identity
|
||||
|
||||
## Key Commands
|
||||
|
||||
### Viewing Commits
|
||||
|
||||
```pwsh
|
||||
# See commits on another branch
|
||||
git log branch-name --oneline
|
||||
|
||||
# See what a specific commit changed
|
||||
git show <commit-hash>
|
||||
|
||||
# See commit graph
|
||||
git log --oneline --graph --all
|
||||
|
||||
# See only commit message (not changes)
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
### Cherry-Picking
|
||||
|
||||
```pwsh
|
||||
# Cherry-pick a single commit
|
||||
git cherry-pick <commit-hash>
|
||||
|
||||
# Cherry-pick multiple commits
|
||||
git cherry-pick <commit-hash1> <commit-hash2>
|
||||
# Cherry-pick multiple commits (in order)
|
||||
git cherry-pick <hash1> <hash2> <hash3>
|
||||
|
||||
# Cherry-pick a range of commits
|
||||
git cherry-pick <start-hash>..<end-hash>
|
||||
|
||||
# If conflicts occur during cherry-pick:
|
||||
# 1. Resolve conflicts in files
|
||||
# 2. Stage the resolved files
|
||||
git add <file>
|
||||
# 3. Continue the cherry-pick
|
||||
git cherry-pick --continue
|
||||
|
||||
# Abort a cherry-pick if something goes wrong
|
||||
git cherry-pick --abort
|
||||
|
||||
# View commit history graph
|
||||
git log --oneline --graph --all
|
||||
```
|
||||
|
||||
## Verification
|
||||
### After Cherry-Pick
|
||||
|
||||
Run the verification script to check your solution:
|
||||
```pwsh
|
||||
# Verify the commit was added
|
||||
git log --oneline
|
||||
|
||||
```bash
|
||||
.\verify.ps1
|
||||
# See what files changed
|
||||
git show HEAD
|
||||
|
||||
# Compare branches
|
||||
git log main..development --oneline
|
||||
```
|
||||
|
||||
The verification will check that:
|
||||
- You're on the main branch
|
||||
- The security bug fix commit has been applied to main
|
||||
- The performance bug fix commit has been applied to main
|
||||
- The experimental features are NOT on main
|
||||
- The commits were cherry-picked (not merged)
|
||||
|
||||
## Challenge Steps
|
||||
|
||||
1. Navigate to the challenge directory
|
||||
2. You're currently on the development branch
|
||||
3. View the commits: `git log --oneline`
|
||||
4. You'll see several commits - identify the bug fixes
|
||||
5. Switch to main branch: `git switch main`
|
||||
6. Cherry-pick the bug fix commits (you'll need their commit hashes)
|
||||
7. Verify the result with `git log --oneline`
|
||||
8. Run the verification script
|
||||
|
||||
## Tips
|
||||
|
||||
- Use `git log development --oneline` to see commits on the development branch
|
||||
- Use `git show <hash>` to view details of a specific commit
|
||||
- You can cherry-pick by commit hash - you only need the first 7 characters
|
||||
- Cherry-pick commits in chronological order (oldest first) to avoid conflicts
|
||||
- If you make a mistake, use `.\reset.ps1` to start over
|
||||
- The commit message will be preserved when cherry-picking
|
||||
|
||||
## Common Cherry-Pick Scenarios
|
||||
## Common Workflows
|
||||
|
||||
### Hotfix to Production
|
||||
You have a critical bug fix on a development branch that needs to go to production immediately:
|
||||
```bash
|
||||
git switch production
|
||||
git cherry-pick <bugfix-commit-hash>
|
||||
```
|
||||
|
||||
### Wrong Branch
|
||||
You accidentally committed on the wrong branch:
|
||||
```bash
|
||||
# On wrong branch, note the commit hash
|
||||
Critical bug found in production:
|
||||
|
||||
```pwsh
|
||||
# You're on feature-new-ui branch
|
||||
# You just committed a critical security fix
|
||||
|
||||
git log --oneline
|
||||
# Switch to correct branch
|
||||
git switch correct-branch
|
||||
git cherry-pick <commit-hash>
|
||||
# Go back and remove from wrong branch
|
||||
git switch wrong-branch
|
||||
git reset --hard HEAD~1
|
||||
# Note the hash of your fix commit
|
||||
|
||||
# Switch to production branch
|
||||
git switch production
|
||||
|
||||
# Apply just that fix
|
||||
git cherry-pick <fix-commit-hash>
|
||||
|
||||
# Deploy to production
|
||||
# Your fix is live, but new UI stays in development
|
||||
```
|
||||
|
||||
### Backporting
|
||||
You need to apply a fix to an older release branch:
|
||||
```bash
|
||||
### Backporting to Old Versions
|
||||
|
||||
```pwsh
|
||||
# You fixed a bug on main
|
||||
git switch main
|
||||
git log --oneline
|
||||
# Note the fix commit hash
|
||||
|
||||
# Apply to older release branch
|
||||
git switch release-2.5
|
||||
git cherry-pick <fix-commit-hash>
|
||||
|
||||
# Apply to even older release
|
||||
git switch release-2.0
|
||||
git cherry-pick <fix-from-main>
|
||||
git cherry-pick <fix-commit-hash>
|
||||
|
||||
# Same fix now on three branches!
|
||||
```
|
||||
|
||||
## What You'll Learn
|
||||
## Troubleshooting
|
||||
|
||||
Cherry-pick is a surgical tool in your Git toolbox. While merge and rebase work with entire branches, cherry-pick lets you be selective about which changes to apply. This is invaluable for managing hotfixes, maintaining multiple release branches, and handling situations where you need specific changes without bringing along everything else. Understanding when to use cherry-pick versus other Git operations is a mark of Git expertise.
|
||||
### "I can't remember the commit hash!"
|
||||
|
||||
```pwsh
|
||||
# See commits on the source branch
|
||||
git log development --oneline
|
||||
|
||||
# Search for specific text in commit messages
|
||||
git log development --oneline --grep="security"
|
||||
|
||||
# See recent commits with more detail
|
||||
git log development --oneline -n 10
|
||||
```
|
||||
|
||||
### "I cherry-picked in the wrong order!"
|
||||
|
||||
Order matters! If commit B depends on commit A, cherry-pick A first:
|
||||
|
||||
```pwsh
|
||||
# Wrong order might cause issues
|
||||
git cherry-pick B # Might fail if it needs changes from A
|
||||
|
||||
# Correct order
|
||||
git cherry-pick A
|
||||
git cherry-pick B
|
||||
```
|
||||
|
||||
### "How do I see what will change before cherry-picking?"
|
||||
|
||||
```pwsh
|
||||
# See what changes are in a commit
|
||||
git show <commit-hash>
|
||||
|
||||
# Compare your current branch with a commit
|
||||
git diff HEAD <commit-hash>
|
||||
```
|
||||
|
||||
## Tips for Success
|
||||
|
||||
💡 **Copy the commit hashes** - Write them down before switching branches
|
||||
💡 **Cherry-pick oldest first** - Apply commits in chronological order
|
||||
💡 **Check your branch** - Always verify you're on the target branch first with `git branch`
|
||||
💡 **Verify after each pick** - Run `git log --oneline` to confirm it worked
|
||||
💡 **Use the graph** - `git log --oneline --graph --all` shows the full picture
|
||||
💡 **Original stays put** - Cherry-pick copies, doesn't move commits
|
||||
|
||||
## What You've Learned
|
||||
|
||||
After completing this module, you understand:
|
||||
|
||||
- ✅ Cherry-pick copies specific commits between branches
|
||||
- ✅ `git cherry-pick <hash>` applies a commit to current branch
|
||||
- ✅ Cherry-picked commits get new hashes but same changes
|
||||
- ✅ Use cherry-pick for selective deployment of changes
|
||||
- ✅ Cherry-pick is different from merge (selective vs all)
|
||||
- ✅ Original commit stays on source branch
|
||||
|
||||
## Next Steps
|
||||
|
||||
Ready to continue? Cherry-pick is a powerful tool for selective change management. Next modules will cover more advanced Git operations.
|
||||
|
||||
To start over:
|
||||
```pwsh
|
||||
.\reset.ps1
|
||||
```
|
||||
|
||||
**Need help?** Run `git status` to see what Git suggests, or `git log --oneline --graph --all` to see the full picture!
|
||||
|
||||
@@ -26,7 +26,8 @@ git init | Out-Null
|
||||
git config user.name "Workshop User" | Out-Null
|
||||
git config user.email "user@workshop.local" | Out-Null
|
||||
|
||||
# Create initial commits on main branch
|
||||
# Detect the default branch name (could be main, master, etc.)
|
||||
# First commit creates the branch, so we detect it after the first commit below
|
||||
$app = @"
|
||||
class App:
|
||||
def __init__(self):
|
||||
@@ -40,6 +41,14 @@ Set-Content -Path "app.py" -Value $app
|
||||
git add app.py
|
||||
git commit -m "Initial app implementation" | Out-Null
|
||||
|
||||
# Detect the main branch name after first commit
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
$readme = @"
|
||||
# Application
|
||||
|
||||
@@ -119,40 +128,29 @@ git add app.py
|
||||
git commit -m "Add beta features framework" | Out-Null
|
||||
|
||||
# Commit 4: Performance bug fix (SHOULD be cherry-picked)
|
||||
$appWithPerformance = @"
|
||||
class App:
|
||||
# This commit adds caching to the existing file to fix performance
|
||||
# It should apply cleanly to main since it doesn't depend on experimental features
|
||||
$performanceCode = @"
|
||||
|
||||
class DataCache:
|
||||
def __init__(self):
|
||||
self.version = '1.0.0'
|
||||
self.experimental_mode = False
|
||||
self.beta_features = []
|
||||
self.cache = {}
|
||||
|
||||
def start(self):
|
||||
print('App started')
|
||||
if self.experimental_mode:
|
||||
self.enable_experimental_features()
|
||||
|
||||
def enable_experimental_features(self):
|
||||
print('Experimental features enabled')
|
||||
|
||||
def add_beta_feature(self, feature):
|
||||
self.beta_features.append(feature)
|
||||
|
||||
def get_data(self, key):
|
||||
def get(self, key):
|
||||
# Use cache to improve performance
|
||||
if key in self.cache:
|
||||
return self.cache[key]
|
||||
data = self.fetch_data(key)
|
||||
self.cache[key] = data
|
||||
return data
|
||||
return None
|
||||
|
||||
def fetch_data(self, key):
|
||||
# Simulate data fetching
|
||||
return {'key': key, 'value': 'data'}
|
||||
def set(self, key, value):
|
||||
self.cache[key] = value
|
||||
|
||||
def clear(self):
|
||||
self.cache.clear()
|
||||
"@
|
||||
|
||||
Set-Content -Path "app.py" -Value $appWithPerformance
|
||||
git add app.py
|
||||
Set-Content -Path "cache.py" -Value $performanceCode
|
||||
git add cache.py
|
||||
git commit -m "Fix performance issue with data caching" | Out-Null
|
||||
|
||||
# Return to module directory
|
||||
@@ -164,12 +162,13 @@ Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan
|
||||
Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow
|
||||
Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green
|
||||
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
||||
Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White
|
||||
Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White
|
||||
Write-Host "4. Switch to main branch: git checkout main" -ForegroundColor White
|
||||
Write-Host "5. Cherry-pick ONLY the bug fix commits to main" -ForegroundColor White
|
||||
Write-Host "6. Do NOT bring the experimental features to main" -ForegroundColor White
|
||||
Write-Host "4. Switch to $mainBranch branch: git checkout $mainBranch" -ForegroundColor White
|
||||
Write-Host "5. Cherry-pick ONLY the bug fix commits to $mainBranch" -ForegroundColor White
|
||||
Write-Host "6. Do NOT bring the experimental features to $mainBranch" -ForegroundColor White
|
||||
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
|
||||
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
||||
|
||||
@@ -32,12 +32,27 @@ if (-not (Test-Path ".git")) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect the main branch name
|
||||
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||
if ($allBranches -contains "main") {
|
||||
$mainBranch = "main"
|
||||
} elseif ($allBranches -contains "master") {
|
||||
$mainBranch = "master"
|
||||
} else {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = $allBranches | Select-Object -First 1
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
}
|
||||
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
|
||||
# Check current branch
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -ne "main") {
|
||||
Write-Host "[FAIL] You should be on the 'main' branch." -ForegroundColor Red
|
||||
if ($currentBranch -ne $mainBranch) {
|
||||
Write-Host "[FAIL] You should be on the '$mainBranch' branch." -ForegroundColor Red
|
||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
||||
Write-Host "Hint: Use 'git checkout main' to switch to main branch" -ForegroundColor Yellow
|
||||
Write-Host "Hint: Use 'git checkout $mainBranch' to switch to $mainBranch branch" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
}
|
||||
@@ -54,15 +69,15 @@ if (Test-Path ".git/CHERRY_PICK_HEAD") {
|
||||
}
|
||||
|
||||
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
|
||||
$mainCommitCount = (git rev-list --count main 2>$null)
|
||||
$mainCommitCount = (git rev-list --count $mainBranch 2>$null)
|
||||
if ($mainCommitCount -ne 4) {
|
||||
Write-Host "[FAIL] Expected 4 commits on main branch, found $mainCommitCount" -ForegroundColor Red
|
||||
Write-Host "[FAIL] Expected 4 commits on $mainBranch branch, found $mainCommitCount" -ForegroundColor Red
|
||||
if ($mainCommitCount -lt 4) {
|
||||
Write-Host "Hint: You should cherry-pick 2 bug fix commits to main" -ForegroundColor Yellow
|
||||
Write-Host "Hint: You should cherry-pick 2 bug fix commits to $mainBranch" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "Hint: You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
|
||||
}
|
||||
Write-Host "`nExpected commits on main:" -ForegroundColor Yellow
|
||||
Write-Host "`nExpected commits on ${mainBranch}:" -ForegroundColor Yellow
|
||||
Write-Host " 1. Initial app implementation" -ForegroundColor White
|
||||
Write-Host " 2. Add README" -ForegroundColor White
|
||||
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
|
||||
@@ -72,9 +87,9 @@ if ($mainCommitCount -ne 4) {
|
||||
}
|
||||
|
||||
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
|
||||
$mergeCommits = git log --merges --oneline main 2>$null
|
||||
$mergeCommits = git log --merges --oneline $mainBranch 2>$null
|
||||
if ($mergeCommits) {
|
||||
Write-Host "[FAIL] Found merge commits on main. You should use cherry-pick, not merge." -ForegroundColor Red
|
||||
Write-Host "[FAIL] Found merge commits on $mainBranch. You should use cherry-pick, not merge." -ForegroundColor Red
|
||||
Write-Host "Hint: Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -82,7 +97,7 @@ if ($mergeCommits) {
|
||||
|
||||
# Check that security.py exists (from the security fix commit)
|
||||
if (-not (Test-Path "security.py")) {
|
||||
Write-Host "[FAIL] security.py not found on main branch." -ForegroundColor Red
|
||||
Write-Host "[FAIL] security.py not found on $mainBranch branch." -ForegroundColor Red
|
||||
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -110,23 +125,32 @@ if (-not (Test-Path "app.py")) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check that app.py has the performance fix (cache) but NOT experimental features
|
||||
$appContent = Get-Content "app.py" -Raw
|
||||
|
||||
# Should have cache (from performance fix)
|
||||
if ($appContent -notmatch "cache") {
|
||||
Write-Host "[FAIL] app.py is missing the performance fix (cache)." -ForegroundColor Red
|
||||
# Check that cache.py exists (from performance fix)
|
||||
if (-not (Test-Path "cache.py")) {
|
||||
Write-Host "[FAIL] cache.py not found on $mainBranch branch." -ForegroundColor Red
|
||||
Write-Host "Hint: You need to cherry-pick the 'Fix performance issue' commit" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($appContent -notmatch "get_data") {
|
||||
Write-Host "[FAIL] app.py is missing the get_data method from performance fix." -ForegroundColor Red
|
||||
# Check that cache.py has the DataCache class
|
||||
$cacheContent = Get-Content "cache.py" -Raw
|
||||
|
||||
if ($cacheContent -notmatch "DataCache") {
|
||||
Write-Host "[FAIL] cache.py is missing the DataCache class." -ForegroundColor Red
|
||||
Set-Location ..
|
||||
exit 1
|
||||
}
|
||||
|
||||
if ($cacheContent -notmatch "def get\(") {
|
||||
Write-Host "[FAIL] cache.py is missing the get method." -ForegroundColor Red
|
||||
Set-Location ..
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Check that app.py does NOT have experimental features
|
||||
$appContent = Get-Content "app.py" -Raw
|
||||
|
||||
# Should NOT have experimental features
|
||||
if ($appContent -match "experimental_mode") {
|
||||
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -ForegroundColor Red
|
||||
@@ -151,7 +175,7 @@ if ($appContent -match "enable_experimental_features") {
|
||||
}
|
||||
|
||||
# Check commit messages to verify cherry-picks
|
||||
$commits = git log --pretty=format:"%s" main 2>$null
|
||||
$commits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||
$commitArray = $commits -split "`n"
|
||||
|
||||
$hasSecurityFix = $false
|
||||
@@ -167,14 +191,14 @@ foreach ($commit in $commitArray) {
|
||||
}
|
||||
|
||||
if (-not $hasSecurityFix) {
|
||||
Write-Host "[FAIL] Security fix commit not found on main branch." -ForegroundColor Red
|
||||
Write-Host "[FAIL] Security fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||
Write-Host "Hint: Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
}
|
||||
|
||||
if (-not $hasPerformanceFix) {
|
||||
Write-Host "[FAIL] Performance fix commit not found on main branch." -ForegroundColor Red
|
||||
Write-Host "[FAIL] Performance fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -194,8 +218,8 @@ Write-Host "`n========================================" -ForegroundColor Green
|
||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
||||
Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
||||
Write-Host "- Cherry-picked the security vulnerability fix to main" -ForegroundColor White
|
||||
Write-Host "- Cherry-picked the performance issue fix to main" -ForegroundColor White
|
||||
Write-Host "- Cherry-picked the security vulnerability fix to $mainBranch" -ForegroundColor White
|
||||
Write-Host "- Cherry-picked the performance issue fix to $mainBranch" -ForegroundColor White
|
||||
Write-Host "- Left experimental features on development branch only" -ForegroundColor White
|
||||
Write-Host "- Kept development branch intact with all commits" -ForegroundColor White
|
||||
Write-Host "`nPerfect use of cherry-pick!" -ForegroundColor Green
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# Module 05: Git Revert - Safe Undoing
|
||||
# Module 06: Git Revert - Safe Undoing
|
||||
|
||||
## About This Module
|
||||
|
||||
Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
|
||||
Welcome to Module 06, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
|
||||
|
||||
**Why revert is important:**
|
||||
- ✅ Safe for shared/pushed commits
|
||||
@@ -17,31 +17,28 @@ Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo c
|
||||
|
||||
By completing this module, you will:
|
||||
|
||||
1. Revert regular commits safely while preserving surrounding changes
|
||||
2. Revert merge commits using the `-m` flag
|
||||
3. Understand merge commit parent numbering
|
||||
4. Handle the re-merge problem that occurs after reverting merges
|
||||
5. Revert multiple commits at once
|
||||
6. Know when to use revert vs. other undo strategies
|
||||
1. Revert commits safely while preserving surrounding changes
|
||||
2. Understand how revert creates new commits instead of erasing history
|
||||
3. Revert multiple commits at once
|
||||
4. Know when to use revert vs. other undo strategies
|
||||
|
||||
## Prerequisites
|
||||
|
||||
Before starting this module, you should be comfortable with:
|
||||
- Creating commits (`git commit`)
|
||||
- Viewing commit history (`git log`)
|
||||
- Understanding branches and merging (Module 03)
|
||||
- Understanding branches (Module 03)
|
||||
|
||||
## Setup
|
||||
|
||||
Run the setup script to create the challenge environment:
|
||||
|
||||
```powershell
|
||||
./setup.ps1
|
||||
```pwsh
|
||||
.\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
|
||||
- `merge-revert` - Merge commit reversion
|
||||
- `multi-revert` - Multiple commit reversion
|
||||
|
||||
## Challenge 1: Reverting a Regular Commit
|
||||
@@ -52,44 +49,57 @@ You're working on a calculator application. A developer added a `divide` functio
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Navigate to the challenge directory:
|
||||
```bash
|
||||
1. **Navigate to the challenge directory:**
|
||||
```pwsh
|
||||
cd challenge
|
||||
```
|
||||
|
||||
2. You should be on the `regular-revert` branch. View the commit history:
|
||||
```bash
|
||||
2. **Check which branch you're on** (you should be on `regular-revert`):
|
||||
```pwsh
|
||||
git branch
|
||||
```
|
||||
The `*` should be next to `regular-revert`.
|
||||
|
||||
3. **View the commit history:**
|
||||
```pwsh
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
3. Find the commit with the broken divide function (message: "Add broken divide function - needs to be reverted!")
|
||||
4. **Find the commit with message:** "Add broken divide function - needs to be reverted!"
|
||||
- Note the commit hash (the 7-character code at the start, like `a1b2c3d`)
|
||||
- Write it down or copy it
|
||||
|
||||
4. Revert that specific commit:
|
||||
```bash
|
||||
5. **Revert that specific commit** (replace `<commit-hash>` with the actual hash):
|
||||
```pwsh
|
||||
git revert <commit-hash>
|
||||
```
|
||||
|
||||
5. Git will open your editor for the revert commit message. The default message is fine—save and close.
|
||||
6. **Visual Studio Code will open** with the revert commit message:
|
||||
- The default message is fine (it says "Revert 'Add broken divide function...'")
|
||||
- Close the editor window to accept the commit message
|
||||
- Git will create the revert commit
|
||||
|
||||
### What to Observe
|
||||
|
||||
After reverting, check:
|
||||
After reverting, check your work:
|
||||
|
||||
```bash
|
||||
# View the new revert commit
|
||||
```pwsh
|
||||
# View the new revert commit in history
|
||||
git log --oneline
|
||||
|
||||
# Check that divide function is gone
|
||||
cat calculator.py | grep "def divide" # Should return nothing
|
||||
# Check that divide.py file is gone (reverted)
|
||||
ls
|
||||
# You should see calculator.py but NOT divide.py
|
||||
|
||||
# Check that modulo function still exists (it came after the bad commit)
|
||||
cat calculator.py | grep "def modulo" # Should find it
|
||||
# Check that modulo function still exists in calculator.py (it came after the bad commit)
|
||||
cat calculator.py
|
||||
# You should see def modulo
|
||||
|
||||
# Check that multiply function still exists (it came before the bad commit)
|
||||
cat calculator.py | grep "def multiply" # Should find it
|
||||
# (You already see it when you cat the file above)
|
||||
```
|
||||
|
||||
**Key insight:** Revert creates a new commit that undoes the changes from the target commit, but leaves all other commits intact.
|
||||
**Key insight:** Revert creates a NEW commit that undoes the changes from the target commit, but leaves all other commits intact.
|
||||
|
||||
### Understanding the Timeline
|
||||
|
||||
@@ -107,134 +117,7 @@ main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → rev
|
||||
|
||||
The revert commit adds a new point in history that undoes the divide changes.
|
||||
|
||||
## Challenge 2: Reverting a Merge Commit
|
||||
|
||||
### Scenario
|
||||
|
||||
Your team merged a `feature-auth` branch that added authentication functionality. After deployment, you discovered the authentication system has critical security issues. You need to revert the entire merge while the security team redesigns the feature.
|
||||
|
||||
**This is different from reverting a regular commit!** Merge commits have **two parents**, so you must tell Git which parent to keep.
|
||||
|
||||
### Understanding Merge Commit Parents
|
||||
|
||||
When you merge a feature branch into main:
|
||||
|
||||
```
|
||||
feature-auth (parent 2)
|
||||
↓
|
||||
C---D
|
||||
/ \
|
||||
A---B-----M ← Merge commit (has TWO parents)
|
||||
↑
|
||||
parent 1 (main)
|
||||
```
|
||||
|
||||
The merge commit `M` has:
|
||||
- **Parent 1**: The branch you merged INTO (main)
|
||||
- **Parent 2**: The branch you merged FROM (feature-auth)
|
||||
|
||||
When reverting a merge, you must specify which parent to keep using the `-m` flag:
|
||||
- `-m 1` means "keep parent 1" (main) - **Most common**
|
||||
- `-m 2` means "keep parent 2" (feature-auth) - Rare
|
||||
|
||||
**In practice:** You almost always use `-m 1` to keep the main branch and undo the feature branch changes.
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the merge-revert branch:
|
||||
```bash
|
||||
git switch merge-revert
|
||||
```
|
||||
|
||||
2. View the commit history and find the merge commit:
|
||||
```bash
|
||||
git log --oneline --graph
|
||||
```
|
||||
|
||||
Look for: "Merge feature-auth branch"
|
||||
|
||||
3. Revert the merge commit using `-m 1`:
|
||||
```bash
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
```
|
||||
|
||||
**Explanation:**
|
||||
- `-m 1` tells Git to keep parent 1 (main branch)
|
||||
- This undoes all changes from the feature-auth branch
|
||||
- Creates a new "revert merge" commit
|
||||
|
||||
4. Save the default commit message and check the result:
|
||||
```bash
|
||||
# Verify auth.py is gone
|
||||
ls auth.py # Should not exist
|
||||
|
||||
# Verify calculator.py no longer imports auth
|
||||
cat calculator.py | grep "from auth" # Should return nothing
|
||||
```
|
||||
|
||||
### What Happens Without -m?
|
||||
|
||||
If you try to revert a merge commit without the `-m` flag:
|
||||
|
||||
```bash
|
||||
git revert <merge-commit-hash>
|
||||
# Error: commit <hash> is a merge but no -m option was given
|
||||
```
|
||||
|
||||
Git doesn't know which parent you want to keep, so it refuses to proceed.
|
||||
|
||||
### The Re-Merge Problem
|
||||
|
||||
**Important gotcha:** After reverting a merge, you **cannot simply re-merge** the same branch!
|
||||
|
||||
Here's why:
|
||||
|
||||
```
|
||||
Initial merge:
|
||||
A---B---M (merged feature-auth)
|
||||
↑
|
||||
All changes from feature-auth are now in main
|
||||
|
||||
After revert:
|
||||
A---B---M---R (reverted merge)
|
||||
↑
|
||||
Changes removed, but Git remembers they were merged
|
||||
|
||||
Attempting to re-merge:
|
||||
A---B---M---R---M2 (try to merge feature-auth again)
|
||||
↑
|
||||
Git thinks: "I already merged these commits,
|
||||
nothing new to add!" (Empty merge)
|
||||
```
|
||||
|
||||
**Solutions if you need to re-merge:**
|
||||
|
||||
1. **Revert the revert** (recommended):
|
||||
```bash
|
||||
git revert <revert-commit-hash>
|
||||
```
|
||||
This brings back all the feature-auth changes.
|
||||
|
||||
2. **Cherry-pick new commits** from the feature branch:
|
||||
```bash
|
||||
git cherry-pick <new-commits>
|
||||
```
|
||||
|
||||
3. **Merge with --no-ff** and resolve conflicts manually (advanced).
|
||||
|
||||
### When to Revert Merges
|
||||
|
||||
Revert merge commits when:
|
||||
- ✅ Feature causes production issues
|
||||
- ✅ Need to temporarily remove a feature
|
||||
- ✅ Discovered critical bugs after merging
|
||||
- ✅ Security issues require immediate rollback
|
||||
|
||||
Don't revert merges when:
|
||||
- ❌ You just need to fix a small bug (fix it with a new commit instead)
|
||||
- ❌ You plan to re-merge the same branch soon (use reset if local, or revert-the-revert later)
|
||||
|
||||
## Challenge 3: Reverting Multiple Commits
|
||||
## Challenge 2: Reverting Multiple Commits
|
||||
|
||||
### Scenario
|
||||
|
||||
@@ -242,51 +125,55 @@ Two separate commits added broken mathematical functions (`square_root` and `log
|
||||
|
||||
### Your Task
|
||||
|
||||
1. Switch to the multi-revert branch:
|
||||
```bash
|
||||
1. **Switch to the multi-revert branch:**
|
||||
```pwsh
|
||||
git switch multi-revert
|
||||
```
|
||||
|
||||
2. View the commit history:
|
||||
```bash
|
||||
2. **View the commit history:**
|
||||
```pwsh
|
||||
git log --oneline
|
||||
```
|
||||
|
||||
Find the two commits:
|
||||
Find the two bad commits:
|
||||
- "Add broken square_root - REVERT THIS!"
|
||||
- "Add broken logarithm - REVERT THIS TOO!"
|
||||
|
||||
Note both commit hashes (write them down)
|
||||
|
||||
3. Revert both commits in one command:
|
||||
```bash
|
||||
3. **Revert both commits in one command** (replace with actual hashes):
|
||||
```pwsh
|
||||
git revert <commit-hash-1> <commit-hash-2>
|
||||
```
|
||||
|
||||
**Important:** List commits from **oldest to newest** for cleanest history.
|
||||
**Important:** List commits from **oldest to newest** for cleanest history (square_root first, then logarithm).
|
||||
|
||||
Alternatively, revert them one at a time:
|
||||
```bash
|
||||
**Alternatively**, revert them one at a time:
|
||||
```pwsh
|
||||
git revert <commit-hash-1>
|
||||
git revert <commit-hash-2>
|
||||
```
|
||||
|
||||
4. Git will prompt for a commit message for each revert. Accept the defaults.
|
||||
4. **Visual Studio Code will open TWICE** (once for each revert):
|
||||
- Close the editor each time to accept the default commit message
|
||||
- Git will create two revert commits
|
||||
|
||||
5. Verify the result:
|
||||
```bash
|
||||
# Check that both bad functions are gone
|
||||
cat calculator.py | grep "def square_root" # Should return nothing
|
||||
cat calculator.py | grep "def logarithm" # Should return nothing
|
||||
5. **Verify the result:**
|
||||
```pwsh
|
||||
# View files - sqrt.py and logarithm.py should be gone
|
||||
ls
|
||||
# You should see calculator.py but NOT sqrt.py or logarithm.py
|
||||
|
||||
# Check that good functions remain
|
||||
cat calculator.py | grep "def power" # Should find it
|
||||
cat calculator.py | grep "def absolute" # Should find it
|
||||
# Check that good functions remain in calculator.py
|
||||
cat calculator.py
|
||||
# You should see def power and def absolute
|
||||
```
|
||||
|
||||
### Multi-Revert Strategies
|
||||
|
||||
**Reverting a range of commits:**
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Revert commits from A to B (inclusive)
|
||||
git revert A^..B
|
||||
|
||||
@@ -296,7 +183,7 @@ git revert HEAD~3..HEAD
|
||||
|
||||
**Reverting without auto-commit:**
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Stage revert changes without committing
|
||||
git revert --no-commit <commit-hash>
|
||||
|
||||
@@ -311,25 +198,29 @@ This is useful when reverting multiple commits and you want one combined revert
|
||||
|
||||
## Verification
|
||||
|
||||
Verify your solutions by running the verification script:
|
||||
After completing both challenges, verify your solutions:
|
||||
|
||||
```bash
|
||||
cd .. # Return to module directory
|
||||
./verify.ps1
|
||||
```pwsh
|
||||
cd .. # Return to module directory (if you're in challenge/)
|
||||
.\verify.ps1
|
||||
```
|
||||
|
||||
Or from inside the challenge directory:
|
||||
|
||||
```pwsh
|
||||
..\verify.ps1
|
||||
```
|
||||
|
||||
The script checks that:
|
||||
- ✅ Revert commits were created (not destructive deletion)
|
||||
- ✅ Bad code is removed
|
||||
- ✅ Good code before and after is preserved
|
||||
- ✅ Merge commits still exist in history
|
||||
- ✅ Proper use of `-m` flag for merge reverts
|
||||
|
||||
## Command Reference
|
||||
|
||||
### Basic Revert
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Revert a specific commit
|
||||
git revert <commit-hash>
|
||||
|
||||
@@ -340,19 +231,22 @@ git revert HEAD
|
||||
git revert HEAD~1
|
||||
```
|
||||
|
||||
### Merge Commit Revert
|
||||
### Reverting Old Commits
|
||||
|
||||
```bash
|
||||
# Revert a merge commit (keep parent 1)
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
```pwsh
|
||||
# Revert a specific commit from any point in history
|
||||
git revert <commit-hash>
|
||||
|
||||
# Revert a merge commit (keep parent 2) - rare
|
||||
git revert -m 2 <merge-commit-hash>
|
||||
# Revert a commit from 5 commits ago
|
||||
git revert HEAD~5
|
||||
|
||||
# View what a commit changed before reverting
|
||||
git show <commit-hash>
|
||||
```
|
||||
|
||||
### Multiple Commits
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Revert multiple specific commits
|
||||
git revert <hash1> <hash2> <hash3>
|
||||
|
||||
@@ -365,7 +259,7 @@ git revert HEAD~3..HEAD
|
||||
|
||||
### Revert Options
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Revert but don't commit automatically
|
||||
git revert --no-commit <commit-hash>
|
||||
|
||||
@@ -399,10 +293,10 @@ Use `git revert` 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`
|
||||
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup
|
||||
- ❌ **Need to combine commits** - Use interactive rebase
|
||||
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup, but more dangerous, stick to revert if in doubt
|
||||
- ❌ **Need to combine commits** - Use interactive rebase IF nothing has been pushed to cloud
|
||||
- ❌ **Reverting creates complex conflicts** - Might need manual fix forward
|
||||
|
||||
## Revert vs. Reset vs. Rebase
|
||||
@@ -419,7 +313,7 @@ Consider alternatives when:
|
||||
|
||||
Sometimes reverting causes conflicts if subsequent changes touched the same code:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# Start revert
|
||||
git revert <commit-hash>
|
||||
|
||||
@@ -432,59 +326,36 @@ git revert <commit-hash>
|
||||
|
||||
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
|
||||
2. Stage resolved files:
|
||||
```bash
|
||||
```pwsh
|
||||
git add <resolved-files>
|
||||
```
|
||||
3. Continue the revert:
|
||||
```bash
|
||||
```pwsh
|
||||
git revert --continue
|
||||
```
|
||||
|
||||
Or abort if you change your mind:
|
||||
```bash
|
||||
```pwsh
|
||||
git revert --abort
|
||||
```
|
||||
|
||||
## Common Mistakes
|
||||
|
||||
### 1. Forgetting -m for Merge Commits
|
||||
### 1. Using Reset on Pushed Commits
|
||||
|
||||
```bash
|
||||
# ❌ Wrong - will fail
|
||||
git revert <merge-commit>
|
||||
|
||||
# ✅ Correct
|
||||
git revert -m 1 <merge-commit>
|
||||
```
|
||||
|
||||
### 2. Trying to Re-Merge After Revert
|
||||
|
||||
```bash
|
||||
# After reverting a merge:
|
||||
git revert -m 1 <merge-commit>
|
||||
|
||||
# ❌ This won't work as expected
|
||||
git merge feature-branch # Empty merge!
|
||||
|
||||
# ✅ Do this instead
|
||||
git revert <the-revert-commit> # Revert the revert
|
||||
```
|
||||
|
||||
### 3. Using Reset on Pushed Commits
|
||||
|
||||
```bash
|
||||
# ❌ NEVER do this with pushed commits
|
||||
```pwsh
|
||||
# ❌ NEVER do this with pushed commits. Or at least try your best to avoid it.
|
||||
git reset --hard HEAD~3
|
||||
|
||||
# ✅ Do this instead
|
||||
git revert HEAD~3..HEAD
|
||||
```
|
||||
|
||||
### 4. Reverting Commits in Wrong Order
|
||||
### 2. Reverting Commits in Wrong Order
|
||||
|
||||
When reverting multiple related commits, revert from newest to oldest:
|
||||
|
||||
```bash
|
||||
```pwsh
|
||||
# If you have: A → B → C (and C depends on B)
|
||||
|
||||
# ✅ Correct order
|
||||
@@ -499,7 +370,7 @@ git revert C
|
||||
## Best Practices
|
||||
|
||||
1. **Write clear revert messages:**
|
||||
```bash
|
||||
```pwsh
|
||||
git revert <hash> -m "Revert authentication - security issue #1234"
|
||||
```
|
||||
|
||||
@@ -527,112 +398,3 @@ git revert C
|
||||
- Revert the minimum necessary
|
||||
- Don't bundle multiple unrelated reverts
|
||||
- One problem = one revert commit
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Commit is a merge but no -m option was given"
|
||||
|
||||
**Problem:** Trying to revert a merge commit without `-m`.
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
git revert -m 1 <merge-commit-hash>
|
||||
```
|
||||
|
||||
### "Empty Revert / No Changes"
|
||||
|
||||
**Problem:** Revert doesn't seem to do anything.
|
||||
|
||||
**Possible causes:**
|
||||
- Commit was already reverted
|
||||
- Subsequent commits already undid the changes
|
||||
- Wrong commit hash
|
||||
|
||||
**Solution:**
|
||||
```bash
|
||||
# Check what the commit actually changed
|
||||
git show <commit-hash>
|
||||
|
||||
# Check if already reverted
|
||||
git log --grep="Revert"
|
||||
```
|
||||
|
||||
### "Conflicts During Revert"
|
||||
|
||||
**Problem:** Revert causes merge conflicts.
|
||||
|
||||
**Why:** Subsequent commits modified the same code.
|
||||
|
||||
**Solution:**
|
||||
1. Manually resolve conflicts in affected files
|
||||
2. `git add <resolved-files>`
|
||||
3. `git revert --continue`
|
||||
|
||||
Or consider fixing forward with a new commit instead of reverting.
|
||||
|
||||
### "Can't Re-Merge After Reverting Merge"
|
||||
|
||||
**Problem:** After reverting a merge, re-merging the branch brings no changes.
|
||||
|
||||
**Solution:** Revert the revert commit:
|
||||
```bash
|
||||
# Find the revert commit
|
||||
git log --oneline
|
||||
|
||||
# Revert the revert (brings changes back)
|
||||
git revert <revert-commit-hash>
|
||||
```
|
||||
|
||||
## Advanced: Revert Internals
|
||||
|
||||
Understanding what revert does under the hood:
|
||||
|
||||
```bash
|
||||
# Revert creates a new commit with inverse changes
|
||||
git revert <commit-hash>
|
||||
|
||||
# This is equivalent to:
|
||||
git diff <commit-hash>^..<commit-hash> > changes.patch
|
||||
patch -R < changes.patch # Apply in reverse
|
||||
git add .
|
||||
git commit -m "Revert '<original message>'"
|
||||
```
|
||||
|
||||
**Key insight:** Revert computes the diff of the target commit, inverts it, and applies it as a new commit.
|
||||
|
||||
## Going Further
|
||||
|
||||
Now that you understand revert, you're ready for:
|
||||
|
||||
- **Module 06: Git Reset** - Learn the dangerous but powerful local history rewriting
|
||||
- **Module 07: Git Stash** - Temporarily set aside uncommitted changes
|
||||
- **Module 08: Multiplayer Git** - Collaborate with advanced workflows
|
||||
|
||||
## Summary
|
||||
|
||||
You've learned:
|
||||
|
||||
- ✅ `git revert` creates new commits that undo previous changes
|
||||
- ✅ Revert is safe for shared/pushed commits
|
||||
- ✅ Merge commits require `-m 1` or `-m 2` flag
|
||||
- ✅ Parent 1 = branch merged into, Parent 2 = branch merged from
|
||||
- ✅ Can't simply re-merge after reverting a merge
|
||||
- ✅ Multiple commits can be reverted in one command
|
||||
- ✅ Revert preserves complete history for audit trails
|
||||
|
||||
**The Golden Rule of Revert:** Use revert for any commit that might be shared with others.
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. Complete all three challenge scenarios
|
||||
2. Run `./verify.ps1` to check your solutions
|
||||
3. Experiment with reverting different commits
|
||||
4. Move on to Module 06: Git Reset (dangerous but powerful!)
|
||||
|
||||
---
|
||||
|
||||
**Need Help?**
|
||||
- Review the command reference above
|
||||
- Check the troubleshooting section
|
||||
- Re-run `./setup.ps1` to start fresh
|
||||
- Practice reverting in different orders to understand the behavior
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Resets the Module 05 challenge environment to start fresh.
|
||||
Resets the Module 06 challenge environment to start fresh.
|
||||
|
||||
.DESCRIPTION
|
||||
This script removes the challenge directory and re-runs setup.ps1
|
||||
to create a fresh challenge environment.
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Resetting Module 05: Git Revert Challenge ===" -ForegroundColor Cyan
|
||||
Write-Host "`n=== Resetting Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
|
||||
|
||||
# Check if challenge directory exists
|
||||
if (Test-Path "challenge") {
|
||||
|
||||
@@ -1,17 +1,16 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Sets up the Module 05 challenge environment for learning git revert.
|
||||
Sets up the Module 06 challenge environment for learning git revert.
|
||||
|
||||
.DESCRIPTION
|
||||
This script creates a challenge directory with three branches demonstrating
|
||||
This script creates a challenge directory with two branches demonstrating
|
||||
different revert scenarios:
|
||||
- regular-revert: Basic revert of a single bad commit
|
||||
- merge-revert: Reverting a merge commit with -m flag
|
||||
- multi-revert: Reverting multiple commits at once
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Setting up Module 05: Git Revert Challenge ===" -ForegroundColor Cyan
|
||||
Write-Host "`n=== Setting up Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
|
||||
|
||||
# Remove existing challenge directory if it exists
|
||||
if (Test-Path "challenge") {
|
||||
@@ -32,6 +31,9 @@ git init | Out-Null
|
||||
git config user.name "Workshop Student"
|
||||
git config user.email "student@example.com"
|
||||
|
||||
# Detect the default branch name after first commit (created below)
|
||||
# Will be detected after the initial commit in SCENARIO 1
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Regular Revert (Basic)
|
||||
# ============================================================================
|
||||
@@ -53,176 +55,60 @@ Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Initial calculator implementation" | Out-Null
|
||||
|
||||
# Detect the main branch name after first commit
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
# Create regular-revert branch
|
||||
git switch -c regular-revert | Out-Null
|
||||
|
||||
# Good commit: Add multiply
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
# Good commit: Add multiply using append
|
||||
$multiplyFunc = @"
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Add-Content -Path "calculator.py" -Value $multiplyFunc
|
||||
git add .
|
||||
git commit -m "Add multiply function" | Out-Null
|
||||
|
||||
# BAD commit: Add broken divide function
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
# BAD commit: Add broken divide function using separate file
|
||||
$divideContent = @"
|
||||
# divide.py - Division functionality
|
||||
|
||||
def divide(a, b):
|
||||
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
|
||||
return a / b # This will crash if b is 0!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Set-Content -Path "divide.py" -Value $divideContent
|
||||
git add .
|
||||
git commit -m "Add broken divide function - needs to be reverted!" | Out-Null
|
||||
|
||||
# Good commit: Add modulo (after bad commit)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def multiply(a, b):
|
||||
"""Multiply two numbers."""
|
||||
return a * b
|
||||
|
||||
def divide(a, b):
|
||||
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
|
||||
return a / b # This will crash if b is 0!
|
||||
# Good commit: Add modulo (after bad commit) using append
|
||||
$moduloFunc = @"
|
||||
|
||||
def modulo(a, b):
|
||||
"""Return remainder of a divided by b."""
|
||||
return a % b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Add-Content -Path "calculator.py" -Value $moduloFunc
|
||||
git add .
|
||||
git commit -m "Add modulo function" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Merge Revert (Merge Commit with -m flag)
|
||||
# SCENARIO 2: Multi Revert (Multiple Bad Commits)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 2: Creating merge-revert scenario..." -ForegroundColor Cyan
|
||||
Write-Host "`nScenario 2: Creating multi-revert branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
|
||||
# Create merge-revert branch
|
||||
git switch -c merge-revert | Out-Null
|
||||
|
||||
# Create a feature branch to merge
|
||||
git switch -c feature-auth | Out-Null
|
||||
|
||||
# Add auth functionality
|
||||
$authContent = @"
|
||||
# auth.py - Authentication module
|
||||
|
||||
def login(username, password):
|
||||
\"\"\"Login user.\"\"\"
|
||||
print(f"Logging in {username}...")
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
\"\"\"Logout user.\"\"\"
|
||||
print(f"Logging out {username}...")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "auth.py" -Value $authContent
|
||||
git add .
|
||||
git commit -m "Add authentication module" | Out-Null
|
||||
|
||||
# Add password validation
|
||||
$authContent = @"
|
||||
# auth.py - Authentication module
|
||||
|
||||
def validate_password(password):
|
||||
\"\"\"Validate password strength.\"\"\"
|
||||
return len(password) >= 8
|
||||
|
||||
def login(username, password):
|
||||
\"\"\"Login user.\"\"\"
|
||||
if not validate_password(password):
|
||||
print("Password too weak!")
|
||||
return False
|
||||
print(f"Logging in {username}...")
|
||||
return True
|
||||
|
||||
def logout(username):
|
||||
\"\"\"Logout user.\"\"\"
|
||||
print(f"Logging out {username}...")
|
||||
return True
|
||||
"@
|
||||
Set-Content -Path "auth.py" -Value $authContent
|
||||
git add .
|
||||
git commit -m "Add password validation" | Out-Null
|
||||
|
||||
# Integrate auth into calculator (part of the feature branch)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
from auth import login
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def secure_divide(a, b, username):
|
||||
"""Secure divide - requires authentication."""
|
||||
if login(username, "password123"):
|
||||
return a / b
|
||||
return None
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Integrate auth into calculator" | Out-Null
|
||||
|
||||
# Switch back to merge-revert and merge feature-auth
|
||||
git switch merge-revert | Out-Null
|
||||
git merge feature-auth --no-ff -m "Merge feature-auth branch" | Out-Null
|
||||
|
||||
Write-Host "[CREATED] merge-revert branch with merge commit to revert" -ForegroundColor Green
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Multi Revert (Multiple Bad Commits)
|
||||
# ============================================================================
|
||||
Write-Host "`nScenario 3: Creating multi-revert branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main
|
||||
git switch main | Out-Null
|
||||
git switch $mainBranch | Out-Null
|
||||
|
||||
# Create multi-revert branch
|
||||
git switch -c multi-revert | Out-Null
|
||||
@@ -243,109 +129,50 @@ Set-Content -Path "calculator.py" -Value $calcContent
|
||||
git add .
|
||||
git commit -m "Reset to basic calculator" | Out-Null
|
||||
|
||||
# Good commit: Add power function
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
# Good commit: Add power function using append
|
||||
$powerFunc = @"
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Add-Content -Path "calculator.py" -Value $powerFunc
|
||||
git add .
|
||||
git commit -m "Add power function" | Out-Null
|
||||
|
||||
# BAD commit 1: Add broken square_root
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
# BAD commit 1: Add broken square_root in separate file
|
||||
$sqrtContent = @"
|
||||
# sqrt.py - Square root functionality
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Set-Content -Path "sqrt.py" -Value $sqrtContent
|
||||
git add .
|
||||
git commit -m "Add broken square_root - REVERT THIS!" | Out-Null
|
||||
|
||||
# BAD commit 2: Add broken logarithm
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
# BAD commit 2: Add broken logarithm in separate file
|
||||
$logContent = @"
|
||||
# logarithm.py - Logarithm functionality
|
||||
|
||||
def logarithm(a):
|
||||
"""BROKEN: Doesn't handle zero or negative numbers!"""
|
||||
import math
|
||||
return math.log(a) # This crashes for a <= 0!
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Set-Content -Path "logarithm.py" -Value $logContent
|
||||
git add .
|
||||
git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null
|
||||
|
||||
# Good commit: Add absolute value (after bad commits)
|
||||
$calcContent = @"
|
||||
# calculator.py - Simple calculator
|
||||
|
||||
def add(a, b):
|
||||
"""Add two numbers."""
|
||||
return a + b
|
||||
|
||||
def subtract(a, b):
|
||||
"""Subtract b from a."""
|
||||
return a - b
|
||||
|
||||
def power(a, b):
|
||||
"""Raise a to the power of b."""
|
||||
return a ** b
|
||||
|
||||
def square_root(a):
|
||||
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||
return a ** 0.5 # This returns NaN for negative numbers!
|
||||
|
||||
def logarithm(a):
|
||||
"""BROKEN: Doesn't handle zero or negative numbers!"""
|
||||
import math
|
||||
return math.log(a) # This crashes for a <= 0!
|
||||
# Good commit: Add absolute value (after bad commits) using append
|
||||
$absoluteFunc = @"
|
||||
|
||||
def absolute(a):
|
||||
"""Return absolute value of a."""
|
||||
return abs(a)
|
||||
"@
|
||||
Set-Content -Path "calculator.py" -Value $calcContent
|
||||
Add-Content -Path "calculator.py" -Value $absoluteFunc
|
||||
git add .
|
||||
git commit -m "Add absolute value function" | Out-Null
|
||||
|
||||
@@ -360,10 +187,9 @@ git switch regular-revert | Out-Null
|
||||
Set-Location ..
|
||||
|
||||
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||
Write-Host "`nThree revert scenarios have been created:" -ForegroundColor Cyan
|
||||
Write-Host " 1. regular-revert - Revert a single bad commit (basic)" -ForegroundColor White
|
||||
Write-Host " 2. merge-revert - Revert a merge commit with -m flag" -ForegroundColor White
|
||||
Write-Host " 3. multi-revert - Revert multiple bad commits" -ForegroundColor White
|
||||
Write-Host "`nTwo revert scenarios have been created:" -ForegroundColor Cyan
|
||||
Write-Host " 1. regular-revert - Revert a single bad commit" -ForegroundColor White
|
||||
Write-Host " 2. multi-revert - Revert multiple bad commits" -ForegroundColor White
|
||||
Write-Host "`nYou are currently on the 'regular-revert' branch." -ForegroundColor Cyan
|
||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
#!/usr/bin/env pwsh
|
||||
<#
|
||||
.SYNOPSIS
|
||||
Verifies the Module 05 challenge solutions.
|
||||
Verifies the Module 06 challenge solutions.
|
||||
|
||||
.DESCRIPTION
|
||||
Checks that all three revert scenarios have been completed correctly:
|
||||
Checks that both revert scenarios have been completed correctly:
|
||||
- regular-revert: Single commit reverted
|
||||
- merge-revert: Merge commit reverted with -m flag
|
||||
- multi-revert: Multiple commits reverted
|
||||
#>
|
||||
|
||||
Write-Host "`n=== Verifying Module 05: Git Revert Solutions ===" -ForegroundColor Cyan
|
||||
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
|
||||
|
||||
$allChecksPassed = $true
|
||||
$originalDir = Get-Location
|
||||
@@ -51,19 +50,19 @@ if ($LASTEXITCODE -ne 0) {
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that calculator.py exists
|
||||
# Check that divide.py is removed (was reverted)
|
||||
if (-not (Test-Path "divide.py")) {
|
||||
Write-Host "[PASS] Broken divide.py successfully reverted (file removed)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] divide.py still exists (should be reverted)" -ForegroundColor Red
|
||||
Write-Host "[HINT] The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that calculator.py exists and has correct content
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# Check that divide function is NOT in the code (was reverted)
|
||||
if ($calcContent -notmatch "def divide") {
|
||||
Write-Host "[PASS] Broken divide function successfully reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] divide function still exists (should be reverted)" -ForegroundColor Red
|
||||
Write-Host "[HINT] The bad commit should be reverted, removing the divide function" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that modulo function still exists (should be preserved)
|
||||
if ($calcContent -match "def modulo") {
|
||||
Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green
|
||||
@@ -87,60 +86,9 @@ if ($LASTEXITCODE -ne 0) {
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 2: Merge Revert Verification
|
||||
# SCENARIO 2: Multi Revert Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 2: Merge Revert ===" -ForegroundColor Cyan
|
||||
|
||||
git switch merge-revert 2>&1 | Out-Null
|
||||
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "[FAIL] merge-revert branch not found" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
} else {
|
||||
# Check that a revert commit for the merge exists
|
||||
$revertMerge = git log --oneline --grep="Revert.*Merge" 2>$null
|
||||
if ($revertMerge) {
|
||||
Write-Host "[PASS] Merge revert commit found" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] No merge revert commit found" -ForegroundColor Red
|
||||
Write-Host "[HINT] Use: git revert -m 1 <merge-commit-hash>" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that the original merge commit still exists (revert doesn't erase it)
|
||||
$mergeCommit = git log --merges --oneline --grep="Merge feature-auth" 2>$null
|
||||
if ($mergeCommit) {
|
||||
Write-Host "[PASS] Original merge commit still in history (not erased)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] Original merge commit not found (this is OK if you used a different approach)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Check that auth.py no longer exists or its effects are reverted
|
||||
if (-not (Test-Path "auth.py")) {
|
||||
Write-Host "[PASS] auth.py removed (merge reverted successfully)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[INFO] auth.py still exists (check if merge was fully reverted)" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
# Check that calculator.py exists
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# After reverting the merge, calculator shouldn't import auth
|
||||
if ($calcContent -notmatch "from auth import") {
|
||||
Write-Host "[PASS] Auth integration reverted from calculator.py" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] calculator.py still imports auth (merge not fully reverted)" -ForegroundColor Red
|
||||
Write-Host "[HINT] Reverting the merge should remove the auth integration" -ForegroundColor Yellow
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 3: Multi Revert Verification
|
||||
# ============================================================================
|
||||
Write-Host "`n=== Scenario 3: Multi Revert ===" -ForegroundColor Cyan
|
||||
Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
|
||||
|
||||
git switch multi-revert 2>&1 | Out-Null
|
||||
|
||||
@@ -160,26 +108,26 @@ if ($LASTEXITCODE -ne 0) {
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that sqrt.py is removed (reverted)
|
||||
if (-not (Test-Path "sqrt.py")) {
|
||||
Write-Host "[PASS] Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] sqrt.py still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that logarithm.py is removed (reverted)
|
||||
if (-not (Test-Path "logarithm.py")) {
|
||||
Write-Host "[PASS] Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] logarithm.py still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check calculator.py content
|
||||
if (Test-Path "calculator.py") {
|
||||
$calcContent = Get-Content "calculator.py" -Raw
|
||||
|
||||
# Check that square_root is NOT in code (reverted)
|
||||
if ($calcContent -notmatch "def square_root") {
|
||||
Write-Host "[PASS] Broken square_root function reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] square_root function still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that logarithm is NOT in code (reverted)
|
||||
if ($calcContent -notmatch "def logarithm") {
|
||||
Write-Host "[PASS] Broken logarithm function reverted" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host "[FAIL] logarithm function still exists (should be reverted)" -ForegroundColor Red
|
||||
$allChecksPassed = $false
|
||||
}
|
||||
|
||||
# Check that power function still exists (good commit before bad ones)
|
||||
if ($calcContent -match "def power") {
|
||||
Write-Host "[PASS] power function preserved" -ForegroundColor Green
|
||||
@@ -211,11 +159,10 @@ if ($allChecksPassed) {
|
||||
Write-Host "=========================================" -ForegroundColor Green
|
||||
Write-Host "`nYou've mastered git revert!" -ForegroundColor Cyan
|
||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
||||
Write-Host " ✓ Reverting regular commits safely" -ForegroundColor White
|
||||
Write-Host " ✓ Reverting merge commits with -m flag" -ForegroundColor White
|
||||
Write-Host " ✓ Reverting commits safely without erasing history" -ForegroundColor White
|
||||
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
|
||||
Write-Host " ✓ Preserving history while undoing changes" -ForegroundColor White
|
||||
Write-Host "`nReady for Module 06: Git Reset!" -ForegroundColor Green
|
||||
Write-Host " ✓ Preserving all other commits while undoing specific changes" -ForegroundColor White
|
||||
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
exit 0
|
||||
} else {
|
||||
|
||||
387
01-essentials/07-stash/README.md
Normal file
387
01-essentials/07-stash/README.md
Normal file
@@ -0,0 +1,387 @@
|
||||
# Module 07: Git Stash - Temporary Storage
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
By the end of this module, you will:
|
||||
- Understand what git stash is and when to use it
|
||||
- Temporarily save work without committing
|
||||
- Switch between branches without losing uncommitted changes
|
||||
- Manage multiple stashes
|
||||
- Apply and remove stashed changes
|
||||
- Understand the difference between stash pop and stash apply
|
||||
|
||||
## Challenge Description
|
||||
|
||||
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
|
||||
|
||||
Your task is to:
|
||||
1. Stash your incomplete work
|
||||
2. Switch to the main branch
|
||||
3. Fix the urgent bug and commit it
|
||||
4. Return to your feature branch
|
||||
5. Restore your stashed changes
|
||||
6. Complete your feature and commit it
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### What is Git Stash?
|
||||
|
||||
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
|
||||
|
||||
### When to Use Stash
|
||||
|
||||
Use stash when you need to:
|
||||
- Switch branches but have uncommitted changes
|
||||
- Pull updates from remote but have local modifications
|
||||
- Quickly test something on a clean working directory
|
||||
- Save work temporarily without creating a commit
|
||||
- Context-switch between tasks
|
||||
|
||||
### Stash vs Commit
|
||||
|
||||
**Stash:**
|
||||
- Temporary storage
|
||||
- Not part of project history
|
||||
- Can be applied to different branches
|
||||
- Easy to discard if not needed
|
||||
- Local only (not pushed to remote)
|
||||
|
||||
**Commit:**
|
||||
- Permanent part of history
|
||||
- Creates a snapshot in the project timeline
|
||||
- Associated with a specific branch
|
||||
- Should be meaningful and complete
|
||||
- Can be pushed to remote
|
||||
|
||||
### The Stash Stack
|
||||
|
||||
Git stash works like a stack (LIFO - Last In, First Out):
|
||||
```
|
||||
stash@{0} <- Most recent stash (top of stack)
|
||||
stash@{1}
|
||||
stash@{2} <- Oldest stash
|
||||
```
|
||||
|
||||
You can have multiple stashes and apply any of them.
|
||||
|
||||
## Setup
|
||||
|
||||
Run the setup script to create the challenge environment:
|
||||
|
||||
```pwsh
|
||||
.\setup.ps1
|
||||
```
|
||||
|
||||
This creates a `challenge/` directory where you're working on a login feature with uncommitted changes, and a critical bug needs fixing on main.
|
||||
|
||||
## Your Task
|
||||
|
||||
### The Scenario
|
||||
|
||||
You're working on a login feature on the `feature-login` branch. Your work is incomplete (has TODOs), so it's not ready to commit.
|
||||
|
||||
Suddenly, your teammate reports a **critical security bug** in production! You need to:
|
||||
1. Temporarily save your incomplete work
|
||||
2. Switch to the main branch
|
||||
3. Fix the urgent bug
|
||||
4. Return to your feature and continue working
|
||||
|
||||
### Step-by-Step Instructions
|
||||
|
||||
1. **Navigate to the challenge directory:**
|
||||
```pwsh
|
||||
cd challenge
|
||||
```
|
||||
|
||||
2. **Check your current status:**
|
||||
```pwsh
|
||||
git status
|
||||
```
|
||||
You should see modified `login.py` (uncommitted changes)
|
||||
|
||||
3. **Stash your work with a message:**
|
||||
```pwsh
|
||||
git stash save "WIP: login feature"
|
||||
```
|
||||
|
||||
4. **Verify working directory is clean:**
|
||||
```pwsh
|
||||
git status
|
||||
```
|
||||
Should say "nothing to commit, working tree clean"
|
||||
|
||||
5. **Switch to main branch:**
|
||||
```pwsh
|
||||
git switch main
|
||||
```
|
||||
|
||||
6. **Open app.py and find the bug:**
|
||||
```pwsh
|
||||
cat app.py
|
||||
```
|
||||
Look for the comment "# BUG: This allows unauthenticated access!"
|
||||
|
||||
7. **Fix the bug** by editing app.py:
|
||||
- Remove the buggy comment line
|
||||
- You can leave the implementation as-is or improve it
|
||||
- The important thing is removing the comment that says "allows unauthenticated access"
|
||||
|
||||
8. **Commit the fix:**
|
||||
```pwsh
|
||||
git add app.py
|
||||
git commit -m "Fix critical security bug"
|
||||
```
|
||||
|
||||
9. **Switch back to your feature branch:**
|
||||
```pwsh
|
||||
git switch feature-login
|
||||
```
|
||||
|
||||
10. **Restore your stashed work:**
|
||||
```pwsh
|
||||
git stash pop
|
||||
```
|
||||
This applies the stash and removes it from the stash stack
|
||||
|
||||
11. **Complete the TODOs in login.py:**
|
||||
- Open login.py in your editor
|
||||
- Complete the login method (verify password and return session)
|
||||
- Add a logout method
|
||||
- Remove all TODO comments
|
||||
|
||||
12. **Commit your completed feature:**
|
||||
```pwsh
|
||||
git add login.py
|
||||
git commit -m "Complete login feature"
|
||||
```
|
||||
|
||||
13. **Verify your solution:**
|
||||
```pwsh
|
||||
..\verify.ps1
|
||||
```
|
||||
|
||||
## Key Stash Commands
|
||||
|
||||
### Basic Operations
|
||||
|
||||
```pwsh
|
||||
# Stash current changes with a message
|
||||
git stash save "description"
|
||||
|
||||
# Stash without a message (not recommended)
|
||||
git stash
|
||||
|
||||
# Stash including untracked files
|
||||
git stash -u
|
||||
|
||||
# List all stashes
|
||||
git stash list
|
||||
|
||||
# Show what's in the most recent stash
|
||||
git stash show
|
||||
|
||||
# Show full diff of stash
|
||||
git stash show -p
|
||||
```
|
||||
|
||||
### Applying Stashes
|
||||
|
||||
```pwsh
|
||||
# Apply most recent stash and remove it (RECOMMENDED)
|
||||
git stash pop
|
||||
|
||||
# Apply most recent stash but keep it in stack
|
||||
git stash apply
|
||||
|
||||
# Apply a specific stash
|
||||
git stash apply stash@{1}
|
||||
|
||||
# Apply a specific stash by number
|
||||
git stash apply 1
|
||||
```
|
||||
|
||||
### Managing Stashes
|
||||
|
||||
```pwsh
|
||||
# Drop (delete) the most recent stash
|
||||
git stash drop
|
||||
|
||||
# Drop a specific stash
|
||||
git stash drop stash@{1}
|
||||
|
||||
# Clear all stashes
|
||||
git stash clear
|
||||
|
||||
# Create a new branch from a stash
|
||||
git stash branch new-branch-name
|
||||
```
|
||||
|
||||
## Understanding Stash vs Pop vs Apply
|
||||
|
||||
### Stash Pop (Recommended)
|
||||
```pwsh
|
||||
git stash pop
|
||||
```
|
||||
- Applies the stash to your working directory
|
||||
- **Removes** the stash from the stack
|
||||
- Use this most of the time
|
||||
|
||||
### Stash Apply (Keep Stash)
|
||||
```pwsh
|
||||
git stash apply
|
||||
```
|
||||
- Applies the stash to your working directory
|
||||
- **Keeps** the stash in the stack
|
||||
- Useful if you want to apply the same changes to multiple branches
|
||||
|
||||
### When to Use Which
|
||||
|
||||
**Use `pop` when:**
|
||||
- You're done with the stash and won't need it again (99% of the time)
|
||||
- You want to keep your stash list clean
|
||||
|
||||
**Use `apply` when:**
|
||||
- You want to test the same changes on different branches
|
||||
- You're not sure if you want to keep the stash yet
|
||||
|
||||
## Verification
|
||||
|
||||
Run the verification script to check your solution:
|
||||
|
||||
```pwsh
|
||||
..\verify.ps1
|
||||
```
|
||||
|
||||
Or from the module directory:
|
||||
|
||||
```pwsh
|
||||
.\verify.ps1
|
||||
```
|
||||
|
||||
The verification will check that:
|
||||
- ✅ The bug fix commit exists on main
|
||||
- ✅ Your feature is completed on the feature-login branch
|
||||
- ✅ All TODOs are removed from login.py
|
||||
- ✅ No uncommitted changes remain
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### "Cannot switch branches - you have uncommitted changes"
|
||||
|
||||
**Problem:** Git won't let you switch branches with uncommitted changes.
|
||||
|
||||
**Solution:**
|
||||
```pwsh
|
||||
# Stash your changes first
|
||||
git stash save "work in progress"
|
||||
|
||||
# Now you can switch
|
||||
git switch other-branch
|
||||
|
||||
# When you come back, restore your work
|
||||
git switch original-branch
|
||||
git stash pop
|
||||
```
|
||||
|
||||
### "I don't remember what's in my stash"
|
||||
|
||||
**Problem:** You stashed something but forgot what it was.
|
||||
|
||||
**Solution:**
|
||||
```pwsh
|
||||
# List all stashes
|
||||
git stash list
|
||||
|
||||
# Show summary of what changed
|
||||
git stash show stash@{0}
|
||||
|
||||
# Show full diff
|
||||
git stash show -p stash@{0}
|
||||
```
|
||||
|
||||
### "Stash conflicts when I apply"
|
||||
|
||||
**Problem:** Applying a stash causes merge conflicts.
|
||||
|
||||
**Solution:**
|
||||
1. Git marks conflicts in your files with `<<<<<<<` markers
|
||||
2. Open the files and resolve conflicts manually
|
||||
3. Stage the resolved files: `git add <file>`
|
||||
4. If you used `pop`, the stash is automatically dropped
|
||||
5. If you used `apply`, manually drop it: `git stash drop`
|
||||
|
||||
### "I accidentally cleared my stash"
|
||||
|
||||
**Problem:** You deleted a stash you still needed.
|
||||
|
||||
**Unfortunately:** Stashes are hard to recover once deleted. Lessons learned:
|
||||
- Use `git stash pop` instead of `git stash drop` when you're unsure
|
||||
- Use `git stash show -p` to preview before dropping
|
||||
- Consider committing work instead of stashing for important changes
|
||||
|
||||
## Tips for Success
|
||||
|
||||
💡 **Always add a message** - `git stash save "your message"` helps you remember what you stashed
|
||||
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
|
||||
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
|
||||
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
|
||||
💡 **Stashes are local** - They don't get pushed to remote repositories
|
||||
💡 **Clean working directory** - Always verify with `git status` after stashing
|
||||
|
||||
## Common Use Cases
|
||||
|
||||
### Quick Branch Switch
|
||||
```pwsh
|
||||
# You're working on feature-A
|
||||
git stash save "feature A progress"
|
||||
git switch hotfix-branch
|
||||
# Fix the issue, commit
|
||||
git switch feature-A
|
||||
git stash pop
|
||||
```
|
||||
|
||||
### Pull with Local Changes
|
||||
```pwsh
|
||||
# You have uncommitted changes
|
||||
git stash save "local changes"
|
||||
git pull
|
||||
git stash pop
|
||||
# Resolve conflicts if any
|
||||
```
|
||||
|
||||
### Test Clean State
|
||||
```pwsh
|
||||
# Stash changes to test on clean code
|
||||
git stash save "testing clean state"
|
||||
# Run tests
|
||||
git stash pop # Restore your changes
|
||||
```
|
||||
|
||||
## What You've Learned
|
||||
|
||||
After completing this module, you understand:
|
||||
|
||||
- ✅ Stash temporarily saves uncommitted changes
|
||||
- ✅ Stash lets you switch contexts without committing
|
||||
- ✅ `git stash pop` applies and removes the stash
|
||||
- ✅ `git stash apply` applies but keeps the stash
|
||||
- ✅ Stashes are local and not pushed to remote
|
||||
- ✅ Stash is essential for handling interruptions and urgent fixes
|
||||
|
||||
**Key Takeaway:** Stash is your "temporary clipboard" for incomplete work. It helps you stay productive when you need to context-switch without making messy "WIP" commits.
|
||||
|
||||
## Next Steps
|
||||
|
||||
Ready to continue? You've now mastered the essential Git commands for daily development:
|
||||
- Committing and history
|
||||
- Branching and merging
|
||||
- Cherry-picking specific changes
|
||||
- Safely reverting commits
|
||||
- Temporarily stashing work
|
||||
|
||||
Move on to **Module 08: Multiplayer Git** to practice collaborating with others!
|
||||
|
||||
To start over:
|
||||
```pwsh
|
||||
.\reset.ps1
|
||||
```
|
||||
@@ -25,6 +25,9 @@ git init | Out-Null
|
||||
git config user.name "Workshop User" | Out-Null
|
||||
git config user.email "user@workshop.local" | Out-Null
|
||||
|
||||
# Detect the default branch name after first commit (created below)
|
||||
# Will be detected after the initial commit
|
||||
|
||||
# Create initial application on main
|
||||
$app = @"
|
||||
class Application:
|
||||
@@ -45,6 +48,14 @@ Set-Content -Path "app.py" -Value $app
|
||||
git add app.py
|
||||
git commit -m "Initial application" | Out-Null
|
||||
|
||||
# Detect the main branch name after first commit
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
$readme = @"
|
||||
# MyApp
|
||||
|
||||
@@ -78,7 +89,7 @@ git add login.py
|
||||
git commit -m "Start login service implementation" | Out-Null
|
||||
|
||||
# Add a critical bug to main branch (simulating a bug that was introduced)
|
||||
git checkout main | Out-Null
|
||||
git checkout $mainBranch | Out-Null
|
||||
|
||||
$appWithBug = @"
|
||||
class Application:
|
||||
@@ -140,16 +151,17 @@ Write-Host "========================================" -ForegroundColor Green
|
||||
Write-Host "`nSituation:" -ForegroundColor Cyan
|
||||
Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White
|
||||
Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow
|
||||
Write-Host "`nUrgent: A critical security bug was found in production (main branch)!" -ForegroundColor Red
|
||||
Write-Host "`nUrgent: A critical security bug was found in production ($mainBranch branch)!" -ForegroundColor Red
|
||||
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
|
||||
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
||||
Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White
|
||||
Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White
|
||||
Write-Host "4. Switch to main: git checkout main" -ForegroundColor White
|
||||
Write-Host "4. Switch to ${mainBranch}: git switch $mainBranch" -ForegroundColor White
|
||||
Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White
|
||||
Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White
|
||||
Write-Host "7. Switch back: git checkout feature-login" -ForegroundColor White
|
||||
Write-Host "7. Switch back: git switch feature-login" -ForegroundColor White
|
||||
Write-Host "8. Restore your work: git stash pop" -ForegroundColor White
|
||||
Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White
|
||||
Write-Host "10. Commit your completed feature" -ForegroundColor White
|
||||
@@ -32,6 +32,21 @@ if (-not (Test-Path ".git")) {
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Detect the main branch name
|
||||
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||
if ($allBranches -contains "main") {
|
||||
$mainBranch = "main"
|
||||
} elseif ($allBranches -contains "master") {
|
||||
$mainBranch = "master"
|
||||
} else {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = $allBranches | Select-Object -First 1
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
}
|
||||
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||
|
||||
# Check current branch
|
||||
$currentBranch = git branch --show-current 2>$null
|
||||
if ($currentBranch -ne "feature-login") {
|
||||
@@ -53,14 +68,22 @@ if ($status) {
|
||||
}
|
||||
|
||||
# Verify main branch has the security fix
|
||||
Write-Host "`nChecking main branch for bug fix..." -ForegroundColor Cyan
|
||||
git checkout main 2>$null | Out-Null
|
||||
Write-Host "`nChecking $mainBranch branch for bug fix..." -ForegroundColor Cyan
|
||||
git checkout $mainBranch 2>$null | Out-Null
|
||||
|
||||
# Check for bug fix commit
|
||||
$mainCommits = git log --pretty=format:"%s" main 2>$null
|
||||
if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
|
||||
Write-Host "[FAIL] No security bug fix commit found on main branch." -ForegroundColor Red
|
||||
Write-Host "Hint: After stashing, switch to main and commit a bug fix" -ForegroundColor Yellow
|
||||
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||
$hasSecurityFix = $false
|
||||
foreach ($commit in $mainCommits) {
|
||||
if ($commit -match "security|Fix.*bug") {
|
||||
$hasSecurityFix = $true
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
if (-not $hasSecurityFix) {
|
||||
Write-Host "[FAIL] No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
|
||||
Write-Host "Hint: After stashing, switch to $mainBranch and commit a bug fix" -ForegroundColor Yellow
|
||||
git checkout feature-login 2>$null | Out-Null
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -68,7 +91,7 @@ if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
|
||||
|
||||
# Check that app.py has been fixed
|
||||
if (-not (Test-Path "app.py")) {
|
||||
Write-Host "[FAIL] app.py not found on main branch." -ForegroundColor Red
|
||||
Write-Host "[FAIL] app.py not found on $mainBranch branch." -ForegroundColor Red
|
||||
git checkout feature-login 2>$null | Out-Null
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -113,7 +136,7 @@ if (-not (Test-Path "login.py")) {
|
||||
$loginContent = Get-Content "login.py" -Raw
|
||||
|
||||
# Check that login method exists and is implemented
|
||||
if ($loginContent -notmatch "login\(username, password\)") {
|
||||
if ($loginContent -notmatch "def login") {
|
||||
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
|
||||
Set-Location ..
|
||||
exit 1
|
||||
@@ -232,23 +232,8 @@ To reuse the repository:
|
||||
|
||||
## 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
|
||||
- **Let conflicts happen** - they're the best learning opportunity
|
||||
- **Walk the room** - help students who get stuck
|
||||
- **Point students to 03_TASKS.md** - Simple explanations of clone, push, pull, and fetch for beginners
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### SSH Issues
|
||||
- Verify SSH key added to Azure DevOps (User Settings → SSH Public Keys)
|
||||
- Test: `ssh -T git@ssh.dev.azure.com`
|
||||
|
||||
### Permission Issues
|
||||
- Check user is added to project
|
||||
- Verify Contribute permission on repository
|
||||
|
||||
### Service Issues
|
||||
- Check status: https://status.dev.azure.com
|
||||
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.
|
||||
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
|
||||
```
|
||||
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,199 +0,0 @@
|
||||
# Module 11: Stash
|
||||
|
||||
## Learning Objectives
|
||||
|
||||
By the end of this module, you will:
|
||||
- Understand what git stash is and when to use it
|
||||
- Temporarily save work without committing
|
||||
- Switch between branches without losing uncommitted changes
|
||||
- Manage multiple stashes
|
||||
- Apply and remove stashed changes
|
||||
- Understand the difference between stash pop and stash apply
|
||||
|
||||
## Challenge Description
|
||||
|
||||
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
|
||||
|
||||
Your task is to:
|
||||
1. Stash your incomplete work
|
||||
2. Switch to the main branch
|
||||
3. Fix the urgent bug and commit it
|
||||
4. Return to your feature branch
|
||||
5. Restore your stashed changes
|
||||
6. Complete your feature and commit it
|
||||
|
||||
## Key Concepts
|
||||
|
||||
### What is Git Stash?
|
||||
|
||||
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
|
||||
|
||||
### When to Use Stash
|
||||
|
||||
Use stash when you need to:
|
||||
- Switch branches but have uncommitted changes
|
||||
- Pull updates from remote but have local modifications
|
||||
- Quickly test something on a clean working directory
|
||||
- Save work temporarily without creating a commit
|
||||
- Context-switch between tasks
|
||||
|
||||
### Stash vs Commit
|
||||
|
||||
**Stash:**
|
||||
- Temporary storage
|
||||
- Not part of project history
|
||||
- Can be applied to different branches
|
||||
- Easy to discard if not needed
|
||||
- Local only (not pushed to remote)
|
||||
|
||||
**Commit:**
|
||||
- Permanent part of history
|
||||
- Creates a snapshot in the project timeline
|
||||
- Associated with a specific branch
|
||||
- Should be meaningful and complete
|
||||
- Can be pushed to remote
|
||||
|
||||
### The Stash Stack
|
||||
|
||||
Git stash works like a stack (LIFO - Last In, First Out):
|
||||
```
|
||||
stash@{0} <- Most recent stash (top of stack)
|
||||
stash@{1}
|
||||
stash@{2} <- Oldest stash
|
||||
```
|
||||
|
||||
You can have multiple stashes and apply any of them.
|
||||
|
||||
## Useful Commands
|
||||
|
||||
```bash
|
||||
# Stash current changes
|
||||
git stash
|
||||
git stash save "description" # With a descriptive message
|
||||
|
||||
# Stash including untracked files
|
||||
git stash -u
|
||||
|
||||
# List all stashes
|
||||
git stash list
|
||||
|
||||
# Apply most recent stash and remove it from stack
|
||||
git stash pop
|
||||
|
||||
# Apply most recent stash but keep it in stack
|
||||
git stash apply
|
||||
|
||||
# Apply a specific stash
|
||||
git stash apply stash@{1}
|
||||
|
||||
# Show what's in a stash
|
||||
git stash show
|
||||
git stash show -p # Show full diff
|
||||
|
||||
# Drop (delete) a stash
|
||||
git stash drop stash@{0}
|
||||
|
||||
# Clear all stashes
|
||||
git stash clear
|
||||
|
||||
# Create a branch from a stash
|
||||
git stash branch new-branch-name
|
||||
```
|
||||
|
||||
## Verification
|
||||
|
||||
Run the verification script to check your solution:
|
||||
|
||||
```bash
|
||||
.\verify.ps1
|
||||
```
|
||||
|
||||
The verification will check that:
|
||||
- The bug fix commit exists on main
|
||||
- Your feature is completed on the feature branch
|
||||
- Changes were properly stashed and restored
|
||||
- No uncommitted changes remain
|
||||
|
||||
## Challenge Steps
|
||||
|
||||
1. Navigate to the challenge directory
|
||||
2. You're on feature-login with uncommitted changes
|
||||
3. Check status: `git status` (you'll see modified files)
|
||||
4. Stash your changes: `git stash save "WIP: login feature"`
|
||||
5. Verify working directory is clean: `git status`
|
||||
6. Switch to main: `git switch main`
|
||||
7. View the bug in app.js and fix it (remove the incorrect line)
|
||||
8. Commit the fix: `git add app.js && git commit -m "Fix critical security bug"`
|
||||
9. Switch back to feature: `git switch feature-login`
|
||||
10. Restore your work: `git stash pop`
|
||||
11. Complete the feature (the TODOs in login.js)
|
||||
12. Commit your completed feature
|
||||
13. Run verification
|
||||
|
||||
## Tips
|
||||
|
||||
- Always use `git stash save "message"` to describe what you're stashing
|
||||
- Use `git stash list` to see all your stashes
|
||||
- `git stash pop` applies and removes the stash (use this most often)
|
||||
- `git stash apply` keeps the stash (useful if you want to apply it to multiple branches)
|
||||
- Stashes are local - they don't get pushed to remote repositories
|
||||
- You can stash even if you have changes to different files
|
||||
- Stash before pulling to avoid merge conflicts
|
||||
- Use `git stash show -p` to preview what's in a stash before applying
|
||||
|
||||
## Common Stash Scenarios
|
||||
|
||||
### Scenario 1: Quick Branch Switch
|
||||
```bash
|
||||
# Working on feature, need to switch to main
|
||||
git stash
|
||||
git switch main
|
||||
# Do work on main
|
||||
git switch feature
|
||||
git stash pop
|
||||
```
|
||||
|
||||
### Scenario 2: Pull with Local Changes
|
||||
```bash
|
||||
# You have local changes but need to pull
|
||||
git stash
|
||||
git pull
|
||||
git stash pop
|
||||
# Resolve any conflicts
|
||||
```
|
||||
|
||||
### Scenario 3: Experimental Changes
|
||||
```bash
|
||||
# Try something experimental
|
||||
git stash # Save current work
|
||||
# Make experimental changes
|
||||
# Decide you don't like it
|
||||
git restore . # Discard experiment
|
||||
git stash pop # Restore original work
|
||||
```
|
||||
|
||||
### Scenario 4: Apply to Multiple Branches
|
||||
```bash
|
||||
# Same fix needed on multiple branches
|
||||
git stash
|
||||
git switch branch1
|
||||
git stash apply
|
||||
git commit -am "Apply fix"
|
||||
git switch branch2
|
||||
git stash apply
|
||||
git commit -am "Apply fix"
|
||||
git stash drop # Clean up when done
|
||||
```
|
||||
|
||||
## Stash Conflicts
|
||||
|
||||
If applying a stash causes conflicts:
|
||||
1. Git will mark the conflicts in your files
|
||||
2. Resolve conflicts manually (like merge conflicts)
|
||||
3. Stage the resolved files: `git add <file>`
|
||||
4. The stash is automatically dropped after successful pop
|
||||
5. If you used `apply`, manually drop it: `git stash drop`
|
||||
|
||||
## What You'll Learn
|
||||
|
||||
Git stash is an essential tool for managing context switches in your daily workflow. It lets you maintain a clean working directory while preserving incomplete work, making it easy to handle interruptions, urgent fixes, and quick branch switches. Mastering stash makes you more efficient and helps avoid the temptation to make "WIP" commits just to switch branches. Think of stash as your temporary workspace that follows you around.
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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.
|
||||
@@ -33,6 +33,9 @@ git init | Out-Null
|
||||
git config user.name "Workshop Student"
|
||||
git config user.email "student@example.com"
|
||||
|
||||
# Detect the default branch name after first commit (created below)
|
||||
# Will be detected after the initial commit
|
||||
|
||||
# ============================================================================
|
||||
# Create initial commit (shared by all scenarios)
|
||||
# ============================================================================
|
||||
@@ -45,6 +48,14 @@ Set-Content -Path "README.md" -Value $readmeContent
|
||||
git add .
|
||||
git commit -m "Initial commit" | Out-Null
|
||||
|
||||
# Detect the main branch name after first commit
|
||||
$mainBranch = git branch --show-current
|
||||
if (-not $mainBranch) {
|
||||
$mainBranch = git config --get init.defaultBranch
|
||||
if (-not $mainBranch) { $mainBranch = "main" }
|
||||
}
|
||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||
|
||||
# ============================================================================
|
||||
# SCENARIO 1: Soft Reset (--soft)
|
||||
# ============================================================================
|
||||
@@ -155,7 +166,7 @@ Write-Host "[CREATED] soft-reset branch with commit to reset --soft" -Foreground
|
||||
Write-Host "`nScenario 2: Creating mixed-reset branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to initial commit and create mixed-reset branch
|
||||
git switch main | Out-Null
|
||||
git switch $mainBranch | Out-Null
|
||||
git switch -c mixed-reset | Out-Null
|
||||
|
||||
# Build up scenario 2 commits
|
||||
@@ -256,7 +267,7 @@ Write-Host "[CREATED] mixed-reset branch with commits to reset --mixed" -Foregro
|
||||
Write-Host "`nScenario 3: Creating hard-reset branch..." -ForegroundColor Cyan
|
||||
|
||||
# Switch back to main and create hard-reset branch
|
||||
git switch main | Out-Null
|
||||
git switch $mainBranch | Out-Null
|
||||
git switch -c hard-reset | Out-Null
|
||||
|
||||
# Reset to basic state
|
||||
@@ -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.
|
||||
14
install.ps1
14
install.ps1
@@ -635,6 +635,20 @@ if ($userChoice -ne "CloneOnly") {
|
||||
-CheckCommand "git" `
|
||||
-MinVersion "2.23" `
|
||||
-AdditionalArgs "-e"
|
||||
|
||||
Write-Host " Setting the init.defaultBranch to be 'main'"
|
||||
git config --global init.defaultBranch main
|
||||
|
||||
Write-Host " Setting the default editor to code, to handle merge messages"
|
||||
git config --global core.editor "code --wait"
|
||||
|
||||
Write-Host " Setting vscode at the default code editor for merge conflicts"
|
||||
git config --global merge.tool vscode
|
||||
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
|
||||
|
||||
Write-Host " Setting vscode as the default code editor for diffs"
|
||||
git config --global diff.tool vscode
|
||||
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
|
||||
|
||||
# Verify Git version specifically
|
||||
if ($results.Git) {
|
||||
|
||||
Reference in New Issue
Block a user