Compare commits
30 Commits
advanced-c
...
c20041efde
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c20041efde | ||
|
|
84ed357feb | ||
|
|
9659d82d2a | ||
|
|
dbba2da5f2 | ||
|
|
1d0003bb70 | ||
|
|
3a4fe8ce9e | ||
|
|
13dd2317f9 | ||
|
|
cac03b69e3 | ||
|
|
19cf66e23d | ||
|
|
a6b33a441e | ||
|
|
690b83cead | ||
|
|
c69b463f84 | ||
|
|
39d8ace75c | ||
|
|
4b9d2449c8 | ||
|
|
5582b9fcbd | ||
|
|
3a6eb0646b | ||
|
|
a898030a9f | ||
|
|
382a125076 | ||
|
|
d80fa74af9 | ||
|
|
7fc0c1342a | ||
|
|
cba2d9bb16 | ||
|
|
968a44f9a5 | ||
|
|
c39573573f | ||
|
|
ee67433fdd | ||
|
|
100e89b23d | ||
| b58080b8e8 | |||
|
|
6cf0418ebd | ||
|
|
a8e9507f89 | ||
| 2240cbe10d | |||
|
|
a895abdd03 |
@@ -11,35 +11,35 @@
|
|||||||
- Committed both required files (welcome.txt and instructions.txt)
|
- Committed both required files (welcome.txt and instructions.txt)
|
||||||
#>
|
#>
|
||||||
|
|
||||||
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
Write-Host "Verifying Module 01: Git Basics Challenge..." -ForegroundColor Cyan
|
Write-Host "Verifying Module 01: Git Basics Challenge..." -ForegroundColor Cyan
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
|
|
||||||
$allChecksPassed = $true
|
$allChecksPassed = $true
|
||||||
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
|
|
||||||
# Check if challenge directory exists
|
# Check if challenge directory exists
|
||||||
if (-not (Test-Path "challenge")) {
|
if (-not (Test-Path $challengeRoot)) {
|
||||||
Write-Host "[FAIL] Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
Write-Fail "Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
# Check if git repository exists
|
||||||
if (-not (Test-Path ".git")) {
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
Write-Host "[FAIL] No git repository found. Did you run 'git init'?" -ForegroundColor Red
|
Write-Fail "No git repository found. Did you run 'git init'?" -ForegroundColor Red
|
||||||
Set-Location ".."
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "[PASS] Git repository initialized" -ForegroundColor Green
|
Write-Pass "Git repository initialized" -ForegroundColor Green
|
||||||
|
|
||||||
# Check if there are any commits
|
# Check if there are any commits
|
||||||
$commitCount = (git rev-list --all --count 2>$null)
|
$commitCount = (git rev-list --all --count 2>$null)
|
||||||
if ($null -eq $commitCount -or $commitCount -eq 0) {
|
if ($null -eq $commitCount -or $commitCount -eq 0) {
|
||||||
Write-Host "[FAIL] No commits found. Have you committed your changes?" -ForegroundColor Red
|
Write-Fail "No commits found. Have you committed your changes?" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[PASS] Found $commitCount commit(s)" -ForegroundColor Green
|
Write-Pass "Found $commitCount commit(s)" -ForegroundColor Green
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if both files are in the git history using git ls-tree
|
# Check if both files are in the git history using git ls-tree
|
||||||
@@ -47,16 +47,16 @@ if ($commitCount -gt 0) {
|
|||||||
$trackedFiles = git ls-tree -r HEAD --name-only 2>$null
|
$trackedFiles = git ls-tree -r HEAD --name-only 2>$null
|
||||||
|
|
||||||
if ($trackedFiles -match "welcome.txt") {
|
if ($trackedFiles -match "welcome.txt") {
|
||||||
Write-Host "[PASS] welcome.txt is committed" -ForegroundColor Green
|
Write-Pass "welcome.txt is committed" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] welcome.txt is not in the commit history" -ForegroundColor Red
|
Write-Fail "welcome.txt is not in the commit history" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($trackedFiles -match "instructions.txt") {
|
if ($trackedFiles -match "instructions.txt") {
|
||||||
Write-Host "[PASS] instructions.txt is committed" -ForegroundColor Green
|
Write-Pass "instructions.txt is committed" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] instructions.txt is not in the commit history" -ForegroundColor Red
|
Write-Fail "instructions.txt is not in the commit history" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -67,8 +67,6 @@ if ($statusOutput) {
|
|||||||
Write-Host "[INFO] You have uncommitted changes. This is OK, but make sure all required files are committed." -ForegroundColor Yellow
|
Write-Host "[INFO] You have uncommitted changes. This is OK, but make sure all required files are committed." -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location ".."
|
|
||||||
|
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
if ($allChecksPassed) {
|
if ($allChecksPassed) {
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ The setup script will create an `answers.md` file in the challenge directory wit
|
|||||||
7. View specific commits: `git show <commit-hash>`
|
7. View specific commits: `git show <commit-hash>`
|
||||||
8. Compare specific commits: `git diff <commit1> <commit2> <file>`
|
8. Compare specific commits: `git diff <commit1> <commit2> <file>`
|
||||||
9. Fill in your answers in `answers.md`
|
9. Fill in your answers in `answers.md`
|
||||||
|
10. Remember to save `answers.md`
|
||||||
|
|
||||||
> **Important Notes:**
|
> **Important Notes:**
|
||||||
> - You can use any Git commands you like to explore the repository
|
> - You can use any Git commands you like to explore the repository
|
||||||
|
|||||||
@@ -10,52 +10,53 @@
|
|||||||
- answers.txt exists with correct information about commit history
|
- answers.txt exists with correct information about commit history
|
||||||
#>
|
#>
|
||||||
|
|
||||||
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 02 Solution ===" -ForegroundColor Cyan
|
Write-Host "`n=== Verifying Module 02 Solution ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
$allChecksPassed = $true
|
$allChecksPassed = $true
|
||||||
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
# Check if challenge directory exists
|
||||||
if (-not (Test-Path "challenge")) {
|
if (-not (Test-Path $challengeRoot)) {
|
||||||
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
|
Write-Fail "Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
# Check if git repository exists
|
||||||
if (-not (Test-Path ".git")) {
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
|
Write-Fail "Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if answers.md exists
|
# Check if answers.md exists
|
||||||
if (-not (Test-Path "answers.md")) {
|
if (-not (Test-Path "$challengeRoot\answers.md")) {
|
||||||
Write-Host "[FAIL] answers.md not found. Did you run setup.ps1?" -ForegroundColor Red
|
Write-Fail "answers.md not found. Did you run setup.ps1?" -ForegroundColor Red
|
||||||
Write-Host "[HINT] The setup script should have created answers.md for you" -ForegroundColor Yellow
|
Write-Hint "The setup script should have created answers.md for you" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[PASS] answers.md exists" -ForegroundColor Green
|
Write-Pass "answers.md exists" -ForegroundColor Green
|
||||||
|
|
||||||
# Read the answers file
|
# Read the answers file
|
||||||
$answers = Get-Content "answers.md" -Raw
|
$answers = Get-Content "$challengeRoot\answers.md" -Raw
|
||||||
$answersLower = $answers.ToLower()
|
$answersLower = $answers.ToLower()
|
||||||
|
|
||||||
# Check 1: Contains "5" or "five" for commit count
|
# Check 1: Contains "5" or "five" for commit count
|
||||||
if ($answersLower -match "5|five|fem") {
|
if ($answersLower -match "5|five|fem") {
|
||||||
Write-Host "[PASS] Correct commit count found" -ForegroundColor Green
|
Write-Pass "Correct commit count found" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Commit count not found or incorrect" -ForegroundColor Red
|
Write-Fail "Commit count not found or incorrect" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use 'git log --oneline' to count commits easily" -ForegroundColor Yellow
|
Write-Hint "Use 'git log --oneline' to count commits easily" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check 2: Contains "database" keyword for third commit
|
# Check 2: Contains "database" keyword for third commit
|
||||||
if ($answersLower -match "database") {
|
if ($answersLower -match "database") {
|
||||||
Write-Host "[PASS] Third commit message identified" -ForegroundColor Green
|
Write-Pass "Third commit message identified" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Third commit message not found" -ForegroundColor Red
|
Write-Fail "Third commit message not found" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use 'git log' to see commit messages in order" -ForegroundColor Yellow
|
Write-Hint "Use 'git log' to see commit messages in order" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -64,38 +65,37 @@ if (-not (Test-Path "answers.md")) {
|
|||||||
$hasDatabase = $answersLower -match "database"
|
$hasDatabase = $answersLower -match "database"
|
||||||
|
|
||||||
if ($hasAuth -and $hasDatabase) {
|
if ($hasAuth -and $hasDatabase) {
|
||||||
Write-Host "[PASS] Both files identified for bug fix commit" -ForegroundColor Green
|
Write-Pass "Both files identified for bug fix commit" -ForegroundColor Green
|
||||||
} elseif ($hasAuth -or $hasDatabase) {
|
} elseif ($hasAuth -or $hasDatabase) {
|
||||||
Write-Host "[PARTIAL] Only one file found - there are TWO files modified in this commit" -ForegroundColor Yellow
|
Write-Host "[PARTIAL] Only one file found - there are TWO files modified in this commit" -ForegroundColor Yellow
|
||||||
Write-Host "[HINT] Use 'git log --stat' or 'git show <commit-hash> --name-only' to see ALL files changed" -ForegroundColor Yellow
|
Write-Hint "Use 'git log --stat' or 'git show <commit-hash> --name-only' to see ALL files changed" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Files modified in bug fix commit not found" -ForegroundColor Red
|
Write-Fail "Files modified in bug fix commit not found" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use 'git log --stat' to see which files were changed in each commit" -ForegroundColor Yellow
|
Write-Hint "Use 'git log --stat' to see which files were changed in each commit" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check 4: Contains "config" keyword for staged file
|
# Check 4: Contains "config" keyword for staged file
|
||||||
if ($answersLower -match "config.py") {
|
if ($answersLower -match "config.py") {
|
||||||
Write-Host "[PASS] Staged file identified" -ForegroundColor Green
|
Write-Pass "Staged file identified" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Staged file not identified" -ForegroundColor Red
|
Write-Fail "Staged file not identified" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use 'git status' or 'git diff --staged' to see staged changes" -ForegroundColor Yellow
|
Write-Hint "Use 'git status' or 'git diff --staged' to see staged changes" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check 5: Contains "unicorn" for the secret code
|
# Check 5: Contains "unicorn" for the secret code
|
||||||
if ($answersLower -match "unicorn") {
|
if ($answersLower -match "unicorn") {
|
||||||
Write-Host "[PASS] Secret code found!" -ForegroundColor Green
|
Write-Pass "Secret code found!" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Secret code not found" -ForegroundColor Red
|
Write-Fail "Secret code not found" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use 'git diff <commit3> <commit4> database.py' to find the secret code" -ForegroundColor Yellow
|
Write-Hint "Use 'git diff <commit3> <commit4> database.py' to find the secret code" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
# Final summary
|
# Final summary
|
||||||
if ($allChecksPassed) {
|
if ($allChecksPassed) {
|
||||||
|
|||||||
@@ -94,7 +94,8 @@ Now practice creating your own branch:
|
|||||||
2. Check the changes you committed before. You'll notice that they're gone!
|
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`)
|
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.
|
- 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
|
- To see changes on the branch you're on, just run `git log --oneline --graph`
|
||||||
|
- Run `git log --oneline --graph --all` to see how the tree is looking, `main` should have diverged
|
||||||
4. Switch back to your branch `git switch my-feature`
|
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!
|
5. The changes from the `main` branch are now gone. Check the changes you committed before. You'll notice they're back!
|
||||||
|
|
||||||
@@ -104,12 +105,14 @@ Bring your work into main:
|
|||||||
|
|
||||||
1. Go back to the main branch `git switch main`
|
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
|
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.
|
- Hint: your `HEAD` is git's way of telling you <q>you're here</q>
|
||||||
|
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`
|
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.
|
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!
|
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.
|
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
|
- 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 Remember, once we've deleted the branch, we can no longer access it, and it's lost forever. That's why there is a safeguard when changes are unmerged when running
|
||||||
|
- Technically it's possible to recover the branch, however this requires using `git reflog` within the git garbage collection period which is usually 30 days-ish. This is however advanced, but I'd recommend asking your local LLM a question like <q>How can I restore a deleted branch</q>. It will probably come up with the right answer
|
||||||
|
|
||||||
You should see your feature branch merged into main!
|
You should see your feature branch merged into main!
|
||||||
|
|
||||||
@@ -139,7 +142,7 @@ git merge another-feature
|
|||||||
|
|
||||||
### What is a Branch?
|
### What is a Branch?
|
||||||
|
|
||||||
A **branch** is a lightweight movable pointer to a commit. When you create a branch, Git creates a new pointer - it doesn't copy all your files!
|
A **branch** is a lightweight movable pointer to a commit. When you create a branch, git creates a new pointer - it doesn't copy all your files!
|
||||||
|
|
||||||
```
|
```
|
||||||
main: A---B---C
|
main: A---B---C
|
||||||
@@ -154,7 +157,7 @@ my-feature: D---E
|
|||||||
|
|
||||||
### What is HEAD?
|
### What is HEAD?
|
||||||
|
|
||||||
`HEAD` points to your current branch. It's Git's way of saying "you are here."
|
`HEAD` points to your current branch. It's git's way of saying "you are here."
|
||||||
|
|
||||||
```pwsh
|
```pwsh
|
||||||
# HEAD points to main
|
# HEAD points to main
|
||||||
@@ -181,7 +184,7 @@ main: A---B---C---M
|
|||||||
feature: D---E
|
feature: D---E
|
||||||
```
|
```
|
||||||
|
|
||||||
Git creates a merge commit `M` that has two parents (C and E).
|
git creates a merge commit `M` that has two parents (C and E).
|
||||||
|
|
||||||
**Fast-forward merge**:
|
**Fast-forward merge**:
|
||||||
```
|
```
|
||||||
@@ -194,7 +197,7 @@ After merge:
|
|||||||
main: A---B---C---D
|
main: A---B---C---D
|
||||||
```
|
```
|
||||||
|
|
||||||
If main hasn't changed, Git just moves the pointer forward. No merge commit needed!
|
If main hasn't changed, git just moves the pointer forward. No merge commit needed!
|
||||||
|
|
||||||
## Key Commands
|
## Key Commands
|
||||||
|
|
||||||
@@ -307,44 +310,6 @@ git merge main
|
|||||||
git pull origin main
|
git pull origin main
|
||||||
```
|
```
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "I'm on the wrong branch!"
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
# Switch to the correct branch
|
|
||||||
git switch correct-branch
|
|
||||||
|
|
||||||
# Check current branch anytime
|
|
||||||
git branch
|
|
||||||
```
|
|
||||||
|
|
||||||
### "I made commits on the wrong branch!"
|
|
||||||
|
|
||||||
Don't panic! You can move commits to another branch:
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
# Create the correct branch from current state
|
|
||||||
git branch correct-branch
|
|
||||||
|
|
||||||
# Switch to wrong branch and remove the commits
|
|
||||||
git switch wrong-branch
|
|
||||||
git reset --hard HEAD~2 # Remove last 2 commits (adjust number)
|
|
||||||
|
|
||||||
# Switch to correct branch - commits are there!
|
|
||||||
git switch correct-branch
|
|
||||||
```
|
|
||||||
|
|
||||||
### "The merge created unexpected results!"
|
|
||||||
|
|
||||||
```pwsh
|
|
||||||
# Undo the merge
|
|
||||||
git merge --abort
|
|
||||||
|
|
||||||
# Or if already committed:
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
### "I want to see what changed in a merge!"
|
### "I want to see what changed in a merge!"
|
||||||
|
|
||||||
```pwsh
|
```pwsh
|
||||||
@@ -368,17 +333,17 @@ git diff main..feature-branch
|
|||||||
|
|
||||||
After completing this module, you understand:
|
After completing this module, you understand:
|
||||||
|
|
||||||
- ✅ Branches create independent lines of development
|
- Branches create independent lines of development
|
||||||
- ✅ `git switch -c` creates a new branch
|
- `git switch -c` creates a new branch
|
||||||
- ✅ Changes in one branch don't affect others
|
- Changes in one branch don't affect others
|
||||||
- ✅ `git merge` combines branches
|
- `git merge` combines branches
|
||||||
- ✅ Merge commits have two parent commits
|
- Merge commits have two parent commits
|
||||||
- ✅ `git log --graph` visualizes branch history
|
- `git log --graph` visualizes branch history
|
||||||
- ✅ Branches are pointers, not copies of files
|
- Branches are pointers, not copies of files
|
||||||
|
|
||||||
## Next Steps
|
## Next Steps
|
||||||
|
|
||||||
Ready to continue? The next module covers **merge conflicts** - what happens when Git can't automatically merge changes.
|
Ready to continue? The next module covers **merge conflicts** - what happens when git can't automatically merge changes.
|
||||||
|
|
||||||
To start over:
|
To start over:
|
||||||
```pwsh
|
```pwsh
|
||||||
@@ -386,4 +351,4 @@ To start over:
|
|||||||
.\setup.ps1
|
.\setup.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
**Need help?** Review the commands above, or run `git status` to see what Git suggests!
|
**Need help?** Review the commands above, or run `git status` to see what git suggests!
|
||||||
|
|||||||
@@ -12,47 +12,20 @@
|
|||||||
|
|
||||||
$script:allChecksPassed = $true
|
$script:allChecksPassed = $true
|
||||||
|
|
||||||
# ============================================================================
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
# Helper Functions
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
function Write-Pass {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[PASS] $Message" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Fail {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
|
||||||
$script:allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Hint {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Info {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Check challenge directory exists
|
# Check challenge directory exists
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
if (-not (Test-Path "challenge")) {
|
if (-not (Test-Path $challengeRoot)) {
|
||||||
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
||||||
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Push-Location "challenge"
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
||||||
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,24 +35,7 @@ Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundCol
|
|||||||
# Detect the main branch name (could be main, master, etc.)
|
# Detect the main branch name (could be main, master, etc.)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Try to get the default branch from remote origin first
|
# Try to get the default branch from remote origin first
|
||||||
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
$mainBranch = Get-MainBranch
|
||||||
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
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -22,16 +22,16 @@ This creates a repository with two feature branches that have conflicting change
|
|||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
A **merge conflict** occurs when Git cannot automatically combine changes because both branches modified the same part of the same file in different ways.
|
A **merge conflict** occurs when git cannot automatically combine changes because both branches modified the same part of the same file in different ways.
|
||||||
|
|
||||||
**When do conflicts happen?**
|
**When do conflicts happen?**
|
||||||
- ✅ Two branches modify the same lines in a file
|
- ✅ Two branches modify the same lines in a file
|
||||||
- ✅ One branch deletes a file that another branch modifies
|
- ✅ One branch deletes a file that another branch modifies
|
||||||
- ✅ Complex changes Git can't merge automatically
|
- ✅ Complex changes git can't merge automatically
|
||||||
- ❌ Different files are changed (no conflict!)
|
- ❌ Different files are changed (no conflict!)
|
||||||
- ❌ Different parts of the same file are changed (no conflict!)
|
- ❌ Different parts of the same file are changed (no conflict!)
|
||||||
|
|
||||||
**Don't fear conflicts!** They're a normal part of collaborative development. Git just needs your help to decide what the final code should look like.
|
**Don't fear conflicts!** They're a normal part of collaborative development. git just needs your help to decide what the final code should look like.
|
||||||
|
|
||||||
## Your Task
|
## Your Task
|
||||||
|
|
||||||
@@ -249,7 +249,7 @@ For this challenge, we want **both settings**, so:
|
|||||||
|
|
||||||
### Part 9: Mark the Conflict as Resolved
|
### Part 9: Mark the Conflict as Resolved
|
||||||
|
|
||||||
Tell Git you've resolved the conflict:
|
Tell git you've resolved the conflict:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Stage the resolved file
|
# Stage the resolved file
|
||||||
@@ -267,7 +267,7 @@ All conflicts fixed but you are still merging.
|
|||||||
(use "git commit" to conclude merge)
|
(use "git commit" to conclude merge)
|
||||||
```
|
```
|
||||||
|
|
||||||
Perfect! Git confirms the conflict is resolved.
|
Perfect! git confirms the conflict is resolved.
|
||||||
|
|
||||||
### Part 10: Complete the Merge
|
### Part 10: Complete the Merge
|
||||||
|
|
||||||
@@ -277,7 +277,7 @@ Commit the merge:
|
|||||||
git commit
|
git commit
|
||||||
```
|
```
|
||||||
|
|
||||||
Git will open an editor with a default merge message. You can accept it or customize it, then save and close.
|
git will open an editor with a default merge message. You can accept it or customize it, then save and close.
|
||||||
|
|
||||||
**Done!** Your merge is complete!
|
**Done!** Your merge is complete!
|
||||||
|
|
||||||
@@ -436,7 +436,7 @@ git diff main..feature-branch
|
|||||||
|
|
||||||
## Merge Tools
|
## Merge Tools
|
||||||
|
|
||||||
Git supports visual merge tools that make resolving conflicts easier:
|
git supports visual merge tools that make resolving conflicts easier:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Configure a merge tool (one-time setup)
|
# Configure a merge tool (one-time setup)
|
||||||
@@ -484,4 +484,4 @@ To start over:
|
|||||||
.\setup.ps1
|
.\setup.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
**Need help?** Review the steps above, or run `git status` to see what Git suggests!
|
**Need help?** Review the steps above, or run `git status` to see what git suggests!
|
||||||
|
|||||||
@@ -11,49 +11,26 @@
|
|||||||
- Ensuring valid JSON syntax
|
- Ensuring valid JSON syntax
|
||||||
#>
|
#>
|
||||||
|
|
||||||
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
$script:allChecksPassed = $true
|
$script:allChecksPassed = $true
|
||||||
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
|
|
||||||
# ============================================================================
|
Write-Host $PSScriptRoot
|
||||||
# Helper Functions
|
|
||||||
# ============================================================================
|
|
||||||
|
|
||||||
function Write-Pass {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[PASS] $Message" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Fail {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
|
||||||
$script:allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Hint {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
function Write-Info {
|
|
||||||
param([string]$Message)
|
|
||||||
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Check challenge directory exists
|
# Check challenge directory exists
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|
||||||
if (-not (Test-Path "challenge")) {
|
if (-not (Test-Path $challengeRoot)) {
|
||||||
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
Write-Error "Challenge directory not found." -ForegroundColor Red
|
||||||
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Push-Location "challenge"
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
|
Write-Error "Not a git repository." -ForegroundColor Red
|
||||||
if (-not (Test-Path ".git")) {
|
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
|
||||||
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,24 +40,7 @@ Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cya
|
|||||||
# Detect the main branch name (could be main, master, etc.)
|
# Detect the main branch name (could be main, master, etc.)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Try to get the default branch from remote origin first
|
# Try to get the default branch from remote origin first
|
||||||
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
$mainBranch = Get-MainBranch
|
||||||
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
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -97,22 +57,20 @@ if ($currentBranch -eq $mainBranch) {
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Check that merge is not in progress
|
# Check that merge is not in progress
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
if (Test-Path ".git/MERGE_HEAD") {
|
if (Test-Path "$challengeRoot\.git\MERGE_HEAD") {
|
||||||
Write-Fail "Merge is still in progress (conflicts not resolved)"
|
Write-Fail "Merge is still in progress (conflicts not resolved)"
|
||||||
Write-Hint "Resolve conflicts in config.json, then: git add config.json && git commit"
|
Write-Hint "Resolve conflicts in config.json, then: git add config.json && git commit"
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
} else {
|
} else {
|
||||||
Write-Pass "No merge in progress (conflicts resolved)"
|
Write-Pass "No merge in progress (conflicts resolved)"
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Check if config.json exists
|
# Check if config.json exists
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
if (-not (Test-Path "config.json")) {
|
if (-not (Test-Path "$challengeRoot\config.json")) {
|
||||||
Write-Fail "File 'config.json' not found"
|
Write-Fail "File 'config.json' not found"
|
||||||
Write-Hint "The config.json file should exist"
|
Write-Hint "The config.json file should exist"
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -120,14 +78,13 @@ if (-not (Test-Path "config.json")) {
|
|||||||
# Verify config.json is valid JSON
|
# Verify config.json is valid JSON
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
try {
|
try {
|
||||||
$configContent = Get-Content "config.json" -Raw
|
$configContent = Get-Content "$challengeRoot\config.json" -Raw
|
||||||
$config = $configContent | ConvertFrom-Json -ErrorAction Stop
|
$config = $configContent | ConvertFrom-Json -ErrorAction Stop
|
||||||
Write-Pass "File 'config.json' is valid JSON"
|
Write-Pass "File 'config.json' is valid JSON"
|
||||||
} catch {
|
} catch {
|
||||||
Write-Fail "File 'config.json' is not valid JSON"
|
Write-Fail "File 'config.json' is not valid JSON"
|
||||||
Write-Hint "Make sure you removed all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
Write-Hint "Make sure you removed all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||||
Write-Hint "Check for missing commas or brackets"
|
Write-Hint "Check for missing commas or brackets"
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -137,7 +94,6 @@ try {
|
|||||||
if ($configContent -match '<<<<<<<|=======|>>>>>>>') {
|
if ($configContent -match '<<<<<<<|=======|>>>>>>>') {
|
||||||
Write-Fail "Conflict markers still present in config.json"
|
Write-Fail "Conflict markers still present in config.json"
|
||||||
Write-Hint "Remove all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
Write-Hint "Remove all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||||
Pop-Location
|
|
||||||
exit 1
|
exit 1
|
||||||
} else {
|
} else {
|
||||||
Write-Pass "No conflict markers in config.json"
|
Write-Pass "No conflict markers in config.json"
|
||||||
@@ -163,8 +119,11 @@ if ($config.app.debug -eq $true) {
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Verify both branches were merged
|
# Verify both branches were merged
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
$addTimeoutMerged = git log --oneline --grep="add-timeout" 2>$null | Select-String "Merge"
|
git merge-base --is-ancestor add-timeout main
|
||||||
$addDebugMerged = git log --oneline --grep="add-debug" 2>$null | Select-String "Merge"
|
$addTimeoutMerged = $LASTEXITCODE -eq 0
|
||||||
|
|
||||||
|
git merge-base --is-ancestor add-debug main
|
||||||
|
$addDebugMerged = $LASTEXITCODE -eq 0
|
||||||
|
|
||||||
if ($addTimeoutMerged) {
|
if ($addTimeoutMerged) {
|
||||||
Write-Pass "add-timeout branch has been merged"
|
Write-Pass "add-timeout branch has been merged"
|
||||||
@@ -197,7 +156,6 @@ if ($totalCommits -ge 5) {
|
|||||||
Write-Hint "Make sure both branches are merged"
|
Write-Hint "Make sure both branches are merged"
|
||||||
}
|
}
|
||||||
|
|
||||||
Pop-Location
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Final summary
|
# Final summary
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
By the end of this module, you will:
|
By the end of this module, you will:
|
||||||
- Understand what cherry-picking is and how it works
|
- Understand what cherry-picking is and how it works
|
||||||
- Know when to use cherry-pick vs merge or rebase
|
- Know when to use cherry-pick vs merge
|
||||||
- Apply specific commits from one branch to another
|
- Apply specific commits from one branch to another
|
||||||
- Understand common use cases for cherry-picking
|
- Understand common use cases for cherry-picking
|
||||||
- Learn how cherry-pick creates new commits with different hashes
|
- Learn how cherry-pick creates new commits with different hashes
|
||||||
@@ -52,10 +52,13 @@ First, see what commits are on the development branch:
|
|||||||
```pwsh
|
```pwsh
|
||||||
cd challenge
|
cd challenge
|
||||||
|
|
||||||
# View all commits on development branch
|
# First let's see what files are in the current branch and notice that there is a file security.py
|
||||||
git log --oneline development
|
ls
|
||||||
|
|
||||||
# View the full commit graph
|
# View all commits on development branch
|
||||||
|
git log --oneline --graph
|
||||||
|
|
||||||
|
# View the full commit graph for all branches
|
||||||
git log --oneline --graph --all
|
git log --oneline --graph --all
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -87,6 +90,9 @@ git switch main
|
|||||||
|
|
||||||
# Verify you're on main
|
# Verify you're on main
|
||||||
git branch
|
git branch
|
||||||
|
|
||||||
|
# See what files exist. Notice that we no longer have a security.py file
|
||||||
|
ls
|
||||||
```
|
```
|
||||||
|
|
||||||
The `*` should be next to `main`.
|
The `*` should be next to `main`.
|
||||||
@@ -96,8 +102,6 @@ The `*` should be next to `main`.
|
|||||||
# See main's commits
|
# See main's commits
|
||||||
git log --oneline
|
git log --oneline
|
||||||
|
|
||||||
# See what files exist
|
|
||||||
ls
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Main should only have the initial app and README - no bug fixes yet, no experimental features.
|
Main should only have the initial app and README - no bug fixes yet, no experimental features.
|
||||||
@@ -113,6 +117,7 @@ Now copy the bug fix commits from development to main:
|
|||||||
2. Cherry-pick the security fix:
|
2. Cherry-pick the security fix:
|
||||||
```pwsh
|
```pwsh
|
||||||
git cherry-pick <security-fix-hash>
|
git cherry-pick <security-fix-hash>
|
||||||
|
ls # we now have the security.py file!
|
||||||
|
|
||||||
# Example if the hash is abc1234:
|
# Example if the hash is abc1234:
|
||||||
# git cherry-pick abc1234
|
# git cherry-pick abc1234
|
||||||
@@ -125,6 +130,7 @@ Now copy the bug fix commits from development to main:
|
|||||||
5. Cherry-pick the performance fix:
|
5. Cherry-pick the performance fix:
|
||||||
```pwsh
|
```pwsh
|
||||||
git cherry-pick <performance-fix-hash>
|
git cherry-pick <performance-fix-hash>
|
||||||
|
ls # we now have the cache.py file!
|
||||||
```
|
```
|
||||||
|
|
||||||
6. Verify both fixes are now on main:
|
6. Verify both fixes are now on main:
|
||||||
|
|||||||
@@ -10,6 +10,8 @@
|
|||||||
the bug fixes to the main branch.
|
the bug fixes to the main branch.
|
||||||
#>
|
#>
|
||||||
|
|
||||||
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
# Remove existing challenge directory if present
|
||||||
if (Test-Path "challenge") {
|
if (Test-Path "challenge") {
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
||||||
@@ -42,11 +44,7 @@ git add app.py
|
|||||||
git commit -m "Initial app implementation" | Out-Null
|
git commit -m "Initial app implementation" | Out-Null
|
||||||
|
|
||||||
# Detect the main branch name after first commit
|
# Detect the main branch name after first commit
|
||||||
$mainBranch = git branch --show-current
|
$mainBranch = Get-MainBranch
|
||||||
if (-not $mainBranch) {
|
|
||||||
$mainBranch = git config --get init.defaultBranch
|
|
||||||
if (-not $mainBranch) { $mainBranch = "main" }
|
|
||||||
}
|
|
||||||
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
$readme = @"
|
$readme = @"
|
||||||
|
|||||||
@@ -9,168 +9,136 @@
|
|||||||
to the main branch without merging the experimental features.
|
to the main branch without merging the experimental features.
|
||||||
#>
|
#>
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
# Check if challenge directory exists
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
if (-not (Test-Path $challengeRoot)) {
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check if git repository exists
|
# Check if git repository exists
|
||||||
if (-not (Test-Path ".git")) {
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
Write-Fail "No git repository found." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Detect the main branch name
|
$mainBranch = Get-MainBranch
|
||||||
$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
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check current branch
|
# Check current branch
|
||||||
$currentBranch = git branch --show-current 2>$null
|
$currentBranch = git branch --show-current 2>$null
|
||||||
if ($currentBranch -ne $mainBranch) {
|
if ($currentBranch -ne $mainBranch) {
|
||||||
Write-Host "[FAIL] You should be on the '$mainBranch' branch." -ForegroundColor Red
|
Write-Fail "You should be on the '$mainBranch' branch." -ForegroundColor Red
|
||||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
||||||
Write-Host "Hint: Use 'git checkout $mainBranch' to switch to $mainBranch branch" -ForegroundColor Yellow
|
Write-Hint "Use 'git checkout $mainBranch' to switch to $mainBranch branch" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check if there's an ongoing cherry-pick
|
# Check if there's an ongoing cherry-pick
|
||||||
if (Test-Path ".git/CHERRY_PICK_HEAD") {
|
if (Test-Path "$challengeRoot\.git\CHERRY_PICK_HEAD") {
|
||||||
Write-Host "[FAIL] Cherry-pick is not complete. There may be unresolved conflicts." -ForegroundColor Red
|
Write-Fail "Cherry-pick is not complete. There may be unresolved conflicts." -ForegroundColor Red
|
||||||
Write-Host "Hint: Resolve any conflicts, then use:" -ForegroundColor Yellow
|
Write-Hint "Resolve any conflicts, then use:" -ForegroundColor Yellow
|
||||||
Write-Host " git add <file>" -ForegroundColor White
|
Write-Host " git add <file>" -ForegroundColor White
|
||||||
Write-Host " git cherry-pick --continue" -ForegroundColor White
|
Write-Host " git cherry-pick --continue" -ForegroundColor White
|
||||||
Write-Host "Or abort with: git cherry-pick --abort" -ForegroundColor White
|
Write-Host "Or abort with: git cherry-pick --abort" -ForegroundColor White
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
|
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
|
||||||
$mainCommitCount = (git rev-list --count $mainBranch 2>$null)
|
$mainCommitCount = (git rev-list --count $mainBranch 2>$null)
|
||||||
if ($mainCommitCount -ne 4) {
|
if ($mainCommitCount -ne 4) {
|
||||||
Write-Host "[FAIL] Expected 4 commits on $mainBranch branch, found $mainCommitCount" -ForegroundColor Red
|
Write-Fail "Expected 4 commits on $mainBranch branch, found $mainCommitCount" -ForegroundColor Red
|
||||||
if ($mainCommitCount -lt 4) {
|
if ($mainCommitCount -lt 4) {
|
||||||
Write-Host "Hint: You should cherry-pick 2 bug fix commits to $mainBranch" -ForegroundColor Yellow
|
Write-Hint "You should cherry-pick 2 bug fix commits to $mainBranch" -ForegroundColor Yellow
|
||||||
} else {
|
} else {
|
||||||
Write-Host "Hint: You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
|
Write-Hint "You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
Write-Host "`nExpected commits on ${mainBranch}:" -ForegroundColor Yellow
|
Write-Host "`nExpected commits on ${mainBranch}:" -ForegroundColor Yellow
|
||||||
Write-Host " 1. Initial app implementation" -ForegroundColor White
|
Write-Host " 1. Initial app implementation" -ForegroundColor White
|
||||||
Write-Host " 2. Add README" -ForegroundColor White
|
Write-Host " 2. Add README" -ForegroundColor White
|
||||||
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
|
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
|
||||||
Write-Host " 4. Fix performance issue with data caching (cherry-picked)" -ForegroundColor White
|
Write-Host " 4. Fix performance issue with data caching (cherry-picked)" -ForegroundColor White
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
|
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
|
||||||
$mergeCommits = git log --merges --oneline $mainBranch 2>$null
|
$mergeCommits = git log --merges --oneline $mainBranch 2>$null
|
||||||
if ($mergeCommits) {
|
if ($mergeCommits) {
|
||||||
Write-Host "[FAIL] Found merge commits on $mainBranch. You should use cherry-pick, not merge." -ForegroundColor Red
|
Write-Fail "Found merge commits on $mainBranch. You should use cherry-pick, not merge." -ForegroundColor Red
|
||||||
Write-Host "Hint: Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
|
Write-Hint "Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that security.py exists (from the security fix commit)
|
# Check that security.py exists (from the security fix commit)
|
||||||
if (-not (Test-Path "security.py")) {
|
if (-not (Test-Path "$challengeRoot\security.py")) {
|
||||||
Write-Host "[FAIL] security.py not found on $mainBranch branch." -ForegroundColor Red
|
Write-Fail "security.py not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
|
Write-Hint "You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that security.py has the security fix
|
# Check that security.py has the security fix
|
||||||
$securityContent = Get-Content "security.py" -Raw
|
$securityContent = Get-Content "$challengeRoot\security.py" -Raw
|
||||||
|
|
||||||
if ($securityContent -notmatch "sanitize_input") {
|
if ($securityContent -notmatch "sanitize_input") {
|
||||||
Write-Host "[FAIL] security.py is missing the sanitize_input function." -ForegroundColor Red
|
Write-Fail "security.py is missing the sanitize_input function." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($securityContent -notmatch "validate_token") {
|
if ($securityContent -notmatch "validate_token") {
|
||||||
Write-Host "[FAIL] security.py is missing the validate_token function." -ForegroundColor Red
|
Write-Fail "security.py is missing the validate_token function." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that app.py exists
|
# Check that app.py exists
|
||||||
if (-not (Test-Path "app.py")) {
|
if (-not (Test-Path "$challengeRoot\app.py")) {
|
||||||
Write-Host "[FAIL] app.py not found." -ForegroundColor Red
|
Write-Fail "app.py not found." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that cache.py exists (from performance fix)
|
# Check that cache.py exists (from performance fix)
|
||||||
if (-not (Test-Path "cache.py")) {
|
if (-not (Test-Path "$challengeRoot\cache.py")) {
|
||||||
Write-Host "[FAIL] cache.py not found on $mainBranch branch." -ForegroundColor Red
|
Write-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
|
Write-Hint "You need to cherry-pick the 'Fix performance issue' commit" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that cache.py has the DataCache class
|
# Check that cache.py has the DataCache class
|
||||||
$cacheContent = Get-Content "cache.py" -Raw
|
$cacheContent = Get-Content "$challengeRoot\cache.py" -Raw
|
||||||
|
|
||||||
if ($cacheContent -notmatch "DataCache") {
|
if ($cacheContent -notmatch "DataCache") {
|
||||||
Write-Host "[FAIL] cache.py is missing the DataCache class." -ForegroundColor Red
|
Write-Fail "cache.py is missing the DataCache class." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($cacheContent -notmatch "def get\(") {
|
if ($cacheContent -notmatch "def get\(") {
|
||||||
Write-Host "[FAIL] cache.py is missing the get method." -ForegroundColor Red
|
Write-Fail "cache.py is missing the get method." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that app.py does NOT have experimental features
|
# Check that app.py does NOT have experimental features
|
||||||
$appContent = Get-Content "app.py" -Raw
|
$appContent = Get-Content "$challengeRoot\app.py" -Raw
|
||||||
|
|
||||||
# Should NOT have experimental features
|
# Should NOT have experimental features
|
||||||
if ($appContent -match "experimental_mode") {
|
if ($appContent -match "experimental_mode") {
|
||||||
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -ForegroundColor Red
|
Write-Fail "app.py contains experimental features (experimental_mode)." -ForegroundColor Red
|
||||||
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
Write-Hint "You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
||||||
Write-Host " The experimental feature commits should stay on development branch only" -ForegroundColor Yellow
|
Write-Host " The experimental feature commits should stay on development branch only" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($appContent -match "beta_features") {
|
if ($appContent -match "beta_features") {
|
||||||
Write-Host "[FAIL] app.py contains experimental features (beta_features)." -ForegroundColor Red
|
Write-Fail "app.py contains experimental features (beta_features)." -ForegroundColor Red
|
||||||
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
Write-Hint "You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($appContent -match "enable_experimental_features") {
|
if ($appContent -match "enable_experimental_features") {
|
||||||
Write-Host "[FAIL] app.py contains experimental features (enable_experimental_features)." -ForegroundColor Red
|
Write-Fail "app.py contains experimental features (enable_experimental_features)." -ForegroundColor Red
|
||||||
Write-Host "Hint: You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
Write-Hint "You should cherry-pick ONLY the bug fixes, not experimental features" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -191,25 +159,22 @@ foreach ($commit in $commitArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasSecurityFix) {
|
if (-not $hasSecurityFix) {
|
||||||
Write-Host "[FAIL] Security fix commit not found on $mainBranch branch." -ForegroundColor Red
|
Write-Fail "Security fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
|
Write-Hint "Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasPerformanceFix) {
|
if (-not $hasPerformanceFix) {
|
||||||
Write-Host "[FAIL] Performance fix commit not found on $mainBranch branch." -ForegroundColor Red
|
Write-Fail "Performance fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
|
Write-Hint "Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Verify development branch still has all commits
|
# Verify development branch still has all commits
|
||||||
$devCommitCount = (git rev-list --count development 2>$null)
|
$devCommitCount = (git rev-list --count development 2>$null)
|
||||||
if ($devCommitCount -ne 6) {
|
if ($devCommitCount -ne 6) {
|
||||||
Write-Host "[FAIL] Development branch should still have 6 commits." -ForegroundColor Red
|
Write-Fail "Development branch should still have 6 commits." -ForegroundColor Red
|
||||||
Write-Host "Found: $devCommitCount commits" -ForegroundColor Yellow
|
Write-Host "Found: $devCommitCount commits" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,5 +191,4 @@ Write-Host "`nPerfect use of cherry-pick!" -ForegroundColor Green
|
|||||||
Write-Host "You selectively applied critical fixes without merging unfinished features.`n" -ForegroundColor Green
|
Write-Host "You selectively applied critical fixes without merging unfinished features.`n" -ForegroundColor Green
|
||||||
Write-Host "Try 'git log --oneline --graph --all' to see both branches." -ForegroundColor Cyan
|
Write-Host "Try 'git log --oneline --graph --all' to see both branches." -ForegroundColor Cyan
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
exit 0
|
||||||
|
|||||||
@@ -52,6 +52,9 @@ You're working on a calculator application. A developer added a `divide` functio
|
|||||||
1. **Navigate to the challenge directory:**
|
1. **Navigate to the challenge directory:**
|
||||||
```pwsh
|
```pwsh
|
||||||
cd challenge
|
cd challenge
|
||||||
|
|
||||||
|
# and check the contents. We should notice that divide.py exists, this will be reverted
|
||||||
|
ls
|
||||||
```
|
```
|
||||||
|
|
||||||
2. **Check which branch you're on** (you should be on `regular-revert`):
|
2. **Check which branch you're on** (you should be on `regular-revert`):
|
||||||
|
|||||||
@@ -9,23 +9,23 @@
|
|||||||
- multi-revert: Multiple commits reverted
|
- multi-revert: Multiple commits reverted
|
||||||
#>
|
#>
|
||||||
|
|
||||||
|
. "$PSScriptRoot\..\..\util.ps1"
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
|
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
$allChecksPassed = $true
|
$allChecksPassed = $true
|
||||||
$originalDir = Get-Location
|
|
||||||
|
$challengeRoot = "$PSScriptRoot\challenge"
|
||||||
|
|
||||||
# Check if challenge directory exists
|
# Check if challenge directory exists
|
||||||
if (-not (Test-Path "challenge")) {
|
if (-not (Test-Path "$challengeRoot")) {
|
||||||
Write-Host "[FAIL] Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
Write-Fail "Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
# Check if git repository exists
|
||||||
if (-not (Test-Path ".git")) {
|
if (-not (Test-Path "$challengeRoot\.git")) {
|
||||||
Write-Host "[FAIL] Not a git repository. Run setup.ps1 first." -ForegroundColor Red
|
Write-Fail "Not a git repository. Run setup.ps1 first." -ForegroundColor Red
|
||||||
Set-Location $originalDir
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -37,50 +37,50 @@ Write-Host "`n=== Scenario 1: Regular Revert ===" -ForegroundColor Cyan
|
|||||||
git switch regular-revert 2>&1 | Out-Null
|
git switch regular-revert 2>&1 | Out-Null
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Host "[FAIL] regular-revert branch not found" -ForegroundColor Red
|
Write-Fail "regular-revert branch not found" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
} else {
|
} else {
|
||||||
# Check that a revert commit exists
|
# Check that a revert commit exists
|
||||||
$revertCommit = git log --oneline --grep="Revert" 2>$null
|
$revertCommit = git log --oneline --grep="Revert" 2>$null
|
||||||
if ($revertCommit) {
|
if ($revertCommit) {
|
||||||
Write-Host "[PASS] Revert commit found" -ForegroundColor Green
|
Write-Pass "Revert commit found" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] No revert commit found" -ForegroundColor Red
|
Write-Fail "No revert commit found" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Use: git revert <commit-hash>" -ForegroundColor Yellow
|
Write-Hint "Use: git revert <commit-hash>" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that divide.py is removed (was reverted)
|
# Check that divide.py is removed (was reverted)
|
||||||
if (-not (Test-Path "divide.py")) {
|
if (-not (Test-Path "$challengeRoot\divide.py")) {
|
||||||
Write-Host "[PASS] Broken divide.py successfully reverted (file removed)" -ForegroundColor Green
|
Write-Pass "Broken divide.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] divide.py still exists (should be reverted)" -ForegroundColor Red
|
Write-Fail "divide.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
Write-Host "[HINT] The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
|
Write-Hint "The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that calculator.py exists and has correct content
|
# Check that calculator.py exists and has correct content
|
||||||
if (Test-Path "calculator.py") {
|
if (Test-Path "$challengeRoot\calculator.py") {
|
||||||
$calcContent = Get-Content "calculator.py" -Raw
|
$calcContent = Get-Content "$challengeRoot\calculator.py" -Raw
|
||||||
|
|
||||||
# Check that modulo function still exists (should be preserved)
|
# Check that modulo function still exists (should be preserved)
|
||||||
if ($calcContent -match "def modulo") {
|
if ($calcContent -match "def modulo") {
|
||||||
Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green
|
Write-Pass "modulo function preserved (good commit after bad one)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] modulo function missing (should still exist)" -ForegroundColor Red
|
Write-Fail "modulo function missing (should still exist)" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Only revert the bad commit, not the good ones after it" -ForegroundColor Yellow
|
Write-Hint "Only revert the bad commit, not the good ones after it" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that multiply function exists (should be preserved)
|
# Check that multiply function exists (should be preserved)
|
||||||
if ($calcContent -match "def multiply") {
|
if ($calcContent -match "def multiply") {
|
||||||
Write-Host "[PASS] multiply function preserved (good commit before bad one)" -ForegroundColor Green
|
Write-Pass "multiply function preserved (good commit before bad one)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] multiply function missing" -ForegroundColor Red
|
Write-Fail "multiply function missing" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] calculator.py not found" -ForegroundColor Red
|
Write-Fail "calculator.py not found" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -93,7 +93,7 @@ Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
|
|||||||
git switch multi-revert 2>&1 | Out-Null
|
git switch multi-revert 2>&1 | Out-Null
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
if ($LASTEXITCODE -ne 0) {
|
||||||
Write-Host "[FAIL] multi-revert branch not found" -ForegroundColor Red
|
Write-Fail "multi-revert branch not found" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
} else {
|
} else {
|
||||||
# Count revert commits
|
# Count revert commits
|
||||||
@@ -101,56 +101,54 @@ if ($LASTEXITCODE -ne 0) {
|
|||||||
$revertCount = ($revertCommits | Measure-Object).Count
|
$revertCount = ($revertCommits | Measure-Object).Count
|
||||||
|
|
||||||
if ($revertCount -ge 2) {
|
if ($revertCount -ge 2) {
|
||||||
Write-Host "[PASS] Found $revertCount revert commits (expected at least 2)" -ForegroundColor Green
|
Write-Pass "Found $revertCount revert commits (expected at least 2)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Found only $revertCount revert commit(s), need at least 2" -ForegroundColor Red
|
Write-Fail "Found only $revertCount revert commit(s), need at least 2" -ForegroundColor Red
|
||||||
Write-Host "[HINT] Revert both bad commits: git revert <commit1> <commit2>" -ForegroundColor Yellow
|
Write-Hint "Revert both bad commits: git revert <commit1> <commit2>" -ForegroundColor Yellow
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that sqrt.py is removed (reverted)
|
# Check that sqrt.py is removed (reverted)
|
||||||
if (-not (Test-Path "sqrt.py")) {
|
if (-not (Test-Path "$challengeRoot\sqrt.py")) {
|
||||||
Write-Host "[PASS] Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
|
Write-Pass "Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] sqrt.py still exists (should be reverted)" -ForegroundColor Red
|
Write-Fail "sqrt.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that logarithm.py is removed (reverted)
|
# Check that logarithm.py is removed (reverted)
|
||||||
if (-not (Test-Path "logarithm.py")) {
|
if (-not (Test-Path "$challengeRoot\logarithm.py")) {
|
||||||
Write-Host "[PASS] Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
|
Write-Pass "Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] logarithm.py still exists (should be reverted)" -ForegroundColor Red
|
Write-Fail "logarithm.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check calculator.py content
|
# Check calculator.py content
|
||||||
if (Test-Path "calculator.py") {
|
if (Test-Path "$challengeRoot\calculator.py") {
|
||||||
$calcContent = Get-Content "calculator.py" -Raw
|
$calcContent = Get-Content "$challengeRoot\calculator.py" -Raw
|
||||||
|
|
||||||
# Check that power function still exists (good commit before bad ones)
|
# Check that power function still exists (good commit before bad ones)
|
||||||
if ($calcContent -match "def power") {
|
if ($calcContent -match "def power") {
|
||||||
Write-Host "[PASS] power function preserved" -ForegroundColor Green
|
Write-Pass "power function preserved" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] power function missing (should still exist)" -ForegroundColor Red
|
Write-Fail "power function missing (should still exist)" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that absolute function still exists (good commit after bad ones)
|
# Check that absolute function still exists (good commit after bad ones)
|
||||||
if ($calcContent -match "def absolute") {
|
if ($calcContent -match "def absolute") {
|
||||||
Write-Host "[PASS] absolute function preserved" -ForegroundColor Green
|
Write-Pass "absolute function preserved" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] absolute function missing (should still exist)" -ForegroundColor Red
|
Write-Fail "absolute function missing (should still exist)" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] calculator.py not found" -ForegroundColor Red
|
Write-Fail "calculator.py not found" -ForegroundColor Red
|
||||||
$allChecksPassed = $false
|
$allChecksPassed = $false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set-Location $originalDir
|
|
||||||
|
|
||||||
# Final summary
|
# Final summary
|
||||||
Write-Host ""
|
Write-Host ""
|
||||||
if ($allChecksPassed) {
|
if ($allChecksPassed) {
|
||||||
|
|||||||
@@ -95,13 +95,14 @@ Suddenly, your teammate reports a **critical security bug** in production! You n
|
|||||||
|
|
||||||
2. **Check your current status:**
|
2. **Check your current status:**
|
||||||
```pwsh
|
```pwsh
|
||||||
git status
|
git status # See which files are changed
|
||||||
|
git diff # See all the changes in a diffview
|
||||||
```
|
```
|
||||||
You should see modified `login.py` (uncommitted changes)
|
You should see modified `login.py` (uncommitted changes)
|
||||||
|
|
||||||
3. **Stash your work with a message:**
|
3. **Stash your work with a message:**
|
||||||
```pwsh
|
```pwsh
|
||||||
git stash save "WIP: login feature"
|
git stash push -m "WIP: login feature"
|
||||||
```
|
```
|
||||||
|
|
||||||
4. **Verify working directory is clean:**
|
4. **Verify working directory is clean:**
|
||||||
@@ -116,15 +117,12 @@ Suddenly, your teammate reports a **critical security bug** in production! You n
|
|||||||
```
|
```
|
||||||
|
|
||||||
6. **Open app.py and find the bug:**
|
6. **Open app.py and find the bug:**
|
||||||
```pwsh
|
|
||||||
cat app.py
|
|
||||||
```
|
|
||||||
Look for the comment "# BUG: This allows unauthenticated access!"
|
Look for the comment "# BUG: This allows unauthenticated access!"
|
||||||
|
|
||||||
7. **Fix the bug** by editing app.py:
|
7. **Fix the bug** by editing app.py:
|
||||||
- Remove the buggy comment line
|
- Remove the buggy comment line
|
||||||
- You can leave the implementation as-is or improve it
|
- You can leave the implementation as-is or improve it
|
||||||
- The important thing is removing the comment that says "allows unauthenticated access"
|
- The important thing is removing the comment that says "BUG: This allows unauthenticated access"
|
||||||
|
|
||||||
8. **Commit the fix:**
|
8. **Commit the fix:**
|
||||||
```pwsh
|
```pwsh
|
||||||
@@ -143,10 +141,8 @@ Suddenly, your teammate reports a **critical security bug** in production! You n
|
|||||||
```
|
```
|
||||||
This applies the stash and removes it from the stash stack
|
This applies the stash and removes it from the stash stack
|
||||||
|
|
||||||
11. **Complete the TODOs in login.py:**
|
11. **Remove all the TODOs in login.py:**
|
||||||
- Open login.py in your editor
|
- Open login.py in your editor
|
||||||
- Complete the login method (verify password and return session)
|
|
||||||
- Add a logout method
|
|
||||||
- Remove all TODO comments
|
- Remove all TODO comments
|
||||||
|
|
||||||
12. **Commit your completed feature:**
|
12. **Commit your completed feature:**
|
||||||
@@ -166,7 +162,7 @@ Suddenly, your teammate reports a **critical security bug** in production! You n
|
|||||||
|
|
||||||
```pwsh
|
```pwsh
|
||||||
# Stash current changes with a message
|
# Stash current changes with a message
|
||||||
git stash save "description"
|
git stash push -m "description"
|
||||||
|
|
||||||
# Stash without a message (not recommended)
|
# Stash without a message (not recommended)
|
||||||
git stash
|
git stash
|
||||||
@@ -194,10 +190,7 @@ git stash pop
|
|||||||
git stash apply
|
git stash apply
|
||||||
|
|
||||||
# Apply a specific stash
|
# Apply a specific stash
|
||||||
git stash apply stash@{1}
|
git stash apply "stash@{1}"
|
||||||
|
|
||||||
# Apply a specific stash by number
|
|
||||||
git stash apply 1
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Managing Stashes
|
### Managing Stashes
|
||||||
@@ -207,7 +200,7 @@ git stash apply 1
|
|||||||
git stash drop
|
git stash drop
|
||||||
|
|
||||||
# Drop a specific stash
|
# Drop a specific stash
|
||||||
git stash drop stash@{1}
|
git stash drop "stash@{1}"
|
||||||
|
|
||||||
# Clear all stashes
|
# Clear all stashes
|
||||||
git stash clear
|
git stash clear
|
||||||
@@ -273,7 +266,7 @@ The verification will check that:
|
|||||||
**Solution:**
|
**Solution:**
|
||||||
```pwsh
|
```pwsh
|
||||||
# Stash your changes first
|
# Stash your changes first
|
||||||
git stash save "work in progress"
|
git stash push -m "work in progress"
|
||||||
|
|
||||||
# Now you can switch
|
# Now you can switch
|
||||||
git switch other-branch
|
git switch other-branch
|
||||||
@@ -293,10 +286,10 @@ git stash pop
|
|||||||
git stash list
|
git stash list
|
||||||
|
|
||||||
# Show summary of what changed
|
# Show summary of what changed
|
||||||
git stash show stash@{0}
|
git stash show "stash@{0}"
|
||||||
|
|
||||||
# Show full diff
|
# Show full diff
|
||||||
git stash show -p stash@{0}
|
git stash show -p "stash@{0}"
|
||||||
```
|
```
|
||||||
|
|
||||||
### "Stash conflicts when I apply"
|
### "Stash conflicts when I apply"
|
||||||
@@ -321,7 +314,7 @@ git stash show -p stash@{0}
|
|||||||
|
|
||||||
## Tips for Success
|
## Tips for Success
|
||||||
|
|
||||||
💡 **Always add a message** - `git stash save "your message"` helps you remember what you stashed
|
💡 **Always add a message** - `git stash push -m "your message"` helps you remember what you stashed
|
||||||
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
|
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
|
||||||
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
|
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
|
||||||
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
|
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
|
||||||
@@ -333,7 +326,7 @@ git stash show -p stash@{0}
|
|||||||
### Quick Branch Switch
|
### Quick Branch Switch
|
||||||
```pwsh
|
```pwsh
|
||||||
# You're working on feature-A
|
# You're working on feature-A
|
||||||
git stash save "feature A progress"
|
git stash push -m "feature A progress"
|
||||||
git switch hotfix-branch
|
git switch hotfix-branch
|
||||||
# Fix the issue, commit
|
# Fix the issue, commit
|
||||||
git switch feature-A
|
git switch feature-A
|
||||||
@@ -343,7 +336,7 @@ git stash pop
|
|||||||
### Pull with Local Changes
|
### Pull with Local Changes
|
||||||
```pwsh
|
```pwsh
|
||||||
# You have uncommitted changes
|
# You have uncommitted changes
|
||||||
git stash save "local changes"
|
git stash push -m "local changes"
|
||||||
git pull
|
git pull
|
||||||
git stash pop
|
git stash pop
|
||||||
# Resolve conflicts if any
|
# Resolve conflicts if any
|
||||||
@@ -352,7 +345,7 @@ git stash pop
|
|||||||
### Test Clean State
|
### Test Clean State
|
||||||
```pwsh
|
```pwsh
|
||||||
# Stash changes to test on clean code
|
# Stash changes to test on clean code
|
||||||
git stash save "testing clean state"
|
git stash push -m "testing clean state"
|
||||||
# Run tests
|
# Run tests
|
||||||
git stash pop # Restore your changes
|
git stash pop # Restore your changes
|
||||||
```
|
```
|
||||||
|
|||||||
@@ -9,48 +9,28 @@
|
|||||||
and completed the feature on the feature branch.
|
and completed the feature on the feature branch.
|
||||||
#>
|
#>
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
$root = $PSScriptRoot
|
||||||
|
|
||||||
# Check if challenge directory exists
|
if (-not (Test-Path "$root/challenge")) {
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
Write-Error "Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check if git repository exists
|
# Check if git repository exists
|
||||||
if (-not (Test-Path ".git")) {
|
if (-not (Test-Path "$root/challenge/.git")) {
|
||||||
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
Write-Fail "No git repository found." -ForegroundColor Red
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Detect the main branch name
|
$mainBranch = Get-MainBranch
|
||||||
$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
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check current branch
|
# Check current branch
|
||||||
$currentBranch = git branch --show-current 2>$null
|
$currentBranch = git branch --show-current 2>$null
|
||||||
if ($currentBranch -ne "feature-login") {
|
if ($currentBranch -ne "feature-login") {
|
||||||
Write-Host "[FAIL] You should be on the 'feature-login' branch." -ForegroundColor Red
|
Write-Fail "You should be on the 'feature-login' branch." -ForegroundColor Red
|
||||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
||||||
Write-Host "Hint: Switch back to feature-login after completing the challenge" -ForegroundColor Yellow
|
Write-Host "Hint: Switch back to feature-login after completing the challenge" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
@@ -60,8 +40,8 @@ if ($currentBranch -ne "feature-login") {
|
|||||||
# Check for uncommitted changes on feature-login
|
# Check for uncommitted changes on feature-login
|
||||||
$status = git status --porcelain 2>$null
|
$status = git status --porcelain 2>$null
|
||||||
if ($status) {
|
if ($status) {
|
||||||
Write-Host "[FAIL] You have uncommitted changes on feature-login." -ForegroundColor Red
|
Write-Fail "You have uncommitted changes on feature-login." -ForegroundColor Red
|
||||||
Write-Host "Hint: After restoring from stash, you should complete and commit the feature" -ForegroundColor Yellow
|
Write-Hint "After restoring from stash, you should complete and commit the feature" -ForegroundColor Yellow
|
||||||
git status --short
|
git status --short
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -69,7 +49,7 @@ if ($status) {
|
|||||||
|
|
||||||
# Verify main branch has the security fix
|
# Verify main branch has the security fix
|
||||||
Write-Host "`nChecking $mainBranch branch for bug fix..." -ForegroundColor Cyan
|
Write-Host "`nChecking $mainBranch branch for bug fix..." -ForegroundColor Cyan
|
||||||
git checkout $mainBranch 2>$null | Out-Null
|
git switch $mainBranch 2>$null | Out-Null
|
||||||
|
|
||||||
# Check for bug fix commit
|
# Check for bug fix commit
|
||||||
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||||
@@ -82,17 +62,17 @@ foreach ($commit in $mainCommits) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasSecurityFix) {
|
if (-not $hasSecurityFix) {
|
||||||
Write-Host "[FAIL] No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
|
Write-Fail "No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: After stashing, switch to $mainBranch and commit a bug fix" -ForegroundColor Yellow
|
Write-Host "Hint: After stashing, switch to $mainBranch and commit a bug fix" -ForegroundColor Yellow
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git switch feature-login 2>$null | Out-Null
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that app.py has been fixed
|
# Check that app.py has been fixed
|
||||||
if (-not (Test-Path "app.py")) {
|
if (-not (Test-Path "app.py")) {
|
||||||
Write-Host "[FAIL] app.py not found on $mainBranch branch." -ForegroundColor Red
|
Write-Fail "app.py not found on $mainBranch branch." -ForegroundColor Red
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git switch feature-login 2>$null | Out-Null
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -102,9 +82,9 @@ $appContent = Get-Content "app.py" -Raw
|
|||||||
# The bug was "return true" in authenticate - it should be fixed now
|
# The bug was "return true" in authenticate - it should be fixed now
|
||||||
# We'll check that the buggy comment is gone or the implementation is improved
|
# We'll check that the buggy comment is gone or the implementation is improved
|
||||||
if ($appContent -match "allows unauthenticated access") {
|
if ($appContent -match "allows unauthenticated access") {
|
||||||
Write-Host "[FAIL] The security bug comment still exists in app.py." -ForegroundColor Red
|
Write-Fail "The security bug comment still exists in app.py." -ForegroundColor Red
|
||||||
Write-Host "Hint: Remove the bug from app.py and commit the fix" -ForegroundColor Yellow
|
Write-Host "Hint: Remove the bug from app.py and commit the fix" -ForegroundColor Yellow
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git switch feature-login 2>$null | Out-Null
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -113,14 +93,14 @@ Write-Host "[PASS] Security bug fixed on main!" -ForegroundColor Green
|
|||||||
|
|
||||||
# Switch back to feature-login
|
# Switch back to feature-login
|
||||||
Write-Host "`nChecking feature-login branch..." -ForegroundColor Cyan
|
Write-Host "`nChecking feature-login branch..." -ForegroundColor Cyan
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git switch feature-login 2>$null | Out-Null
|
||||||
|
|
||||||
# Check for completed feature commit
|
# Check for completed feature commit
|
||||||
$featureCommits = git log --pretty=format:"%s" feature-login 2>$null
|
$featureCommits = git log --pretty=format:"%s" feature-login 2>$null
|
||||||
$commitCount = ($featureCommits -split "`n").Count
|
$commitCount = ($featureCommits -split "`n").Count
|
||||||
|
|
||||||
if ($commitCount -lt 3) {
|
if ($commitCount -lt 3) {
|
||||||
Write-Host "[FAIL] Expected at least 3 commits on feature-login." -ForegroundColor Red
|
Write-Fail "Expected at least 3 commits on feature-login." -ForegroundColor Red
|
||||||
Write-Host "Hint: You should have the initial commits plus your completed feature commit" -ForegroundColor Yellow
|
Write-Host "Hint: You should have the initial commits plus your completed feature commit" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -128,7 +108,7 @@ if ($commitCount -lt 3) {
|
|||||||
|
|
||||||
# Check that login.py exists
|
# Check that login.py exists
|
||||||
if (-not (Test-Path "login.py")) {
|
if (-not (Test-Path "login.py")) {
|
||||||
Write-Host "[FAIL] login.py not found on feature-login branch." -ForegroundColor Red
|
Write-Fail "login.py not found on feature-login branch." -ForegroundColor Red
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -137,22 +117,22 @@ $loginContent = Get-Content "login.py" -Raw
|
|||||||
|
|
||||||
# Check that login method exists and is implemented
|
# Check that login method exists and is implemented
|
||||||
if ($loginContent -notmatch "def login") {
|
if ($loginContent -notmatch "def login") {
|
||||||
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
|
Write-Fail "login.py should have a login method." -ForegroundColor Red
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that TODOs are completed (no TODO comments should remain)
|
# Check that TODOs are completed (no TODO comments should remain)
|
||||||
if ($loginContent -match "TODO") {
|
if ($loginContent -match "TODO") {
|
||||||
Write-Host "[FAIL] login.py still contains TODO comments." -ForegroundColor Red
|
Write-Fail "login.py still contains TODO comments." -ForegroundColor Red
|
||||||
Write-Host "Hint: Complete all the TODOs in login.py before committing" -ForegroundColor Yellow
|
Write-Hint "Complete all the TODOs in login.py before committing" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that password verification is implemented
|
# Check that password verification is implemented
|
||||||
if ($loginContent -notmatch "password") {
|
if ($loginContent -notmatch "password") {
|
||||||
Write-Host "[FAIL] login method should verify the password." -ForegroundColor Red
|
Write-Fail "login method should verify the password." -ForegroundColor Red
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -160,7 +140,7 @@ if ($loginContent -notmatch "password") {
|
|||||||
# Check that the feature has been committed (not just in working directory)
|
# Check that the feature has been committed (not just in working directory)
|
||||||
$lastCommit = git log -1 --pretty=format:"%s" 2>$null
|
$lastCommit = git log -1 --pretty=format:"%s" 2>$null
|
||||||
if ($lastCommit -notmatch "login|feature|complete|implement") {
|
if ($lastCommit -notmatch "login|feature|complete|implement") {
|
||||||
Write-Host "[FAIL] Your completed feature should be committed." -ForegroundColor Red
|
Write-Fail "Your completed feature should be committed." -ForegroundColor Red
|
||||||
Write-Host "Last commit: $lastCommit" -ForegroundColor Yellow
|
Write-Host "Last commit: $lastCommit" -ForegroundColor Yellow
|
||||||
Write-Host "Hint: After popping the stash and completing the TODOs, commit the feature" -ForegroundColor Yellow
|
Write-Host "Hint: After popping the stash and completing the TODOs, commit the feature" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ Participants need:
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Pre-Workshop Setup
|
## Setup
|
||||||
|
|
||||||
### Step 1: Add User Accounts
|
### Step 1: Add User Accounts
|
||||||
|
|
||||||
@@ -131,15 +131,6 @@ git add numbers.txt
|
|||||||
git commit -m "feat: add shuffled numbers for challenge"
|
git commit -m "feat: add shuffled numbers for challenge"
|
||||||
git push
|
git push
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Verify Student Access
|
|
||||||
|
|
||||||
Students added to the project automatically have access. Verify:
|
|
||||||
|
|
||||||
1. Go to **Project Settings** → **Repositories** → **number-challenge**
|
|
||||||
2. Click **Security** tab
|
|
||||||
3. Verify project team has **Contribute** permission
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## During the Workshop
|
## During the Workshop
|
||||||
@@ -153,12 +144,12 @@ Students added to the project automatically have access. Verify:
|
|||||||
### The Exercise Flow
|
### The Exercise Flow
|
||||||
|
|
||||||
1. **Students pull** the latest changes
|
1. **Students pull** the latest changes
|
||||||
2. **One person** moves a number to its correct position
|
2. **One person** creates a branch, moves a number to its correct position
|
||||||
3. **They commit and push**
|
3. **They commit and push**
|
||||||
4. **Others pull** and see the change
|
4. **Others pull** and see the change
|
||||||
5. **Repeat** until sorted
|
5. **Repeat** until sorted
|
||||||
|
|
||||||
### Creating Conflicts (The Learning Moment)
|
### Same branch conflicts
|
||||||
|
|
||||||
Conflicts happen naturally when multiple people edit at once. You can encourage this:
|
Conflicts happen naturally when multiple people edit at once. You can encourage this:
|
||||||
|
|
||||||
@@ -166,13 +157,6 @@ Conflicts happen naturally when multiple people edit at once. You can encourage
|
|||||||
- Watch them experience the push rejection
|
- Watch them experience the push rejection
|
||||||
- Guide them through pulling and resolving the conflict
|
- Guide them through pulling and resolving the conflict
|
||||||
|
|
||||||
### Monitoring Progress
|
|
||||||
|
|
||||||
Check progress in Azure DevOps:
|
|
||||||
|
|
||||||
- **Repos → Commits**: See who's contributing
|
|
||||||
- **Repos → Files → numbers.txt**: See current state
|
|
||||||
|
|
||||||
### Common Issues
|
### Common Issues
|
||||||
|
|
||||||
**"I can't push!"**
|
**"I can't push!"**
|
||||||
@@ -235,5 +219,3 @@ To reuse the repository:
|
|||||||
- **Keep groups small** (2 people per repository) for more interaction
|
- **Keep groups small** (2 people per repository) for more interaction
|
||||||
- **Encourage communication** - the exercise works best when people talk
|
- **Encourage communication** - the exercise works best when people talk
|
||||||
- **Let conflicts happen** - they're the best learning opportunity
|
- **Let conflicts happen** - they're the best learning opportunity
|
||||||
- **Walk the room** - help students who get stuck
|
|
||||||
- **Point students to 03_TASKS.md** - Simple explanations of clone, push, pull, and fetch for beginners
|
|
||||||
|
|||||||
@@ -20,15 +20,15 @@ SSH (Secure Shell) keys provide a secure way to authenticate with Azure DevOps w
|
|||||||
Before starting, ensure you have:
|
Before starting, ensure you have:
|
||||||
|
|
||||||
- **Git 2.23 or higher** installed
|
- **Git 2.23 or higher** installed
|
||||||
```powershell
|
```pwsh
|
||||||
git --version
|
git --version
|
||||||
```
|
```
|
||||||
|
|
||||||
- **Azure DevOps account** with access to your organization/project
|
- **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)
|
- If you don't ask your organisation for an invitation
|
||||||
|
|
||||||
- **PowerShell 7+ or Bash terminal** for running commands
|
- **PowerShell 7+ or Bash terminal** for running commands
|
||||||
```powershell
|
```pwsh
|
||||||
pwsh --version
|
pwsh --version
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -42,18 +42,18 @@ SSH authentication uses a key pair: a private key (stays on your computer) and a
|
|||||||
|
|
||||||
Open your terminal and run:
|
Open your terminal and run:
|
||||||
|
|
||||||
```powershell
|
```pwsh
|
||||||
ssh-keygen -t rsa
|
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.
|
**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.
|
||||||
|
|
||||||
### Save Location
|
### Save Location
|
||||||
|
|
||||||
When prompted for the file location, press `Enter` to accept the default:
|
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):
|
Enter file in which to save the key (C:\Users\YourName\.ssh\id_rsa):
|
||||||
```
|
```
|
||||||
|
|
||||||
**Default locations:**
|
**Default locations:**
|
||||||
@@ -61,7 +61,7 @@ Enter file in which to save the key (/Users/yourname/.ssh/id_rsa):
|
|||||||
|
|
||||||
### Passphrase (Optional but Recommended)
|
### Passphrase (Optional but Recommended)
|
||||||
|
|
||||||
You'll be prompted to enter a passphrase, just press `Enter` no password is needed:
|
You'll be prompted to enter a passphrase, just press `Enter` no password is needed (recommended but not needed):
|
||||||
|
|
||||||
```
|
```
|
||||||
Enter passphrase (empty for no passphrase):
|
Enter passphrase (empty for no passphrase):
|
||||||
@@ -72,9 +72,8 @@ Enter same passphrase again:
|
|||||||
|
|
||||||
Check that your keys were created:
|
Check that your keys were created:
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
**Windows PowerShell:**
|
**Windows PowerShell:**
|
||||||
```powershell
|
```pwsh
|
||||||
dir $HOME\.ssh\
|
dir $HOME\.ssh\
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -109,27 +108,17 @@ Now you'll upload your public key to Azure DevOps.
|
|||||||
|
|
||||||
Open your terminal and display your public key:
|
Open your terminal and display your public key:
|
||||||
|
|
||||||
**Linux/Mac:**
|
|
||||||
```bash
|
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
**Windows PowerShell:**
|
**Windows PowerShell:**
|
||||||
```powershell
|
```pwsh
|
||||||
type $HOME\.ssh\id_rsa.pub
|
type $HOME\.ssh\id_rsa.pub
|
||||||
```
|
```
|
||||||
|
|
||||||
**Windows Command Prompt:**
|
|
||||||
```cmd
|
|
||||||
type %USERPROFILE%\.ssh\id_rsa.pub
|
|
||||||
```
|
|
||||||
|
|
||||||
The output will look like this:
|
The output will look like this:
|
||||||
```
|
```
|
||||||
ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABgQC2YbXnrSK5TTflZSwUv9KUedvI4p3JJ4dHgwp/SeJGqMNWnOMDbzQQzYT7E39w9Q8ItrdWsK4vRLGY2B1rQ+BpS6nn4KhTanMXLTaUFDlg6I1Yn5S3cTTe8dMAoa14j3CZfoSoRRgK8E+ktNb0o0nBMuZJlLkgEtPIz28fwU1vcHoSK7jFp5KL0pjf37RYZeHkbpI7hdCG2qHtdrC35gzdirYPJOekErF5VFRrLZaIRSSsX0V4XzwY2k1hxM037o/h6qcTLWfi5ugbyrdscL8BmhdGNH4Giwqd1k3MwSyiswRuAuclYv27oKnFVBRT+n649px4g3Vqa8dh014wM2HDjMGENIkHx0hcV9BWdfBfTSCJengmosGW+wQfmaNUo4WpAbwZD73ALNsoLg5Yl1tB6ZZ5mHwLRY3LG2BbQZMZRCELUyvbh8ZsRksNN/2zcS44RIQdObV8/4hcLse30+NQ7GRaMnJeAMRz4Rpzbb02y3w0wNQFp/evj1nN4WTz6l8= your@email.com
|
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).
|
**Copy the entire output** (from `ssh-rsa` to and including your email address).
|
||||||
|
|
||||||
|
|
||||||
### Paste and Name Your Key
|
### Paste and Name Your Key
|
||||||
@@ -153,49 +142,30 @@ Now that SSH is configured, you can use it for all Git operations.
|
|||||||
|
|
||||||
To clone a repository using SSH:
|
To clone a repository using SSH:
|
||||||
|
|
||||||
```bash
|
```pwsh
|
||||||
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Example** (replace placeholders with your actual values):
|
**Example** (replace placeholders with your actual values):
|
||||||
```bash
|
```pwsh
|
||||||
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
|
git clone git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project
|
||||||
```
|
```
|
||||||
|
|
||||||
**How to find your SSH URL:**
|
**How to find your SSH URL:**
|
||||||
1. Navigate to your repository in Azure DevOps
|
1. Navigate to your repository in Azure DevOps
|
||||||
|

|
||||||
2. Click **Clone** in the top-right
|
2. Click **Clone** in the top-right
|
||||||
3. Select **SSH** from the dropdown
|
3. Select **SSH** from the dropdown
|
||||||
4. Copy the SSH URL
|
4. Copy the SSH URL
|
||||||
|
|
||||||

|

|
||||||
*Select SSH from the clone dialog to get your repository's 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
|
### Daily Git Operations
|
||||||
|
|
||||||
All standard Git commands now work seamlessly with SSH:
|
All standard Git commands now work seamlessly with SSH:
|
||||||
|
|
||||||
```bash
|
```pwsh
|
||||||
# Pull latest changes
|
# Pull latest changes
|
||||||
git pull
|
git pull
|
||||||
|
|
||||||
@@ -214,9 +184,7 @@ git push -u origin feature-branch
|
|||||||
---
|
---
|
||||||
|
|
||||||
## Additional Resources
|
## 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)
|
- **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)
|
- **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)
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -225,25 +193,16 @@ git push -u origin feature-branch
|
|||||||
|
|
||||||
### Common Commands
|
### Common Commands
|
||||||
|
|
||||||
```bash
|
```pwsh
|
||||||
# Generate RSA key
|
# Generate RSA key
|
||||||
ssh-keygen -t
|
ssh-keygen -t
|
||||||
|
|
||||||
# Display public key (Linux/Mac)
|
# Display public key
|
||||||
cat ~/.ssh/id_rsa.pub
|
|
||||||
|
|
||||||
# Display public key (Windows)
|
|
||||||
type $HOME\.ssh\id_rsa.pub
|
type $HOME\.ssh\id_rsa.pub
|
||||||
|
|
||||||
# Test SSH connection
|
# Clone with SSH. You can find this url on Azure DevOps
|
||||||
ssh -T git@ssh.dev.azure.com
|
|
||||||
|
|
||||||
# Clone with SSH
|
|
||||||
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
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
|
# Check remote URL
|
||||||
git remote -v
|
git remote -v
|
||||||
```
|
```
|
||||||
@@ -256,7 +215,7 @@ git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
|||||||
|
|
||||||
**Example:**
|
**Example:**
|
||||||
```
|
```
|
||||||
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
|
git@ssh.dev.azure.com:v3/novenco/software/git-workshop
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -12,7 +12,6 @@ Learn to collaborate on a shared repository using:
|
|||||||
|
|
||||||
```
|
```
|
||||||
1. Create branch → 2. Make changes → 3. Push branch
|
1. Create branch → 2. Make changes → 3. Push branch
|
||||||
↓
|
|
||||||
6. Delete branch ← 5. Merge PR ← 4. Create PR
|
6. Delete branch ← 5. Merge PR ← 4. Create PR
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -110,9 +109,15 @@ git pull
|
|||||||
|
|
||||||
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.
|
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>`
|
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.
|
1. Person A pushes their branch `git push feature-1`
|
||||||
|
2. Person B pushes their branch `git push feature-2`
|
||||||
|
3. Now merge `feature-1` branch first, going throught the Pull Request flow (see Step 5).
|
||||||
4. Then merge `feature-2` branch second, and notice you'll get a MERGE CONFLICT.
|
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
|
5. The owner of `feature-2` (Person B) is now tasked with resolving the conflict.
|
||||||
|
|
||||||
|
In order to solve merge conflicts through a Shared Server (Azure DevOps) you have to do merges in <q>reverse</q>, meaning: Instead of, like in module <q>03-branching-and-merging</q>, merging the feature-branch into the `main` branch, we merge the `main` branch into the feature branch.
|
||||||
|
|
||||||
|
Doing so ensures that the `main` branch maintains the integrity it's supposed to have and the conflicts are solved on the feature-branch side before being merged into the `main` branch. The idea here being that, we ONLY modify the main branch through merges facilitated (and reviewed) by Pull Requests. We want the `main` branch to be as stable as possible.
|
||||||
```pwsh
|
```pwsh
|
||||||
# First get the latest changes on main
|
# First get the latest changes on main
|
||||||
git switch main
|
git switch main
|
||||||
@@ -123,7 +128,8 @@ git pull
|
|||||||
|
|
||||||
# Now we resolve the merge. We're merging the main branch INTO the feature-2 branch.
|
# Now we resolve the merge. We're merging the main branch INTO the feature-2 branch.
|
||||||
git merge main
|
git merge main
|
||||||
# Resolve the merge conflict in numbers.txt
|
# Resolve the merge conflict in numbers.txt by opening in VSCode and choosing the changes you want.
|
||||||
|
# How you solve it is up to you.
|
||||||
# Once resolved
|
# Once resolved
|
||||||
git add numbers.txt
|
git add numbers.txt
|
||||||
git commit
|
git commit
|
||||||
@@ -137,30 +143,29 @@ git pull
|
|||||||
|
|
||||||
| Command | What It Does |
|
| Command | What It Does |
|
||||||
|---------|--------------|
|
|---------|--------------|
|
||||||
| `git switch -c <name>` | Create and switch to new branch |
|
| `git switch -c <branch-name>` | Create and switch to new branch |
|
||||||
| `git push -u origin <branch>` | Push branch to Azure DevOps |
|
| `git switch <branch-name>` | Switch to branch |
|
||||||
| `git switch main` | Switch to main branch |
|
| `git push` | Push branch to Azure DevOps |
|
||||||
| `git pull` | Get latest changes from remote |
|
| `git pull` | Get latest changes from remote |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Common Issues
|
## Cheatsheet
|
||||||
|
|
||||||
### "My PR has conflicts"
|
### Solving merge conflicts
|
||||||
1. Update your branch with latest main:
|
```pwsh
|
||||||
```powershell
|
|
||||||
git switch main
|
git switch main
|
||||||
git pull
|
git pull
|
||||||
git switch <branch-name>
|
git switch <branch-name>
|
||||||
git merge main
|
git merge main
|
||||||
|
# ... Solve the conflicts
|
||||||
|
git push
|
||||||
```
|
```
|
||||||
2. Resolve conflicts in VS Code
|
|
||||||
3. Commit and push again
|
|
||||||
|
|
||||||
### "I need to make more changes to my PR"
|
### "I need to make more changes to my PR"
|
||||||
|
|
||||||
Just commit and push to the same branch - the PR updates automatically:
|
Just commit and push to the same branch - the PR updates automatically:
|
||||||
```powershell
|
```pwsh
|
||||||
git add .
|
git add .
|
||||||
git commit -m "fix: address review feedback"
|
git commit -m "fix: address review feedback"
|
||||||
git push
|
git push
|
||||||
|
|||||||
148
01-essentials/08-multiplayer/README.md
Normal file
148
01-essentials/08-multiplayer/README.md
Normal file
@@ -0,0 +1,148 @@
|
|||||||
|
# Multiplayer Git
|
||||||
|
|
||||||
|
Work with others using branches and pull requests.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Learn to collaborate on a shared repository using:
|
||||||
|
- **Branches** - work independently without breaking main
|
||||||
|
- **Pull Requests** - review and merge changes safely
|
||||||
|
|
||||||
|
## The Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Create branch → 2. Make changes → 3. Push branch
|
||||||
|
↓
|
||||||
|
6. Delete branch ← 5. Merge PR ← 4. Create PR
|
||||||
|
```
|
||||||
|
|
||||||
|
This is how professional teams work together on code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Clone the Repository
|
||||||
|
|
||||||
|
Get the repository URL from your facilitator, then:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone <repository-url>
|
||||||
|
code <repository-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Create a Branch
|
||||||
|
|
||||||
|
Never work directly on `main`. Create your own branch:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch -c <branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a new branch and switches to it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Make Changes
|
||||||
|
|
||||||
|
1. Open `numbers.txt` in VS Code
|
||||||
|
2. Move one number to its correct position
|
||||||
|
3. Save the file (`Ctrl+S`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Commit and Push
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: move 7 to correct position"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
Your branch is now on Azure DevOps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Create a Pull Request
|
||||||
|
|
||||||
|
1. Go to Azure DevOps in your browser
|
||||||
|
2. Navigate to **Repos** → **Pull Requests**
|
||||||
|
3. Click **New Pull Request**
|
||||||
|
4. Set:
|
||||||
|
- **Source branch:** `feature/<your-name>`
|
||||||
|
- **Target branch:** `main`
|
||||||
|
5. Add a title describing your change
|
||||||
|
6. Click **Create**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Review and Merge
|
||||||
|
|
||||||
|
1. Review the changes shown in the PR
|
||||||
|
2. If everything looks good, click **Complete**
|
||||||
|
3. Select **Complete merge**
|
||||||
|
4. Your changes are now in `main`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7: Update Your Local Main
|
||||||
|
|
||||||
|
After merging, update your local copy:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 8: Repeat
|
||||||
|
|
||||||
|
1. Create a new branch for your next change
|
||||||
|
2. Make changes, commit, push
|
||||||
|
3. Create another PR
|
||||||
|
4. Continue until all numbers are sorted
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Command | What It Does |
|
||||||
|
|---------|--------------|
|
||||||
|
| `git switch -c <name>` | Create and switch to new branch |
|
||||||
|
| `git push -u origin <branch>` | Push branch to Azure DevOps |
|
||||||
|
| `git switch main` | Switch to main branch |
|
||||||
|
| `git pull` | Get latest changes from remote |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### "I accidentally committed to main"
|
||||||
|
|
||||||
|
Switch to a new branch and push from there:
|
||||||
|
```powershell
|
||||||
|
git switch -c feature/<your-name>
|
||||||
|
git push -u origin feature/<your-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
### "My PR has conflicts"
|
||||||
|
|
||||||
|
1. Update your branch with latest main:
|
||||||
|
```powershell
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
git switch feature/<your-name>
|
||||||
|
git merge main
|
||||||
|
```
|
||||||
|
2. Resolve conflicts in VS Code
|
||||||
|
3. Commit and push again
|
||||||
|
|
||||||
|
### "I need to make more changes to my PR"
|
||||||
|
|
||||||
|
Just commit and push to the same branch - the PR updates automatically:
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: address review feedback"
|
||||||
|
git push
|
||||||
|
```
|
||||||
BIN
01-essentials/08-multiplayer/images/05_repos.png
Normal file
BIN
01-essentials/08-multiplayer/images/05_repos.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 53 KiB |
BIN
01-essentials/08-multiplayer/images/06_choose_ssh.png
Normal file
BIN
01-essentials/08-multiplayer/images/06_choose_ssh.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 165 KiB |
279
02-GET-STARTED.md
Normal file
279
02-GET-STARTED.md
Normal file
@@ -0,0 +1,279 @@
|
|||||||
|
# Git Workshop
|
||||||
|
|
||||||
|
A hands-on Git workshop with 8 progressive modules covering essential Git skills from basic commits to real-world collaboration. Each module presents a practical scenario you must solve using Git commands, with automated verification to confirm your solution.
|
||||||
|
|
||||||
|
Perfect for developers who want to master Git fundamentals and professional workflows including branching, conflict resolution, and team collaboration.
|
||||||
|
|
||||||
|
## Workshop Structure
|
||||||
|
|
||||||
|
### Essentials - Core Git Skills (8 modules)
|
||||||
|
|
||||||
|
Master fundamental Git concepts and collaborative workflows:
|
||||||
|
|
||||||
|
- **Module 01: Git Basics** - Initialize repositories, stage changes, make commits
|
||||||
|
- **Module 02: Viewing History** - Use git log and git diff to explore project history
|
||||||
|
- **Module 03: Branching and Merging** - Create branches and merge them
|
||||||
|
- **Module 04: Merge Conflicts** - Resolve conflicts when branches have competing changes
|
||||||
|
- **Module 05: Cherry-Pick** - Apply specific commits from one branch to another
|
||||||
|
- **Module 06: Git Revert** - Safe undoing - preserve history while reversing changes
|
||||||
|
- **Module 07: Stash** - Temporarily save work without committing
|
||||||
|
- **Module 08: Multiplayer Git** - **The Great Print Project** - Real cloud-based collaboration with teammates
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
Install these tools before starting:
|
||||||
|
|
||||||
|
**PowerShell 7+**
|
||||||
|
|
||||||
|
Download here <https://learn.microsoft.com/en-us/powershell/scripting/install/install-powershell-on-windows?view=powershell-7.5#msi>.
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
winget install Microsoft.PowerShell
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
**Git 2.23+**
|
||||||
|
|
||||||
|
Download here <https://git-scm.com/install/windows> or install using the command down below
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
winget install Git.Git
|
||||||
|
```
|
||||||
|
|
||||||
|
**Visual Studio Code**
|
||||||
|
|
||||||
|
Download here <https://code.visualstudio.com/download> or install using the command down below
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
winget install Microsoft.VisualStudioCode
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also run the one-shot install script if the project is cloned locally. It will check if you have the prerequisites install and clone down the project if not already cloned. Then it will configure git to have sane default.
|
||||||
|
```pwsh
|
||||||
|
./install.ps1
|
||||||
|
```
|
||||||
|
or if you don't have the project locally run.
|
||||||
|
|
||||||
|
|
||||||
|
REMEMBER read through the script before running it. As a general practice, you shouldn't get comfortable doing this kind of execution, and so it's important to review executing remote scripts.
|
||||||
|
```pwsh
|
||||||
|
Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
||||||
|
```
|
||||||
|
|
||||||
|
## How to Use This Workshop
|
||||||
|
|
||||||
|
|
||||||
|
### For Local Modules (01-07)
|
||||||
|
|
||||||
|
1. Navigate to a module directory (e.g., `01-essentials/01-basics`)
|
||||||
|
2. Read the `README.md` to understand the challenge
|
||||||
|
3. Run `./setup.ps1` to create the challenge environment
|
||||||
|
4. Complete the challenge using git commands
|
||||||
|
5. Run `./verify.ps1` to check if you've solved it correctly
|
||||||
|
6. If you mess up the challenge just run `.\reset.ps1`
|
||||||
|
6. Move to the next module
|
||||||
|
|
||||||
|
**Quick Reference**: See [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) for a comprehensive list of all Git commands covered in this workshop. Don't worry about memorizing everything - use this as a reference when you need to look up command syntax!
|
||||||
|
|
||||||
|
### For Module 08: Multiplayer Git
|
||||||
|
|
||||||
|
**This module is different!** It uses Azure DevOps for authentic cloud-based collaboration:
|
||||||
|
|
||||||
|
1. Navigate to `01-essentials/08-multiplayer`
|
||||||
|
2. Read the `README.md` for complete instructions
|
||||||
|
3. **No setup script** - you'll clone from Azure DevOps (URL provided by facilitator)
|
||||||
|
4. Work with a partner on shared branches
|
||||||
|
5. Experience real merge conflicts and pull requests
|
||||||
|
6. Use SSH keys for secure authentication (best practice)
|
||||||
|
7. **No verify script** - success is visual (your code appears in the final output)
|
||||||
|
|
||||||
|
**Facilitators**: See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for server setup and workshop guidance.
|
||||||
|
|
||||||
|
## Running PowerShell Scripts
|
||||||
|
|
||||||
|
Most modules include setup, verification, and reset scripts.
|
||||||
|
|
||||||
|
If you encounter an "execution policy" error when running scripts, open PowerShell as Administrator and run:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
|
```
|
||||||
|
|
||||||
|
And to revert back to the default policy
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy Restricted -Scope CurrentUser
|
||||||
|
```
|
||||||
|
|
||||||
|
Then run scripts using:
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
.\verify.ps1
|
||||||
|
.\reset.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Git Configuration
|
||||||
|
|
||||||
|
Before starting the workshop, configure Git with your identity and recommended settings:
|
||||||
|
|
||||||
|
### Required: Set Your Identity
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
git config --global user.name "Your Name"
|
||||||
|
git config --global user.email "your.email@example.com"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended: VS Code as Default Editor
|
||||||
|
|
||||||
|
Set VS Code as your default editor for commit messages, diffs, and merges:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Set VS Code as the default editor
|
||||||
|
git config --global core.editor "code --wait"
|
||||||
|
|
||||||
|
# Set VS Code as the diff tool
|
||||||
|
git config --global diff.tool vscode
|
||||||
|
git config --global difftool.vscode.cmd "code --wait --diff $LOCAL $REMOTE"
|
||||||
|
|
||||||
|
# Set VS Code as the merge tool
|
||||||
|
git config --global merge.tool vscode
|
||||||
|
git config --global mergetool.vscode.cmd "code --wait --merge $MERGED"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Recommended: Set Default Branch Name
|
||||||
|
|
||||||
|
Set "main" as the default branch name for new repositories:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
git config --global init.defaultBranch main
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Your Configuration
|
||||||
|
|
||||||
|
Check that your settings are correct:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
git config --global --list
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify Installation:**
|
||||||
|
|
||||||
|
Check that you have the required software installed:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Git 2.23+
|
||||||
|
git --version
|
||||||
|
|
||||||
|
# PowerShell 7+
|
||||||
|
pwsh --version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Basic Command Line Knowledge
|
||||||
|
|
||||||
|
You should know how to:
|
||||||
|
- Navigate directories (`cd`, `ls` or `dir`)
|
||||||
|
- See your current location (`pwd`)
|
||||||
|
- Read text files (`cat` or `type`)
|
||||||
|
|
||||||
|
Don't worry if you're not an expert - we'll guide you through each step!
|
||||||
|
|
||||||
|
## Common Git Terms
|
||||||
|
|
||||||
|
New to Git? Here are the key terms you'll encounter:
|
||||||
|
|
||||||
|
- **Repository (or "repo")**: A project folder tracked by Git, containing your files and their complete history
|
||||||
|
- **Commit**: A snapshot of your project at a specific point in time (like a save point in a video game)
|
||||||
|
- **Staging Area**: A preparation area where you select which changes to include in your next commit
|
||||||
|
- **Working Directory**: The actual files you see and edit on your computer
|
||||||
|
- **Branch**: An independent line of development (like a parallel universe for your code)
|
||||||
|
- **HEAD**: A pointer showing which commit you're currently working from
|
||||||
|
- **main/master**: The primary branch in a repository (main is the modern convention)
|
||||||
|
- **Merge**: Combining changes from different branches
|
||||||
|
- **Remote**: A version of your repository hosted elsewhere (like GitHub, GitLab, or Gitea)
|
||||||
|
- **Pull Request (PR)**: A request to merge your changes into another branch (used for code review)
|
||||||
|
- **Conflict**: When Git can't automatically merge changes because both versions modified the same lines
|
||||||
|
|
||||||
|
Don't worry if these don't make sense yet - you'll learn them hands-on as you progress!
|
||||||
|
|
||||||
|
## Getting Started
|
||||||
|
|
||||||
|
Once installed, start with Essentials Module 01:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
cd 01-essentials\01-basics
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Follow the instructions in each module's README.md file.
|
||||||
|
|
||||||
|
## What Makes This Workshop Different
|
||||||
|
|
||||||
|
- **Hands-On Practice**: Each module creates a real Git scenario you must solve
|
||||||
|
- **Automated Verification**: Scripts check your solution instantly (modules 01-07)
|
||||||
|
- **Progressive Difficulty**: Builds from basics to advanced Git techniques
|
||||||
|
- **Reset Anytime**: Each local module includes a reset script for a fresh start
|
||||||
|
- **Self-Paced**: Learn at your own speed with detailed README guides
|
||||||
|
- **Real Collaboration**: Module 08 uses an actual Git server for authentic teamwork
|
||||||
|
- **Comprehensive Coverage**: From `git init` to advanced rebasing and bisecting
|
||||||
|
|
||||||
|
## Learning Path
|
||||||
|
|
||||||
|
The modules are designed to build on each other:
|
||||||
|
|
||||||
|
### Recommended Progression
|
||||||
|
|
||||||
|
**Phase 1: Core Fundamentals (Essentials 01-02)**
|
||||||
|
- Git Basics, History
|
||||||
|
- **Goal**: Understand commits and history
|
||||||
|
|
||||||
|
**Phase 2: Collaboration Basics (Essentials 03-04)**
|
||||||
|
- Branching, Merging, and Conflict Resolution
|
||||||
|
- **Goal**: Work with multiple branches and resolve conflicts
|
||||||
|
|
||||||
|
**Phase 3: Workflow Tools (Essentials 05-07)**
|
||||||
|
- Cherry-Pick, Revert, and Stash
|
||||||
|
- **Goal**: Manage your work effectively
|
||||||
|
|
||||||
|
**Phase 4: Real Collaboration (Essentials 08)**
|
||||||
|
- **Multiplayer Git - The Great Reordering of Numbers Project**
|
||||||
|
- **Goal**: Apply all skills with real teammates on a cloud server
|
||||||
|
- **Note**: This is a capstone module - bring everything together!
|
||||||
|
|
||||||
|
### Alternative Paths
|
||||||
|
|
||||||
|
**Fast Track (half-day workshop):**
|
||||||
|
- Essentials 01-04 + Essentials 08 (Multiplayer)
|
||||||
|
|
||||||
|
**Team Workshop:**
|
||||||
|
- Essentials 01-04 then jump to 08 (Multiplayer) for collaborative practice
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
- **Don't skip modules** - each builds on previous concepts
|
||||||
|
- **Read the README.md thoroughly** before starting each challenge
|
||||||
|
- **Keep [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) open** as a quick reference
|
||||||
|
- **Experiment freely** - you can always run `./reset.ps1` to start over
|
||||||
|
- **Use `git status` and `git log --oneline --graph --all`** frequently to visualize repository state
|
||||||
|
- **If stuck**, check the Key Concepts section in the module's README
|
||||||
|
- **For Module 08**, work with a partner - collaboration is the point!
|
||||||
|
|
||||||
|
## Skills You'll Master
|
||||||
|
|
||||||
|
By completing this workshop, you'll be able to:
|
||||||
|
|
||||||
|
- Create and manage Git repositories with confidence
|
||||||
|
- Navigate project history and understand what changed when
|
||||||
|
- Use branches effectively for parallel development
|
||||||
|
- Merge branches and resolve conflicts like a pro
|
||||||
|
- Apply specific commits with cherry-pick
|
||||||
|
- Safely undo changes with revert
|
||||||
|
- Use stash to manage work-in-progress without commits
|
||||||
|
- **Collaborate with teammates on shared repositories**
|
||||||
|
- **Resolve real merge conflicts in a team environment**
|
||||||
|
- **Create and review pull requests**
|
||||||
|
- **Use Git on a real cloud server (Azure DevOps)**
|
||||||
|
- **Use SSH keys for secure Git authentication**
|
||||||
|
|
||||||
|
These are professional-level Git skills used daily by developers at tech companies.
|
||||||
@@ -1,717 +0,0 @@
|
|||||||
# Module 06: Git Reset - Dangerous History Rewriting
|
|
||||||
|
|
||||||
## ⚠️ CRITICAL SAFETY WARNING ⚠️
|
|
||||||
|
|
||||||
**Git reset is DESTRUCTIVE and DANGEROUS when misused!**
|
|
||||||
|
|
||||||
Before using `git reset`, always ask yourself:
|
|
||||||
|
|
||||||
```
|
|
||||||
Have I pushed these commits to a remote repository?
|
|
||||||
├─ YES → ❌ DO NOT USE RESET!
|
|
||||||
│ Use git revert instead (Module 05)
|
|
||||||
│ Rewriting pushed history breaks collaboration!
|
|
||||||
│
|
|
||||||
└─ NO → ✅ Proceed with reset (local cleanup only)
|
|
||||||
Choose your mode carefully:
|
|
||||||
--soft (safest), --mixed (moderate), --hard (DANGEROUS)
|
|
||||||
```
|
|
||||||
|
|
||||||
**The Golden Rule:** NEVER reset commits that have been pushed/shared.
|
|
||||||
|
|
||||||
## About This Module
|
|
||||||
|
|
||||||
Welcome to Module 06, where you'll learn the powerful but dangerous `git reset` command. Unlike `git revert` (Module 05) which safely creates new commits, **reset erases commits from history**.
|
|
||||||
|
|
||||||
**Why reset exists:**
|
|
||||||
- ✅ Clean up messy local commit history before pushing
|
|
||||||
- ✅ Undo commits you haven't shared yet
|
|
||||||
- ✅ Unstage files from the staging area
|
|
||||||
- ✅ Recover from mistakes (with reflog)
|
|
||||||
|
|
||||||
**Why reset is dangerous:**
|
|
||||||
- ⚠️ Erases commits permanently (without reflog)
|
|
||||||
- ⚠️ Breaks repositories if used on pushed commits
|
|
||||||
- ⚠️ Can lose work if used incorrectly
|
|
||||||
- ⚠️ Confuses teammates if they have your commits
|
|
||||||
|
|
||||||
**Key principle:** Reset is for polishing LOCAL history before sharing.
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By completing this module, you will:
|
|
||||||
|
|
||||||
1. Understand the three reset modes: --soft, --mixed, --hard
|
|
||||||
2. Reset commits while keeping changes staged (--soft)
|
|
||||||
3. Reset commits and unstage changes (--mixed)
|
|
||||||
4. Reset commits and discard everything (--hard)
|
|
||||||
5. Know when reset is appropriate (local only!)
|
|
||||||
6. Understand when to use revert instead
|
|
||||||
7. Use reflog to recover from mistakes
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before starting this module, you should:
|
|
||||||
- Be comfortable with commits and staging (`git add`, `git commit`)
|
|
||||||
- Understand `git revert` from Module 05
|
|
||||||
- **Know the difference between local and pushed commits!**
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Run the setup script to create the challenge environment:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
./setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a `challenge/` directory with three branches demonstrating different reset modes:
|
|
||||||
- `soft-reset` - Reset with --soft (keep changes staged)
|
|
||||||
- `mixed-reset` - Reset with --mixed (unstage changes)
|
|
||||||
- `hard-reset` - Reset with --hard (discard everything)
|
|
||||||
|
|
||||||
**Remember:** These are all LOCAL commits that have NEVER been pushed!
|
|
||||||
|
|
||||||
## Understanding Reset Modes
|
|
||||||
|
|
||||||
Git reset has three modes that control what happens to your changes:
|
|
||||||
|
|
||||||
| Mode | Commits | Staging Area | Working Directory |
|
|
||||||
|------|---------|--------------|-------------------|
|
|
||||||
| **--soft** | ✂️ Removed | ✅ Kept (staged) | ✅ Kept |
|
|
||||||
| **--mixed** (default) | ✂️ Removed | ✂️ Cleared | ✅ Kept (unstaged) |
|
|
||||||
| **--hard** | ✂️ Removed | ✂️ Cleared | ✂️ **LOST!** |
|
|
||||||
|
|
||||||
**Visual explanation:**
|
|
||||||
|
|
||||||
```
|
|
||||||
Before reset (3 commits):
|
|
||||||
A → B → C → HEAD
|
|
||||||
|
|
||||||
After git reset --soft HEAD~1:
|
|
||||||
A → B → HEAD
|
|
||||||
↑
|
|
||||||
C's changes are staged
|
|
||||||
|
|
||||||
After git reset --mixed HEAD~1 (or just git reset HEAD~1):
|
|
||||||
A → B → HEAD
|
|
||||||
↑
|
|
||||||
C's changes are unstaged (in working directory)
|
|
||||||
|
|
||||||
After git reset --hard HEAD~1:
|
|
||||||
A → B → HEAD
|
|
||||||
↑
|
|
||||||
C's changes are GONE (discarded completely!)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Challenge 1: Soft Reset (Safest)
|
|
||||||
|
|
||||||
### Scenario
|
|
||||||
|
|
||||||
You committed "feature C" but immediately realized the implementation is wrong. You want to undo the commit but keep the changes staged so you can edit and re-commit them properly.
|
|
||||||
|
|
||||||
**Use case:** Fixing the last commit's message or contents.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory:
|
|
||||||
```bash
|
|
||||||
cd challenge
|
|
||||||
```
|
|
||||||
|
|
||||||
2. You should be on the `soft-reset` branch. View the commits:
|
|
||||||
```bash
|
|
||||||
git log --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see:
|
|
||||||
- "Add feature C - needs better implementation!"
|
|
||||||
- "Add feature B"
|
|
||||||
- "Add feature A"
|
|
||||||
- "Initial project setup"
|
|
||||||
|
|
||||||
3. View the current state:
|
|
||||||
```bash
|
|
||||||
git status
|
|
||||||
# Should be clean
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Reset the last commit with --soft:
|
|
||||||
```bash
|
|
||||||
git reset --soft HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
5. Check what happened:
|
|
||||||
```bash
|
|
||||||
# Commit is gone
|
|
||||||
git log --oneline
|
|
||||||
# Should only show 3 commits now (feature C commit removed)
|
|
||||||
|
|
||||||
# Changes are still staged
|
|
||||||
git status
|
|
||||||
# Should show "Changes to be committed"
|
|
||||||
|
|
||||||
# View the staged changes
|
|
||||||
git diff --cached
|
|
||||||
# Should show feature C code ready to be re-committed
|
|
||||||
```
|
|
||||||
|
|
||||||
### What to Observe
|
|
||||||
|
|
||||||
After `--soft` reset:
|
|
||||||
- ✅ Commit removed from history
|
|
||||||
- ✅ Changes remain in staging area
|
|
||||||
- ✅ Working directory unchanged
|
|
||||||
- ✅ Ready to edit and re-commit
|
|
||||||
|
|
||||||
**When to use --soft:**
|
|
||||||
- Fix the last commit message (though `commit --amend` is simpler)
|
|
||||||
- Combine multiple commits into one
|
|
||||||
- Re-do a commit with better changes
|
|
||||||
|
|
||||||
## Challenge 2: Mixed Reset (Default, Moderate)
|
|
||||||
|
|
||||||
### Scenario
|
|
||||||
|
|
||||||
You committed two experimental features that aren't ready. You want to remove both commits and have the changes back in your working directory (unstaged) so you can review and selectively re-commit them.
|
|
||||||
|
|
||||||
**Use case:** Undoing commits and starting over with more careful staging.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
1. Switch to the mixed-reset branch:
|
|
||||||
```bash
|
|
||||||
git switch mixed-reset
|
|
||||||
```
|
|
||||||
|
|
||||||
2. View the commits:
|
|
||||||
```bash
|
|
||||||
git log --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see:
|
|
||||||
- "Add debug mode - REMOVE THIS TOO!"
|
|
||||||
- "Add experimental feature X - REMOVE THIS!"
|
|
||||||
- "Add logging system"
|
|
||||||
- "Add application lifecycle"
|
|
||||||
|
|
||||||
3. Reset the last TWO commits (default is --mixed):
|
|
||||||
```bash
|
|
||||||
git reset HEAD~2
|
|
||||||
# This is equivalent to: git reset --mixed HEAD~2
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Check what happened:
|
|
||||||
```bash
|
|
||||||
# Commits are gone
|
|
||||||
git log --oneline
|
|
||||||
# Should only show 2 commits (lifecycle + logging)
|
|
||||||
|
|
||||||
# NO staged changes
|
|
||||||
git diff --cached
|
|
||||||
# Should be empty
|
|
||||||
|
|
||||||
# Changes are in working directory (unstaged)
|
|
||||||
git status
|
|
||||||
# Should show "Changes not staged for commit"
|
|
||||||
|
|
||||||
# View the unstaged changes
|
|
||||||
git diff
|
|
||||||
# Should show experimental and debug code
|
|
||||||
```
|
|
||||||
|
|
||||||
### What to Observe
|
|
||||||
|
|
||||||
After `--mixed` reset (the default):
|
|
||||||
- ✅ Commits removed from history
|
|
||||||
- ✅ Staging area cleared
|
|
||||||
- ✅ Changes moved to working directory (unstaged)
|
|
||||||
- ✅ Can selectively stage and re-commit parts
|
|
||||||
|
|
||||||
**When to use --mixed (default):**
|
|
||||||
- Undo commits and start over with clean staging
|
|
||||||
- Split one large commit into multiple smaller ones
|
|
||||||
- Review changes before re-committing
|
|
||||||
- Most common reset mode for cleanup
|
|
||||||
|
|
||||||
## Challenge 3: Hard Reset (MOST DANGEROUS!)
|
|
||||||
|
|
||||||
### ⚠️ EXTREME CAUTION REQUIRED ⚠️
|
|
||||||
|
|
||||||
**This will PERMANENTLY DELETE your work!**
|
|
||||||
|
|
||||||
Only use `--hard` when you're absolutely sure you want to throw away changes.
|
|
||||||
|
|
||||||
### Scenario
|
|
||||||
|
|
||||||
You committed completely broken code that you want to discard entirely. There's no salvaging it—you just want it gone.
|
|
||||||
|
|
||||||
**Use case:** Throwing away failed experiments or completely wrong code.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
1. Switch to the hard-reset branch:
|
|
||||||
```bash
|
|
||||||
git switch hard-reset
|
|
||||||
```
|
|
||||||
|
|
||||||
2. View the commits and the broken code:
|
|
||||||
```bash
|
|
||||||
git log --oneline
|
|
||||||
# Shows "Add broken helper D - DISCARD COMPLETELY!"
|
|
||||||
|
|
||||||
cat utils.py
|
|
||||||
# Shows the broken helper_d function
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Reset the last commit with --hard:
|
|
||||||
```bash
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
**WARNING:** This will permanently discard all changes from that commit!
|
|
||||||
|
|
||||||
4. Check what happened:
|
|
||||||
```bash
|
|
||||||
# Commit is gone
|
|
||||||
git log --oneline
|
|
||||||
# Should only show 2 commits
|
|
||||||
|
|
||||||
# NO staged changes
|
|
||||||
git diff --cached
|
|
||||||
# Empty
|
|
||||||
|
|
||||||
# NO unstaged changes
|
|
||||||
git diff
|
|
||||||
# Empty
|
|
||||||
|
|
||||||
# Working directory clean
|
|
||||||
git status
|
|
||||||
# "nothing to commit, working tree clean"
|
|
||||||
|
|
||||||
# File doesn't have broken code
|
|
||||||
cat utils.py
|
|
||||||
# helper_d is completely gone
|
|
||||||
```
|
|
||||||
|
|
||||||
### What to Observe
|
|
||||||
|
|
||||||
After `--hard` reset:
|
|
||||||
- ✅ Commit removed from history
|
|
||||||
- ✅ Staging area cleared
|
|
||||||
- ✅ Working directory reset to match
|
|
||||||
- ⚠️ All changes from that commit PERMANENTLY DELETED
|
|
||||||
|
|
||||||
**When to use --hard:**
|
|
||||||
- Discarding failed experiments completely
|
|
||||||
- Throwing away work you don't want (CAREFUL!)
|
|
||||||
- Cleaning up after mistakes (use reflog to recover if needed)
|
|
||||||
- Resetting to a known good state
|
|
||||||
|
|
||||||
**⚠️ WARNING:** Files in the discarded commit are NOT gone forever—they're still in reflog for about 90 days. See "Recovery with Reflog" section below.
|
|
||||||
|
|
||||||
## Understanding HEAD~N Syntax
|
|
||||||
|
|
||||||
When resetting, you specify where to reset to:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reset to the commit before HEAD
|
|
||||||
git reset HEAD~1
|
|
||||||
|
|
||||||
# Reset to 2 commits before HEAD
|
|
||||||
git reset HEAD~2
|
|
||||||
|
|
||||||
# Reset to 3 commits before HEAD
|
|
||||||
git reset HEAD~3
|
|
||||||
|
|
||||||
# Reset to a specific commit hash
|
|
||||||
git reset abc123
|
|
||||||
|
|
||||||
# Reset to a branch
|
|
||||||
git reset main
|
|
||||||
```
|
|
||||||
|
|
||||||
**Visualization:**
|
|
||||||
|
|
||||||
```
|
|
||||||
HEAD~3 HEAD~2 HEAD~1 HEAD
|
|
||||||
↓ ↓ ↓ ↓
|
|
||||||
A → B → C → D → E
|
|
||||||
↑
|
|
||||||
Current commit
|
|
||||||
```
|
|
||||||
|
|
||||||
- `git reset HEAD~1` moves HEAD from E to D
|
|
||||||
- `git reset HEAD~2` moves HEAD from E to C
|
|
||||||
- `git reset abc123` moves HEAD to that specific commit
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Verify your solutions by running the verification script:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd .. # Return to module directory
|
|
||||||
./verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The script checks that:
|
|
||||||
- ✅ Commits were reset (count decreased)
|
|
||||||
- ✅ --soft: Changes remain staged
|
|
||||||
- ✅ --mixed: Changes are unstaged
|
|
||||||
- ✅ --hard: Everything is clean
|
|
||||||
|
|
||||||
## Recovery with Reflog
|
|
||||||
|
|
||||||
**Good news:** Even `--hard` reset doesn't immediately destroy commits!
|
|
||||||
|
|
||||||
Git keeps a "reflog" (reference log) of where HEAD has been for about 90 days. You can use this to recover "lost" commits.
|
|
||||||
|
|
||||||
### How to Recover from a Reset
|
|
||||||
|
|
||||||
1. View the reflog:
|
|
||||||
```bash
|
|
||||||
git reflog
|
|
||||||
```
|
|
||||||
|
|
||||||
Output example:
|
|
||||||
```
|
|
||||||
abc123 HEAD@{0}: reset: moving to HEAD~1
|
|
||||||
def456 HEAD@{1}: commit: Add broken helper D
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Find the commit you want to recover (def456 in this example)
|
|
||||||
|
|
||||||
3. Reset back to it:
|
|
||||||
```bash
|
|
||||||
git reset def456
|
|
||||||
# Or use the reflog reference:
|
|
||||||
git reset HEAD@{1}
|
|
||||||
```
|
|
||||||
|
|
||||||
4. Your "lost" commit is back!
|
|
||||||
|
|
||||||
### Reflog Safety Net
|
|
||||||
|
|
||||||
**Important:**
|
|
||||||
- Reflog entries expire after ~90 days (configurable)
|
|
||||||
- Reflog is LOCAL to your repository (not shared)
|
|
||||||
- `git gc` can clean up old reflog entries
|
|
||||||
- If you really lose a commit, check reflog first!
|
|
||||||
|
|
||||||
**Pro tip:** Before doing dangerous operations, note your current commit hash:
|
|
||||||
```bash
|
|
||||||
git log --oneline | head -1
|
|
||||||
# abc123 Current work
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use Git Reset
|
|
||||||
|
|
||||||
Use `git reset` when:
|
|
||||||
|
|
||||||
- ✅ **Commits are LOCAL only** (never pushed)
|
|
||||||
- ✅ **Cleaning up messy history** before sharing
|
|
||||||
- ✅ **Undoing recent commits** you don't want
|
|
||||||
- ✅ **Combining commits** into one clean commit
|
|
||||||
- ✅ **Unstaging files** (mixed mode)
|
|
||||||
- ✅ **Polishing commit history** before pull request
|
|
||||||
|
|
||||||
**Golden Rule:** Only reset commits that are local to your machine!
|
|
||||||
|
|
||||||
## When NOT to Use Git Reset
|
|
||||||
|
|
||||||
DO NOT use `git reset` when:
|
|
||||||
|
|
||||||
- ❌ **Commits are pushed/shared** with others
|
|
||||||
- ❌ **Teammates have your commits** (breaks their repos)
|
|
||||||
- ❌ **In public repositories** (use revert instead)
|
|
||||||
- ❌ **Unsure if pushed** (check `git log origin/main`)
|
|
||||||
- ❌ **On main/master branch** after push
|
|
||||||
- ❌ **Need audit trail** of changes
|
|
||||||
|
|
||||||
**Use git revert instead** (Module 05) for pushed commits!
|
|
||||||
|
|
||||||
## Decision Tree: Reset vs Revert
|
|
||||||
|
|
||||||
```
|
|
||||||
Need to undo a commit?
|
|
||||||
│
|
|
||||||
├─ Have you pushed this commit?
|
|
||||||
│ │
|
|
||||||
│ ├─ YES → Use git revert (Module 05)
|
|
||||||
│ │ Safe for shared history
|
|
||||||
│ │ Preserves complete audit trail
|
|
||||||
│ │
|
|
||||||
│ └─ NO → Can use git reset (local only)
|
|
||||||
│ │
|
|
||||||
│ ├─ Want to keep changes?
|
|
||||||
│ │ │
|
|
||||||
│ │ ├─ Keep staged → git reset --soft
|
|
||||||
│ │ └─ Keep unstaged → git reset --mixed
|
|
||||||
│ │
|
|
||||||
│ └─ Discard everything? → git reset --hard
|
|
||||||
│ (CAREFUL!)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Reset vs Revert vs Rebase
|
|
||||||
|
|
||||||
| Command | History | Safety | Use Case |
|
|
||||||
|---------|---------|--------|----------|
|
|
||||||
| **reset** | Erases | ⚠️ Dangerous | Local cleanup before push |
|
|
||||||
| **revert** | Preserves | ✅ Safe | Undo pushed commits |
|
|
||||||
| **rebase** | Rewrites | ⚠️ Dangerous | Polish history before push |
|
|
||||||
|
|
||||||
**This module teaches reset.** You learned revert in Module 05.
|
|
||||||
|
|
||||||
## Command Reference
|
|
||||||
|
|
||||||
### Basic Reset
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reset last commit, keep changes staged
|
|
||||||
git reset --soft HEAD~1
|
|
||||||
|
|
||||||
# Reset last commit, unstage changes (default)
|
|
||||||
git reset HEAD~1
|
|
||||||
git reset --mixed HEAD~1 # Same as above
|
|
||||||
|
|
||||||
# Reset last commit, discard everything (DANGEROUS!)
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
|
|
||||||
# Reset multiple commits
|
|
||||||
git reset --soft HEAD~3 # Last 3 commits
|
|
||||||
|
|
||||||
# Reset to specific commit
|
|
||||||
git reset --soft abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
### Unstaging Files
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Unstage a specific file (common use of reset)
|
|
||||||
git reset HEAD filename.txt
|
|
||||||
|
|
||||||
# Unstage all files
|
|
||||||
git reset HEAD .
|
|
||||||
|
|
||||||
# This is the same as:
|
|
||||||
git restore --staged filename.txt # Modern syntax
|
|
||||||
```
|
|
||||||
|
|
||||||
### Reflog and Recovery
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View reflog
|
|
||||||
git reflog
|
|
||||||
|
|
||||||
# Recover from reset
|
|
||||||
git reset --hard HEAD@{1}
|
|
||||||
git reset --hard abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
### Check Before Reset
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Check if commits are pushed
|
|
||||||
git log origin/main..HEAD
|
|
||||||
# If output is empty, commits are pushed (DO NOT RESET)
|
|
||||||
# If output shows commits, they're local (safe to reset)
|
|
||||||
|
|
||||||
# Another way to check
|
|
||||||
git log --oneline --graph --all
|
|
||||||
# Look for origin/main marker
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Mistakes
|
|
||||||
|
|
||||||
### 1. Resetting Pushed Commits
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ❌ NEVER do this if you've pushed!
|
|
||||||
git push
|
|
||||||
# ... time passes ...
|
|
||||||
git reset --hard HEAD~3 # BREAKS teammate repos!
|
|
||||||
|
|
||||||
# ✅ Do this instead
|
|
||||||
git revert HEAD~3..HEAD # Safe for shared history
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Using --hard Without Thinking
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ❌ Dangerous - loses work!
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
|
|
||||||
# ✅ Better - keep changes to review
|
|
||||||
git reset --mixed HEAD~1
|
|
||||||
# Now you can review changes and decide
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Resetting Without Checking If Pushed
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ❌ Risky - are these commits pushed?
|
|
||||||
git reset HEAD~5
|
|
||||||
|
|
||||||
# ✅ Check first
|
|
||||||
git log origin/main..HEAD # Local commits only
|
|
||||||
git reset HEAD~5 # Now safe if output showed commits
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4. Forgetting Reflog Exists
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# ❌ Panic after accidental --hard reset
|
|
||||||
# "I lost my work!"
|
|
||||||
|
|
||||||
# ✅ Check reflog first!
|
|
||||||
git reflog # Find the "lost" commit
|
|
||||||
git reset --hard HEAD@{1} # Recover it
|
|
||||||
```
|
|
||||||
|
|
||||||
## Best Practices
|
|
||||||
|
|
||||||
1. **Always check if commits are pushed before reset:**
|
|
||||||
```bash
|
|
||||||
git log origin/main..HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
2. **Prefer --mixed over --hard:**
|
|
||||||
- You can always discard changes later
|
|
||||||
- Hard to recover if you use --hard by mistake
|
|
||||||
|
|
||||||
3. **Commit often locally, reset before push:**
|
|
||||||
- Make many small local commits
|
|
||||||
- Reset/squash into clean commits before pushing
|
|
||||||
|
|
||||||
4. **Use descriptive commit messages even for local commits:**
|
|
||||||
- Helps when reviewing before reset
|
|
||||||
- Useful when checking reflog
|
|
||||||
|
|
||||||
5. **Know your escape hatch:**
|
|
||||||
```bash
|
|
||||||
git reflog # Your safety net!
|
|
||||||
```
|
|
||||||
|
|
||||||
6. **Communicate with team:**
|
|
||||||
- NEVER reset shared branches (main, develop, etc.)
|
|
||||||
- Only reset your personal feature branches
|
|
||||||
- Only before pushing!
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### "I accidentally reset with --hard and lost work!"
|
|
||||||
|
|
||||||
**Solution:** Check reflog:
|
|
||||||
```bash
|
|
||||||
git reflog
|
|
||||||
# Find the commit before your reset
|
|
||||||
git reset --hard HEAD@{1} # Or the commit hash
|
|
||||||
```
|
|
||||||
|
|
||||||
**Prevention:** Always use --mixed first, then discard if really needed.
|
|
||||||
|
|
||||||
### "I reset but teammates still have my commits"
|
|
||||||
|
|
||||||
**Problem:** You reset and pushed with --force after they pulled.
|
|
||||||
|
|
||||||
**Impact:** Their repository is now broken/inconsistent.
|
|
||||||
|
|
||||||
**Solution:** Communicate! They need to:
|
|
||||||
```bash
|
|
||||||
git fetch
|
|
||||||
git reset --hard origin/main # Or whatever branch
|
|
||||||
```
|
|
||||||
|
|
||||||
**Prevention:** NEVER reset pushed commits!
|
|
||||||
|
|
||||||
### "Reset didn't do what I expected"
|
|
||||||
|
|
||||||
**Issue:** Wrong mode or wrong HEAD~N count.
|
|
||||||
|
|
||||||
**Solution:** Check current state:
|
|
||||||
```bash
|
|
||||||
git status
|
|
||||||
git diff
|
|
||||||
git diff --cached
|
|
||||||
git log --oneline
|
|
||||||
```
|
|
||||||
|
|
||||||
Undo the reset:
|
|
||||||
```bash
|
|
||||||
git reflog
|
|
||||||
git reset HEAD@{1} # Go back to before your reset
|
|
||||||
```
|
|
||||||
|
|
||||||
### "Can't reset - 'fatal: ambiguous argument HEAD~1'"
|
|
||||||
|
|
||||||
**Issue:** No commits to reset (probably first commit).
|
|
||||||
|
|
||||||
**Solution:** You can't reset before the first commit. If you want to remove the first commit entirely:
|
|
||||||
```bash
|
|
||||||
rm -rf .git # Nuclear option - deletes entire repo
|
|
||||||
git init # Start over
|
|
||||||
```
|
|
||||||
|
|
||||||
## Advanced: Reset Internals
|
|
||||||
|
|
||||||
Understanding what reset does under the hood:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reset moves the branch pointer
|
|
||||||
# Before:
|
|
||||||
main → A → B → C (HEAD)
|
|
||||||
|
|
||||||
# After git reset --soft HEAD~1:
|
|
||||||
main → A → B (HEAD)
|
|
||||||
↑
|
|
||||||
C still exists in reflog, just not in branch history
|
|
||||||
|
|
||||||
# The commit object C is still in .git/objects
|
|
||||||
# It's just unreachable from any branch
|
|
||||||
```
|
|
||||||
|
|
||||||
**Key insight:** Reset moves the HEAD and branch pointers backward. The commits still exist temporarily in reflog until garbage collection.
|
|
||||||
|
|
||||||
## Going Further
|
|
||||||
|
|
||||||
Now that you understand reset, you're ready for:
|
|
||||||
|
|
||||||
- **Module 07: Git Stash** - Temporarily save uncommitted work
|
|
||||||
- **Module 08: Multiplayer Git** - Collaborate with complex workflows
|
|
||||||
- **Interactive Rebase** - Advanced history polishing (beyond this workshop)
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
You've learned:
|
|
||||||
|
|
||||||
- ✅ `git reset` rewrites history by moving HEAD backward
|
|
||||||
- ✅ `--soft` keeps changes staged (safest)
|
|
||||||
- ✅ `--mixed` (default) unstages changes
|
|
||||||
- ✅ `--hard` discards everything (most dangerous)
|
|
||||||
- ✅ NEVER reset pushed/shared commits
|
|
||||||
- ✅ Use reflog to recover from mistakes
|
|
||||||
- ✅ Check if commits are pushed before resetting
|
|
||||||
- ✅ Use revert (Module 05) for shared commits
|
|
||||||
|
|
||||||
**The Critical Rule:** Reset is for LOCAL commits ONLY. Once you push, use revert!
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
1. Complete all three challenge scenarios
|
|
||||||
2. Run `./verify.ps1` to check your solutions
|
|
||||||
3. Practice checking if commits are pushed before reset
|
|
||||||
4. Move on to Module 07: Git Stash
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**⚠️ FINAL REMINDER ⚠️**
|
|
||||||
|
|
||||||
**Before any `git reset` command, ask yourself:**
|
|
||||||
|
|
||||||
> "Have I pushed these commits?"
|
|
||||||
|
|
||||||
If YES → Use `git revert` instead!
|
|
||||||
|
|
||||||
If NO → Proceed carefully, choose the right mode.
|
|
||||||
|
|
||||||
**When in doubt, use --mixed instead of --hard!**
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the Module 06 challenge environment to start fresh.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script removes the challenge directory and re-runs setup.ps1
|
|
||||||
to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Resetting Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
Write-Host "[OK] Challenge directory removed" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] No existing challenge directory found" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup to create fresh environment
|
|
||||||
Write-Host "`nRunning setup to create fresh challenge environment..." -ForegroundColor Cyan
|
|
||||||
& "$PSScriptRoot/setup.ps1"
|
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the Module 06 challenge environment for learning git reset.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script creates a challenge directory with three branches demonstrating
|
|
||||||
different reset scenarios:
|
|
||||||
- soft-reset: Reset with --soft (keeps changes staged)
|
|
||||||
- mixed-reset: Reset with --mixed (unstages changes)
|
|
||||||
- hard-reset: Reset with --hard (discards everything) + reflog recovery
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Setting up Module 06: Git Reset Challenge ===" -ForegroundColor Cyan
|
|
||||||
Write-Host "⚠️ WARNING: Git reset is DANGEROUS - use with extreme caution! ⚠️" -ForegroundColor Red
|
|
||||||
|
|
||||||
# Remove existing challenge directory if it exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create fresh challenge directory
|
|
||||||
Write-Host "Creating challenge directory..." -ForegroundColor Green
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize Git repository
|
|
||||||
Write-Host "Initializing Git repository..." -ForegroundColor Green
|
|
||||||
git init | Out-Null
|
|
||||||
|
|
||||||
# Configure git for this repository
|
|
||||||
git config user.name "Workshop Student"
|
|
||||||
git config user.email "student@example.com"
|
|
||||||
|
|
||||||
# Detect the default branch name after first commit (created below)
|
|
||||||
# Will be detected after the initial commit
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Create initial commit (shared by all scenarios)
|
|
||||||
# ============================================================================
|
|
||||||
$readmeContent = @"
|
|
||||||
# Git Reset Practice
|
|
||||||
|
|
||||||
This repository contains practice scenarios for learning git reset.
|
|
||||||
"@
|
|
||||||
Set-Content -Path "README.md" -Value $readmeContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial commit" | Out-Null
|
|
||||||
|
|
||||||
# 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)
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`nScenario 1: Creating soft-reset branch..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Create soft-reset branch from initial commit
|
|
||||||
git switch -c soft-reset | Out-Null
|
|
||||||
|
|
||||||
# Build up scenario 1 commits
|
|
||||||
$projectContent = @"
|
|
||||||
# project.py - Main project file
|
|
||||||
|
|
||||||
def initialize():
|
|
||||||
"""Initialize the project."""
|
|
||||||
print("Project initialized")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
initialize()
|
|
||||||
print("Running application...")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "project.py" -Value $projectContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial project setup" | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add feature A
|
|
||||||
$projectContent = @"
|
|
||||||
# project.py - Main project file
|
|
||||||
|
|
||||||
def initialize():
|
|
||||||
"""Initialize the project."""
|
|
||||||
print("Project initialized")
|
|
||||||
|
|
||||||
def feature_a():
|
|
||||||
"""Feature A implementation."""
|
|
||||||
print("Feature A is working")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
initialize()
|
|
||||||
feature_a()
|
|
||||||
print("Running application...")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "project.py" -Value $projectContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add feature A" | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add feature B
|
|
||||||
$projectContent = @"
|
|
||||||
# project.py - Main project file
|
|
||||||
|
|
||||||
def initialize():
|
|
||||||
"""Initialize the project."""
|
|
||||||
print("Project initialized")
|
|
||||||
|
|
||||||
def feature_a():
|
|
||||||
"""Feature A implementation."""
|
|
||||||
print("Feature A is working")
|
|
||||||
|
|
||||||
def feature_b():
|
|
||||||
"""Feature B implementation."""
|
|
||||||
print("Feature B is working")
|
|
||||||
|
|
||||||
def main():
|
|
||||||
initialize()
|
|
||||||
feature_a()
|
|
||||||
feature_b()
|
|
||||||
print("Running application...")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "project.py" -Value $projectContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add feature B" | Out-Null
|
|
||||||
|
|
||||||
# BAD commit: Add feature C (wrong implementation)
|
|
||||||
$projectContent = @"
|
|
||||||
# project.py - Main project file
|
|
||||||
|
|
||||||
def initialize():
|
|
||||||
"""Initialize the project."""
|
|
||||||
print("Project initialized")
|
|
||||||
|
|
||||||
def feature_a():
|
|
||||||
"""Feature A implementation."""
|
|
||||||
print("Feature A is working")
|
|
||||||
|
|
||||||
def feature_b():
|
|
||||||
"""Feature B implementation."""
|
|
||||||
print("Feature B is working")
|
|
||||||
|
|
||||||
def feature_c():
|
|
||||||
"""Feature C implementation - WRONG!"""
|
|
||||||
print("Feature C has bugs!") # This needs to be re-implemented
|
|
||||||
|
|
||||||
def main():
|
|
||||||
initialize()
|
|
||||||
feature_a()
|
|
||||||
feature_b()
|
|
||||||
feature_c()
|
|
||||||
print("Running application...")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "project.py" -Value $projectContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add feature C - needs better implementation!" | Out-Null
|
|
||||||
|
|
||||||
Write-Host "[CREATED] soft-reset branch with commit to reset --soft" -ForegroundColor Green
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 2: Mixed Reset (--mixed, default)
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`nScenario 2: Creating mixed-reset branch..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Switch back to initial commit and create mixed-reset branch
|
|
||||||
git switch $mainBranch | Out-Null
|
|
||||||
git switch -c mixed-reset | Out-Null
|
|
||||||
|
|
||||||
# Build up scenario 2 commits
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Application entry point
|
|
||||||
|
|
||||||
def start():
|
|
||||||
"""Start the application."""
|
|
||||||
print("Application started")
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Stop the application."""
|
|
||||||
print("Application stopped")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add application lifecycle" | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add logging
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Application entry point
|
|
||||||
|
|
||||||
def log(message):
|
|
||||||
"""Log a message."""
|
|
||||||
print(f"[LOG] {message}")
|
|
||||||
|
|
||||||
def start():
|
|
||||||
"""Start the application."""
|
|
||||||
log("Application started")
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Stop the application."""
|
|
||||||
log("Application stopped")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add logging system" | Out-Null
|
|
||||||
|
|
||||||
# BAD commit 1: Add experimental feature X
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Application entry point
|
|
||||||
|
|
||||||
def log(message):
|
|
||||||
"""Log a message."""
|
|
||||||
print(f"[LOG] {message}")
|
|
||||||
|
|
||||||
def experimental_feature_x():
|
|
||||||
"""Experimental feature - NOT READY!"""
|
|
||||||
log("Feature X is experimental and buggy")
|
|
||||||
|
|
||||||
def start():
|
|
||||||
"""Start the application."""
|
|
||||||
log("Application started")
|
|
||||||
experimental_feature_x()
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Stop the application."""
|
|
||||||
log("Application stopped")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add experimental feature X - REMOVE THIS!" | Out-Null
|
|
||||||
|
|
||||||
# BAD commit 2: Add debug mode (also not ready)
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Application entry point
|
|
||||||
|
|
||||||
DEBUG_MODE = True # Should not be committed!
|
|
||||||
|
|
||||||
def log(message):
|
|
||||||
"""Log a message."""
|
|
||||||
print(f"[LOG] {message}")
|
|
||||||
|
|
||||||
def experimental_feature_x():
|
|
||||||
"""Experimental feature - NOT READY!"""
|
|
||||||
log("Feature X is experimental and buggy")
|
|
||||||
|
|
||||||
def start():
|
|
||||||
"""Start the application."""
|
|
||||||
if DEBUG_MODE:
|
|
||||||
log("DEBUG MODE ACTIVE!")
|
|
||||||
log("Application started")
|
|
||||||
experimental_feature_x()
|
|
||||||
|
|
||||||
def stop():
|
|
||||||
"""Stop the application."""
|
|
||||||
log("Application stopped")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add debug mode - REMOVE THIS TOO!" | Out-Null
|
|
||||||
|
|
||||||
Write-Host "[CREATED] mixed-reset branch with commits to reset --mixed" -ForegroundColor Green
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 3: Hard Reset (--hard) + Reflog Recovery
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`nScenario 3: Creating hard-reset branch..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Switch back to main and create hard-reset branch
|
|
||||||
git switch $mainBranch | Out-Null
|
|
||||||
git switch -c hard-reset | Out-Null
|
|
||||||
|
|
||||||
# Reset to basic state
|
|
||||||
$utilsContent = @"
|
|
||||||
# utils.py - Utility functions
|
|
||||||
|
|
||||||
def helper_a():
|
|
||||||
"""Helper function A."""
|
|
||||||
return "Helper A"
|
|
||||||
|
|
||||||
def helper_b():
|
|
||||||
"""Helper function B."""
|
|
||||||
return "Helper B"
|
|
||||||
"@
|
|
||||||
Set-Content -Path "utils.py" -Value $utilsContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add utility helpers" | Out-Null
|
|
||||||
|
|
||||||
# Good commit: Add helper C
|
|
||||||
$utilsContent = @"
|
|
||||||
# utils.py - Utility functions
|
|
||||||
|
|
||||||
def helper_a():
|
|
||||||
"""Helper function A."""
|
|
||||||
return "Helper A"
|
|
||||||
|
|
||||||
def helper_b():
|
|
||||||
"""Helper function B."""
|
|
||||||
return "Helper B"
|
|
||||||
|
|
||||||
def helper_c():
|
|
||||||
"""Helper function C."""
|
|
||||||
return "Helper C"
|
|
||||||
"@
|
|
||||||
Set-Content -Path "utils.py" -Value $utilsContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add helper C" | Out-Null
|
|
||||||
|
|
||||||
# BAD commit: Add broken helper D (completely wrong)
|
|
||||||
$utilsContent = @"
|
|
||||||
# utils.py - Utility functions
|
|
||||||
|
|
||||||
def helper_a():
|
|
||||||
"""Helper function A."""
|
|
||||||
return "Helper A"
|
|
||||||
|
|
||||||
def helper_b():
|
|
||||||
"""Helper function B."""
|
|
||||||
return "Helper B"
|
|
||||||
|
|
||||||
def helper_c():
|
|
||||||
"""Helper function C."""
|
|
||||||
return "Helper C"
|
|
||||||
|
|
||||||
def helper_d():
|
|
||||||
"""COMPLETELY BROKEN - throw away!"""
|
|
||||||
# This is all wrong and needs to be discarded
|
|
||||||
broken_code = "This doesn't even make sense"
|
|
||||||
return broken_code.nonexistent_method() # Will crash!
|
|
||||||
"@
|
|
||||||
Set-Content -Path "utils.py" -Value $utilsContent
|
|
||||||
git add .
|
|
||||||
git commit -m "Add broken helper D - DISCARD COMPLETELY!" | Out-Null
|
|
||||||
|
|
||||||
Write-Host "[CREATED] hard-reset branch with commit to reset --hard" -ForegroundColor Green
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# Return to soft-reset to start
|
|
||||||
# ============================================================================
|
|
||||||
git switch soft-reset | Out-Null
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n=== Setup Complete! ===`n" -ForegroundColor Green
|
|
||||||
Write-Host "Three reset scenarios have been created:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. soft-reset - Reset --soft (keep changes staged)" -ForegroundColor White
|
|
||||||
Write-Host " 2. mixed-reset - Reset --mixed (unstage changes)" -ForegroundColor White
|
|
||||||
Write-Host " 3. hard-reset - Reset --hard (discard everything) + reflog recovery" -ForegroundColor White
|
|
||||||
Write-Host "`n⚠️ CRITICAL SAFETY REMINDER ⚠️" -ForegroundColor Red
|
|
||||||
Write-Host "NEVER use git reset on commits that have been PUSHED!" -ForegroundColor Red
|
|
||||||
Write-Host "These scenarios are LOCAL ONLY for practice." -ForegroundColor Yellow
|
|
||||||
Write-Host "`nYou are currently on the 'soft-reset' branch." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
|
||||||
Write-Host " 2. Read the README.md for detailed instructions" -ForegroundColor White
|
|
||||||
Write-Host " 3. Complete each reset challenge" -ForegroundColor White
|
|
||||||
Write-Host " 4. Run '..\\verify.ps1' to check your solutions" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,231 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the Module 06 challenge solutions.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that all three reset scenarios have been completed correctly:
|
|
||||||
- soft-reset: Commit reset but changes remain staged
|
|
||||||
- mixed-reset: Commits reset and changes unstaged
|
|
||||||
- hard-reset: Everything reset and discarded
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 06: Git Reset Solutions ===" -ForegroundColor Cyan
|
|
||||||
Write-Host "⚠️ Remember: NEVER reset pushed commits! ⚠️" -ForegroundColor Red
|
|
||||||
|
|
||||||
$allChecksPassed = $true
|
|
||||||
$originalDir = Get-Location
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "challenge")) {
|
|
||||||
Write-Host "[FAIL] Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] Not a git repository. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location $originalDir
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 1: Soft Reset Verification
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`n=== Scenario 1: Soft Reset ===`n" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
git switch soft-reset 2>&1 | Out-Null
|
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Host "[FAIL] soft-reset branch not found" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
# Count commits (should be 4: Initial + project setup + feature A + feature B)
|
|
||||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
|
||||||
|
|
||||||
if ($commitCount -eq 4) {
|
|
||||||
Write-Host "[PASS] Commit count is 4 (feature C commit was reset)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Expected 4 commits, found $commitCount" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use: git reset --soft HEAD~1" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if changes are staged
|
|
||||||
$stagedChanges = git diff --cached --name-only 2>$null
|
|
||||||
if ($stagedChanges) {
|
|
||||||
Write-Host "[PASS] Changes are staged (feature C code in staging area)" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Verify the staged changes contain feature C code
|
|
||||||
$stagedContent = git diff --cached 2>$null
|
|
||||||
if ($stagedContent -match "feature_c") {
|
|
||||||
Write-Host "[PASS] Staged changes contain feature C code" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] Staged changes don't seem to contain feature C" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] No staged changes found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] After --soft reset, changes should remain staged" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check working directory has no unstaged changes to tracked files
|
|
||||||
$unstagedChanges = git diff --name-only 2>$null
|
|
||||||
if (-not $unstagedChanges) {
|
|
||||||
Write-Host "[PASS] No unstaged changes (all changes are staged)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] Found unstaged changes (expected only staged changes)" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 2: Mixed Reset Verification
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`n=== Scenario 2: Mixed Reset ===`n" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
git switch mixed-reset 2>&1 | Out-Null
|
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Host "[FAIL] mixed-reset branch not found" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
# Count commits (should be 3: Initial + lifecycle + logging, bad commits removed)
|
|
||||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
|
||||||
|
|
||||||
if ($commitCount -eq 3) {
|
|
||||||
Write-Host "[PASS] Commit count is 3 (both bad commits were reset)" -ForegroundColor Green
|
|
||||||
} elseif ($commitCount -eq 4) {
|
|
||||||
Write-Host "[INFO] Commit count is 4 (one commit reset, need to reset one more)" -ForegroundColor Yellow
|
|
||||||
Write-Host "[HINT] Use: git reset HEAD~1 (or git reset --mixed HEAD~1)" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use: git reset --mixed HEAD~2 to remove both bad commits" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there are NO staged changes
|
|
||||||
$stagedChanges = git diff --cached --name-only 2>$null
|
|
||||||
if (-not $stagedChanges) {
|
|
||||||
Write-Host "[PASS] No staged changes (--mixed unstages everything)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Found staged changes (--mixed should unstage)" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] After --mixed reset, changes should be unstaged" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there ARE unstaged changes in working directory
|
|
||||||
$unstagedChanges = git diff --name-only 2>$null
|
|
||||||
if ($unstagedChanges) {
|
|
||||||
Write-Host "[PASS] Unstaged changes present in working directory" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Verify unstaged changes contain the experimental/debug code
|
|
||||||
$workingContent = git diff 2>$null
|
|
||||||
if ($workingContent -match "experimental|DEBUG") {
|
|
||||||
Write-Host "[PASS] Unstaged changes contain the reset code" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] Unstaged changes don't contain expected code" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] No unstaged changes found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] After --mixed reset, changes should be in working directory (unstaged)" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# ============================================================================
|
|
||||||
# SCENARIO 3: Hard Reset Verification
|
|
||||||
# ============================================================================
|
|
||||||
Write-Host "`n=== Scenario 3: Hard Reset ===`n" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
git switch hard-reset 2>&1 | Out-Null
|
|
||||||
|
|
||||||
if ($LASTEXITCODE -ne 0) {
|
|
||||||
Write-Host "[FAIL] hard-reset branch not found" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
# Count commits (should be 3: Initial + utilities + helper C, bad commit removed)
|
|
||||||
$commitCount = [int](git rev-list --count HEAD 2>$null)
|
|
||||||
|
|
||||||
if ($commitCount -eq 3) {
|
|
||||||
Write-Host "[PASS] Commit count is 3 (broken commit was reset)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Expected 3 commits, found $commitCount" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use: git reset --hard HEAD~1" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there are NO staged changes
|
|
||||||
$stagedChanges = git diff --cached --name-only 2>$null
|
|
||||||
if (-not $stagedChanges) {
|
|
||||||
Write-Host "[PASS] No staged changes (--hard discards everything)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Found staged changes (--hard should discard all)" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there are NO unstaged changes
|
|
||||||
$unstagedChanges = git diff --name-only 2>$null
|
|
||||||
if (-not $unstagedChanges) {
|
|
||||||
Write-Host "[PASS] No unstaged changes (--hard discards everything)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Found unstaged changes (--hard should discard all)" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check working directory is clean
|
|
||||||
$statusOutput = git status --porcelain 2>$null
|
|
||||||
if (-not $statusOutput) {
|
|
||||||
Write-Host "[PASS] Working directory is completely clean" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] Working directory has some changes" -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify the file doesn't have the broken code
|
|
||||||
if (Test-Path "utils.py") {
|
|
||||||
$utilsContent = Get-Content "utils.py" -Raw
|
|
||||||
if ($utilsContent -notmatch "helper_d") {
|
|
||||||
Write-Host "[PASS] Broken helper_d function is gone" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Broken helper_d still exists (wasn't reset)" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($utilsContent -match "helper_c") {
|
|
||||||
Write-Host "[PASS] Good helper_c function is preserved" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Good helper_c function missing" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location $originalDir
|
|
||||||
|
|
||||||
# Final summary
|
|
||||||
Write-Host ""
|
|
||||||
if ($allChecksPassed) {
|
|
||||||
Write-Host "==========================================" -ForegroundColor Green
|
|
||||||
Write-Host " CONGRATULATIONS! ALL SCENARIOS PASSED!" -ForegroundColor Green
|
|
||||||
Write-Host "==========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou've mastered git reset!" -ForegroundColor Cyan
|
|
||||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
|
||||||
Write-Host " ✓ Resetting commits with --soft (keep staged)" -ForegroundColor White
|
|
||||||
Write-Host " ✓ Resetting commits with --mixed (unstage)" -ForegroundColor White
|
|
||||||
Write-Host " ✓ Resetting commits with --hard (discard all)" -ForegroundColor White
|
|
||||||
Write-Host " ✓ The DANGER of reset on shared history" -ForegroundColor White
|
|
||||||
Write-Host "`n⚠️ CRITICAL REMINDER ⚠️" -ForegroundColor Red
|
|
||||||
Write-Host "NEVER use 'git reset' on commits you've already PUSHED!" -ForegroundColor Red
|
|
||||||
Write-Host "Always use 'git revert' (Module 05) for shared commits!" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nReady for Module 07: Git Stash!" -ForegroundColor Green
|
|
||||||
Write-Host ""
|
|
||||||
exit 0
|
|
||||||
} else {
|
|
||||||
Write-Host "[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
|
|
||||||
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
|
|
||||||
Write-Host "[REMINDER] Reset is ONLY for local, un-pushed commits!" -ForegroundColor Yellow
|
|
||||||
Write-Host ""
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
@@ -1,130 +0,0 @@
|
|||||||
# Module 07: Rebasing
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what rebasing is and how it works
|
|
||||||
- Know the difference between merge and rebase
|
|
||||||
- Perform a rebase to integrate changes from one branch to another
|
|
||||||
- Understand when to use rebase vs merge
|
|
||||||
- Know the golden rule of rebasing
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You have a repository with a `main` branch and a `feature` branch. While you were working on the feature branch, new commits were added to `main`. You want to incorporate those changes into your feature branch while maintaining a clean, linear history.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Review the current commit history
|
|
||||||
2. Rebase the `feature` branch onto `main`
|
|
||||||
3. Verify that the history is now linear
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What is Rebasing?
|
|
||||||
|
|
||||||
Rebasing is the process of moving or combining a sequence of commits to a new base commit. Instead of creating a merge commit like `git merge` does, rebasing rewrites the commit history by replaying your commits on top of another branch.
|
|
||||||
|
|
||||||
### Rebase vs Merge
|
|
||||||
|
|
||||||
**Merge:**
|
|
||||||
```
|
|
||||||
A---B---C feature
|
|
||||||
/ \
|
|
||||||
D---E---F---G main
|
|
||||||
```
|
|
||||||
Creates a merge commit (G) that ties the histories together.
|
|
||||||
|
|
||||||
**Rebase:**
|
|
||||||
```
|
|
||||||
A'--B'--C' feature
|
|
||||||
/
|
|
||||||
D---E---F main
|
|
||||||
```
|
|
||||||
Replays commits A, B, C on top of F, creating new commits A', B', C' with the same changes but different commit hashes.
|
|
||||||
|
|
||||||
### Benefits of Rebasing
|
|
||||||
|
|
||||||
- **Cleaner history**: Linear history is easier to read and understand
|
|
||||||
- **Simpler log**: No merge commits cluttering the history
|
|
||||||
- **Easier bisecting**: Finding bugs with `git bisect` is simpler with linear history
|
|
||||||
|
|
||||||
### The Golden Rule of Rebasing
|
|
||||||
|
|
||||||
**Never rebase commits that have been pushed to a public/shared repository.**
|
|
||||||
|
|
||||||
Why? Because rebasing rewrites history by creating new commits. If others have based work on the original commits, rebasing will cause serious problems for collaborators.
|
|
||||||
|
|
||||||
**Safe to rebase:**
|
|
||||||
- Local commits not yet pushed
|
|
||||||
- Feature branches you're working on alone
|
|
||||||
- Cleaning up your work before creating a pull request
|
|
||||||
|
|
||||||
**Never rebase:**
|
|
||||||
- Commits already pushed to a shared branch (like `main` or `develop`)
|
|
||||||
- Commits that others might have based work on
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Rebase current branch onto another branch
|
|
||||||
git rebase <branch-name>
|
|
||||||
|
|
||||||
# View commit history as a graph
|
|
||||||
git log --oneline --graph --all
|
|
||||||
|
|
||||||
# If conflicts occur during rebase:
|
|
||||||
# 1. Resolve conflicts in files
|
|
||||||
# 2. Stage the resolved files
|
|
||||||
git add <file>
|
|
||||||
# 3. Continue the rebase
|
|
||||||
git rebase --continue
|
|
||||||
|
|
||||||
# Abort a rebase if something goes wrong
|
|
||||||
git rebase --abort
|
|
||||||
|
|
||||||
# Check which branch you're on
|
|
||||||
git branch
|
|
||||||
|
|
||||||
# Switch to a branch
|
|
||||||
git switch <branch-name>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- You're on the feature branch
|
|
||||||
- The rebase was completed successfully
|
|
||||||
- The history is linear (no merge commits)
|
|
||||||
- All commits from both branches are present
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Always check your commit graph with `git log --oneline --graph --all` before and after rebasing
|
|
||||||
- If you encounter conflicts during rebase, resolve them just like merge conflicts
|
|
||||||
- Use `git rebase --abort` if you want to cancel the rebase and start over
|
|
||||||
- Rebasing rewrites history, so the commit hashes will change
|
|
||||||
- Only rebase local commits that haven't been shared with others
|
|
||||||
|
|
||||||
## When to Use Rebase vs Merge
|
|
||||||
|
|
||||||
**Use Rebase when:**
|
|
||||||
- You want a clean, linear history
|
|
||||||
- Working on a local feature branch that hasn't been shared
|
|
||||||
- Updating your feature branch with the latest changes from main
|
|
||||||
- You want to clean up commits before submitting a pull request
|
|
||||||
|
|
||||||
**Use Merge when:**
|
|
||||||
- Working on a shared/public branch
|
|
||||||
- You want to preserve the complete history including when branches diverged
|
|
||||||
- You're merging a completed feature into main
|
|
||||||
- You want to be safe and avoid rewriting history
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Rebasing is a powerful tool for maintaining a clean project history. While merging is safer and preserves exact history, rebasing creates a more readable linear timeline. Understanding both techniques and knowing when to use each is essential for effective Git workflow management.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the rebasing challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Removes the existing challenge directory and runs setup.ps1
|
|
||||||
to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "Running setup script...`n" -ForegroundColor Cyan
|
|
||||||
& ".\setup.ps1"
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the rebasing challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with diverged branches to practice rebasing.
|
|
||||||
The feature branch needs to be rebased onto main.
|
|
||||||
#>
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create challenge directory
|
|
||||||
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize git repository
|
|
||||||
git init | Out-Null
|
|
||||||
git config user.name "Workshop User" | Out-Null
|
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
|
||||||
|
|
||||||
# Create initial file and commit
|
|
||||||
$readme = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A sample project for learning Git rebasing.
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $readme
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Initial commit" | Out-Null
|
|
||||||
|
|
||||||
# Create and switch to feature branch
|
|
||||||
git checkout -b feature | Out-Null
|
|
||||||
|
|
||||||
# Add commits on feature branch
|
|
||||||
$feature1 = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A sample project for learning Git rebasing.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Feature A: User authentication
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $feature1
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add feature A" | Out-Null
|
|
||||||
|
|
||||||
$feature2 = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A sample project for learning Git rebasing.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- Feature A: User authentication
|
|
||||||
- Feature B: Data validation
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $feature2
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add feature B" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main and add commits (simulating other work happening on main)
|
|
||||||
git checkout main | Out-Null
|
|
||||||
|
|
||||||
$main1 = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A sample project for learning Git rebasing.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Run \`npm install\` to install dependencies.
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $main1
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add installation instructions" | Out-Null
|
|
||||||
|
|
||||||
$main2 = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A sample project for learning Git rebasing.
|
|
||||||
|
|
||||||
## Installation
|
|
||||||
|
|
||||||
Run \`npm install\` to install dependencies.
|
|
||||||
|
|
||||||
## Configuration
|
|
||||||
|
|
||||||
Copy \`config.example.json\` to \`config.json\` and update settings.
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $main2
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add configuration instructions" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to feature branch
|
|
||||||
git checkout feature | Out-Null
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "Challenge environment created!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou are now on the 'feature' branch." -ForegroundColor Cyan
|
|
||||||
Write-Host "The 'main' branch has new commits that aren't in your feature branch." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
|
||||||
Write-Host "2. View the commit history: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host "3. Rebase the 'feature' branch onto 'main'" -ForegroundColor White
|
|
||||||
Write-Host "4. View the history again to see the linear result" -ForegroundColor White
|
|
||||||
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
|
||||||
@@ -1,154 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the rebasing challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user successfully rebased the feature branch onto main,
|
|
||||||
resulting in a clean, linear history.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check current branch
|
|
||||||
$currentBranch = git branch --show-current 2>$null
|
|
||||||
if ($currentBranch -ne "feature") {
|
|
||||||
Write-Host "[FAIL] You should be on the 'feature' branch." -ForegroundColor Red
|
|
||||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
|
||||||
Write-Host "Hint: Use 'git checkout feature' to switch to feature branch" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if there's an ongoing rebase
|
|
||||||
if (Test-Path ".git/rebase-merge") {
|
|
||||||
Write-Host "[FAIL] Rebase is not complete. There may be unresolved conflicts." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Resolve any conflicts, then use:" -ForegroundColor Yellow
|
|
||||||
Write-Host " git add <file>" -ForegroundColor White
|
|
||||||
Write-Host " git rebase --continue" -ForegroundColor White
|
|
||||||
Write-Host "Or abort with: git rebase --abort" -ForegroundColor White
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all commits on feature branch
|
|
||||||
$featureCommits = git log --oneline feature 2>$null
|
|
||||||
if (-not $featureCommits) {
|
|
||||||
Write-Host "[FAIL] No commits found on feature branch." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check commit count (should be 5: 1 initial + 2 main + 2 feature)
|
|
||||||
$commitCount = (git rev-list --count feature 2>$null)
|
|
||||||
if ($commitCount -ne 5) {
|
|
||||||
Write-Host "[FAIL] Expected 5 commits on feature branch, found $commitCount" -ForegroundColor Red
|
|
||||||
Write-Host "Hint: The rebased feature branch should contain:" -ForegroundColor Yellow
|
|
||||||
Write-Host " - 1 initial commit" -ForegroundColor White
|
|
||||||
Write-Host " - 2 commits from main" -ForegroundColor White
|
|
||||||
Write-Host " - 2 commits from feature" -ForegroundColor White
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get commit messages in reverse chronological order (newest first)
|
|
||||||
$commits = git log --pretty=format:"%s" feature 2>$null
|
|
||||||
|
|
||||||
# Convert to array and reverse to get chronological order (oldest first)
|
|
||||||
$commitArray = $commits -split "`n"
|
|
||||||
[array]::Reverse($commitArray)
|
|
||||||
|
|
||||||
# Check that commits are in the expected order after rebase
|
|
||||||
$expectedOrder = @(
|
|
||||||
"Initial commit",
|
|
||||||
"Add installation instructions",
|
|
||||||
"Add configuration instructions",
|
|
||||||
"Add feature A",
|
|
||||||
"Add feature B"
|
|
||||||
)
|
|
||||||
|
|
||||||
$orderCorrect = $true
|
|
||||||
for ($i = 0; $i -lt $expectedOrder.Length; $i++) {
|
|
||||||
if ($commitArray[$i] -ne $expectedOrder[$i]) {
|
|
||||||
$orderCorrect = $false
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $orderCorrect) {
|
|
||||||
Write-Host "[FAIL] Commit order is incorrect after rebase." -ForegroundColor Red
|
|
||||||
Write-Host "Expected order (oldest to newest):" -ForegroundColor Yellow
|
|
||||||
$expectedOrder | ForEach-Object { Write-Host " $_" -ForegroundColor White }
|
|
||||||
Write-Host "`nActual order:" -ForegroundColor Yellow
|
|
||||||
$commitArray | ForEach-Object { Write-Host " $_" -ForegroundColor White }
|
|
||||||
Write-Host "`nHint: The feature commits should come AFTER the main commits" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that main branch still has only 3 commits (initial + 2 main commits)
|
|
||||||
$mainCommitCount = (git rev-list --count main 2>$null)
|
|
||||||
if ($mainCommitCount -ne 3) {
|
|
||||||
Write-Host "[FAIL] Main branch should have 3 commits, found $mainCommitCount" -ForegroundColor Red
|
|
||||||
Write-Host "Hint: You should rebase feature onto main, not the other way around" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there are no merge commits (parent count should be 1 for all commits)
|
|
||||||
$mergeCommits = git log --merges --oneline feature 2>$null
|
|
||||||
if ($mergeCommits) {
|
|
||||||
Write-Host "[FAIL] Found merge commits in history. Rebasing should create a linear history." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Use 'git rebase main' instead of 'git merge main'" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify that feature branch contains all commits from main
|
|
||||||
$mainCommits = git log --pretty=format:"%s" main 2>$null
|
|
||||||
$featureCommitMessages = git log --pretty=format:"%s" feature 2>$null
|
|
||||||
|
|
||||||
foreach ($mainCommit in ($mainCommits -split "`n")) {
|
|
||||||
if ($featureCommitMessages -notcontains $mainCommit) {
|
|
||||||
Write-Host "[FAIL] Feature branch is missing commits from main." -ForegroundColor Red
|
|
||||||
Write-Host "Missing: $mainCommit" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Success!
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
|
||||||
Write-Host "- Rebased the feature branch onto main" -ForegroundColor White
|
|
||||||
Write-Host "- Created a clean, linear commit history" -ForegroundColor White
|
|
||||||
Write-Host "- Preserved all commits from both branches" -ForegroundColor White
|
|
||||||
Write-Host "`nYour feature branch now has a linear history!" -ForegroundColor Green
|
|
||||||
Write-Host "Run 'git log --oneline --graph --all' to see the result.`n" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
|
||||||
@@ -1,159 +0,0 @@
|
|||||||
# Module 08: Interactive Rebase
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what interactive rebase is and when to use it
|
|
||||||
- Learn the different interactive rebase commands (pick, reword, squash, fixup, drop)
|
|
||||||
- Clean up commit history by squashing related commits
|
|
||||||
- Reword commit messages to be more descriptive
|
|
||||||
- Use reset and commit techniques to achieve similar results
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You have a feature branch with messy commit history - multiple "WIP" commits, typos in commit messages, and commits that should be combined. Before submitting a pull request, you want to clean up this history.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Review the current commit history with its messy commits
|
|
||||||
2. Combine the four feature commits into a single, well-described commit
|
|
||||||
3. Keep the initial commit unchanged
|
|
||||||
4. End up with a clean, professional commit history
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What is Interactive Rebase?
|
|
||||||
|
|
||||||
Interactive rebase (`git rebase -i`) is a powerful tool that lets you modify commit history. Unlike regular rebase which simply moves commits, interactive rebase lets you:
|
|
||||||
- Reorder commits
|
|
||||||
- Edit commit messages (reword)
|
|
||||||
- Combine multiple commits (squash/fixup)
|
|
||||||
- Split commits into smaller ones
|
|
||||||
- Delete commits entirely
|
|
||||||
- Edit the contents of commits
|
|
||||||
|
|
||||||
### Interactive Rebase Commands
|
|
||||||
|
|
||||||
When you run `git rebase -i HEAD~3`, Git opens an editor with a list of commits and commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
pick abc1234 First commit
|
|
||||||
pick def5678 Second commit
|
|
||||||
pick ghi9012 Third commit
|
|
||||||
```
|
|
||||||
|
|
||||||
You can change `pick` to other commands:
|
|
||||||
|
|
||||||
- **pick**: Keep the commit as-is
|
|
||||||
- **reword**: Keep the commit but edit its message
|
|
||||||
- **edit**: Pause the rebase to amend the commit
|
|
||||||
- **squash**: Combine this commit with the previous one, keeping both messages
|
|
||||||
- **fixup**: Like squash, but discard this commit's message
|
|
||||||
- **drop**: Remove the commit entirely
|
|
||||||
|
|
||||||
### When to Use Interactive Rebase
|
|
||||||
|
|
||||||
Interactive rebase is perfect for:
|
|
||||||
- Cleaning up work-in-progress commits before creating a pull request
|
|
||||||
- Fixing typos in commit messages
|
|
||||||
- Combining related commits into logical units
|
|
||||||
- Removing debug commits or experimental code
|
|
||||||
- Creating a clean, professional history
|
|
||||||
|
|
||||||
**Remember**: Only rebase commits that haven't been pushed to a shared branch!
|
|
||||||
|
|
||||||
## Alternative Approach: Reset and Recommit
|
|
||||||
|
|
||||||
Since interactive rebase requires an interactive editor, this challenge uses an alternative approach that achieves the same result:
|
|
||||||
|
|
||||||
1. **Reset soft**: Move HEAD back while keeping changes staged
|
|
||||||
```bash
|
|
||||||
git reset --soft HEAD~4
|
|
||||||
```
|
|
||||||
This keeps all changes from the last 4 commits but "uncommits" them.
|
|
||||||
|
|
||||||
2. **Create a new commit**: Commit all changes with a clean message
|
|
||||||
```bash
|
|
||||||
git commit -m "Your new clean commit message"
|
|
||||||
```
|
|
||||||
|
|
||||||
This technique is useful when you want to combine multiple commits into one without using interactive rebase.
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View commit history
|
|
||||||
git log --oneline
|
|
||||||
|
|
||||||
# See the last N commits in detail
|
|
||||||
git log -n 5
|
|
||||||
|
|
||||||
# Reset to N commits back, keeping changes staged
|
|
||||||
git reset --soft HEAD~N
|
|
||||||
|
|
||||||
# Reset to N commits back, keeping changes unstaged
|
|
||||||
git reset --mixed HEAD~N
|
|
||||||
|
|
||||||
# Reset to N commits back, discarding all changes (DANGEROUS!)
|
|
||||||
git reset --hard HEAD~N
|
|
||||||
|
|
||||||
# Create a new commit
|
|
||||||
git commit -m "message"
|
|
||||||
|
|
||||||
# Amend the last commit
|
|
||||||
git commit --amend -m "new message"
|
|
||||||
|
|
||||||
# Interactive rebase (requires interactive editor)
|
|
||||||
git rebase -i HEAD~N
|
|
||||||
|
|
||||||
# Check current status
|
|
||||||
git status
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- You have exactly 2 commits total (initial + your combined feature commit)
|
|
||||||
- The feature commit contains all the changes from the original 4 feature commits
|
|
||||||
- The commit message is clean and descriptive
|
|
||||||
- All expected files are present
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. View the current messy commit history: `git log --oneline`
|
|
||||||
3. Use `git reset --soft HEAD~4` to uncommit the last 4 commits while keeping changes
|
|
||||||
4. Check status with `git status` - you should see all changes staged
|
|
||||||
5. Create a new commit with a clean message: `git commit -m "Add user profile feature with validation"`
|
|
||||||
6. Verify with `git log --oneline` - you should now have clean history
|
|
||||||
7. Run the verification script
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Always check your history with `git log --oneline` before and after
|
|
||||||
- `git reset --soft` is safer than `--hard` because it keeps your changes
|
|
||||||
- You can use `git reset --soft HEAD~N` where N is the number of commits to undo
|
|
||||||
- If you make a mistake, run `.\reset.ps1` to start over
|
|
||||||
- In real projects with interactive rebase, you would use `git rebase -i HEAD~N` and modify the commit list in the editor
|
|
||||||
|
|
||||||
## What is Git Reset?
|
|
||||||
|
|
||||||
`git reset` moves the current branch pointer to a different commit:
|
|
||||||
|
|
||||||
- **--soft**: Moves HEAD but keeps changes staged
|
|
||||||
- **--mixed** (default): Moves HEAD and unstages changes, but keeps them in working directory
|
|
||||||
- **--hard**: Moves HEAD and discards all changes (dangerous!)
|
|
||||||
|
|
||||||
Reset is useful for:
|
|
||||||
- Undoing commits while keeping changes (`--soft`)
|
|
||||||
- Unstaging files (`--mixed`)
|
|
||||||
- Discarding commits entirely (`--hard`)
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Cleaning up commit history is an important skill for professional development. Whether you use interactive rebase or reset-and-recommit, the goal is the same: create a clear, logical history that makes your code review easier and your project history more understandable. Messy WIP commits are fine during development, but cleaning them up before merging shows attention to detail and makes your codebase more maintainable.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the interactive rebase challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Removes the existing challenge directory and runs setup.ps1
|
|
||||||
to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "Running setup script...`n" -ForegroundColor Cyan
|
|
||||||
& ".\setup.ps1"
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the interactive rebase challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with messy commit history that needs to be
|
|
||||||
cleaned up using reset and recommit techniques.
|
|
||||||
#>
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create challenge directory
|
|
||||||
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize git repository
|
|
||||||
git init | Out-Null
|
|
||||||
git config user.name "Workshop User" | Out-Null
|
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
|
||||||
|
|
||||||
# Create initial commit
|
|
||||||
$readme = @"
|
|
||||||
# User Management System
|
|
||||||
|
|
||||||
A simple user management application.
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $readme
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Initial commit" | Out-Null
|
|
||||||
|
|
||||||
# Create messy commits that should be squashed
|
|
||||||
|
|
||||||
# Commit 1: WIP user profile
|
|
||||||
$userProfile = @"
|
|
||||||
class UserProfile:
|
|
||||||
def __init__(self, name, email):
|
|
||||||
self.name = name
|
|
||||||
self.email = email
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "user_profile.py" -Value $userProfile
|
|
||||||
git add user_profile.py
|
|
||||||
git commit -m "WIP: user profile" | Out-Null
|
|
||||||
|
|
||||||
# Commit 2: Add validation (typo in message)
|
|
||||||
$userProfileWithValidation = @"
|
|
||||||
class UserProfile:
|
|
||||||
def __init__(self, name, email):
|
|
||||||
self.name = name
|
|
||||||
self.email = email
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
if not self.name or not self.email:
|
|
||||||
raise ValueError('Name and email are required')
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "user_profile.py" -Value $userProfileWithValidation
|
|
||||||
git add user_profile.py
|
|
||||||
git commit -m "add validaton" | Out-Null # Intentional typo
|
|
||||||
|
|
||||||
# Commit 3: Fix validation
|
|
||||||
$userProfileFixed = @"
|
|
||||||
class UserProfile:
|
|
||||||
def __init__(self, name, email):
|
|
||||||
self.name = name
|
|
||||||
self.email = email
|
|
||||||
|
|
||||||
def validate(self):
|
|
||||||
if not self.name or not self.email:
|
|
||||||
raise ValueError('Name and email are required')
|
|
||||||
if '@' not in self.email:
|
|
||||||
raise ValueError('Invalid email format')
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "user_profile.py" -Value $userProfileFixed
|
|
||||||
git add user_profile.py
|
|
||||||
git commit -m "fix validation bug" | Out-Null
|
|
||||||
|
|
||||||
# Commit 4: Add tests (another WIP commit)
|
|
||||||
$tests = @"
|
|
||||||
import unittest
|
|
||||||
from user_profile import UserProfile
|
|
||||||
|
|
||||||
class TestUserProfile(unittest.TestCase):
|
|
||||||
def test_validate_correct_user_data(self):
|
|
||||||
user = UserProfile('John', 'john@example.com')
|
|
||||||
self.assertTrue(user.validate())
|
|
||||||
|
|
||||||
def test_reject_missing_name(self):
|
|
||||||
user = UserProfile('', 'john@example.com')
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
user.validate()
|
|
||||||
|
|
||||||
def test_reject_invalid_email(self):
|
|
||||||
user = UserProfile('John', 'invalid-email')
|
|
||||||
with self.assertRaises(ValueError):
|
|
||||||
user.validate()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
unittest.main()
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "test_user_profile.py" -Value $tests
|
|
||||||
git add test_user_profile.py
|
|
||||||
git commit -m "WIP tests" | Out-Null
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "Challenge environment created!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou have a repository with messy commit history:" -ForegroundColor Cyan
|
|
||||||
Write-Host "- WIP commits" -ForegroundColor Yellow
|
|
||||||
Write-Host "- Typos in commit messages" -ForegroundColor Yellow
|
|
||||||
Write-Host "- Multiple commits that should be combined" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
|
||||||
Write-Host "2. View the messy history: git log --oneline" -ForegroundColor White
|
|
||||||
Write-Host "3. Use 'git reset --soft HEAD~4' to uncommit the last 4 commits" -ForegroundColor White
|
|
||||||
Write-Host "4. Create a single clean commit with a descriptive message" -ForegroundColor White
|
|
||||||
Write-Host " Example: git commit -m 'Add user profile feature with validation'" -ForegroundColor White
|
|
||||||
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the interactive rebase challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user successfully cleaned up the commit history
|
|
||||||
by combining multiple messy commits into a single clean commit.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check commit count (should be exactly 2: initial + combined feature commit)
|
|
||||||
$commitCount = (git rev-list --count HEAD 2>$null)
|
|
||||||
if ($commitCount -ne 2) {
|
|
||||||
Write-Host "[FAIL] Expected exactly 2 commits, found $commitCount" -ForegroundColor Red
|
|
||||||
if ($commitCount -gt 2) {
|
|
||||||
Write-Host "Hint: You have too many commits. Use 'git reset --soft HEAD~N' to combine them." -ForegroundColor Yellow
|
|
||||||
Write-Host " Where N is the number of commits to undo (should be 4 in this case)." -ForegroundColor Yellow
|
|
||||||
} else {
|
|
||||||
Write-Host "Hint: You may have reset too far back. Run ../reset.ps1 to start over." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get commit messages
|
|
||||||
$commits = git log --pretty=format:"%s" 2>$null
|
|
||||||
$commitArray = $commits -split "`n"
|
|
||||||
|
|
||||||
# Check that first commit is still the initial commit
|
|
||||||
if ($commitArray[1] -ne "Initial commit") {
|
|
||||||
Write-Host "[FAIL] The initial commit should remain unchanged." -ForegroundColor Red
|
|
||||||
Write-Host "Expected first commit: 'Initial commit'" -ForegroundColor Yellow
|
|
||||||
Write-Host "Found: '$($commitArray[1])'" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that the second commit message is clean (not WIP, not with typos)
|
|
||||||
$featureCommit = $commitArray[0]
|
|
||||||
if ($featureCommit -match "WIP|wip") {
|
|
||||||
Write-Host "[FAIL] Commit message still contains 'WIP'." -ForegroundColor Red
|
|
||||||
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
|
|
||||||
Write-Host "Hint: Create a clean, descriptive commit message." -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($featureCommit -match "validaton") {
|
|
||||||
Write-Host "[FAIL] Commit message contains a typo ('validaton')." -ForegroundColor Red
|
|
||||||
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
|
|
||||||
Write-Host "Hint: Use a properly spelled commit message." -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that commit message is not empty and has reasonable length
|
|
||||||
if ($featureCommit.Length -lt 10) {
|
|
||||||
Write-Host "[FAIL] Commit message is too short. Be more descriptive." -ForegroundColor Red
|
|
||||||
Write-Host "Current message: '$featureCommit'" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that required files exist
|
|
||||||
if (-not (Test-Path "user_profile.py")) {
|
|
||||||
Write-Host "[FAIL] user_profile.py not found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path "test_user_profile.py")) {
|
|
||||||
Write-Host "[FAIL] test_user_profile.py not found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that user_profile.py contains all expected features
|
|
||||||
$userProfileContent = Get-Content "user_profile.py" -Raw
|
|
||||||
|
|
||||||
# Should have the class
|
|
||||||
if ($userProfileContent -notmatch "class UserProfile") {
|
|
||||||
Write-Host "[FAIL] user_profile.py should contain UserProfile class." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Should have validation method
|
|
||||||
if ($userProfileContent -notmatch "def validate\(") {
|
|
||||||
Write-Host "[FAIL] user_profile.py should contain validate() method." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Should have email format validation (the final fix from commit 3)
|
|
||||||
if ($userProfileContent -notmatch "'@'.*in.*email|email.*in.*'@'") {
|
|
||||||
Write-Host "[FAIL] user_profile.py should contain email format validation." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Make sure all changes from all 4 commits are included." -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that test file has content
|
|
||||||
$testContent = Get-Content "test_user_profile.py" -Raw
|
|
||||||
|
|
||||||
if ($testContent -notmatch "class.*TestUserProfile") {
|
|
||||||
Write-Host "[FAIL] test_user_profile.py should contain TestUserProfile tests." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that we have at least 3 test cases
|
|
||||||
$testMatches = ([regex]::Matches($testContent, "def test_")).Count
|
|
||||||
if ($testMatches -lt 3) {
|
|
||||||
Write-Host "[FAIL] test_user_profile.py should contain at least 3 test cases." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify that the latest commit contains changes to both files
|
|
||||||
$filesInLastCommit = git diff-tree --no-commit-id --name-only -r HEAD 2>$null
|
|
||||||
if ($filesInLastCommit -notcontains "user_profile.py") {
|
|
||||||
Write-Host "[FAIL] The feature commit should include user_profile.py" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($filesInLastCommit -notcontains "test_user_profile.py") {
|
|
||||||
Write-Host "[FAIL] The feature commit should include test_user_profile.py" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Success!
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
|
||||||
Write-Host "- Combined 4 messy commits into 1 clean commit" -ForegroundColor White
|
|
||||||
Write-Host "- Created a descriptive commit message" -ForegroundColor White
|
|
||||||
Write-Host "- Preserved all code changes" -ForegroundColor White
|
|
||||||
Write-Host "- Cleaned up the commit history" -ForegroundColor White
|
|
||||||
Write-Host "`nYour commit history is now clean and professional!" -ForegroundColor Green
|
|
||||||
Write-Host "Run 'git log --oneline' to see the result.`n" -ForegroundColor Cyan
|
|
||||||
Write-Host "Key takeaway: Clean commit history makes code review easier" -ForegroundColor Yellow
|
|
||||||
Write-Host "and your project more maintainable.`n" -ForegroundColor Yellow
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
|
||||||
@@ -1,208 +0,0 @@
|
|||||||
# Module 13: Worktrees
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what Git worktrees are and when to use them
|
|
||||||
- Create and manage multiple working directories for the same repository
|
|
||||||
- Work on multiple branches simultaneously
|
|
||||||
- Understand the benefits of worktrees over stashing or cloning
|
|
||||||
- Remove and clean up worktrees
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You're working on a feature when an urgent bug report comes in. Instead of stashing your work or creating a separate clone, you'll use Git worktrees to work on both the feature and the bugfix simultaneously in different directories.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Create a worktree for the bugfix on a separate branch
|
|
||||||
2. Fix the bug in the worktree
|
|
||||||
3. Commit and verify the fix
|
|
||||||
4. Continue working on your feature in the main working directory
|
|
||||||
5. Clean up the worktree when done
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What are Git Worktrees?
|
|
||||||
|
|
||||||
A worktree is an additional working directory attached to the same repository. Each worktree can have a different branch checked out, allowing you to work on multiple branches simultaneously without switching.
|
|
||||||
|
|
||||||
### Traditional Workflow vs Worktrees
|
|
||||||
|
|
||||||
**Traditional (switching branches):**
|
|
||||||
```
|
|
||||||
main-repo/
|
|
||||||
- Switch to bugfix branch
|
|
||||||
- Fix bug
|
|
||||||
- Switch back to feature branch
|
|
||||||
- Continue feature work
|
|
||||||
- (Requires stashing or committing incomplete work)
|
|
||||||
```
|
|
||||||
|
|
||||||
**With Worktrees:**
|
|
||||||
```
|
|
||||||
main-repo/ <- feature branch
|
|
||||||
worktrees/bugfix/ <- bugfix branch
|
|
||||||
|
|
||||||
Work in both simultaneously!
|
|
||||||
```
|
|
||||||
|
|
||||||
### Why Use Worktrees?
|
|
||||||
|
|
||||||
**Advantages:**
|
|
||||||
- Work on multiple branches at the same time
|
|
||||||
- No need to stash or commit incomplete work
|
|
||||||
- Each worktree has its own working directory and index
|
|
||||||
- Share the same Git history (one `.git` directory)
|
|
||||||
- Faster than cloning the entire repository
|
|
||||||
- Perfect for code reviews, comparisons, or parallel development
|
|
||||||
|
|
||||||
**Use Cases:**
|
|
||||||
- Urgent bug fixes while working on a feature
|
|
||||||
- Code reviews (checkout PR in separate worktree)
|
|
||||||
- Comparing implementations side by side
|
|
||||||
- Running tests on one branch while coding on another
|
|
||||||
- Building different versions simultaneously
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# List all worktrees
|
|
||||||
git worktree list
|
|
||||||
|
|
||||||
# Add a new worktree
|
|
||||||
git worktree add <path> <branch>
|
|
||||||
git worktree add ../bugfix bugfix-branch
|
|
||||||
|
|
||||||
# Create new branch in worktree
|
|
||||||
git worktree add <path> -b <new-branch>
|
|
||||||
git worktree add ../feature-new -b feature-new
|
|
||||||
|
|
||||||
# Remove a worktree
|
|
||||||
git worktree remove <path>
|
|
||||||
git worktree remove ../bugfix
|
|
||||||
|
|
||||||
# Prune stale worktree information
|
|
||||||
git worktree prune
|
|
||||||
|
|
||||||
# Move a worktree
|
|
||||||
git worktree move <old-path> <new-path>
|
|
||||||
|
|
||||||
# Lock a worktree (prevent deletion)
|
|
||||||
git worktree lock <path>
|
|
||||||
git worktree unlock <path>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- You created a worktree for the bugfix
|
|
||||||
- The bug was fixed and committed in the worktree
|
|
||||||
- Your feature work continued in the main directory
|
|
||||||
- Both branches have the expected changes
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. You're in main-repo with a feature branch checked out
|
|
||||||
3. View current worktrees: `git worktree list`
|
|
||||||
4. Create a worktree for bugfix: `git worktree add ../bugfix-worktree -b bugfix`
|
|
||||||
5. Navigate to the worktree: `cd ../bugfix-worktree`
|
|
||||||
6. Fix the bug in calculator.js (fix the divide by zero check)
|
|
||||||
7. Commit the fix: `git add . && git commit -m "Fix divide by zero bug"`
|
|
||||||
8. Go back to main repo: `cd ../main-repo`
|
|
||||||
9. Continue working on your feature
|
|
||||||
10. Add a new method to calculator.js
|
|
||||||
11. Commit your feature
|
|
||||||
12. List worktrees: `git worktree list`
|
|
||||||
13. Remove the worktree: `git worktree remove ../bugfix-worktree`
|
|
||||||
14. Run verification
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Worktree paths are typically siblings of your main repo (use `../worktree-name`)
|
|
||||||
- Each worktree must have a different branch checked out
|
|
||||||
- Can't checkout the same branch in multiple worktrees
|
|
||||||
- The main `.git` directory is shared, so commits in any worktree are visible everywhere
|
|
||||||
- Worktrees are listed in `.git/worktrees/`
|
|
||||||
- Use `git worktree remove` to clean up, or just delete the directory and run `git worktree prune`
|
|
||||||
- Worktrees persist across restarts until explicitly removed
|
|
||||||
|
|
||||||
## Common Worktree Workflows
|
|
||||||
|
|
||||||
### Urgent Bugfix
|
|
||||||
```bash
|
|
||||||
# Currently on feature branch with uncommitted changes
|
|
||||||
git worktree add ../hotfix -b hotfix
|
|
||||||
|
|
||||||
cd ../hotfix
|
|
||||||
# Fix the bug
|
|
||||||
git add .
|
|
||||||
git commit -m "Fix critical bug"
|
|
||||||
git push origin hotfix
|
|
||||||
|
|
||||||
cd ../main-repo
|
|
||||||
# Continue working on feature
|
|
||||||
```
|
|
||||||
|
|
||||||
### Code Review
|
|
||||||
```bash
|
|
||||||
# Review a pull request without switching branches
|
|
||||||
git fetch origin pull/123/head:pr-123
|
|
||||||
git worktree add ../review-pr-123 pr-123
|
|
||||||
|
|
||||||
cd ../review-pr-123
|
|
||||||
# Review code, test it
|
|
||||||
# Run: npm test, npm start, etc.
|
|
||||||
|
|
||||||
cd ../main-repo
|
|
||||||
git worktree remove ../review-pr-123
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Development
|
|
||||||
```bash
|
|
||||||
# Work on two features simultaneously
|
|
||||||
git worktree add ../feature-a -b feature-a
|
|
||||||
git worktree add ../feature-b -b feature-b
|
|
||||||
|
|
||||||
# Terminal 1
|
|
||||||
cd feature-a && code .
|
|
||||||
|
|
||||||
# Terminal 2
|
|
||||||
cd feature-b && code .
|
|
||||||
```
|
|
||||||
|
|
||||||
### Build Comparison
|
|
||||||
```bash
|
|
||||||
# Compare builds between branches
|
|
||||||
git worktree add ../release-build release-v2.0
|
|
||||||
|
|
||||||
cd ../release-build
|
|
||||||
npm run build
|
|
||||||
# Test production build
|
|
||||||
|
|
||||||
# Meanwhile, continue development in main repo
|
|
||||||
```
|
|
||||||
|
|
||||||
## Worktree vs Other Approaches
|
|
||||||
|
|
||||||
### vs Stashing
|
|
||||||
- **Stash**: Temporary, one at a time, requires branch switching
|
|
||||||
- **Worktree**: Persistent, multiple simultaneously, no switching
|
|
||||||
|
|
||||||
### vs Cloning
|
|
||||||
- **Clone**: Full copy, separate `.git`, uses more disk space
|
|
||||||
- **Worktree**: Shared `.git`, less disk space, instant sync
|
|
||||||
|
|
||||||
### vs Branch Switching
|
|
||||||
- **Switching**: Requires clean working directory, one branch at a time
|
|
||||||
- **Worktree**: Keep dirty working directory, multiple branches active
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Git worktrees are a powerful but underutilized feature that can significantly improve your workflow. They eliminate the need for constant branch switching, stashing, or maintaining multiple clones. Whether you're handling urgent fixes, reviewing code, or comparing implementations, worktrees provide a clean and efficient solution. Once you understand worktrees, you'll find many situations where they're the perfect tool for the job.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the worktrees challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Removes the existing challenge directory and runs setup.ps1
|
|
||||||
to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "Running setup script...`n" -ForegroundColor Cyan
|
|
||||||
& ".\setup.ps1"
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the worktrees challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with a feature in progress, ready for
|
|
||||||
demonstrating the use of worktrees for parallel work.
|
|
||||||
#>
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create challenge directory
|
|
||||||
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Create main repository
|
|
||||||
New-Item -ItemType Directory -Path "main-repo" | Out-Null
|
|
||||||
Set-Location "main-repo"
|
|
||||||
|
|
||||||
# Initialize git repository
|
|
||||||
git init | Out-Null
|
|
||||||
git config user.name "Workshop User" | Out-Null
|
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
|
||||||
|
|
||||||
# Create initial calculator with a bug
|
|
||||||
$calculator = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
# BUG: No division by zero check!
|
|
||||||
def divide(self, a, b):
|
|
||||||
return a / b
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $calculator
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Initial calculator implementation" | Out-Null
|
|
||||||
|
|
||||||
$readme = @"
|
|
||||||
# Calculator Project
|
|
||||||
|
|
||||||
A simple calculator with basic operations.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
- Addition
|
|
||||||
- Subtraction
|
|
||||||
- Multiplication
|
|
||||||
- Division (has a bug!)
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $readme
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add README" | Out-Null
|
|
||||||
|
|
||||||
# Create feature branch and start working on it
|
|
||||||
git checkout -b feature-advanced-math | Out-Null
|
|
||||||
|
|
||||||
# Add work in progress on feature branch
|
|
||||||
$calculatorWithFeature = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
# BUG: No division by zero check!
|
|
||||||
def divide(self, a, b):
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
# New feature: power function (work in progress)
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
# TODO: Add square root function
|
|
||||||
# TODO: Add logarithm function
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $calculatorWithFeature
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add power function (WIP: more math functions coming)" | Out-Null
|
|
||||||
|
|
||||||
# Return to challenge directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "Challenge environment created!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nSituation:" -ForegroundColor Cyan
|
|
||||||
Write-Host "You're working on the 'feature-advanced-math' branch" -ForegroundColor White
|
|
||||||
Write-Host "You have plans to add more math functions (see TODOs)" -ForegroundColor White
|
|
||||||
Write-Host "`nUrgent: A critical bug was discovered in the divide function!" -ForegroundColor Red
|
|
||||||
Write-Host "It doesn't check for division by zero." -ForegroundColor Red
|
|
||||||
Write-Host "`nInstead of stashing your feature work, use a worktree:" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to main-repo: cd challenge/main-repo" -ForegroundColor White
|
|
||||||
Write-Host "2. Create a worktree: git worktree add ../bugfix-worktree -b bugfix" -ForegroundColor White
|
|
||||||
Write-Host "3. Go to worktree: cd ../bugfix-worktree" -ForegroundColor White
|
|
||||||
Write-Host "4. Fix the bug in calculator.py:" -ForegroundColor White
|
|
||||||
Write-Host " Add a check: if (b === 0) throw new Error('Division by zero');" -ForegroundColor White
|
|
||||||
Write-Host "5. Commit the fix: git add . && git commit -m 'Fix divide by zero bug'" -ForegroundColor White
|
|
||||||
Write-Host "6. Return to main-repo: cd ../main-repo" -ForegroundColor White
|
|
||||||
Write-Host "7. Complete your feature: Add square root method to calculator.py" -ForegroundColor White
|
|
||||||
Write-Host "8. Commit: git add . && git commit -m 'Add square root function'" -ForegroundColor White
|
|
||||||
Write-Host "9. Clean up worktree: git worktree remove ../bugfix-worktree" -ForegroundColor White
|
|
||||||
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the worktrees challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user successfully used worktrees to fix a bug
|
|
||||||
while continuing work on a feature.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Check if main-repo exists
|
|
||||||
if (-not (Test-Path "main-repo")) {
|
|
||||||
Write-Host "[FAIL] main-repo directory not found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location "main-repo"
|
|
||||||
|
|
||||||
# Check if it's a git repository
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] main-repo is not a git repository." -ForegroundColor Red
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if bugfix branch exists
|
|
||||||
$branches = git branch --all 2>$null
|
|
||||||
if ($branches -notmatch "bugfix") {
|
|
||||||
Write-Host "[FAIL] bugfix branch not found." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Create a worktree with: git worktree add ../bugfix-worktree -b bugfix" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[PASS] Bugfix branch exists!" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Check bugfix branch for the fix
|
|
||||||
git checkout bugfix 2>$null | Out-Null
|
|
||||||
|
|
||||||
if (-not (Test-Path "calculator.py")) {
|
|
||||||
Write-Host "[FAIL] calculator.py not found on bugfix branch." -ForegroundColor Red
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
$bugfixCalc = Get-Content "calculator.py" -Raw
|
|
||||||
|
|
||||||
# Check if division by zero check was added
|
|
||||||
if ($bugfixCalc -notmatch "b === 0|b == 0|division by zero|divide by zero") {
|
|
||||||
Write-Host "[FAIL] Division by zero check not found in bugfix branch." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Add a check in the divide method to prevent division by zero" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for commit on bugfix branch
|
|
||||||
$bugfixCommits = git log --pretty=format:"%s" bugfix 2>$null
|
|
||||||
if ($bugfixCommits -notmatch "bug|fix|division|divide") {
|
|
||||||
Write-Host "[FAIL] No bugfix commit found on bugfix branch." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Commit your fix with a descriptive message" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[PASS] Bug fixed on bugfix branch!" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Check feature branch for continued work
|
|
||||||
git checkout feature-advanced-math 2>$null | Out-Null
|
|
||||||
|
|
||||||
$featureCalc = Get-Content "calculator.py" -Raw
|
|
||||||
|
|
||||||
# Check if square root function was added
|
|
||||||
if ($featureCalc -notmatch "sqrt|squareRoot") {
|
|
||||||
Write-Host "[FAIL] Square root function not found on feature branch." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Add the square root method to calculator.py on feature-advanced-math branch" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that feature work was committed
|
|
||||||
$featureCommits = git log --pretty=format:"%s" feature-advanced-math 2>$null
|
|
||||||
$featureCommitArray = $featureCommits -split "`n"
|
|
||||||
|
|
||||||
# Should have at least 3 commits: initial + README + power + sqrt
|
|
||||||
if ($featureCommitArray.Count -lt 4) {
|
|
||||||
Write-Host "[FAIL] Not enough commits on feature branch." -ForegroundColor Red
|
|
||||||
Write-Host "Expected: initial, README, power, and square root commits" -ForegroundColor Yellow
|
|
||||||
Write-Host "Found $($featureCommitArray.Count) commits" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if the latest feature commit is about square root
|
|
||||||
if ($featureCommitArray[0] -notmatch "sqrt|square|root") {
|
|
||||||
Write-Host "[FAIL] Latest commit on feature branch should be about square root." -ForegroundColor Red
|
|
||||||
Write-Host "Latest commit: $($featureCommitArray[0])" -ForegroundColor Yellow
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[PASS] Feature work completed!" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Check if worktree was cleaned up (bugfix-worktree should not exist or be removed)
|
|
||||||
Set-Location ..
|
|
||||||
$worktreeStillExists = Test-Path "bugfix-worktree"
|
|
||||||
|
|
||||||
if ($worktreeStillExists) {
|
|
||||||
Write-Host "[WARNING] bugfix-worktree directory still exists." -ForegroundColor Yellow
|
|
||||||
Write-Host "Hint: Clean up with: git worktree remove ../bugfix-worktree" -ForegroundColor Yellow
|
|
||||||
# Don't fail on this, just warn
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check worktree list
|
|
||||||
Set-Location "main-repo"
|
|
||||||
$worktrees = git worktree list 2>$null
|
|
||||||
|
|
||||||
# Verify that the concept was understood (they should have created the worktree at some point)
|
|
||||||
# We can check this by looking for the bugfix branch existence
|
|
||||||
if ($branches -notmatch "bugfix") {
|
|
||||||
Write-Host "[FAIL] No evidence of worktree usage." -ForegroundColor Red
|
|
||||||
Set-Location ../..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Success!
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
|
||||||
Write-Host "- Created a worktree for the bugfix" -ForegroundColor White
|
|
||||||
Write-Host "- Fixed the division by zero bug" -ForegroundColor White
|
|
||||||
Write-Host "- Committed the fix on the bugfix branch" -ForegroundColor White
|
|
||||||
Write-Host "- Continued feature work in parallel" -ForegroundColor White
|
|
||||||
Write-Host "- Added the square root function" -ForegroundColor White
|
|
||||||
Write-Host "- Committed the feature work" -ForegroundColor White
|
|
||||||
|
|
||||||
if (-not $worktreeStillExists) {
|
|
||||||
Write-Host "- Cleaned up the worktree" -ForegroundColor White
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "`nYou now understand Git worktrees!" -ForegroundColor Green
|
|
||||||
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
|
|
||||||
Write-Host "Worktrees let you work on multiple branches simultaneously" -ForegroundColor White
|
|
||||||
Write-Host "without stashing, switching, or cloning the repository.`n" -ForegroundColor White
|
|
||||||
|
|
||||||
Set-Location ../..
|
|
||||||
exit 0
|
|
||||||
@@ -1,237 +0,0 @@
|
|||||||
# Module 14: Bisect - Finding Bugs with Binary Search
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what git bisect is and when to use it
|
|
||||||
- Use binary search to find the commit that introduced a bug
|
|
||||||
- Mark commits as good or bad during bisection
|
|
||||||
- Automate bisect with test scripts
|
|
||||||
- Understand the efficiency of binary search for debugging
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
A bug has appeared in your calculator application, but you don't know which commit introduced it. The project has many commits, and manually checking each one would take too long. You'll use `git bisect` to efficiently find the culprit commit using binary search.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Start a bisect session
|
|
||||||
2. Mark the current commit as bad (bug exists)
|
|
||||||
3. Mark an old commit as good (bug didn't exist)
|
|
||||||
4. Test commits and mark them good or bad
|
|
||||||
5. Let Git find the first bad commit
|
|
||||||
6. Identify what change introduced the bug
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What is Git Bisect?
|
|
||||||
|
|
||||||
Git bisect uses binary search to find the commit that introduced a bug. Instead of checking every commit linearly, it cuts the search space in half with each test, making it extremely efficient.
|
|
||||||
|
|
||||||
### Binary Search Efficiency
|
|
||||||
|
|
||||||
**Linear Search (manual checking):**
|
|
||||||
- 100 commits = up to 100 tests
|
|
||||||
- 1000 commits = up to 1000 tests
|
|
||||||
|
|
||||||
**Binary Search (bisect):**
|
|
||||||
- 100 commits = ~7 tests
|
|
||||||
- 1000 commits = ~10 tests
|
|
||||||
|
|
||||||
Formula: log₂(n) tests needed for n commits
|
|
||||||
|
|
||||||
### How Bisect Works
|
|
||||||
|
|
||||||
```
|
|
||||||
Commits: A---B---C---D---E---F---G---H
|
|
||||||
✓ ✓ ✓ ? ? ? ? ✗
|
|
||||||
|
|
||||||
1. Start: Mark H (bad) and A (good)
|
|
||||||
2. Git checks middle: E
|
|
||||||
3. You test E: bad ✗
|
|
||||||
|
|
||||||
Commits: A---B---C---D---E
|
|
||||||
✓ ✓ ✓ ? ✗
|
|
||||||
|
|
||||||
4. Git checks middle: C
|
|
||||||
5. You test C: good ✓
|
|
||||||
|
|
||||||
Commits: C---D---E
|
|
||||||
✓ ? ✗
|
|
||||||
|
|
||||||
6. Git checks: D
|
|
||||||
7. You test D: bad ✗
|
|
||||||
|
|
||||||
Result: D is the first bad commit!
|
|
||||||
```
|
|
||||||
|
|
||||||
### When to Use Bisect
|
|
||||||
|
|
||||||
Use bisect when:
|
|
||||||
- You know a bug exists now but didn't exist in the past
|
|
||||||
- You have many commits to check
|
|
||||||
- You can reliably test for the bug
|
|
||||||
- You want to find exactly when something broke
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Start bisect session
|
|
||||||
git bisect start
|
|
||||||
|
|
||||||
# Mark current commit as bad
|
|
||||||
git bisect bad
|
|
||||||
|
|
||||||
# Mark a commit as good
|
|
||||||
git bisect good <commit-hash>
|
|
||||||
git bisect good HEAD~10
|
|
||||||
|
|
||||||
# After testing current commit
|
|
||||||
git bisect good # This commit is fine
|
|
||||||
git bisect bad # This commit has the bug
|
|
||||||
|
|
||||||
# Skip a commit (if you can't test it)
|
|
||||||
git bisect skip
|
|
||||||
|
|
||||||
# End bisect session and return to original state
|
|
||||||
git bisect reset
|
|
||||||
|
|
||||||
# Visualize bisect process
|
|
||||||
git bisect visualize
|
|
||||||
git bisect view
|
|
||||||
|
|
||||||
# Automate with a test script
|
|
||||||
git bisect run <test-script>
|
|
||||||
git bisect run npm test
|
|
||||||
git bisect run ./test.sh
|
|
||||||
|
|
||||||
# Show bisect log
|
|
||||||
git bisect log
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- You completed a bisect session
|
|
||||||
- You identified the correct commit that introduced the bug
|
|
||||||
- You understand which change caused the problem
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. View the bug: run the calculator and see it fails
|
|
||||||
3. Start bisect: `git bisect start`
|
|
||||||
4. Mark current as bad: `git bisect bad`
|
|
||||||
5. Mark old commit as good: `git bisect good HEAD~10`
|
|
||||||
6. Git will checkout a middle commit
|
|
||||||
7. Test the current commit (run the test or check manually)
|
|
||||||
8. Mark it: `git bisect good` or `git bisect bad`
|
|
||||||
9. Repeat testing until Git identifies the bad commit
|
|
||||||
10. Note the commit hash and message
|
|
||||||
11. End bisect: `git bisect reset`
|
|
||||||
12. Check the identified commit: `git show <bad-commit-hash>`
|
|
||||||
13. Create a file named `bug-commit.txt` with the bad commit hash
|
|
||||||
14. Run verification
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Always start with a known good commit (far enough back)
|
|
||||||
- Keep a clear way to test each commit (script or manual steps)
|
|
||||||
- Use `git bisect log` to see your progress
|
|
||||||
- `git bisect reset` returns you to your original state
|
|
||||||
- You can bisect on any criteria, not just bugs (performance, features, etc.)
|
|
||||||
- Automate with `git bisect run` for faster results
|
|
||||||
- Each bisect step cuts remaining commits in half
|
|
||||||
- Skip commits you can't build/test with `git bisect skip`
|
|
||||||
|
|
||||||
## Manual vs Automated Bisect
|
|
||||||
|
|
||||||
### Manual Bisect
|
|
||||||
```powershell
|
|
||||||
git bisect start
|
|
||||||
git bisect bad
|
|
||||||
git bisect good HEAD~20
|
|
||||||
|
|
||||||
# For each commit Git checks out:
|
|
||||||
npm test
|
|
||||||
git bisect good # or bad
|
|
||||||
|
|
||||||
git bisect reset
|
|
||||||
```
|
|
||||||
|
|
||||||
### Automated Bisect
|
|
||||||
```powershell
|
|
||||||
git bisect start
|
|
||||||
git bisect bad
|
|
||||||
git bisect good HEAD~20
|
|
||||||
git bisect run npm test
|
|
||||||
# Git automatically tests each commit
|
|
||||||
git bisect reset
|
|
||||||
```
|
|
||||||
|
|
||||||
The test script should exit with:
|
|
||||||
- 0 for good (test passes)
|
|
||||||
- 1-127 (except 125) for bad (test fails)
|
|
||||||
- 125 for skip (can't test this commit)
|
|
||||||
|
|
||||||
## Bisect Workflow Example
|
|
||||||
|
|
||||||
### Finding a Performance Regression
|
|
||||||
```powershell
|
|
||||||
# App is slow now, was fast 50 commits ago
|
|
||||||
git bisect start
|
|
||||||
git bisect bad
|
|
||||||
git bisect good HEAD~50
|
|
||||||
|
|
||||||
# Create test script
|
|
||||||
@'
|
|
||||||
$output = npm start 2>&1 | Select-String "Started in"
|
|
||||||
if ($output -match "Started in (\d+)") {
|
|
||||||
if ([int]$Matches[1] -gt 5000) { exit 1 } # Slow
|
|
||||||
else { exit 0 } # Fast
|
|
||||||
}
|
|
||||||
exit 1
|
|
||||||
'@ | Out-File -FilePath test.ps1
|
|
||||||
|
|
||||||
git bisect run pwsh test.ps1
|
|
||||||
# Git finds the commit that made it slow
|
|
||||||
```
|
|
||||||
|
|
||||||
### Finding When a Feature Broke
|
|
||||||
```powershell
|
|
||||||
git bisect start
|
|
||||||
git bisect bad
|
|
||||||
git bisect good v1.0.0 # Last known good version
|
|
||||||
|
|
||||||
# For each commit
|
|
||||||
npm test -- user-login.test.js
|
|
||||||
git bisect good # or bad
|
|
||||||
|
|
||||||
# Found! Commit abc123 broke login
|
|
||||||
git show abc123
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Bisect Pitfalls
|
|
||||||
|
|
||||||
### Pitfall 1: Testing Incorrectly
|
|
||||||
- Make sure your test is consistent
|
|
||||||
- Automate when possible to avoid human error
|
|
||||||
- Use the same test for every commit
|
|
||||||
|
|
||||||
### Pitfall 2: Wrong Good Commit
|
|
||||||
- If the "good" commit actually has the bug, bisect will fail
|
|
||||||
- Choose a commit you're confident was working
|
|
||||||
|
|
||||||
### Pitfall 3: Multiple Bugs
|
|
||||||
- Bisect finds one commit at a time
|
|
||||||
- If multiple bugs exist, they might confuse the search
|
|
||||||
- Fix found bugs and bisect again for others
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Git bisect is a powerful debugging tool that turns a tedious manual search into an efficient automated process. By leveraging binary search, you can quickly pinpoint problematic commits even in repositories with thousands of commits. This is invaluable for debugging regressions, performance issues, or any situation where something that worked before is now broken. Mastering bisect makes you a more effective debugger and shows deep Git proficiency.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the bisect challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Removes the existing challenge directory and runs setup.ps1
|
|
||||||
to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "Running setup script...`n" -ForegroundColor Cyan
|
|
||||||
& ".\setup.ps1"
|
|
||||||
@@ -1,311 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the bisect challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with multiple commits where a bug is
|
|
||||||
introduced in one of them. Students use bisect to find it.
|
|
||||||
#>
|
|
||||||
|
|
||||||
# Remove existing challenge directory if present
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Path "challenge" -Recurse -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create challenge directory
|
|
||||||
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize git repository
|
|
||||||
git init | Out-Null
|
|
||||||
git config user.name "Workshop User" | Out-Null
|
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
|
||||||
|
|
||||||
# Commit 1: Initial calculator
|
|
||||||
$calc1 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc1
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Initial calculator with add function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 2: Add subtract
|
|
||||||
$calc2 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc2
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add subtract function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 3: Add multiply
|
|
||||||
$calc3 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc3
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add multiply function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 4: Add divide
|
|
||||||
$calc4 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc4
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add divide function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 5: Add modulo
|
|
||||||
$calc5 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc5
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add modulo function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 6: BUG - Introduce error in add function
|
|
||||||
$calc6 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a - b # BUG: Should be a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc6
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Refactor add function for clarity" | Out-Null
|
|
||||||
|
|
||||||
# Commit 7: Add power function (bug still exists)
|
|
||||||
$calc7 = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a - b # BUG: Should be a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc7
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add power function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 8: Add square root
|
|
||||||
$calc8 = @"
|
|
||||||
import math
|
|
||||||
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a - b # BUG: Should be a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
def sqrt(self, a):
|
|
||||||
return math.sqrt(a)
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc8
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add square root function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 9: Add absolute value
|
|
||||||
$calc9 = @"
|
|
||||||
import math
|
|
||||||
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a - b # BUG: Should be a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
def sqrt(self, a):
|
|
||||||
return math.sqrt(a)
|
|
||||||
|
|
||||||
def abs(self, a):
|
|
||||||
return abs(a)
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc9
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add absolute value function" | Out-Null
|
|
||||||
|
|
||||||
# Commit 10: Add max function
|
|
||||||
$calc10 = @"
|
|
||||||
import math
|
|
||||||
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a - b # BUG: Should be a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def multiply(self, a, b):
|
|
||||||
return a * b
|
|
||||||
|
|
||||||
def divide(self, a, b):
|
|
||||||
if b == 0:
|
|
||||||
raise ValueError('Division by zero')
|
|
||||||
return a / b
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
def sqrt(self, a):
|
|
||||||
return math.sqrt(a)
|
|
||||||
|
|
||||||
def abs(self, a):
|
|
||||||
return abs(a)
|
|
||||||
|
|
||||||
def max(self, a, b):
|
|
||||||
return a if a > b else b
|
|
||||||
"@
|
|
||||||
Set-Content -Path "calculator.py" -Value $calc10
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add max function" | Out-Null
|
|
||||||
|
|
||||||
# Create a test file
|
|
||||||
$test = @"
|
|
||||||
import sys
|
|
||||||
from calculator import Calculator
|
|
||||||
|
|
||||||
calc = Calculator()
|
|
||||||
|
|
||||||
# Test addition (this will fail due to bug)
|
|
||||||
result = calc.add(5, 3)
|
|
||||||
if result != 8:
|
|
||||||
print(f'FAIL: add(5, 3) returned {result}, expected 8')
|
|
||||||
sys.exit(1)
|
|
||||||
|
|
||||||
print('PASS: All tests passed')
|
|
||||||
sys.exit(0)
|
|
||||||
"@
|
|
||||||
Set-Content -Path "test.py" -Value $test
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "Challenge environment created!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nSituation:" -ForegroundColor Cyan
|
|
||||||
Write-Host "The calculator has a bug - addition doesn't work correctly!" -ForegroundColor Red
|
|
||||||
Write-Host "calc.add(5, 3) returns 2 instead of 8" -ForegroundColor Red
|
|
||||||
Write-Host "`nThe bug was introduced somewhere in the last 10 commits." -ForegroundColor Yellow
|
|
||||||
Write-Host "Manually checking each commit would be tedious." -ForegroundColor Yellow
|
|
||||||
Write-Host "Use git bisect to find it efficiently!" -ForegroundColor Green
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
|
||||||
Write-Host "2. Test the bug: python test.py (it will fail)" -ForegroundColor White
|
|
||||||
Write-Host "3. Start bisect: git bisect start" -ForegroundColor White
|
|
||||||
Write-Host "4. Mark current as bad: git bisect bad" -ForegroundColor White
|
|
||||||
Write-Host "5. Mark old commit as good: git bisect good HEAD~10" -ForegroundColor White
|
|
||||||
Write-Host "6. Git will checkout a commit - test it: python test.py" -ForegroundColor White
|
|
||||||
Write-Host "7. Mark result: git bisect good (if test passes) or git bisect bad (if it fails)" -ForegroundColor White
|
|
||||||
Write-Host "8. Repeat until Git finds the first bad commit" -ForegroundColor White
|
|
||||||
Write-Host "9. Note the commit hash" -ForegroundColor White
|
|
||||||
Write-Host "10. End bisect: git bisect reset" -ForegroundColor White
|
|
||||||
Write-Host "11. Create bug-commit.txt with the bad commit hash" -ForegroundColor White
|
|
||||||
Write-Host "`nHint: The bug is in commit 6 ('Refactor add function for clarity')" -ForegroundColor Cyan
|
|
||||||
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
|
||||||
@@ -1,133 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the bisect challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user successfully used git bisect to find
|
|
||||||
the commit that introduced the bug.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Set-Location "challenge" -ErrorAction SilentlyContinue
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "../verify.ps1")) {
|
|
||||||
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not (Test-Path ".")) {
|
|
||||||
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Make sure we're not in a bisect session
|
|
||||||
$bisectHead = Test-Path ".git/BISECT_HEAD"
|
|
||||||
if ($bisectHead) {
|
|
||||||
Write-Host "[FAIL] You're still in a bisect session." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: End the bisect with: git bisect reset" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if bug-commit.txt exists
|
|
||||||
if (-not (Test-Path "bug-commit.txt")) {
|
|
||||||
Write-Host "[FAIL] bug-commit.txt not found." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: After finding the bad commit, create a file with its hash:" -ForegroundColor Yellow
|
|
||||||
Write-Host " echo 'commit-hash' > bug-commit.txt" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read the commit hash from the file
|
|
||||||
$userCommit = (Get-Content "bug-commit.txt" -Raw).Trim()
|
|
||||||
|
|
||||||
if (-not $userCommit) {
|
|
||||||
Write-Host "[FAIL] bug-commit.txt is empty." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Get all commit hashes
|
|
||||||
$allCommits = git log --pretty=format:"%H" --reverse 2>$null
|
|
||||||
$commitArray = $allCommits -split "`n"
|
|
||||||
|
|
||||||
# The bug was introduced in commit 6 (index 5 in 0-based array)
|
|
||||||
# This is the "Refactor add function for clarity" commit
|
|
||||||
$badCommitMessage = git log --pretty=format:"%s" --grep="Refactor add function" 2>$null
|
|
||||||
$actualBadCommit = git log --pretty=format:"%H" --grep="Refactor add function" 2>$null
|
|
||||||
|
|
||||||
if (-not $actualBadCommit) {
|
|
||||||
Write-Host "[FAIL] Could not find the expected bad commit." -ForegroundColor Red
|
|
||||||
Write-Host "Something may be wrong with the repository setup." -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if user found the correct commit
|
|
||||||
if ($userCommit -ne $actualBadCommit) {
|
|
||||||
# Maybe they provided a short hash
|
|
||||||
if ($actualBadCommit -like "$userCommit*") {
|
|
||||||
Write-Host "[PASS] Correct commit identified (using short hash)!" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Incorrect commit identified." -ForegroundColor Red
|
|
||||||
Write-Host "You identified: $userCommit" -ForegroundColor Yellow
|
|
||||||
Write-Host "Expected: $actualBadCommit" -ForegroundColor Yellow
|
|
||||||
Write-Host "Expected commit message: 'Refactor add function for clarity'" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nHint: Use git bisect to find where the add function broke:" -ForegroundColor Yellow
|
|
||||||
Write-Host " git bisect start" -ForegroundColor White
|
|
||||||
Write-Host " git bisect bad" -ForegroundColor White
|
|
||||||
Write-Host " git bisect good HEAD~10" -ForegroundColor White
|
|
||||||
Write-Host " # Then test with: node test.py" -ForegroundColor White
|
|
||||||
Write-Host " git bisect good # or bad" -ForegroundColor White
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "[PASS] Correct commit identified!" -ForegroundColor Green
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify the commit actually has the bug
|
|
||||||
git checkout $actualBadCommit 2>$null | Out-Null
|
|
||||||
$calcContent = Get-Content "calculator.py" -Raw
|
|
||||||
|
|
||||||
if ($calcContent -notmatch "add\(a, b\)[\s\S]*?return a - b") {
|
|
||||||
Write-Host "[WARNING] The identified commit doesn't seem to have the expected bug." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Return to latest commit
|
|
||||||
git checkout $(git branch --show-current 2>$null) 2>$null | Out-Null
|
|
||||||
if (-not $?) {
|
|
||||||
git checkout main 2>$null | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
# Success!
|
|
||||||
Write-Host "`n========================================" -ForegroundColor Green
|
|
||||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
|
||||||
Write-Host "- Used git bisect to perform a binary search" -ForegroundColor White
|
|
||||||
Write-Host "- Tested commits systematically" -ForegroundColor White
|
|
||||||
Write-Host "- Identified the exact commit that introduced the bug" -ForegroundColor White
|
|
||||||
Write-Host "- Found: '$badCommitMessage'" -ForegroundColor White
|
|
||||||
Write-Host "`nThe bug was in commit 6 out of 10 commits." -ForegroundColor Yellow
|
|
||||||
Write-Host "Manual checking: up to 10 tests" -ForegroundColor Yellow
|
|
||||||
Write-Host "With bisect: only ~4 tests needed!" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou now understand git bisect!" -ForegroundColor Green
|
|
||||||
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
|
|
||||||
Write-Host "Bisect uses binary search to efficiently find bugs," -ForegroundColor White
|
|
||||||
Write-Host "saving massive amounts of time in large codebases.`n" -ForegroundColor White
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
|
||||||
@@ -1,169 +0,0 @@
|
|||||||
# Module 05: Git Blame - Code Archaeology
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
In this module, you will:
|
|
||||||
- Use `git blame` to find who made specific changes
|
|
||||||
- Understand blame output format and information
|
|
||||||
- Track down problematic code changes
|
|
||||||
- Learn when and why to use `git blame`
|
|
||||||
- Investigate code history to understand context
|
|
||||||
|
|
||||||
## Challenge
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
Run the setup script to create your challenge environment:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a `challenge/` directory with a Git repository that has a security issue - someone committed hardcoded credentials!
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
Your team has discovered a security vulnerability: hardcoded credentials were added to the codebase. Your job is to investigate who made this change and document your findings.
|
|
||||||
|
|
||||||
The setup script will create an `investigation.md` file in the challenge directory with questions for you to answer. Use `git blame` and other Git commands to track down the responsible developer.
|
|
||||||
|
|
||||||
**Scenario:**
|
|
||||||
- Someone added hardcoded login credentials (`username: "admin"`, `password: "admin123"`) to `app.py`
|
|
||||||
- This is a critical security issue
|
|
||||||
- You need to identify who made this change so the team can discuss it with them
|
|
||||||
|
|
||||||
**Suggested Approach:**
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory: `cd challenge`
|
|
||||||
2. Open `investigation.md` to see the questions
|
|
||||||
3. Examine `app.py` to find the suspicious line
|
|
||||||
4. Use `git blame` to find who wrote that line
|
|
||||||
5. Use `git blame -e` to see email addresses
|
|
||||||
6. Use `git show` to see the full commit details
|
|
||||||
7. Document your findings in `investigation.md`
|
|
||||||
|
|
||||||
> **Important Notes:**
|
|
||||||
> - `git blame` shows who last modified each line
|
|
||||||
> - Each line shows: commit hash, author, date, line number, and content
|
|
||||||
> - Use `-e` flag to show email addresses
|
|
||||||
> - Use `-L` to focus on specific line ranges
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
- **Git Blame**: Shows the revision and author who last modified each line of a file
|
|
||||||
- **Code Archaeology**: Using Git history to understand when and why code changed
|
|
||||||
- **Author Attribution**: Identifying who wrote specific code for context, not punishment
|
|
||||||
- **Commit Context**: Understanding the full story behind a change
|
|
||||||
|
|
||||||
## Understanding Git Blame Output
|
|
||||||
|
|
||||||
When you run `git blame app.py`, you'll see output like this:
|
|
||||||
|
|
||||||
```
|
|
||||||
a1b2c3d4 (John Doe 2024-01-15 10:30:45 +0000 1) # app.py - Main application
|
|
||||||
a1b2c3d4 (John Doe 2024-01-15 10:30:45 +0000 2)
|
|
||||||
e5f6g7h8 (Jane Smith 2024-01-16 14:20:10 +0000 3) from auth import login
|
|
||||||
e5f6g7h8 (Jane Smith 2024-01-16 14:20:10 +0000 4)
|
|
||||||
i9j0k1l2 (Bob Wilson 2024-01-17 09:15:30 +0000 5) def main():
|
|
||||||
i9j0k1l2 (Bob Wilson 2024-01-17 09:15:30 +0000 6) login("admin", "admin123")
|
|
||||||
```
|
|
||||||
|
|
||||||
### Breaking It Down
|
|
||||||
|
|
||||||
Each line shows:
|
|
||||||
1. **Commit Hash** (`a1b2c3d4`) - The commit that last changed this line
|
|
||||||
2. **Author Name** (`John Doe`) - Who made the change
|
|
||||||
3. **Date/Time** (`2024-01-15 10:30:45 +0000`) - When it was changed
|
|
||||||
4. **Line Number** (`1`) - The line number in the current file
|
|
||||||
5. **Line Content** (`# app.py - Main application`) - The actual code
|
|
||||||
|
|
||||||
### Useful Git Blame Options
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git blame <file> # Basic blame output
|
|
||||||
git blame -e <file> # Show email addresses instead of names
|
|
||||||
git blame -L 10,20 <file> # Only show lines 10-20
|
|
||||||
git blame -L 10,+5 <file> # Show 5 lines starting from line 10
|
|
||||||
git blame -w <file> # Ignore whitespace changes
|
|
||||||
git blame <commit> <file> # Blame as of specific commit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Following Up After Blame
|
|
||||||
|
|
||||||
Once you find the commit hash:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git show <commit-hash> # See the full commit details
|
|
||||||
git log -p <commit-hash> # See commit with diff
|
|
||||||
git show <commit-hash> --stat # See which files were changed
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use Git Blame
|
|
||||||
|
|
||||||
**Good reasons to use `git blame`:**
|
|
||||||
- 🔍 Understanding why code was written a certain way
|
|
||||||
- 📚 Finding context for a piece of code
|
|
||||||
- 🐛 Identifying when a bug was introduced
|
|
||||||
- 💡 Discovering the thought process behind a decision
|
|
||||||
- 👥 Finding who to ask about specific code
|
|
||||||
|
|
||||||
**Not for blaming:**
|
|
||||||
- ❌ Finding someone to blame for mistakes
|
|
||||||
- ❌ Tracking "productivity" or code ownership
|
|
||||||
- ❌ Punishing developers for old code
|
|
||||||
|
|
||||||
**Remember:** Code archaeology is about understanding, not blaming!
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
### Investigation Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Find who changed each line
|
|
||||||
git blame <file>
|
|
||||||
git blame -e <file> # With email addresses
|
|
||||||
|
|
||||||
# Focus on specific lines
|
|
||||||
git blame -L 10,20 <file> # Lines 10-20
|
|
||||||
git blame -L :function_name <file> # Specific function (Git 2.20+)
|
|
||||||
|
|
||||||
# See historical blame
|
|
||||||
git blame <commit>^ <file> # Blame before a specific commit
|
|
||||||
|
|
||||||
# Combine with grep
|
|
||||||
git blame <file> | grep "pattern" # Find who wrote lines matching pattern
|
|
||||||
```
|
|
||||||
|
|
||||||
### Context Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# See full commit details
|
|
||||||
git show <commit-hash>
|
|
||||||
git log -1 <commit-hash> # Just the commit message
|
|
||||||
|
|
||||||
# See all commits by author
|
|
||||||
git log --author="name"
|
|
||||||
|
|
||||||
# See what else changed in that commit
|
|
||||||
git show <commit-hash> --stat
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Once you've completed your investigation in `investigation.md`, verify your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification script will check that you've identified the correct developer.
|
|
||||||
|
|
||||||
## Need to Start Over?
|
|
||||||
|
|
||||||
If you want to reset the challenge and start fresh:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will remove the challenge directory and run the setup script again, giving you a clean slate.
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the Module 05 challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script removes the challenge directory and re-runs the setup script
|
|
||||||
to give you a fresh start.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Resetting Module 05 Challenge ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Remove challenge directory if it exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
Write-Host "Challenge directory removed." -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "No challenge directory found to remove." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "`nRunning setup script..." -ForegroundColor Cyan
|
|
||||||
& "./setup.ps1"
|
|
||||||
@@ -1,323 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the Module 05 challenge environment for git blame investigation.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script creates a challenge directory with a Git repository that
|
|
||||||
contains a security vulnerability (hardcoded credentials) for students
|
|
||||||
to investigate using git blame.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Setting up Module 05 Challenge ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Remove existing challenge directory if it exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create fresh challenge directory
|
|
||||||
Write-Host "Creating challenge directory..." -ForegroundColor Green
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize Git repository
|
|
||||||
Write-Host "Initializing Git repository..." -ForegroundColor Green
|
|
||||||
git init | Out-Null
|
|
||||||
|
|
||||||
# Commit 1: Initial project structure (by Alice)
|
|
||||||
Write-Host "Creating initial project structure..." -ForegroundColor Green
|
|
||||||
git config user.name "Alice Johnson"
|
|
||||||
git config user.email "alice@example.com"
|
|
||||||
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
# Application initialization code here
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial project structure" | Out-Null
|
|
||||||
|
|
||||||
# Commit 2: Add authentication module (by Bob)
|
|
||||||
Write-Host "Adding authentication module..." -ForegroundColor Green
|
|
||||||
git config user.name "Bob Chen"
|
|
||||||
git config user.email "bob@example.com"
|
|
||||||
|
|
||||||
$authContent = @"
|
|
||||||
# auth.py - Authentication module
|
|
||||||
|
|
||||||
def login(username, password):
|
|
||||||
# Authenticate user
|
|
||||||
print(f"Logging in user: {username}")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def logout(username):
|
|
||||||
# Log out user
|
|
||||||
print(f"Logging out user: {username}")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "auth.py" -Value $authContent
|
|
||||||
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
from auth import login, logout
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
# Application initialization code here
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add authentication module" | Out-Null
|
|
||||||
|
|
||||||
# Commit 3: Add database connection (by Carol)
|
|
||||||
Write-Host "Adding database connection..." -ForegroundColor Green
|
|
||||||
git config user.name "Carol Martinez"
|
|
||||||
git config user.email "carol@example.com"
|
|
||||||
|
|
||||||
$databaseContent = @"
|
|
||||||
# database.py - Database connection module
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
# Connect to database
|
|
||||||
print("Connecting to database...")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def disconnect():
|
|
||||||
# Disconnect from database
|
|
||||||
print("Disconnecting from database...")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "database.py" -Value $databaseContent
|
|
||||||
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
from auth import login, logout
|
|
||||||
from database import connect, disconnect
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
connect()
|
|
||||||
# Application initialization code here
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add database connection" | Out-Null
|
|
||||||
|
|
||||||
# Commit 4: Add hardcoded credentials (THE SECURITY ISSUE - by Suspicious Developer)
|
|
||||||
Write-Host "Adding suspicious change..." -ForegroundColor Green
|
|
||||||
git config user.name "Suspicious Developer"
|
|
||||||
git config user.email "guilty@email.com"
|
|
||||||
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
from auth import login, logout
|
|
||||||
from database import connect, disconnect
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
connect()
|
|
||||||
# Quick fix for testing - TODO: Remove before production!
|
|
||||||
if login("admin", "admin123"):
|
|
||||||
print("Admin logged in successfully")
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add quick test login for debugging" | Out-Null
|
|
||||||
|
|
||||||
# Commit 5: Add logging (by David - innocent commit after the security issue)
|
|
||||||
Write-Host "Adding logging module..." -ForegroundColor Green
|
|
||||||
git config user.name "David Lee"
|
|
||||||
git config user.email "david@example.com"
|
|
||||||
|
|
||||||
$loggingContent = @"
|
|
||||||
# logging_config.py - Logging configuration
|
|
||||||
|
|
||||||
import logging
|
|
||||||
|
|
||||||
def setup_logging():
|
|
||||||
logging.basicConfig(
|
|
||||||
level=logging.INFO,
|
|
||||||
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
|
|
||||||
)
|
|
||||||
return logging.getLogger(__name__)
|
|
||||||
"@
|
|
||||||
Set-Content -Path "logging_config.py" -Value $loggingContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add logging configuration" | Out-Null
|
|
||||||
|
|
||||||
# Reset git config
|
|
||||||
git config user.name "Workshop Student"
|
|
||||||
git config user.email "student@example.com"
|
|
||||||
|
|
||||||
# Create investigation.md template
|
|
||||||
Write-Host "Creating investigation template..." -ForegroundColor Green
|
|
||||||
$investigationTemplate = @"
|
|
||||||
# Security Investigation Report
|
|
||||||
|
|
||||||
## Incident Overview
|
|
||||||
|
|
||||||
A security vulnerability has been discovered in the codebase: hardcoded credentials in `app.py`.
|
|
||||||
|
|
||||||
**Your task:** Use git blame and related Git commands to investigate this security issue and document your findings.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Question 1: What line number contains the hardcoded password?
|
|
||||||
|
|
||||||
Look at `app.py` and find the line with `"admin123"`.
|
|
||||||
|
|
||||||
**Your Answer:**
|
|
||||||
|
|
||||||
<!-- Write the line number here -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Question 2: Who added the hardcoded credentials?
|
|
||||||
|
|
||||||
Use `git blame` to find the email address of the developer who wrote the line with the hardcoded credentials.
|
|
||||||
|
|
||||||
**Suggested commands:**
|
|
||||||
``````bash
|
|
||||||
# View blame with email addresses
|
|
||||||
git blame -e app.py
|
|
||||||
|
|
||||||
# Or focus on specific lines (if you know the line range)
|
|
||||||
git blame -L 8,10 app.py
|
|
||||||
|
|
||||||
# Look for the line containing login("admin", "admin123")
|
|
||||||
``````
|
|
||||||
|
|
||||||
**Your Answer (provide the email address):**
|
|
||||||
|
|
||||||
<!-- Write the email address here -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Question 3: What was the commit message for the change that introduced the hardcoded credentials?
|
|
||||||
|
|
||||||
Once you've found the commit hash from git blame, use `git show` or `git log` to see the full commit message.
|
|
||||||
|
|
||||||
**Suggested commands:**
|
|
||||||
``````bash
|
|
||||||
# After finding the commit hash from git blame
|
|
||||||
git show <commit-hash>
|
|
||||||
git log -1 <commit-hash>
|
|
||||||
``````
|
|
||||||
|
|
||||||
**Your Answer:**
|
|
||||||
|
|
||||||
<!-- Write the commit message here -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Question 4: How many files were modified in the commit that added the hardcoded credentials?
|
|
||||||
|
|
||||||
Use `git show` with the `--stat` flag to see which files were changed.
|
|
||||||
|
|
||||||
**Suggested commands:**
|
|
||||||
``````bash
|
|
||||||
git show <commit-hash> --stat
|
|
||||||
git show <commit-hash> --name-only
|
|
||||||
``````
|
|
||||||
|
|
||||||
**Your Answer:**
|
|
||||||
|
|
||||||
<!-- Write the number or list the files here -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Question 5: When was this security vulnerability introduced?
|
|
||||||
|
|
||||||
Use the timestamp from git blame to determine when the vulnerable code was committed.
|
|
||||||
|
|
||||||
**Your Answer (date and time):**
|
|
||||||
|
|
||||||
<!-- Write the date/time here -->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
Based on your investigation, what actions should the team take?
|
|
||||||
|
|
||||||
**Your Recommendations:**
|
|
||||||
|
|
||||||
<!-- Write your recommendations here, for example:
|
|
||||||
- Remove hardcoded credentials
|
|
||||||
- Implement proper environment variables
|
|
||||||
- Add pre-commit hooks to prevent secrets
|
|
||||||
- Review with the developer who made the change
|
|
||||||
-->
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference - Investigation Commands
|
|
||||||
|
|
||||||
**Finding Who Changed What:**
|
|
||||||
``````bash
|
|
||||||
git blame <file> # Show who last modified each line
|
|
||||||
git blame -e <file> # Show with email addresses
|
|
||||||
git blame -L 10,20 <file> # Blame specific line range
|
|
||||||
``````
|
|
||||||
|
|
||||||
**Getting Commit Details:**
|
|
||||||
``````bash
|
|
||||||
git show <commit-hash> # See full commit details
|
|
||||||
git show <commit-hash> --stat # See files changed
|
|
||||||
git log -1 <commit-hash> # See commit message only
|
|
||||||
git log -p <commit-hash> # See commit with diff
|
|
||||||
``````
|
|
||||||
|
|
||||||
**Searching History:**
|
|
||||||
``````bash
|
|
||||||
git log --all --grep="keyword" # Search commit messages
|
|
||||||
git log --author="name" # See commits by author
|
|
||||||
git log --since="2 weeks ago" # Recent commits
|
|
||||||
``````
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
When you're done with your investigation, run ``..\verify.ps1`` to check your answers!
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "investigation.md" -Value $investigationTemplate
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
|
||||||
Write-Host "`nYour investigation environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nScenario: Someone committed hardcoded credentials to app.py!" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
|
||||||
Write-Host " 2. Open 'investigation.md' to see the investigation questions" -ForegroundColor White
|
|
||||||
Write-Host " 3. Use 'git blame -e app.py' to start your investigation" -ForegroundColor White
|
|
||||||
Write-Host " 4. Fill in your findings in 'investigation.md'" -ForegroundColor White
|
|
||||||
Write-Host " 5. Run '..\verify.ps1' to check your investigation" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,114 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the Module 05 challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script checks that:
|
|
||||||
- The challenge directory exists
|
|
||||||
- A Git repository exists
|
|
||||||
- investigation.md exists with correct findings about the security issue
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 05 Solution ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
$allChecksPassed = $true
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "challenge")) {
|
|
||||||
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if investigation.md exists
|
|
||||||
if (-not (Test-Path "investigation.md")) {
|
|
||||||
Write-Host "[FAIL] investigation.md not found. Did you run setup.ps1?" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] The setup script should have created investigation.md for you" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
} else {
|
|
||||||
Write-Host "[PASS] investigation.md exists" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Read the investigation file
|
|
||||||
$investigation = Get-Content "investigation.md" -Raw
|
|
||||||
$investigationLower = $investigation.ToLower()
|
|
||||||
|
|
||||||
# Check 1: Line number (line 8 contains the hardcoded password)
|
|
||||||
if ($investigationLower -match "8") {
|
|
||||||
Write-Host "[PASS] Correct line number identified" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Line number not found or incorrect" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Look at app.py to find which line contains 'admin123'" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 2: Email address (guilty@email.com)
|
|
||||||
if ($investigationLower -match "guilty@email\.com") {
|
|
||||||
Write-Host "[PASS] Correct email address found using git blame!" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Developer's email address not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use 'git blame -e app.py' to see who changed each line with email addresses" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 3: Commit message (contains "test" or "debug" or "quick")
|
|
||||||
if ($investigationLower -match "test|debug|quick") {
|
|
||||||
Write-Host "[PASS] Commit message identified" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Commit message not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use 'git show <commit-hash>' to see the commit message" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 4: Number of files (1 file - only app.py)
|
|
||||||
if ($investigationLower -match "1|one|app\.py") {
|
|
||||||
Write-Host "[PASS] Number of files modified identified" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Number of files modified not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Use 'git show <commit-hash> --stat' to see which files were changed" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 5: Some mention of timestamp/date (flexible check)
|
|
||||||
# We're just checking they attempted to answer this
|
|
||||||
if ($investigationLower -match "202|date|time|\d{4}-\d{2}-\d{2}") {
|
|
||||||
Write-Host "[PASS] Timestamp/date documented" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Timestamp/date not documented" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] The git blame output shows the date and time of each change" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
# Final summary
|
|
||||||
if ($allChecksPassed) {
|
|
||||||
Write-Host "`n" -NoNewline
|
|
||||||
Write-Host "=====================================" -ForegroundColor Green
|
|
||||||
Write-Host " INVESTIGATION COMPLETE!" -ForegroundColor Green
|
|
||||||
Write-Host "=====================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nExcellent detective work! You've successfully used git blame to track down the security issue." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nYou now know how to:" -ForegroundColor Cyan
|
|
||||||
Write-Host " - Use git blame to find who modified each line" -ForegroundColor White
|
|
||||||
Write-Host " - Read and interpret git blame output" -ForegroundColor White
|
|
||||||
Write-Host " - Use git blame with -e flag to show email addresses" -ForegroundColor White
|
|
||||||
Write-Host " - Find commit details after identifying changes with blame" -ForegroundColor White
|
|
||||||
Write-Host " - Conduct code archaeology to understand code history" -ForegroundColor White
|
|
||||||
Write-Host "`nRemember: git blame is for understanding, not blaming!" -ForegroundColor Yellow
|
|
||||||
Write-Host "`nReady for the next module!" -ForegroundColor Green
|
|
||||||
Write-Host ""
|
|
||||||
} else {
|
|
||||||
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
|
|
||||||
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
|
|
||||||
Write-Host ""
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
@@ -1,448 +0,0 @@
|
|||||||
# Module 06: Merge Strategies - Fast-Forward vs Three-Way
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
In this module, you will:
|
|
||||||
- Understand the difference between fast-forward and three-way merges
|
|
||||||
- Learn when Git automatically chooses each strategy
|
|
||||||
- Force specific merge behavior with `--no-ff` and `--ff-only` flags
|
|
||||||
- Understand the trade-offs between linear and branched history
|
|
||||||
- Make informed decisions about merge strategies for different workflows
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
Before starting this module, you should have completed:
|
|
||||||
- Module 03: Branching Basics
|
|
||||||
- Module 04: Merging Branches
|
|
||||||
|
|
||||||
## Challenge
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
Run the setup script to create your challenge environment:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a `challenge/` directory with scenarios for both fast-forward and three-way merges.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
You'll experiment with both types of merges and learn how to control Git's merge behavior.
|
|
||||||
|
|
||||||
**Part 1: Fast-Forward Merge**
|
|
||||||
1. Merge `feature-fast-forward` into main
|
|
||||||
2. Observe Git's "Fast-forward" message
|
|
||||||
3. Examine the linear history
|
|
||||||
|
|
||||||
**Part 2: Three-Way Merge**
|
|
||||||
4. Merge `feature-divergent` into main
|
|
||||||
5. Observe Git creates a merge commit
|
|
||||||
6. Examine the branched history
|
|
||||||
|
|
||||||
**Part 3: Force Merge Commit**
|
|
||||||
7. Merge `feature-optional` into main using `--no-ff`
|
|
||||||
8. Compare with the fast-forward from Part 1
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory: `cd challenge`
|
|
||||||
2. View all branches: `git branch -a`
|
|
||||||
3. View the current graph: `git log --oneline --graph --all`
|
|
||||||
4. Merge feature-fast-forward: `git merge feature-fast-forward`
|
|
||||||
5. Check the log: `git log --oneline --graph`
|
|
||||||
6. Merge feature-divergent: `git merge feature-divergent`
|
|
||||||
7. Check the log: `git log --oneline --graph --all`
|
|
||||||
8. Merge feature-optional with --no-ff: `git merge --no-ff feature-optional`
|
|
||||||
9. Compare the results: `git log --oneline --graph --all`
|
|
||||||
|
|
||||||
> **Key Questions to Consider:**
|
|
||||||
> - Which merges created merge commits?
|
|
||||||
> - Which merge kept a linear history?
|
|
||||||
> - How does `--no-ff` change Git's behavior?
|
|
||||||
> - When would you prefer each approach?
|
|
||||||
|
|
||||||
## Understanding Merge Strategies
|
|
||||||
|
|
||||||
### Fast-Forward Merge
|
|
||||||
|
|
||||||
A **fast-forward merge** happens when the target branch (e.g., `main`) hasn't changed since the feature branch was created. Git simply "fast-forwards" the branch pointer to the latest commit on the feature branch.
|
|
||||||
|
|
||||||
**Before the merge:**
|
|
||||||
```
|
|
||||||
main: A---B
|
|
||||||
\
|
|
||||||
feature: C---D
|
|
||||||
```
|
|
||||||
|
|
||||||
In this scenario:
|
|
||||||
- Commit B is where `feature` branched off from `main`
|
|
||||||
- Commits C and D are new commits on the `feature` branch
|
|
||||||
- `main` has NO new commits since the branch split
|
|
||||||
- The history is **linear** (straight line from A to D)
|
|
||||||
|
|
||||||
**After `git merge feature` (on main):**
|
|
||||||
```
|
|
||||||
main: A---B---C---D
|
|
||||||
↑
|
|
||||||
feature
|
|
||||||
```
|
|
||||||
|
|
||||||
**What happened:**
|
|
||||||
- Git moved the `main` pointer forward to commit D
|
|
||||||
- NO merge commit was created
|
|
||||||
- The history remains linear (a straight line)
|
|
||||||
- Both `main` and `feature` now point to the same commit (D)
|
|
||||||
|
|
||||||
**Command:**
|
|
||||||
```bash
|
|
||||||
git switch main
|
|
||||||
git merge feature-fast-forward
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output you'll see:**
|
|
||||||
```
|
|
||||||
Updating abc123..def456
|
|
||||||
Fast-forward
|
|
||||||
new-feature.py | 10 ++++++++++
|
|
||||||
1 file changed, 10 insertions(+)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notice the "Fast-forward" message!**
|
|
||||||
|
|
||||||
**Characteristics:**
|
|
||||||
- ✅ Keeps history linear and clean
|
|
||||||
- ✅ Simpler to read in `git log`
|
|
||||||
- ✅ No extra merge commit
|
|
||||||
- ❌ Loses visibility that work was done on a branch
|
|
||||||
- ❌ Harder to revert entire features at once
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Three-Way Merge
|
|
||||||
|
|
||||||
A **three-way merge** happens when BOTH branches have new commits since they diverged. Git must combine changes from both branches, which creates a special merge commit.
|
|
||||||
|
|
||||||
**Before the merge:**
|
|
||||||
```
|
|
||||||
main: A---B---C---E
|
|
||||||
\
|
|
||||||
feature: D---F
|
|
||||||
```
|
|
||||||
|
|
||||||
In this scenario:
|
|
||||||
- Commit B is where `feature` branched off from `main`
|
|
||||||
- Commits C and E are new commits on `main`
|
|
||||||
- Commits D and F are new commits on `feature`
|
|
||||||
- The branches have **diverged** (both have unique commits)
|
|
||||||
|
|
||||||
**After `git merge feature` (on main):**
|
|
||||||
```
|
|
||||||
main: A---B---C---E---M
|
|
||||||
\ /
|
|
||||||
feature: D---F---/
|
|
||||||
```
|
|
||||||
|
|
||||||
**What happened:**
|
|
||||||
- Git created a new **merge commit** (M)
|
|
||||||
- Commit M has TWO parent commits: E (from main) and F (from feature)
|
|
||||||
- The merge commit combines changes from both branches
|
|
||||||
- The history shows the branches converging
|
|
||||||
|
|
||||||
**Why it's called "three-way":**
|
|
||||||
Git uses THREE commits to perform the merge:
|
|
||||||
1. **Commit B** - The common ancestor (where branches split)
|
|
||||||
2. **Commit E** - The latest commit on `main`
|
|
||||||
3. **Commit F** - The latest commit on `feature`
|
|
||||||
|
|
||||||
Git compares all three to figure out what changed on each branch and how to combine them.
|
|
||||||
|
|
||||||
**Command:**
|
|
||||||
```bash
|
|
||||||
git switch main
|
|
||||||
git merge feature-divergent
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output you'll see:**
|
|
||||||
```
|
|
||||||
Merge made by the 'ort' strategy.
|
|
||||||
feature-code.py | 5 +++++
|
|
||||||
1 file changed, 5 insertions(+)
|
|
||||||
```
|
|
||||||
|
|
||||||
**Notice it says "Merge made by the 'ort' strategy" instead of "Fast-forward"!**
|
|
||||||
|
|
||||||
**Characteristics:**
|
|
||||||
- ✅ Preserves feature branch history
|
|
||||||
- ✅ Shows when features were merged
|
|
||||||
- ✅ Easier to revert entire features (revert the merge commit)
|
|
||||||
- ✅ Clear visualization in git log --graph
|
|
||||||
- ❌ Creates more commits (merge commits)
|
|
||||||
- ❌ History can become complex with many merges
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### When Does Each Type Happen?
|
|
||||||
|
|
||||||
| Situation | Merge Type | Merge Commit? | Git's Behavior |
|
|
||||||
|-----------|------------|---------------|----------------|
|
|
||||||
| Target branch (main) has NO new commits | Fast-Forward | ❌ No | Automatic |
|
|
||||||
| Target branch (main) HAS new commits | Three-Way | ✅ Yes | Automatic |
|
|
||||||
| You use `--no-ff` flag | Three-Way | ✅ Yes | Forced |
|
|
||||||
| You use `--ff-only` flag | Fast-Forward | ❌ No | Fails if not possible |
|
|
||||||
|
|
||||||
**Git chooses automatically** based on the branch state, but you can override this behavior!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Controlling Merge Behavior
|
|
||||||
|
|
||||||
### Force a Merge Commit with `--no-ff`
|
|
||||||
|
|
||||||
Even if a fast-forward is possible, you can force Git to create a merge commit:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git merge --no-ff feature-optional
|
|
||||||
```
|
|
||||||
|
|
||||||
**Before (fast-forward would be possible):**
|
|
||||||
```
|
|
||||||
main: A---B
|
|
||||||
\
|
|
||||||
feature: C---D
|
|
||||||
```
|
|
||||||
|
|
||||||
**After `git merge --no-ff feature` (on main):**
|
|
||||||
```
|
|
||||||
main: A---B-------M
|
|
||||||
\ /
|
|
||||||
feature: C---D
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice that even though main didn't change, a merge commit (M) was created!
|
|
||||||
|
|
||||||
**When to use `--no-ff`:**
|
|
||||||
- ✅ When you want to preserve the feature branch in history
|
|
||||||
- ✅ For important features that might need to be reverted
|
|
||||||
- ✅ In team workflows where you want to see when features were merged
|
|
||||||
- ✅ When following a branching model like Git Flow
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
# You've finished a major feature on feature-auth
|
|
||||||
git switch main
|
|
||||||
git merge --no-ff feature-auth -m "Merge feature-auth: Add user authentication system"
|
|
||||||
```
|
|
||||||
|
|
||||||
This creates a clear marker in history showing when and what was merged.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Require Fast-Forward with `--ff-only`
|
|
||||||
|
|
||||||
You can make Git fail the merge if a fast-forward isn't possible:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git merge --ff-only feature-branch
|
|
||||||
```
|
|
||||||
|
|
||||||
**What happens:**
|
|
||||||
- ✅ If fast-forward is possible, Git merges
|
|
||||||
- ❌ If branches have diverged, Git refuses to merge
|
|
||||||
|
|
||||||
**Output when it fails:**
|
|
||||||
```
|
|
||||||
fatal: Not possible to fast-forward, aborting.
|
|
||||||
```
|
|
||||||
|
|
||||||
**When to use `--ff-only`:**
|
|
||||||
- ✅ When you want to keep history strictly linear
|
|
||||||
- ✅ To ensure you rebase before merging
|
|
||||||
- ✅ In workflows that prohibit merge commits
|
|
||||||
|
|
||||||
**Example workflow:**
|
|
||||||
```bash
|
|
||||||
# Try to merge
|
|
||||||
git merge --ff-only feature-branch
|
|
||||||
|
|
||||||
# If it fails, rebase first
|
|
||||||
git switch feature-branch
|
|
||||||
git rebase main
|
|
||||||
git switch main
|
|
||||||
git merge --ff-only feature-branch # Now it works!
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Visualizing the Difference
|
|
||||||
|
|
||||||
### Linear History (Fast-Forward)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --graph
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
* d1e2f3g (HEAD -> main, feature) Add feature C
|
|
||||||
* a4b5c6d Add feature B
|
|
||||||
* 7e8f9g0 Add feature A
|
|
||||||
* 1a2b3c4 Initial commit
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice the straight line! No branching visible.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Branched History (Three-Way Merge)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --graph --all
|
|
||||||
```
|
|
||||||
|
|
||||||
```
|
|
||||||
* m1e2r3g (HEAD -> main) Merge branch 'feature'
|
|
||||||
|\
|
|
||||||
| * f4e5a6t (feature) Add feature implementation
|
|
||||||
| * u7r8e9s Feature setup
|
|
||||||
* | a1b2c3d Update documentation on main
|
|
||||||
|/
|
|
||||||
* 0i1n2i3t Initial commit
|
|
||||||
```
|
|
||||||
|
|
||||||
Notice the branching pattern! You can see:
|
|
||||||
- Where the branch split (`|/`)
|
|
||||||
- Commits on each branch (`|`)
|
|
||||||
- Where branches merged (`* merge commit`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Decision Guide: Which Strategy to Use?
|
|
||||||
|
|
||||||
### Use Fast-Forward When:
|
|
||||||
- ✅ Working on personal projects
|
|
||||||
- ✅ Want simplest, cleanest history
|
|
||||||
- ✅ Small changes or bug fixes
|
|
||||||
- ✅ Don't need to track feature branches
|
|
||||||
- ✅ History readability is top priority
|
|
||||||
|
|
||||||
### Use Three-Way Merge (or force with --no-ff) When:
|
|
||||||
- ✅ Working in teams
|
|
||||||
- ✅ Want to preserve feature context
|
|
||||||
- ✅ Need to revert features as units
|
|
||||||
- ✅ Following Git Flow or similar workflow
|
|
||||||
- ✅ Important to see when features were integrated
|
|
||||||
|
|
||||||
### Force Fast-Forward (--ff-only) When:
|
|
||||||
- ✅ Enforcing rebase workflow
|
|
||||||
- ✅ Maintaining strictly linear history
|
|
||||||
- ✅ Integration branch requires clean history
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Comparison Table
|
|
||||||
|
|
||||||
| Aspect | Fast-Forward | Three-Way |
|
|
||||||
|--------|-------------|-----------|
|
|
||||||
| **When it happens** | Target branch unchanged | Both branches have new commits |
|
|
||||||
| **Merge commit created?** | ❌ No | ✅ Yes |
|
|
||||||
| **History appearance** | Linear | Branched |
|
|
||||||
| **Command output** | "Fast-forward" | "Merge made by..." |
|
|
||||||
| **Git log --graph** | Straight line | Fork and merge pattern |
|
|
||||||
| **Can revert entire feature?** | ❌ No (must revert each commit) | ✅ Yes (revert merge commit) |
|
|
||||||
| **Force it** | `--ff-only` | `--no-ff` |
|
|
||||||
| **Best for** | Solo work, small changes | Team work, features |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
### Merging with Strategy Control
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git merge <branch> # Let Git decide automatically
|
|
||||||
git merge --no-ff <branch> # Force a merge commit
|
|
||||||
git merge --ff-only <branch> # Only merge if fast-forward possible
|
|
||||||
git merge --abort # Cancel a merge in progress
|
|
||||||
```
|
|
||||||
|
|
||||||
### Viewing Different Merge Types
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# See all merges
|
|
||||||
git log --merges # Only merge commits
|
|
||||||
git log --no-merges # Hide merge commits
|
|
||||||
|
|
||||||
# Visualize history
|
|
||||||
git log --oneline --graph # See branch structure
|
|
||||||
git log --oneline --graph --all # Include all branches
|
|
||||||
git log --first-parent # Follow only main branch line
|
|
||||||
|
|
||||||
# Check if merge would be fast-forward
|
|
||||||
git merge-base main feature # Find common ancestor
|
|
||||||
git log main..feature # See commits unique to feature
|
|
||||||
```
|
|
||||||
|
|
||||||
### Configuring Default Behavior
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Disable fast-forward by default
|
|
||||||
git config merge.ff false # Always create merge commits
|
|
||||||
|
|
||||||
# Only allow fast-forward
|
|
||||||
git config merge.ff only # Refuse non-fast-forward merges
|
|
||||||
|
|
||||||
# Reset to default
|
|
||||||
git config --unset merge.ff
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common Patterns in the Wild
|
|
||||||
|
|
||||||
### GitHub Flow (Simple)
|
|
||||||
- Use fast-forward when possible
|
|
||||||
- Short-lived feature branches
|
|
||||||
- Merge to main frequently
|
|
||||||
|
|
||||||
### Git Flow (Structured)
|
|
||||||
- Always use `--no-ff` for features
|
|
||||||
- Preserve branch history
|
|
||||||
- Complex release management
|
|
||||||
|
|
||||||
### Rebase Workflow
|
|
||||||
- Always use `--ff-only`
|
|
||||||
- Rebase before merging
|
|
||||||
- Strictly linear history
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Once you've completed all three merges, verify your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification script will check that you've experienced both types of merges and used the `--no-ff` flag.
|
|
||||||
|
|
||||||
## Need to Start Over?
|
|
||||||
|
|
||||||
If you want to reset the challenge and start fresh:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will remove the challenge directory and run the setup script again, giving you a clean slate.
|
|
||||||
|
|
||||||
## What's Next?
|
|
||||||
|
|
||||||
Now that you understand merge strategies, you can make informed decisions about your workflow. Consider:
|
|
||||||
|
|
||||||
- **For personal projects:** Fast-forward merges keep history simple
|
|
||||||
- **For team projects:** Three-way merges preserve context
|
|
||||||
- **For open source:** Follow the project's contribution guidelines
|
|
||||||
|
|
||||||
The best strategy depends on your team's needs and workflow!
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the Module 06 challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script removes the challenge directory and re-runs the setup script
|
|
||||||
to give you a fresh start.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Resetting Module 06 Challenge ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Remove challenge directory if it exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
Write-Host "Challenge directory removed." -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "No challenge directory found to remove." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
Write-Host "`nRunning setup script..." -ForegroundColor Cyan
|
|
||||||
& "./setup.ps1"
|
|
||||||
@@ -1,221 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the Module 06 challenge environment for learning merge strategies.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script creates a challenge directory with a Git repository that
|
|
||||||
contains scenarios for both fast-forward and three-way merges, allowing
|
|
||||||
students to compare different merge strategies.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Setting up Module 06 Challenge ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Remove existing challenge directory if it exists
|
|
||||||
if (Test-Path "challenge") {
|
|
||||||
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
|
||||||
Remove-Item -Recurse -Force "challenge"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Create fresh challenge directory
|
|
||||||
Write-Host "Creating challenge directory..." -ForegroundColor Green
|
|
||||||
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Initialize Git repository
|
|
||||||
Write-Host "Initializing Git repository..." -ForegroundColor Green
|
|
||||||
git init | Out-Null
|
|
||||||
|
|
||||||
# Configure git for this repository
|
|
||||||
git config user.name "Workshop Student"
|
|
||||||
git config user.email "student@example.com"
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# Scenario 1: Fast-Forward Merge Setup
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
Write-Host "Setting up fast-forward merge scenario..." -ForegroundColor Green
|
|
||||||
|
|
||||||
# Commit 1: Initial structure on main
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Application started")
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial application structure" | Out-Null
|
|
||||||
|
|
||||||
# Create feature-fast-forward branch (main won't change after this)
|
|
||||||
git switch -c feature-fast-forward | Out-Null
|
|
||||||
|
|
||||||
# Commit on feature-fast-forward
|
|
||||||
$utilsContent = @"
|
|
||||||
# utils.py - Utility functions
|
|
||||||
|
|
||||||
def format_string(text):
|
|
||||||
"""Format a string to title case."""
|
|
||||||
return text.title()
|
|
||||||
|
|
||||||
def validate_input(text):
|
|
||||||
"""Validate user input."""
|
|
||||||
return text and len(text) > 0
|
|
||||||
"@
|
|
||||||
Set-Content -Path "utils.py" -Value $utilsContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add utility functions" | Out-Null
|
|
||||||
|
|
||||||
# Second commit on feature-fast-forward
|
|
||||||
$utilsContent = @"
|
|
||||||
# utils.py - Utility functions
|
|
||||||
|
|
||||||
def format_string(text):
|
|
||||||
"""Format a string to title case."""
|
|
||||||
return text.title()
|
|
||||||
|
|
||||||
def validate_input(text):
|
|
||||||
"""Validate user input."""
|
|
||||||
return text and len(text) > 0
|
|
||||||
|
|
||||||
def sanitize_input(text):
|
|
||||||
"""Remove dangerous characters from input."""
|
|
||||||
return text.replace("<", "").replace(">", "")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "utils.py" -Value $utilsContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add input sanitization" | Out-Null
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# Scenario 2: Three-Way Merge Setup
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
Write-Host "Setting up three-way merge scenario..." -ForegroundColor Green
|
|
||||||
|
|
||||||
# Switch back to main
|
|
||||||
git switch main | Out-Null
|
|
||||||
|
|
||||||
# Create feature-divergent branch
|
|
||||||
git switch -c feature-divergent | Out-Null
|
|
||||||
|
|
||||||
# Commit on feature-divergent
|
|
||||||
$authContent = @"
|
|
||||||
# auth.py - Authentication module
|
|
||||||
|
|
||||||
def authenticate(username, password):
|
|
||||||
"""Authenticate a user."""
|
|
||||||
print(f"Authenticating: {username}")
|
|
||||||
# TODO: Implement actual authentication
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "auth.py" -Value $authContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add authentication module" | Out-Null
|
|
||||||
|
|
||||||
# Second commit on feature-divergent
|
|
||||||
$authContent = @"
|
|
||||||
# auth.py - Authentication module
|
|
||||||
|
|
||||||
def authenticate(username, password):
|
|
||||||
"""Authenticate a user."""
|
|
||||||
print(f"Authenticating: {username}")
|
|
||||||
# TODO: Implement actual authentication
|
|
||||||
return True
|
|
||||||
|
|
||||||
def check_permissions(user, resource):
|
|
||||||
"""Check if user has permission for resource."""
|
|
||||||
print(f"Checking permissions for {user}")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "auth.py" -Value $authContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add permission checking" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main and make a commit (creates divergence)
|
|
||||||
git switch main | Out-Null
|
|
||||||
|
|
||||||
$readmeContent = @"
|
|
||||||
# My Application
|
|
||||||
|
|
||||||
A Python application with utilities and authentication.
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- String formatting and validation
|
|
||||||
- User authentication
|
|
||||||
- Permission management
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Install Python 3.8+
|
|
||||||
2. Run: python app.py
|
|
||||||
"@
|
|
||||||
Set-Content -Path "README.md" -Value $readmeContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add README documentation" | Out-Null
|
|
||||||
|
|
||||||
# ========================================
|
|
||||||
# Scenario 3: Optional Fast-Forward for --no-ff
|
|
||||||
# ========================================
|
|
||||||
|
|
||||||
Write-Host "Setting up --no-ff demonstration scenario..." -ForegroundColor Green
|
|
||||||
|
|
||||||
# Create feature-optional branch (main won't change after this)
|
|
||||||
git switch -c feature-optional | Out-Null
|
|
||||||
|
|
||||||
# Commit on feature-optional
|
|
||||||
$configContent = @"
|
|
||||||
# config.py - Configuration settings
|
|
||||||
|
|
||||||
DEBUG_MODE = False
|
|
||||||
LOG_LEVEL = "INFO"
|
|
||||||
DATABASE_URL = "sqlite:///app.db"
|
|
||||||
"@
|
|
||||||
Set-Content -Path "config.py" -Value $configContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add configuration module" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main
|
|
||||||
git switch main | Out-Null
|
|
||||||
|
|
||||||
# Return to module directory
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
|
||||||
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nYou have THREE scenarios set up:" -ForegroundColor Yellow
|
|
||||||
Write-Host "`n Scenario 1: Fast-Forward Merge" -ForegroundColor White
|
|
||||||
Write-Host " Branch: feature-fast-forward" -ForegroundColor Cyan
|
|
||||||
Write-Host " Status: main has NOT changed since branch was created" -ForegroundColor Cyan
|
|
||||||
Write-Host " Result: Will fast-forward (no merge commit)" -ForegroundColor Green
|
|
||||||
Write-Host "`n Scenario 2: Three-Way Merge" -ForegroundColor White
|
|
||||||
Write-Host " Branch: feature-divergent" -ForegroundColor Cyan
|
|
||||||
Write-Host " Status: BOTH main and branch have new commits" -ForegroundColor Cyan
|
|
||||||
Write-Host " Result: Will create merge commit" -ForegroundColor Green
|
|
||||||
Write-Host "`n Scenario 3: Force Merge Commit" -ForegroundColor White
|
|
||||||
Write-Host " Branch: feature-optional" -ForegroundColor Cyan
|
|
||||||
Write-Host " Status: Could fast-forward, but we'll use --no-ff" -ForegroundColor Cyan
|
|
||||||
Write-Host " Result: Will create merge commit even though fast-forward is possible" -ForegroundColor Green
|
|
||||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
|
||||||
Write-Host " 2. View initial state: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host " 3. Merge fast-forward: git merge feature-fast-forward" -ForegroundColor White
|
|
||||||
Write-Host " 4. View result: git log --oneline --graph" -ForegroundColor White
|
|
||||||
Write-Host " 5. Merge divergent: git merge feature-divergent" -ForegroundColor White
|
|
||||||
Write-Host " 6. View result: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host " 7. Merge with --no-ff: git merge --no-ff feature-optional" -ForegroundColor White
|
|
||||||
Write-Host " 8. View final result: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host " 9. Compare all three merges!" -ForegroundColor White
|
|
||||||
Write-Host " 10. Run '..\verify.ps1' to check your solution" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,140 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the Module 06 challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script checks that:
|
|
||||||
- The challenge directory exists
|
|
||||||
- A Git repository exists
|
|
||||||
- All three feature branches have been merged
|
|
||||||
- Appropriate merge strategies were used
|
|
||||||
- Student understands the difference between fast-forward and three-way merges
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 06 Solution ===" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
$allChecksPassed = $true
|
|
||||||
|
|
||||||
# Check if challenge directory exists
|
|
||||||
if (-not (Test-Path "challenge")) {
|
|
||||||
Write-Host "[FAIL] Challenge directory not found. Did you run setup.ps1?" -ForegroundColor Red
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location "challenge"
|
|
||||||
|
|
||||||
# Check if git repository exists
|
|
||||||
if (-not (Test-Path ".git")) {
|
|
||||||
Write-Host "[FAIL] Not a git repository. Did you run setup.ps1?" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check current branch is main
|
|
||||||
$currentBranch = git branch --show-current 2>$null
|
|
||||||
if ($currentBranch -eq "main") {
|
|
||||||
Write-Host "[PASS] Currently on main branch" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Not on main branch (currently on: $currentBranch)" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Switch to main with: git switch main" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 1: Fast-Forward Merge - utils.py should exist
|
|
||||||
if (Test-Path "utils.py") {
|
|
||||||
Write-Host "[PASS] utils.py exists (feature-fast-forward merged)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] utils.py not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Merge feature-fast-forward: git merge feature-fast-forward" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 2: Three-Way Merge - auth.py should exist
|
|
||||||
if (Test-Path "auth.py") {
|
|
||||||
Write-Host "[PASS] auth.py exists (feature-divergent merged)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] auth.py not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Merge feature-divergent: git merge feature-divergent" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check 3: --no-ff Merge - config.py should exist
|
|
||||||
if (Test-Path "config.py") {
|
|
||||||
Write-Host "[PASS] config.py exists (feature-optional merged)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] config.py not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Merge feature-optional: git merge --no-ff feature-optional" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for merge commits
|
|
||||||
$mergeCommits = git log --merges --oneline 2>$null
|
|
||||||
$mergeCount = ($mergeCommits | Measure-Object -Line).Lines
|
|
||||||
|
|
||||||
if ($mergeCount -ge 2) {
|
|
||||||
Write-Host "[PASS] Found $mergeCount merge commit(s)" -ForegroundColor Green
|
|
||||||
Write-Host "[INFO] Expected: 1 from three-way merge + 1 from --no-ff = 2 total" -ForegroundColor Cyan
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Only found $mergeCount merge commit(s), expected at least 2" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Make sure to:" -ForegroundColor Yellow
|
|
||||||
Write-Host " 1. Merge feature-divergent (creates merge commit)" -ForegroundColor Yellow
|
|
||||||
Write-Host " 2. Merge feature-optional with --no-ff flag (forces merge commit)" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Provide detailed merge analysis
|
|
||||||
Write-Host "`n--- Merge Analysis ---" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# Count total commits
|
|
||||||
$totalCommits = git rev-list --count HEAD 2>$null
|
|
||||||
Write-Host "[INFO] Total commits on main: $totalCommits" -ForegroundColor Cyan
|
|
||||||
|
|
||||||
# List merge commits
|
|
||||||
if ($mergeCommits) {
|
|
||||||
Write-Host "[INFO] Merge commits found:" -ForegroundColor Cyan
|
|
||||||
$mergeCommits | ForEach-Object {
|
|
||||||
Write-Host " $_" -ForegroundColor White
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if branches still exist
|
|
||||||
$branches = git branch 2>$null
|
|
||||||
Write-Host "[INFO] Existing branches:" -ForegroundColor Cyan
|
|
||||||
$branches | ForEach-Object {
|
|
||||||
Write-Host " $_" -ForegroundColor White
|
|
||||||
}
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
|
|
||||||
# Final summary
|
|
||||||
if ($allChecksPassed) {
|
|
||||||
Write-Host "`n" -NoNewline
|
|
||||||
Write-Host "=====================================" -ForegroundColor Green
|
|
||||||
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
|
|
||||||
Write-Host "=====================================" -ForegroundColor Green
|
|
||||||
Write-Host "`nYou've mastered Git merge strategies!" -ForegroundColor Cyan
|
|
||||||
Write-Host "`nYou now understand:" -ForegroundColor Cyan
|
|
||||||
Write-Host " - Fast-forward merges (linear history, no merge commit)" -ForegroundColor White
|
|
||||||
Write-Host " - Three-way merges (divergent branches, creates merge commit)" -ForegroundColor White
|
|
||||||
Write-Host " - How to force merge commits with --no-ff flag" -ForegroundColor White
|
|
||||||
Write-Host " - When to use each merge strategy" -ForegroundColor White
|
|
||||||
Write-Host " - Trade-offs between linear and branched history" -ForegroundColor White
|
|
||||||
Write-Host "`nKey Takeaways:" -ForegroundColor Yellow
|
|
||||||
Write-Host " - Git chooses the strategy automatically based on branch state" -ForegroundColor Cyan
|
|
||||||
Write-Host " - Use --no-ff to preserve feature branch history" -ForegroundColor Cyan
|
|
||||||
Write-Host " - Use --ff-only to enforce linear history" -ForegroundColor Cyan
|
|
||||||
Write-Host " - Different workflows prefer different strategies" -ForegroundColor Cyan
|
|
||||||
Write-Host "`nNow you can make informed decisions about merge strategies for your projects!" -ForegroundColor Green
|
|
||||||
Write-Host ""
|
|
||||||
} else {
|
|
||||||
Write-Host "`n[SUMMARY] Some checks failed. Review the hints above and try again." -ForegroundColor Red
|
|
||||||
Write-Host "[INFO] You can run this verification script as many times as needed." -ForegroundColor Yellow
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "Quick Reference:" -ForegroundColor Cyan
|
|
||||||
Write-Host " git merge feature-fast-forward # Fast-forward merge" -ForegroundColor White
|
|
||||||
Write-Host " git merge feature-divergent # Three-way merge" -ForegroundColor White
|
|
||||||
Write-Host " git merge --no-ff feature-optional # Force merge commit" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
@@ -110,7 +110,7 @@ Nothing creates unnecessary conflicts like two people reformatting the same file
|
|||||||
|
|
||||||
**Do this:**
|
**Do this:**
|
||||||
- Agree on code formatting standards as a team
|
- Agree on code formatting standards as a team
|
||||||
- Use automatic formatters (Prettier, Black, etc.)
|
- Use automatic formatters (Prettier, Black, clang-format, etc.)
|
||||||
- Configure your editor to format on save
|
- Configure your editor to format on save
|
||||||
- Run formatters before committing
|
- Run formatters before committing
|
||||||
|
|
||||||
@@ -122,16 +122,22 @@ Renaming a widely-used function or moving files around will conflict with almost
|
|||||||
|
|
||||||
**Do this:**
|
**Do this:**
|
||||||
- Announce refactors to the team before starting
|
- Announce refactors to the team before starting
|
||||||
|
- This is important to coordinate merges of features that are being worked before the great refactoring.
|
||||||
|
A large renaming of namespaces or moving of files becomes hard to deal with when you rely on the old file-structure before the great merge.
|
||||||
|
Instead you should *freeze* any further features until the great refactoring has been completed and then resume feature-building
|
||||||
- Do them quickly and push immediately
|
- Do them quickly and push immediately
|
||||||
- Consider doing them when others aren't actively working
|
- Consider doing them when others aren't actively working
|
||||||
- Keep refactoring commits separate from feature work
|
- Keep refactoring commits separate from feature work
|
||||||
|
This keeps your commits clean and doesn't *dirty* the actual work with actual feature work (which also makes it easy to cherry-pick while minimizing conflicts).
|
||||||
|
|
||||||
## The Golden Rules
|
## The Golden Rules
|
||||||
|
|
||||||
1. **Sync frequently** - Pull before you start, pull before you push
|
1. **Sync frequently** - Pull before you start, pull before you push
|
||||||
2. **Commit small** - Many small commits beat one large commit
|
2. **Commit small** - Many small commits beat one large commit
|
||||||
|
A way to think of it is to keep commit *atomic*. In other words try to make them self-contained units that are easy to `cherry-pick` at a later time.
|
||||||
3. **Talk to your team** - A quick message prevents hours of conflict resolution
|
3. **Talk to your team** - A quick message prevents hours of conflict resolution
|
||||||
4. **Stay focused** - One branch = one purpose
|
Communication is key. Even though alot is delegated to the process itself, it's still a good idea to keep communicating with the team regarding larger changes or features being built.
|
||||||
|
4. **Stay focused** - One branch = one purpose (and ususally one person)
|
||||||
5. **Push promptly** - Don't sit on finished work
|
5. **Push promptly** - Don't sit on finished work
|
||||||
|
|
||||||
## When Conflicts Do Happen
|
## When Conflicts Do Happen
|
||||||
@@ -140,7 +146,7 @@ Even with best practices, conflicts will occur. When they do:
|
|||||||
|
|
||||||
1. **Don't panic** - Conflicts are normal, not failures
|
1. **Don't panic** - Conflicts are normal, not failures
|
||||||
2. **Read carefully** - Understand both sides before choosing
|
2. **Read carefully** - Understand both sides before choosing
|
||||||
3. **Test after resolving** - Make sure the merged code actually works
|
3. **Test after resolving** - Make sure the merged code actually works. A usual, and rather basic test flow is build -> test -> run
|
||||||
4. **Ask if unsure** - If you don't understand the other person's code, ask them
|
4. **Ask if unsure** - If you don't understand the other person's code, ask them
|
||||||
|
|
||||||
Remember: merge conflicts are a communication problem as much as a technical one. The best tool for reducing conflicts is talking to your team.
|
Remember: merge conflicts are a communication problem as much as a technical one. The best tool for reducing conflicts is talking to your team.
|
||||||
@@ -287,7 +287,7 @@ git stash
|
|||||||
Save your uncommitted changes temporarily and revert to a clean working directory.
|
Save your uncommitted changes temporarily and revert to a clean working directory.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git stash save "description"
|
git stash push -m "<description>"
|
||||||
```
|
```
|
||||||
Stash changes with a descriptive message.
|
Stash changes with a descriptive message.
|
||||||
|
|
||||||
9
06-FAQ.md
Normal file
9
06-FAQ.md
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
# Questions
|
||||||
|
|
||||||
|
## Is it possible to recover lost branches or commits?
|
||||||
|
|
||||||
|
Yes it is, however this is dependent on whether or not git has done an internal garbage collection which there usually is a retention period of 30 days.
|
||||||
|
|
||||||
|
**Beware** this is advanced territory and is usually only relevant when you've messed up with a `git reset` or a deletion of a branch
|
||||||
|
|
||||||
|
To read more, you can read the documentation here <https://git-scm.com/docs/git-reflog>
|
||||||
337
INSTALLATION.md
337
INSTALLATION.md
@@ -1,337 +0,0 @@
|
|||||||
# Installation Guide for Windows 11
|
|
||||||
|
|
||||||
This guide will help you install everything needed for the Git Workshop on Windows 11.
|
|
||||||
|
|
||||||
## Quick Start (Automated Installation)
|
|
||||||
|
|
||||||
**Easiest option:** Run our oneshot installation script that installs all prerequisites and clones the repository automatically.
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
This will:
|
|
||||||
- Install PowerShell 7, Git 2.23+, and Visual Studio Code
|
|
||||||
- Clone the git-workshop repository to `~/git-workshop`
|
|
||||||
- Leave you ready to start the workshop immediately
|
|
||||||
|
|
||||||
**Alternative:** If you've already cloned the repository, you can run the local installation script:
|
|
||||||
|
|
||||||
1. Open **PowerShell** or **Windows Terminal**
|
|
||||||
2. Navigate to the git-workshop directory
|
|
||||||
3. Run the installation script:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\install-prerequisites.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The script will:
|
|
||||||
- Check if tools are already installed
|
|
||||||
- Install PowerShell 7, Git 2.23+, and Visual Studio Code
|
|
||||||
- Prompt you for optional tools (Python 3.12, Windows Terminal)
|
|
||||||
- Show clear progress and verify each installation
|
|
||||||
- Display Git configuration instructions when complete
|
|
||||||
|
|
||||||
**If you prefer manual installation**, continue with the detailed steps below.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
You'll need administrator access to install software on your Windows 11 machine.
|
|
||||||
|
|
||||||
## What You'll Install
|
|
||||||
|
|
||||||
1. **PowerShell 7** - Modern cross-platform PowerShell (replaces the older Windows PowerShell 5.1)
|
|
||||||
2. **Git** - Version control system (2.23 or later)
|
|
||||||
3. **Visual Studio Code** - Modern code editor with excellent Git integration
|
|
||||||
|
|
||||||
## Manual Installation Steps
|
|
||||||
|
|
||||||
### 1. Install PowerShell 7
|
|
||||||
|
|
||||||
PowerShell 7 is the modern, cross-platform version of PowerShell. Windows 11 comes with PowerShell 5.1, but we recommend PowerShell 7 for the best experience.
|
|
||||||
|
|
||||||
**Option A: Using winget (Recommended)**
|
|
||||||
|
|
||||||
Open **Windows Terminal** or **Command Prompt** and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install --id Microsoft.PowerShell --source winget
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Manual Download**
|
|
||||||
|
|
||||||
1. Visit https://github.com/PowerShell/PowerShell/releases/latest
|
|
||||||
2. Download the file ending in `-win-x64.msi` (e.g., `PowerShell-7.4.1-win-x64.msi`)
|
|
||||||
3. Run the installer
|
|
||||||
4. Accept all defaults
|
|
||||||
|
|
||||||
**Verify Installation:**
|
|
||||||
|
|
||||||
Open a new terminal and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
pwsh --version
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see version 7.x.x or higher.
|
|
||||||
|
|
||||||
**Important:** After installing PowerShell 7, use it instead of the older "Windows PowerShell 5.1". Look for "PowerShell 7" in your Start menu or Windows Terminal.
|
|
||||||
|
|
||||||
### 2. Install Git
|
|
||||||
|
|
||||||
Git is the version control system you'll learn in this workshop. You need version 2.23 or later.
|
|
||||||
|
|
||||||
**Option A: Using winget (Recommended)**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install --id Git.Git -e --source winget
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Manual Download**
|
|
||||||
|
|
||||||
1. Visit https://git-scm.com/downloads
|
|
||||||
2. Click "Windows"
|
|
||||||
3. Download the 64-bit installer
|
|
||||||
4. Run the installer with these recommended settings:
|
|
||||||
- **Default editor**: Choose "Visual Studio Code" (we'll install it next)
|
|
||||||
- **PATH environment**: Select "Git from the command line and also from 3rd-party software"
|
|
||||||
- **Line ending conversions**: Choose "Checkout Windows-style, commit Unix-style line endings"
|
|
||||||
- **Terminal emulator**: Choose "Use Windows' default console window"
|
|
||||||
- All other settings: Accept defaults
|
|
||||||
|
|
||||||
**Verify Installation:**
|
|
||||||
|
|
||||||
Open a **new** PowerShell window and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
git --version
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see version 2.23 or higher (e.g., `git version 2.43.0`).
|
|
||||||
|
|
||||||
### 3. Install Visual Studio Code
|
|
||||||
|
|
||||||
VS Code is a free, powerful code editor with excellent Git integration.
|
|
||||||
|
|
||||||
**Option A: Using winget (Recommended)**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install --id Microsoft.VisualStudioCode --source winget
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Manual Download**
|
|
||||||
|
|
||||||
1. Visit https://code.visualstudio.com/
|
|
||||||
2. Click "Download for Windows"
|
|
||||||
3. Run the installer
|
|
||||||
4. During installation, check these options:
|
|
||||||
- ✅ Add "Open with Code" action to Windows Explorer file context menu
|
|
||||||
- ✅ Add "Open with Code" action to Windows Explorer directory context menu
|
|
||||||
- ✅ Register Code as an editor for supported file types
|
|
||||||
- ✅ Add to PATH
|
|
||||||
|
|
||||||
**Verify Installation:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
code --version
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see version information.
|
|
||||||
|
|
||||||
**Recommended VS Code Extensions:**
|
|
||||||
|
|
||||||
Open VS Code and install these extensions for the best Git experience:
|
|
||||||
|
|
||||||
1. **GitLens** - Supercharge Git capabilities
|
|
||||||
- Press `Ctrl+Shift+X` to open Extensions
|
|
||||||
- Search for "GitLens"
|
|
||||||
- Click Install
|
|
||||||
|
|
||||||
2. **Git Graph** - View Git history visually
|
|
||||||
- Search for "Git Graph"
|
|
||||||
- Click Install
|
|
||||||
|
|
||||||
3. **PowerShell** - Better PowerShell support
|
|
||||||
- Search for "PowerShell"
|
|
||||||
- Install the one from Microsoft
|
|
||||||
|
|
||||||
## Configure Git
|
|
||||||
|
|
||||||
Before making your first commit, tell Git who you are:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
git config --global user.name "Your Name"
|
|
||||||
git config --global user.email "your.email@example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Verify your configuration:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
git config --global user.name
|
|
||||||
git config --global user.email
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see your name and email printed.
|
|
||||||
|
|
||||||
**Optional: Set VS Code as Git's Default Editor**
|
|
||||||
|
|
||||||
If you installed Git before VS Code, configure Git to use VS Code:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
git config --global core.editor "code --wait"
|
|
||||||
```
|
|
||||||
|
|
||||||
## PowerShell Execution Policy
|
|
||||||
|
|
||||||
When running PowerShell scripts (`.ps1` files) in this workshop, you might encounter an error about execution policies.
|
|
||||||
|
|
||||||
**If you see an error like "script cannot be loaded because running scripts is disabled":**
|
|
||||||
|
|
||||||
Open **PowerShell 7 as Administrator** and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
```
|
|
||||||
|
|
||||||
This allows you to run local scripts while maintaining security for downloaded scripts.
|
|
||||||
|
|
||||||
## Running Scripts in the Workshop
|
|
||||||
|
|
||||||
After installation, you can run workshop scripts using:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\setup.ps1
|
|
||||||
.\verify.ps1
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example workflow:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Navigate to a module
|
|
||||||
cd 01-essentials\01-basics
|
|
||||||
|
|
||||||
# Run the setup script
|
|
||||||
.\setup.ps1
|
|
||||||
|
|
||||||
# Complete the challenge using Git commands
|
|
||||||
# ...
|
|
||||||
|
|
||||||
# Verify your solution
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Optional: Python (for Module 08 only)
|
|
||||||
|
|
||||||
Module 08 (Multiplayer Git) uses Python for "The Great Print Project". You only need this for that specific module.
|
|
||||||
|
|
||||||
**Install Python 3.12:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install --id Python.Python.3.12 --source winget
|
|
||||||
```
|
|
||||||
|
|
||||||
**Verify installation:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
python --version
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see Python 3.12.x or higher.
|
|
||||||
|
|
||||||
## Optional: Windows Terminal (Highly Recommended)
|
|
||||||
|
|
||||||
Windows Terminal provides a modern terminal experience with tabs, better colors, and PowerShell 7 integration.
|
|
||||||
|
|
||||||
**Install:**
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
winget install --id Microsoft.WindowsTerminal --source winget
|
|
||||||
```
|
|
||||||
|
|
||||||
Or install from the **Microsoft Store** (search for "Windows Terminal").
|
|
||||||
|
|
||||||
**After installation:**
|
|
||||||
- Press `Win+X` and select "Windows Terminal"
|
|
||||||
- Or search "Terminal" in the Start menu
|
|
||||||
- PowerShell 7 should be the default profile
|
|
||||||
|
|
||||||
## Verify Complete Installation
|
|
||||||
|
|
||||||
Run these commands to verify everything is installed correctly:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# PowerShell version (should be 7.x.x)
|
|
||||||
pwsh --version
|
|
||||||
|
|
||||||
# Git version (should be 2.23 or higher)
|
|
||||||
git --version
|
|
||||||
|
|
||||||
# VS Code version
|
|
||||||
code --version
|
|
||||||
|
|
||||||
# Git configuration
|
|
||||||
git config --global user.name
|
|
||||||
git config --global user.email
|
|
||||||
|
|
||||||
# Optional: Python (for Module 08)
|
|
||||||
python --version
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Git command not found
|
|
||||||
|
|
||||||
If `git --version` doesn't work after installation:
|
|
||||||
1. Close and reopen your terminal (Git needs a new terminal to update PATH)
|
|
||||||
2. Restart your computer if the problem persists
|
|
||||||
|
|
||||||
### VS Code command not found
|
|
||||||
|
|
||||||
If `code --version` doesn't work:
|
|
||||||
1. Ensure you checked "Add to PATH" during installation
|
|
||||||
2. Close and reopen your terminal
|
|
||||||
3. If still not working, reinstall VS Code with the PATH option enabled
|
|
||||||
|
|
||||||
### PowerShell execution policy errors
|
|
||||||
|
|
||||||
If you can't run `.ps1` scripts:
|
|
||||||
1. Open PowerShell 7 **as Administrator**
|
|
||||||
2. Run: `Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser`
|
|
||||||
3. Close admin PowerShell and try again in a regular PowerShell window
|
|
||||||
|
|
||||||
### winget command not found
|
|
||||||
|
|
||||||
If `winget` doesn't work:
|
|
||||||
1. Update Windows 11 to the latest version (Settings → Windows Update)
|
|
||||||
2. Install "App Installer" from the Microsoft Store
|
|
||||||
3. Restart your computer
|
|
||||||
|
|
||||||
## You're Ready!
|
|
||||||
|
|
||||||
Once all verification commands work, you're ready to start the workshop!
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
# Clone or download the git-workshop repository
|
|
||||||
# Navigate to it
|
|
||||||
cd path\to\git-workshop
|
|
||||||
|
|
||||||
# Start with Module 01
|
|
||||||
cd 01-essentials\01-basics
|
|
||||||
|
|
||||||
# Read the instructions
|
|
||||||
code README.md
|
|
||||||
|
|
||||||
# Run setup and begin!
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Steps
|
|
||||||
|
|
||||||
- Read the main [README.md](README.md) for workshop overview
|
|
||||||
- Check [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) for Git command reference
|
|
||||||
- Start with Module 01: `01-essentials\01-basics`
|
|
||||||
|
|
||||||
Happy learning!
|
|
||||||
502
README.md
502
README.md
@@ -1,502 +0,0 @@
|
|||||||
# Git Workshop
|
|
||||||
|
|
||||||
A comprehensive, hands-on Git workshop with 15 progressive modules covering everything from basic commits to advanced debugging techniques and real-world collaboration. Each module presents a real-world scenario that you must solve using Git commands, with automated verification to confirm your solution.
|
|
||||||
|
|
||||||
Perfect for developers who want to move beyond basic Git usage and master professional workflows including rebasing, conflict resolution, collaboration, and advanced Git techniques.
|
|
||||||
|
|
||||||
## Workshop Structure
|
|
||||||
|
|
||||||
The workshop is organized into two tracks:
|
|
||||||
|
|
||||||
### 01 Essentials - Core Git Skills (8 modules)
|
|
||||||
|
|
||||||
Master fundamental Git concepts and collaborative workflows:
|
|
||||||
|
|
||||||
- **Module 01: Git Basics** - Initialize repositories, stage changes, make commits
|
|
||||||
- **Module 02: Viewing History** - Use git log and git diff to explore project history
|
|
||||||
- **Module 03: Branching and Merging** - Create branches, merge them, and resolve conflicts (checkpoint-based)
|
|
||||||
- **Module 04: Cherry-Pick** - Apply specific commits from one branch to another
|
|
||||||
- **Module 05: Git Revert** - Safe undoing - preserve history while reversing changes (includes merge commit reversion)
|
|
||||||
- **Module 06: Git Reset** - Dangerous history rewriting - local cleanup only (NEVER on pushed commits!)
|
|
||||||
- **Module 07: Stash** - Temporarily save work without committing
|
|
||||||
- **Module 08: Multiplayer Git** - **The Great Print Project** - Real cloud-based collaboration with teammates
|
|
||||||
|
|
||||||
### 02 Advanced - Professional Techniques (6 modules)
|
|
||||||
|
|
||||||
Advanced Git workflows for power users:
|
|
||||||
|
|
||||||
- **Module 01: Rebasing** - Rebase branches to create linear history
|
|
||||||
- **Module 02: Interactive Rebase** - Clean up commit history before submitting pull requests
|
|
||||||
- **Module 03: Worktrees** - Work on multiple branches simultaneously
|
|
||||||
- **Module 04: Bisect** - Use binary search to find bug-introducing commits
|
|
||||||
- **Module 05: Blame** - Code archaeology - investigate who changed what and when
|
|
||||||
- **Module 06: Merge Strategies** - Master fast-forward vs three-way merges and when to use each
|
|
||||||
|
|
||||||
## How to Use This Workshop
|
|
||||||
|
|
||||||
### For Local Modules (01-08 in Essentials, all Advanced modules)
|
|
||||||
|
|
||||||
1. Navigate to a module directory (e.g., `01-essentials/01-basics`)
|
|
||||||
2. Read the `README.md` to understand the challenge
|
|
||||||
3. Run `./setup.ps1` to create the challenge environment
|
|
||||||
4. Complete the challenge using git commands
|
|
||||||
5. Run `./verify.ps1` to check if you've solved it correctly
|
|
||||||
6. Move to the next module
|
|
||||||
|
|
||||||
**Quick Reference**: See [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) for a comprehensive list of all Git commands covered in this workshop. Don't worry about memorizing everything - use this as a reference when you need to look up command syntax!
|
|
||||||
|
|
||||||
### For Module 08: Multiplayer Git
|
|
||||||
|
|
||||||
**This module is different!** It uses Azure DevOps for authentic cloud-based collaboration:
|
|
||||||
|
|
||||||
1. Navigate to `01-essentials/08-multiplayer`
|
|
||||||
2. Read the `README.md` for complete instructions
|
|
||||||
3. **No setup script** - you'll clone from Azure DevOps (URL provided by facilitator)
|
|
||||||
4. Work with a partner on shared branches
|
|
||||||
5. Experience real merge conflicts and pull requests
|
|
||||||
6. Use SSH keys for secure authentication (best practice)
|
|
||||||
7. **No verify script** - success is visual (your code appears in the final output)
|
|
||||||
|
|
||||||
**Facilitators**: See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for server setup and workshop guidance.
|
|
||||||
|
|
||||||
## Running PowerShell Scripts
|
|
||||||
|
|
||||||
Most modules include setup, verification, and reset scripts.
|
|
||||||
|
|
||||||
If you encounter an "execution policy" error when running scripts, open PowerShell as Administrator and run:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run scripts using:
|
|
||||||
```powershell
|
|
||||||
.\setup.ps1
|
|
||||||
.\verify.ps1
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
## Requirements
|
|
||||||
|
|
||||||
### Prerequisites
|
|
||||||
|
|
||||||
Install these tools before starting:
|
|
||||||
|
|
||||||
**PowerShell 7+**
|
|
||||||
```powershell
|
|
||||||
winget install Microsoft.PowerShell
|
|
||||||
```
|
|
||||||
|
|
||||||
**Git 2.23+**
|
|
||||||
```powershell
|
|
||||||
winget install Git.Git
|
|
||||||
```
|
|
||||||
|
|
||||||
**Visual Studio Code**
|
|
||||||
```powershell
|
|
||||||
winget install Microsoft.VisualStudioCode
|
|
||||||
```
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
**Option 1: Oneshot Installation (Recommended)**
|
|
||||||
Install everything and clone the repository in one command:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
irm https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | iex
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option 2: Manual Setup**
|
|
||||||
1. Install the prerequisites above
|
|
||||||
2. Clone this repository:
|
|
||||||
```powershell
|
|
||||||
git clone https://git.frod.dk/floppydiscen/git-workshop.git
|
|
||||||
```
|
|
||||||
3. Configure Git:
|
|
||||||
```powershell
|
|
||||||
git config --global user.name "Your Name"
|
|
||||||
git config --global user.email "your.email@example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
**Quick Check:**
|
|
||||||
|
|
||||||
You need the following software installed:
|
|
||||||
|
|
||||||
- **Git 2.23+** - Version control system
|
|
||||||
```powershell
|
|
||||||
git --version
|
|
||||||
```
|
|
||||||
|
|
||||||
- **PowerShell 7+**
|
|
||||||
```powershell
|
|
||||||
pwsh --version
|
|
||||||
```
|
|
||||||
|
|
||||||
- **Python 3.6+** (for Module 08 only)
|
|
||||||
```powershell
|
|
||||||
python --version
|
|
||||||
```
|
|
||||||
|
|
||||||
**First-Time Git Configuration:**
|
|
||||||
|
|
||||||
Before making your first commit, configure Git with your identity:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
git config --global user.name "Your Name"
|
|
||||||
git config --global user.email "your.email@example.com"
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify your configuration:
|
|
||||||
```powershell
|
|
||||||
git config --global user.name
|
|
||||||
git config --global user.email
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic Command Line Knowledge
|
|
||||||
|
|
||||||
You should know how to:
|
|
||||||
- Navigate directories (`cd`, `ls` or `dir`)
|
|
||||||
- See your current location (`pwd`)
|
|
||||||
- Read text files (`cat` or `type`)
|
|
||||||
|
|
||||||
Don't worry if you're not an expert - we'll guide you through each step!
|
|
||||||
|
|
||||||
## Optional: Install Glow for Pretty Markdown
|
|
||||||
|
|
||||||
This workshop includes many markdown files with instructions. You can install `glow` to render them beautifully in your terminal:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\install-glow.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
After installation, read markdown files with:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\bin\glow.exe README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
**Pro tip**: Use `glow -p` for pager mode on longer files!
|
|
||||||
|
|
||||||
Without glow, you can still read markdown files with any text editor or `cat README.md`.
|
|
||||||
|
|
||||||
## Common Git Terms
|
|
||||||
|
|
||||||
New to Git? Here are the key terms you'll encounter:
|
|
||||||
|
|
||||||
- **Repository (or "repo")**: A project folder tracked by Git, containing your files and their complete history
|
|
||||||
- **Commit**: A snapshot of your project at a specific point in time (like a save point in a video game)
|
|
||||||
- **Staging Area (or "Index")**: A preparation area where you select which changes to include in your next commit
|
|
||||||
- **Working Directory**: The actual files you see and edit on your computer
|
|
||||||
- **Branch**: An independent line of development (like a parallel universe for your code)
|
|
||||||
- **HEAD**: A pointer showing which commit you're currently working from
|
|
||||||
- **main/master**: The primary branch in a repository (main is the modern convention)
|
|
||||||
- **Merge**: Combining changes from different branches
|
|
||||||
- **Remote**: A version of your repository hosted elsewhere (like GitHub, GitLab, or Gitea)
|
|
||||||
- **Pull Request (PR)**: A request to merge your changes into another branch (used for code review)
|
|
||||||
- **Conflict**: When Git can't automatically merge changes because both versions modified the same lines
|
|
||||||
|
|
||||||
Don't worry if these don't make sense yet - you'll learn them hands-on as you progress!
|
|
||||||
|
|
||||||
## Getting Started
|
|
||||||
|
|
||||||
**First time?** Make sure you have Git and PowerShell installed - see [INSTALLATION.md](INSTALLATION.md) for Windows 11 setup instructions.
|
|
||||||
|
|
||||||
Once installed, start with Essentials Module 01:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd 01-essentials\01-basics
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Follow the instructions in each module's README.md file.
|
|
||||||
|
|
||||||
## What Makes This Workshop Different
|
|
||||||
|
|
||||||
- **Hands-On Practice**: Each module creates a real Git scenario you must solve
|
|
||||||
- **Automated Verification**: Scripts check your solution instantly (modules 01-08)
|
|
||||||
- **Progressive Difficulty**: Builds from basics to advanced Git techniques
|
|
||||||
- **Reset Anytime**: Each local module includes a reset script for a fresh start
|
|
||||||
- **Self-Paced**: Learn at your own speed with detailed README guides
|
|
||||||
- **Real Collaboration**: Module 08 uses an actual Git server for authentic teamwork
|
|
||||||
- **Comprehensive Coverage**: From `git init` to advanced rebasing and bisecting
|
|
||||||
|
|
||||||
## Learning Path
|
|
||||||
|
|
||||||
The modules are designed to build on each other:
|
|
||||||
|
|
||||||
### Recommended Progression
|
|
||||||
|
|
||||||
**Phase 1: Core Fundamentals (Essentials 01-02)**
|
|
||||||
- Git Basics, History
|
|
||||||
- **Goal**: Understand commits and history
|
|
||||||
|
|
||||||
**Phase 2: Collaboration Basics (Essentials 03)**
|
|
||||||
- Branching and Merging (checkpoint-based: branching, merging, conflicts)
|
|
||||||
- **Goal**: Work with multiple branches and resolve conflicts
|
|
||||||
|
|
||||||
**Phase 3: Workflow Tools (Essentials 04-06)**
|
|
||||||
- Cherry-Pick, Reset vs Revert, Stash
|
|
||||||
- **Goal**: Manage your work effectively
|
|
||||||
|
|
||||||
**Phase 4: Real Collaboration (Essentials 07)**
|
|
||||||
- **Multiplayer Git - The Great Print Project**
|
|
||||||
- **Goal**: Apply all skills with real teammates on a cloud server
|
|
||||||
- **Note**: This is a capstone module - bring everything together!
|
|
||||||
|
|
||||||
**Phase 5: Advanced Techniques (Advanced 01-06)**
|
|
||||||
- Rebasing, Interactive Rebase, Worktrees, Bisect, Blame, Merge Strategies
|
|
||||||
- **Goal**: Master professional Git workflows
|
|
||||||
- **When**: After completing Essentials and feeling confident
|
|
||||||
|
|
||||||
### Alternative Paths
|
|
||||||
|
|
||||||
**Fast Track (1 day workshop):**
|
|
||||||
- Essentials 01-03 + Essentials 07 (Multiplayer)
|
|
||||||
|
|
||||||
**Solo Learner:**
|
|
||||||
- Complete Essentials 01-06, skip 07 (requires partners and server)
|
|
||||||
- Or complete all Advanced modules for deep mastery
|
|
||||||
|
|
||||||
**Team Workshop:**
|
|
||||||
- Essentials 01-03 then jump to 07 (Multiplayer) for collaborative practice
|
|
||||||
|
|
||||||
## Tips for Success
|
|
||||||
|
|
||||||
- **Don't skip modules** - each builds on previous concepts
|
|
||||||
- **Read the README.md thoroughly** before starting each challenge
|
|
||||||
- **Keep [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) open** as a quick reference
|
|
||||||
- **Experiment freely** - you can always run `./reset.ps1` to start over
|
|
||||||
- **Use `git log --oneline --graph --all`** frequently to visualize repository state
|
|
||||||
- **If stuck**, check the Key Concepts section in the module's README
|
|
||||||
- **Consider installing glow** for better markdown reading experience
|
|
||||||
- **For Module 07**, work with a partner - collaboration is the point!
|
|
||||||
|
|
||||||
## Skills You'll Master
|
|
||||||
|
|
||||||
By completing this workshop, you'll be able to:
|
|
||||||
|
|
||||||
### From Essentials Track:
|
|
||||||
- ✅ Create and manage Git repositories with confidence
|
|
||||||
- ✅ Navigate project history and understand what changed when
|
|
||||||
- ✅ Use branches effectively for parallel development
|
|
||||||
- ✅ Merge branches and resolve conflicts like a pro
|
|
||||||
- ✅ Apply specific commits with cherry-pick
|
|
||||||
- ✅ Choose correctly between reset and revert
|
|
||||||
- ✅ Use stash to manage work-in-progress without commits
|
|
||||||
- ✅ **Collaborate with teammates on shared repositories**
|
|
||||||
- ✅ **Resolve real merge conflicts in a team environment**
|
|
||||||
- ✅ **Create and review pull requests**
|
|
||||||
- ✅ **Use Git on a real cloud server (Azure DevOps)**
|
|
||||||
- ✅ **Use SSH keys for secure Git authentication**
|
|
||||||
|
|
||||||
### From Advanced Track:
|
|
||||||
- ✅ Rebase to maintain clean, linear history
|
|
||||||
- ✅ Clean up messy commits before submitting pull requests
|
|
||||||
- ✅ Work on multiple branches simultaneously with worktrees
|
|
||||||
- ✅ Debug efficiently by finding bug-introducing commits with bisect
|
|
||||||
- ✅ Investigate code history with git blame
|
|
||||||
- ✅ Master different merge strategies and when to use each
|
|
||||||
|
|
||||||
These are professional-level Git skills used daily by developers at top tech companies.
|
|
||||||
|
|
||||||
## For Workshop Facilitators
|
|
||||||
|
|
||||||
This repository can be used for **self-paced learning** or as a **facilitated workshop**.
|
|
||||||
|
|
||||||
### Self-Paced Distribution
|
|
||||||
|
|
||||||
Before distributing this workshop to attendees for self-study:
|
|
||||||
|
|
||||||
1. **Delete the root `.git` directory**: This prevents confusion and ensures attendees practice git from scratch
|
|
||||||
```powershell
|
|
||||||
Remove-Item -Path .git -Recurse -Force
|
|
||||||
```
|
|
||||||
2. Each module's `challenge/` directory will become its own independent git repository when attendees run `setup.ps1`
|
|
||||||
3. This isolation ensures each module provides a clean learning environment
|
|
||||||
|
|
||||||
**Note**: Module 08 (Multiplayer) requires you to set up a Git server - see facilitator guide below.
|
|
||||||
|
|
||||||
### Facilitated Workshop
|
|
||||||
|
|
||||||
For running this as a full-day instructor-led workshop:
|
|
||||||
|
|
||||||
1. **See [WORKSHOP-AGENDA.md](WORKSHOP-AGENDA.md)** - Complete agenda with timing, activities, and facilitation tips
|
|
||||||
2. **See [PRESENTATION-OUTLINE.md](PRESENTATION-OUTLINE.md)** - Slide deck outline for presentations
|
|
||||||
3. **Workshop covers:** Essentials 01-05 + Module 08 (Multiplayer collaboration exercise)
|
|
||||||
4. **Duration:** 6-7 hours including breaks
|
|
||||||
5. **Format:** Mix of presentation, live demos, and hands-on challenges
|
|
||||||
|
|
||||||
**Facilitator preparation:**
|
|
||||||
- Review the workshop agenda thoroughly
|
|
||||||
- Set up Git server for Module 08 (see below)
|
|
||||||
- Ensure all participants have prerequisites installed (Git, PowerShell, Python)
|
|
||||||
- Prepare slides using the presentation outline
|
|
||||||
- Test all modules on a clean machine
|
|
||||||
- Create student accounts on your Git server
|
|
||||||
|
|
||||||
The workshop format combines instructor-led sessions with self-paced hands-on modules for an engaging learning experience.
|
|
||||||
|
|
||||||
### Setting Up Module 08: Multiplayer Git
|
|
||||||
|
|
||||||
Module 08 requires a Git server for authentic collaboration using **Azure DevOps**.
|
|
||||||
|
|
||||||
**Azure DevOps Setup**
|
|
||||||
|
|
||||||
Use Azure DevOps as the cloud-based Git platform for this module:
|
|
||||||
|
|
||||||
**Benefits:**
|
|
||||||
- 💰 Free tier supports up to 5 users with full access
|
|
||||||
- 🌐 Cloud-hosted - no server maintenance required
|
|
||||||
- 🔒 Enterprise-grade security and reliability
|
|
||||||
- 🔑 Built-in SSH key support (industry best practice)
|
|
||||||
- 👥 Perfect for workshops with any number of students (use Stakeholder licenses for >5 users)
|
|
||||||
- 📊 Built-in pull request workflows and code review tools
|
|
||||||
|
|
||||||
**Setup Steps:**
|
|
||||||
|
|
||||||
1. **Create Azure DevOps Organization** (if you don't have one):
|
|
||||||
- Sign up at [dev.azure.com](https://dev.azure.com) with a Microsoft account
|
|
||||||
- Create a new organization for your workshop
|
|
||||||
|
|
||||||
2. **Set up SSH authentication** (recommended for all users):
|
|
||||||
- See [AZURE-DEVOPS-SSH-SETUP.md](AZURE-DEVOPS-SSH-SETUP.md) for complete SSH key setup instructions
|
|
||||||
- SSH provides secure, passwordless authentication (industry standard)
|
|
||||||
|
|
||||||
3. **Configure workshop repository and users**:
|
|
||||||
- See `01-essentials/08-multiplayer/FACILITATOR-SETUP.md` for detailed workshop preparation:
|
|
||||||
- Adding student accounts to Azure DevOps
|
|
||||||
- Creating The Great Print Project repository
|
|
||||||
- Configuring branch policies
|
|
||||||
- Pairing students
|
|
||||||
- Monitoring progress
|
|
||||||
- Troubleshooting SSH and authentication issues
|
|
||||||
|
|
||||||
**Alternative: GitHub / GitLab / Bitbucket**
|
|
||||||
|
|
||||||
While this workshop uses Azure DevOps, the skills learned apply to any Git platform:
|
|
||||||
- The workflow is identical across all platforms
|
|
||||||
- SSH authentication works the same way everywhere
|
|
||||||
- Pull request concepts transfer directly
|
|
||||||
- Students can apply these skills to any Git hosting service
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Repository Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
git-workshop/
|
|
||||||
├── README.md # This file
|
|
||||||
├── INSTALLATION.md # Windows 11 installation guide (PowerShell 7, Git, VS Code)
|
|
||||||
├── install-prerequisites.ps1 # Automated installation script (one-shot setup)
|
|
||||||
├── GIT-CHEATSHEET.md # Quick reference for all Git commands
|
|
||||||
├── WORKSHOP-AGENDA.md # Facilitator guide for running workshops
|
|
||||||
├── PRESENTATION-OUTLINE.md # Slide deck outline
|
|
||||||
├── AZURE-DEVOPS-SSH-SETUP.md # SSH authentication best practices for Azure DevOps
|
|
||||||
├── install-glow.ps1 # Install glow markdown renderer
|
|
||||||
│
|
|
||||||
├── 01-essentials/ # Core Git skills (8 modules)
|
|
||||||
│ ├── 01-basics/ # Initialize, commit, status
|
|
||||||
│ ├── 02-history/ # Log, diff, show
|
|
||||||
│ ├── 03-branching-and-merging/ # Branches, merging, conflicts (checkpoint-based)
|
|
||||||
│ ├── 04-cherry-pick/ # Apply specific commits
|
|
||||||
│ ├── 05-revert/ # Safe undoing (includes merge commits)
|
|
||||||
│ ├── 06-reset/ # Dangerous local cleanup
|
|
||||||
│ ├── 07-stash/ # Save work-in-progress
|
|
||||||
│ └── 08-multiplayer/ # Real collaboration (cloud-based)
|
|
||||||
│ ├── README.md # Student guide
|
|
||||||
│ └── FACILITATOR-SETUP.md # Server setup guide
|
|
||||||
│
|
|
||||||
└── 02-advanced/ # Professional techniques (6 modules)
|
|
||||||
├── 01-rebasing/ # Linear history with rebase
|
|
||||||
├── 02-interactive-rebase/ # Clean up commits
|
|
||||||
├── 03-worktrees/ # Multiple branches simultaneously
|
|
||||||
├── 04-bisect/ # Find bugs with binary search
|
|
||||||
├── 05-blame/ # Code archaeology
|
|
||||||
└── 06-merge-strategies/ # Master merge techniques
|
|
||||||
```
|
|
||||||
|
|
||||||
## What's Unique About This Workshop
|
|
||||||
|
|
||||||
### The Great Print Project (Module 08)
|
|
||||||
|
|
||||||
Unlike any other Git tutorial, Module 08 provides **real collaborative experience**:
|
|
||||||
|
|
||||||
- **Real Git server**: Not simulated - actual Azure DevOps cloud repository
|
|
||||||
- **Real teammates**: Work in pairs on shared branches
|
|
||||||
- **Real conflicts**: Both partners edit the same code and must resolve conflicts together
|
|
||||||
- **Real pull requests**: Create PRs, review code, merge to main
|
|
||||||
- **Real success**: When all pairs merge, run `python main.py` and see everyone's contributions!
|
|
||||||
- **Real security**: Use SSH keys for authentication (industry best practice)
|
|
||||||
|
|
||||||
**The challenge**: Each pair implements 3 Python functions (e.g., `print_b()`, `print_c()`, `print_d()`) in a shared repository. When complete, the program prints the alphabet A-Z and numbers 0-9.
|
|
||||||
|
|
||||||
**What students learn**:
|
|
||||||
- Push/pull workflow with real teammates
|
|
||||||
- Handling rejected pushes (partner pushed first!)
|
|
||||||
- Resolving actual merge conflicts (not simulated)
|
|
||||||
- Creating meaningful pull requests
|
|
||||||
- Reviewing others' code
|
|
||||||
- Staying synchronized with a team
|
|
||||||
|
|
||||||
This is how professional developers actually work - no simulation, no shortcuts.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Frequently Asked Questions
|
|
||||||
|
|
||||||
**Q: Do I need to complete all modules?**
|
|
||||||
A: No! Essentials 01-05 covers what most developers use daily. Complete 06-09 and Advanced modules to deepen your skills.
|
|
||||||
|
|
||||||
**Q: Can I do Module 08 (Multiplayer) without a partner?**
|
|
||||||
A: Not recommended - collaboration is the point. If solo, skip to Advanced modules or wait until you can pair with someone.
|
|
||||||
|
|
||||||
**Q: How long does the workshop take?**
|
|
||||||
A:
|
|
||||||
- Essentials 01-05: 3-4 hours
|
|
||||||
- Full Essentials (01-09): 6-7 hours
|
|
||||||
- All modules: 12-15 hours
|
|
||||||
- Self-paced over several days works great!
|
|
||||||
|
|
||||||
**Q: I'm stuck on a module. What should I do?**
|
|
||||||
A:
|
|
||||||
1. Re-read the module README.md
|
|
||||||
2. Check [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md)
|
|
||||||
3. Run `./reset.ps1` to start fresh
|
|
||||||
4. Use `git status` and `git log --graph` to understand current state
|
|
||||||
5. For Module 08, ask your partner or facilitator
|
|
||||||
|
|
||||||
**Q: Can I use this for a team workshop at my company?**
|
|
||||||
A: Absolutely! See the "For Workshop Facilitators" section above. The materials are designed for both self-study and instructor-led workshops.
|
|
||||||
|
|
||||||
**Q: Do I need internet access?**
|
|
||||||
A: Modules 01-07 work completely offline. Module 08 requires internet to access the Git server.
|
|
||||||
|
|
||||||
**Q: What if I prefer GitHub/GitLab instead of Azure DevOps?**
|
|
||||||
A: The skills are identical across all Git platforms. Module 08 uses Azure DevOps but everything you learn applies directly to GitHub, GitLab, Bitbucket, and any other Git hosting service. The SSH authentication, pull request workflow, and collaboration patterns are the same everywhere.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Found a bug or have a suggestion? Please open an issue or submit a pull request!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
This workshop is provided as-is for educational purposes.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
**Ready to master Git?**
|
|
||||||
|
|
||||||
First-time setup (Windows 11): See [INSTALLATION.md](INSTALLATION.md)
|
|
||||||
|
|
||||||
Then start with Essentials Module 01 and begin your journey!
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
cd 01-essentials\01-basics
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Happy Learning! 🚀
|
|
||||||
12
install.ps1
12
install.ps1
@@ -1,7 +1,9 @@
|
|||||||
#!/usr/bin/env pwsh
|
#!/usr/bin/env pwsh
|
||||||
<#
|
<#
|
||||||
.SYNOPSIS
|
.SYNOPSIS
|
||||||
Installs all prerequisites for Git Workshop using winget and clones the repository.
|
Installs all prerequisites for Git Workshop using winget (which is a CLI
|
||||||
|
tool to the Windows Package Manager Server) and clones the workshop
|
||||||
|
repository.
|
||||||
|
|
||||||
.DESCRIPTION
|
.DESCRIPTION
|
||||||
This script automates the installation of required tools for the Git Workshop:
|
This script automates the installation of required tools for the Git Workshop:
|
||||||
@@ -17,9 +19,11 @@ each installation succeeded. At the end, it clones the repository and can
|
|||||||
open it in VSCode for immediate workshop access.
|
open it in VSCode for immediate workshop access.
|
||||||
|
|
||||||
One-shot installation:
|
One-shot installation:
|
||||||
|
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
||||||
|
|
||||||
.EXAMPLE
|
.EXAMPLE
|
||||||
|
PS> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||||
PS> Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
PS> Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
||||||
Runs the complete installation and setup in one command.
|
Runs the complete installation and setup in one command.
|
||||||
|
|
||||||
@@ -638,17 +642,21 @@ if ($userChoice -ne "CloneOnly") {
|
|||||||
|
|
||||||
Write-Host " Setting the init.defaultBranch to be 'main'"
|
Write-Host " Setting the init.defaultBranch to be 'main'"
|
||||||
git config --global init.defaultBranch main
|
git config --global init.defaultBranch main
|
||||||
|
Write-Success " Set 'main' as default branch"
|
||||||
|
|
||||||
Write-Host " Setting the default editor to code, to handle merge messages"
|
Write-Host " Setting the default editor to code, to handle merge messages"
|
||||||
git config --global core.editor "code --wait"
|
git config --global core.editor "code --wait"
|
||||||
|
Write-Success " Visual Studio Code set as core.editor"
|
||||||
|
|
||||||
Write-Host " Setting vscode at the default code editor for merge conflicts"
|
Write-Host " Setting vscode at the default code editor for merge conflicts"
|
||||||
git config --global merge.tool vscode
|
git config --global merge.tool vscode
|
||||||
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
|
git config --global mergetool.vscode.cmd 'code --wait --merge $MERGED'
|
||||||
|
Write-Success " Visual Studio Code set as mergetool"
|
||||||
|
|
||||||
Write-Host " Setting vscode as the default code editor for diffs"
|
Write-Host " Setting vscode as the default code editor for diffs"
|
||||||
git config --global diff.tool vscode
|
git config --global diff.tool vscode
|
||||||
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
|
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
|
||||||
|
Write-Success " Visual Studio Code set as difftool"
|
||||||
|
|
||||||
# Verify Git version specifically
|
# Verify Git version specifically
|
||||||
if ($results.Git) {
|
if ($results.Git) {
|
||||||
|
|||||||
49
util.ps1
Normal file
49
util.ps1
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Utility functions for writing to stdout and for advanced git commands that are reused again and again
|
||||||
|
#>
|
||||||
|
|
||||||
|
function Write-Pass {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[PASS] $Message" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Fail {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
||||||
|
$script:allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Hint {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Info {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Error {
|
||||||
|
param([string] $Message)
|
||||||
|
Write-Host "[ERROR] $Message" -ForegroundColor Red
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function Get-LocalBranches {
|
||||||
|
return git for-each-ref --format='%(refname:short)' refs/heads
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-MainBranch {
|
||||||
|
$mainBranch = git branch --show-current 2>$null
|
||||||
|
$allBranches = Get-LocalBranches
|
||||||
|
if ($allBranches -contains "main") {
|
||||||
|
$mainBranch = "main"
|
||||||
|
} elseif ($allBranches -contains "master") {
|
||||||
|
$mainBranch = "master"
|
||||||
|
} else {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
}
|
||||||
|
|
||||||
|
return $mainBranch
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user