Compare commits
62 Commits
7f34cf2d08
...
advanced-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e2e36690ea | ||
|
|
356b6268ba | ||
|
|
bd69774191 | ||
|
|
fcaf97f60b | ||
|
|
2a5eb137f6 | ||
|
|
b0d2d43c8b | ||
|
|
cdd695b250 | ||
|
|
0474a6de0e | ||
|
|
575e083f33 | ||
|
|
aa24c50b45 | ||
|
|
939bd397f1 | ||
|
|
3130874981 | ||
|
|
a34b7d155f | ||
|
|
1c20a06c21 | ||
|
|
988ce3bc92 | ||
|
|
9fbdd941fa | ||
|
|
40341d21a7 | ||
|
|
8a82253fc2 | ||
|
|
f0522e14bc | ||
|
|
0183a06134 | ||
|
|
c3d9d3337c | ||
|
|
ced65740a3 | ||
|
|
bf07cb1868 | ||
|
|
7b638d27de | ||
|
|
ea5cbccc75 | ||
|
|
76c4182186 | ||
|
|
5b4c544fee | ||
|
|
74a23dbbca | ||
|
|
25077cd589 | ||
|
|
8b3ba808d1 | ||
|
|
9e03a9624a | ||
|
|
b2b8a2cfff | ||
|
|
7e2f8d64fb | ||
|
|
eadf8cfe6a | ||
|
|
9e22f84a53 | ||
|
|
daa787842a | ||
|
|
a392e8c97d | ||
|
|
009a3a9104 | ||
|
|
32a0e89f72 | ||
|
|
91c46718c6 | ||
|
|
c99e238814 | ||
|
|
2633ee2b71 | ||
|
|
c28151cc19 | ||
|
|
07faa14b7a | ||
|
|
09f25d6eae | ||
|
|
cbefeaf4d2 | ||
|
|
7066e648d5 | ||
|
|
14cfc2feeb | ||
|
|
734b49bc7d | ||
|
|
985c4a0a8a | ||
|
|
f55cb444e7 | ||
|
|
6d2e099eb4 | ||
|
|
f696044461 | ||
|
|
e7ce41cbbc | ||
|
|
34f2607b22 | ||
|
|
bb01592b6c | ||
|
|
5492f17a5a | ||
|
|
5f78245734 | ||
|
|
8d63b2d22e | ||
|
|
039debe744 | ||
|
|
a0a9a5a0d9 | ||
|
|
0a9f7c1842 |
@@ -29,20 +29,25 @@ Your goal is to commit both `welcome.txt` and `instructions.txt` to a git reposi
|
|||||||
1. Navigate into the `challenge` directory: `cd challenge`
|
1. Navigate into the `challenge` directory: `cd challenge`
|
||||||
2. **Initialize a new git repository**: `git init` (this is your first step!)
|
2. **Initialize a new git repository**: `git init` (this is your first step!)
|
||||||
3. Check the status of your repository: `git status`
|
3. Check the status of your repository: `git status`
|
||||||
4. Stage the files you want to commit: `git add welcome.txt` (or `git add .` to stage all files)
|
4. Stage the file you want to commit: `git add welcome.txt` (or `git add .` to stage all files)
|
||||||
5. Create a commit: `git commit -m "Your commit message"`
|
5. Check the status again and see the difference `git status`. Notice the file is now *staged* and ready to be committed.
|
||||||
6. Verify both files are committed: `git ls-tree -r HEAD --name-only`
|
6. Create a commit: `git commit -m "add welcome.txt"`
|
||||||
|
5. Check the status again and see the difference `git status`. Notice the file no longer appears in the output.
|
||||||
|
7. Stage the next file: `git add instructions.txt` (or `git add .` to stage all files)
|
||||||
|
8. Check the status again and see the difference `git status`. Notice the file is now *staged* and ready to be committed.
|
||||||
|
9. Create a commit: `git commit -m "add instructions.txt"`
|
||||||
|
10. Check the status again and see the difference `git status`. Notice that the files are now not shown in status. If and when you change something about the file you will once again see it in the `git status` command.
|
||||||
|
|
||||||
**Important Notes**:
|
**Important Notes**:
|
||||||
- The challenge directory is NOT a git repository until you run `git init`. This is intentional - you're learning to start from scratch!
|
- The challenge directory is NOT a git repository until you run `git init`. This is intentional - you're learning to start from scratch!
|
||||||
- You can commit both files together in one commit, or separately in multiple commits - it's up to you!
|
- You can commit both files together in one commit, or separately in multiple commits (use `git add .` to add all files in the folder) - it's up to you!
|
||||||
- The verification script checks that both files are committed, not the specific commit messages or order
|
- The verification script checks that both files are committed, not the specific commit messages or order
|
||||||
|
|
||||||
### Key Concepts
|
### Key Concepts
|
||||||
|
|
||||||
- **Repository**: A directory tracked by git, containing your project files and their history
|
- **Repository**: A directory tracked by git, containing your project files and their history
|
||||||
- **Working Directory**: The files you see and edit
|
- **Working Directory**: The files you see and edit
|
||||||
- **Staging Area (Index)**: A preparation area for your next commit
|
- **Staging Area**: A preparation area for your next commit, you first add the files to the stage, and then you commit the files to repository.
|
||||||
- **Commit**: A snapshot of your staged changes
|
- **Commit**: A snapshot of your staged changes
|
||||||
|
|
||||||
### Useful Commands
|
### Useful Commands
|
||||||
@@ -42,7 +42,7 @@ if (-not (Test-Path "answers.md")) {
|
|||||||
$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") {
|
if ($answersLower -match "5|five|fem") {
|
||||||
Write-Host "[PASS] Correct commit count found" -ForegroundColor Green
|
Write-Host "[PASS] Correct commit count found" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Commit count not found or incorrect" -ForegroundColor Red
|
Write-Host "[FAIL] Commit count not found or incorrect" -ForegroundColor Red
|
||||||
@@ -76,7 +76,7 @@ if (-not (Test-Path "answers.md")) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check 4: Contains "config" keyword for staged file
|
# Check 4: Contains "config" keyword for staged file
|
||||||
if ($answersLower -match "config") {
|
if ($answersLower -match "config.py") {
|
||||||
Write-Host "[PASS] Staged file identified" -ForegroundColor Green
|
Write-Host "[PASS] Staged file identified" -ForegroundColor Green
|
||||||
} else {
|
} else {
|
||||||
Write-Host "[FAIL] Staged file not identified" -ForegroundColor Red
|
Write-Host "[FAIL] Staged file not identified" -ForegroundColor Red
|
||||||
389
01-essentials/03-branching-and-merging/README.md
Normal file
389
01-essentials/03-branching-and-merging/README.md
Normal file
@@ -0,0 +1,389 @@
|
|||||||
|
# Module 03: Branching and Merging
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By the end of this module, you will:
|
||||||
|
- Understand what branches are and why they're useful
|
||||||
|
- Create and switch between branches
|
||||||
|
- Make commits on different branches
|
||||||
|
- Merge branches together
|
||||||
|
- Visualize branch history with `git log --graph`
|
||||||
|
- Understand merge commits and how they work
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Create the challenge environment:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a repository with a realistic project history showing multiple merged feature branches.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Branching** lets you create independent lines of development. Think of it like parallel universes for your code - you can experiment on one branch without affecting others.
|
||||||
|
|
||||||
|
**Merging** combines work from different branches back together.
|
||||||
|
|
||||||
|
### Why Use Branches?
|
||||||
|
|
||||||
|
- **Experiment safely** - Try new ideas without breaking main code
|
||||||
|
- **Work in parallel** - Multiple features can be developed simultaneously
|
||||||
|
- **Organize work** - Each feature/fix gets its own branch
|
||||||
|
- **Collaborate better** - Team members work on separate branches
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
### Part 1: Explore the Repository
|
||||||
|
|
||||||
|
First, explore the existing history to see what merging looks like:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
cd challenge
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see a visual graph showing:
|
||||||
|
- The `main` branch timeline
|
||||||
|
- Feature branches that split off from main
|
||||||
|
- Merge points where branches come back together
|
||||||
|
|
||||||
|
**Study the graph:**
|
||||||
|
- Look for the `*` symbols (commits)
|
||||||
|
- Notice the `|`, `/`, and `\` characters (branch lines)
|
||||||
|
- Find the merge commits (they have two parent lines converging)
|
||||||
|
|
||||||
|
**Explore the branches:**
|
||||||
|
```pwsh
|
||||||
|
# See all branches
|
||||||
|
git branch --all
|
||||||
|
|
||||||
|
# Check which files exist on different branches
|
||||||
|
git ls-tree --name-only feature-login
|
||||||
|
git ls-tree --name-only feature-api
|
||||||
|
git ls-tree --name-only main
|
||||||
|
```
|
||||||
|
|
||||||
|
**View specific merges:**
|
||||||
|
```pwsh
|
||||||
|
# See all merge commits
|
||||||
|
git log --merges --oneline
|
||||||
|
|
||||||
|
# See details of a specific merge
|
||||||
|
git show <merge-commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part 2: Create Your Own Branch
|
||||||
|
|
||||||
|
Now practice creating your own branch:
|
||||||
|
|
||||||
|
1. Create a new branch with the name `my-feature` using `git switch -c my-feature`
|
||||||
|
2. Check which branch you're on with `git branch`. The `*` shows which branch you're currently on.
|
||||||
|
|
||||||
|
### Part 3: Make Commits on Your Branch
|
||||||
|
|
||||||
|
1. Create a new file called `DOCS.md` and add some content. What doesn't matter, just something.
|
||||||
|
2. Add and commit the `DOCS.md` file (use the commands from `01-basics` module).
|
||||||
|
3. Add some more content to the `DOCS.md` file, add and commit the changes.
|
||||||
|
4. Now check how the tree is looking with `git log --oneline --graph --all`
|
||||||
|
|
||||||
|
**Important:** Changes on your branch don't affect main!
|
||||||
|
|
||||||
|
1. Change back to the `main` branch `git switch main`
|
||||||
|
2. Check the changes you committed before. You'll notice that they're gone!
|
||||||
|
3. Now edit a file or create a new file (perhaps GUIDE.md, content of the file doesn't matter) and add it and commit it on your `main` branch (hint: `git add .`, `git commit -m`)
|
||||||
|
- This way we create diverging branches. The `main` branch has changes as well as your new `my-feature` branch.
|
||||||
|
- Run `git log --oneline --graph --all` to see how the tree is looking
|
||||||
|
4. Switch back to your branch `git switch my-feature`
|
||||||
|
5. The changes from the `main` branch are now gone. Check the changes you committed before. You'll notice they're back!
|
||||||
|
|
||||||
|
### Part 4: Merge Your Branch
|
||||||
|
|
||||||
|
Bring your work into main:
|
||||||
|
|
||||||
|
1. Go back to the main branch `git switch main`
|
||||||
|
2. Run a `git log --oneline --graph --all` to see that the `HEAD` is on your `main` branch
|
||||||
|
3. It might be wise to first ensure that we're using Visual Studio Code to handle merge messages. If you haven't already set `git config --global core.editor "code --wait"` this sets Visual Studio Code to be the default editor for anything Git related.
|
||||||
|
4. Let's merge the recently created branch `git merge my-feature`
|
||||||
|
5. Visual Studio Code should open with a commit message. In order to solidify the commit simply close the window. That tells Git that the commit message has been written and the change should be committed.
|
||||||
|
6. Now run `git log --oneline --graph --all` and see your changes merge into the `main` branch!
|
||||||
|
7. Now let's clean up a bit, run `git branch -d my-feature` to remove the recently merged branch.
|
||||||
|
- If you hadn't merged the branch first this command would fail as Git will warn you that you have changes not merged into the `main` branch
|
||||||
|
|
||||||
|
You should see your feature branch merged into main!
|
||||||
|
|
||||||
|
### Part 5: Practice More
|
||||||
|
|
||||||
|
Create additional branches to practice:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Create another feature
|
||||||
|
git switch -c another-feature
|
||||||
|
|
||||||
|
# Add changes and commits
|
||||||
|
# ... your work ...
|
||||||
|
|
||||||
|
# Merge it
|
||||||
|
git switch main
|
||||||
|
git merge another-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify your work:**
|
||||||
|
```pwsh
|
||||||
|
# From the module directory (not inside challenge/)
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Understanding Branches
|
||||||
|
|
||||||
|
### 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!
|
||||||
|
|
||||||
|
```
|
||||||
|
main: A---B---C
|
||||||
|
\
|
||||||
|
my-feature: D---E
|
||||||
|
```
|
||||||
|
|
||||||
|
- Both branches share commits A and B
|
||||||
|
- Main has commit C
|
||||||
|
- My-feature has commits D and E
|
||||||
|
- They're independent!
|
||||||
|
|
||||||
|
### What is HEAD?
|
||||||
|
|
||||||
|
`HEAD` points to your current branch. It's Git's way of saying "you are here."
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# HEAD points to main
|
||||||
|
git switch main
|
||||||
|
|
||||||
|
# Now HEAD points to my-feature
|
||||||
|
git switch my-feature
|
||||||
|
```
|
||||||
|
|
||||||
|
## Understanding Merging
|
||||||
|
|
||||||
|
### Types of Merges
|
||||||
|
|
||||||
|
**Three-way merge** (most common):
|
||||||
|
```
|
||||||
|
Before:
|
||||||
|
main: A---B---C
|
||||||
|
\
|
||||||
|
feature: D---E
|
||||||
|
|
||||||
|
After merge:
|
||||||
|
main: A---B---C---M
|
||||||
|
\ /
|
||||||
|
feature: D---E
|
||||||
|
```
|
||||||
|
|
||||||
|
Git creates a merge commit `M` that has two parents (C and E).
|
||||||
|
|
||||||
|
**Fast-forward merge**:
|
||||||
|
```
|
||||||
|
Before:
|
||||||
|
main: A---B
|
||||||
|
\
|
||||||
|
feature: C---D
|
||||||
|
|
||||||
|
After merge:
|
||||||
|
main: A---B---C---D
|
||||||
|
```
|
||||||
|
|
||||||
|
If main hasn't changed, Git just moves the pointer forward. No merge commit needed!
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Branching
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# List all branches (* shows current branch)
|
||||||
|
git branch
|
||||||
|
|
||||||
|
# Create a new branch
|
||||||
|
git branch feature-name
|
||||||
|
|
||||||
|
# Create AND switch to new branch
|
||||||
|
git switch -c feature-name
|
||||||
|
|
||||||
|
# Switch to existing branch
|
||||||
|
git switch branch-name
|
||||||
|
|
||||||
|
# Switch to previous branch
|
||||||
|
git switch -
|
||||||
|
|
||||||
|
# Delete a branch (only if merged)
|
||||||
|
git branch -d feature-name
|
||||||
|
|
||||||
|
# Force delete a branch
|
||||||
|
git branch -D feature-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merging
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Merge a branch into your current branch
|
||||||
|
git merge branch-name
|
||||||
|
|
||||||
|
# Abort a merge if something goes wrong
|
||||||
|
git merge --abort
|
||||||
|
|
||||||
|
# Force a merge commit (even if fast-forward possible)
|
||||||
|
git merge --no-ff branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
### Viewing History
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Visual branch graph
|
||||||
|
git log --oneline --graph --all
|
||||||
|
|
||||||
|
# Compact history
|
||||||
|
git log --oneline
|
||||||
|
|
||||||
|
# Only merge commits
|
||||||
|
git log --merges
|
||||||
|
|
||||||
|
# Show which branches have been merged into main
|
||||||
|
git branch --merged main
|
||||||
|
|
||||||
|
# Show which branches haven't been merged
|
||||||
|
git branch --no-merged main
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Creating a Feature
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Start from main
|
||||||
|
git switch main
|
||||||
|
|
||||||
|
# Create feature branch
|
||||||
|
git switch -c feature-awesome
|
||||||
|
|
||||||
|
# Make changes
|
||||||
|
echo "cool stuff" > feature.txt
|
||||||
|
git add .
|
||||||
|
git commit -m "Add awesome feature"
|
||||||
|
|
||||||
|
# More changes...
|
||||||
|
echo "more cool stuff" >> feature.txt
|
||||||
|
git add .
|
||||||
|
git commit -m "Improve awesome feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Merging a Feature
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Switch to main
|
||||||
|
git switch main
|
||||||
|
|
||||||
|
# Merge your feature
|
||||||
|
git merge feature-awesome
|
||||||
|
|
||||||
|
# Delete the feature branch (optional)
|
||||||
|
git branch -d feature-awesome
|
||||||
|
```
|
||||||
|
|
||||||
|
### Keeping Main Updated While Working
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# You're on feature-awesome
|
||||||
|
git switch feature-awesome
|
||||||
|
|
||||||
|
# Main branch has new commits from others
|
||||||
|
# Bring those into your feature branch
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
git switch feature-awesome
|
||||||
|
git merge main
|
||||||
|
|
||||||
|
# Or in one command (more advanced):
|
||||||
|
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!"
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Show the merge commit
|
||||||
|
git show <merge-commit-hash>
|
||||||
|
|
||||||
|
# Compare two branches before merging
|
||||||
|
git diff main..feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
💡 **Branch often** - Branches are cheap! Create one for each feature or experiment.
|
||||||
|
💡 **Commit before switching** - Always commit (or stash) changes before switching branches.
|
||||||
|
💡 **Keep branches focused** - One feature per branch makes merging easier.
|
||||||
|
💡 **Delete merged branches** - Clean up with `git branch -d branch-name` after merging.
|
||||||
|
💡 **Use descriptive names** - `feature-login` is better than `stuff` or `branch1`.
|
||||||
|
💡 **Visualize often** - Run `git log --oneline --graph --all` to understand your history.
|
||||||
|
|
||||||
|
## What You've Learned
|
||||||
|
|
||||||
|
After completing this module, you understand:
|
||||||
|
|
||||||
|
- ✅ Branches create independent lines of development
|
||||||
|
- ✅ `git switch -c` creates a new branch
|
||||||
|
- ✅ Changes in one branch don't affect others
|
||||||
|
- ✅ `git merge` combines branches
|
||||||
|
- ✅ Merge commits have two parent commits
|
||||||
|
- ✅ `git log --graph` visualizes branch history
|
||||||
|
- ✅ Branches are pointers, not copies of files
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to continue? The next module covers **merge conflicts** - what happens when Git can't automatically merge changes.
|
||||||
|
|
||||||
|
To start over:
|
||||||
|
```pwsh
|
||||||
|
.\reset.ps1
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Need help?** Review the commands above, or run `git status` to see what Git suggests!
|
||||||
23
01-essentials/03-branching-and-merging/reset.ps1
Executable file
23
01-essentials/03-branching-and-merging/reset.ps1
Executable file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Resets the Module 03 challenge environment.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script removes the challenge directory, allowing you to start fresh.
|
||||||
|
Run setup.ps1 again after resetting to recreate the environment.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Resetting Module 03 Challenge ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if (Test-Path "challenge") {
|
||||||
|
Write-Host "Removing challenge directory..." -ForegroundColor Yellow
|
||||||
|
Remove-Item -Recurse -Force "challenge"
|
||||||
|
Write-Host "`n[SUCCESS] Challenge environment reset complete!" -ForegroundColor Green
|
||||||
|
Write-Host "`nRun .\setup.ps1 to create a fresh challenge environment." -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
} else {
|
||||||
|
Write-Host "`n[INFO] No challenge directory found. Nothing to reset." -ForegroundColor Yellow
|
||||||
|
Write-Host "Run .\setup.ps1 to create the challenge environment." -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
315
01-essentials/03-branching-and-merging/setup.ps1
Executable file
315
01-essentials/03-branching-and-merging/setup.ps1
Executable file
@@ -0,0 +1,315 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets up the Module 03 challenge environment for branching and merging.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script creates a challenge directory with a Git repository containing
|
||||||
|
a realistic project history with multiple merged branches. Students will see
|
||||||
|
what branching and merging looks like in practice, then create their own
|
||||||
|
branches to experiment.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Setting up Module 03: Branching and Merging ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# 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 (could be main, master, etc.)
|
||||||
|
# First commit creates the branch, so we detect it after that
|
||||||
|
$mainBranch = git branch --show-current
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Fallback: Get default branch name from git config
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Ultimate fallback: use "main"
|
||||||
|
$mainBranch = "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Create a realistic project history with multiple merged branches
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating project history with multiple branches..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Initial commits on main
|
||||||
|
$readmeContent = @"
|
||||||
|
# My Application
|
||||||
|
|
||||||
|
A sample application for learning Git branching and merging.
|
||||||
|
"@
|
||||||
|
Set-Content -Path "README.md" -Value $readmeContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit" | Out-Null
|
||||||
|
|
||||||
|
$mainContent = @"
|
||||||
|
# main.py - Main application file
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("Welcome to the Application!")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
"@
|
||||||
|
Set-Content -Path "main.py" -Value $mainContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add main application file" | Out-Null
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Branch 1: feature-login (will be merged)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating feature-login branch..." -ForegroundColor Cyan
|
||||||
|
git switch -c feature-login | Out-Null
|
||||||
|
|
||||||
|
$loginContent = @"
|
||||||
|
# login.py - User authentication
|
||||||
|
|
||||||
|
def login(username, password):
|
||||||
|
"""Authenticate a user."""
|
||||||
|
print(f"Logging in user: {username}")
|
||||||
|
return True
|
||||||
|
"@
|
||||||
|
Set-Content -Path "login.py" -Value $loginContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add login module" | Out-Null
|
||||||
|
|
||||||
|
$loginContent = @"
|
||||||
|
# login.py - User authentication
|
||||||
|
|
||||||
|
def validate_password(password):
|
||||||
|
"""Validate password strength."""
|
||||||
|
return len(password) >= 8
|
||||||
|
|
||||||
|
def login(username, password):
|
||||||
|
"""Authenticate a user."""
|
||||||
|
if not validate_password(password):
|
||||||
|
print("Password too weak!")
|
||||||
|
return False
|
||||||
|
print(f"Logging in user: {username}")
|
||||||
|
return True
|
||||||
|
"@
|
||||||
|
Set-Content -Path "login.py" -Value $loginContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add password validation" | Out-Null
|
||||||
|
|
||||||
|
# Switch back to main branch
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
|
||||||
|
$appContent = @"
|
||||||
|
# app.py - Application entry point
|
||||||
|
|
||||||
|
from main import main
|
||||||
|
|
||||||
|
def run():
|
||||||
|
"""Run the application."""
|
||||||
|
print("Starting application...")
|
||||||
|
main()
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
run()
|
||||||
|
"@
|
||||||
|
Set-Content -Path "app.py" -Value $appContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add app.py entry point" | Out-Null
|
||||||
|
|
||||||
|
# Merge feature-login into $mainBranch
|
||||||
|
Write-Host "Merging feature-login into $mainBranch..." -ForegroundColor Green
|
||||||
|
git merge feature-login --no-edit | Out-Null
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Branch 2: feature-api (will be merged)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating feature-api branch..." -ForegroundColor Cyan
|
||||||
|
git switch -c feature-api | Out-Null
|
||||||
|
|
||||||
|
$apiContent = @"
|
||||||
|
# api.py - API endpoints
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
"""Retrieve data from API."""
|
||||||
|
return {"status": "ok", "data": []}
|
||||||
|
|
||||||
|
def post_data(data):
|
||||||
|
"""Send data to API."""
|
||||||
|
print(f"Posting data: {data}")
|
||||||
|
return {"status": "ok"}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "api.py" -Value $apiContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add API module" | Out-Null
|
||||||
|
|
||||||
|
$apiContent = @"
|
||||||
|
# api.py - API endpoints
|
||||||
|
|
||||||
|
def get_data():
|
||||||
|
"""Retrieve data from API."""
|
||||||
|
return {"status": "ok", "data": []}
|
||||||
|
|
||||||
|
def post_data(data):
|
||||||
|
"""Send data to API."""
|
||||||
|
print(f"Posting data: {data}")
|
||||||
|
return {"status": "ok"}
|
||||||
|
|
||||||
|
def delete_data(id):
|
||||||
|
"""Delete data by ID."""
|
||||||
|
print(f"Deleting data: {id}")
|
||||||
|
return {"status": "ok"}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "api.py" -Value $apiContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add delete endpoint to API" | Out-Null
|
||||||
|
|
||||||
|
# Switch back to main branch and add documentation
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
|
||||||
|
$readmeContent = @"
|
||||||
|
# My Application
|
||||||
|
|
||||||
|
A sample application for learning Git branching and merging.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- User authentication
|
||||||
|
- Main application logic
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Run: python app.py
|
||||||
|
"@
|
||||||
|
Set-Content -Path "README.md" -Value $readmeContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Update README with setup instructions" | Out-Null
|
||||||
|
|
||||||
|
# Merge feature-api into $mainBranch
|
||||||
|
Write-Host "Merging feature-api into $mainBranch..." -ForegroundColor Green
|
||||||
|
git merge feature-api --no-edit | Out-Null
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Branch 3: feature-database (will be merged)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating feature-database branch..." -ForegroundColor Cyan
|
||||||
|
git switch -c feature-database | Out-Null
|
||||||
|
|
||||||
|
$dbContent = @"
|
||||||
|
# database.py - Database operations
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Connect to database."""
|
||||||
|
print("Connecting to database...")
|
||||||
|
self.connection = True
|
||||||
|
|
||||||
|
def query(self, sql):
|
||||||
|
"""Execute SQL query."""
|
||||||
|
print(f"Executing: {sql}")
|
||||||
|
return []
|
||||||
|
"@
|
||||||
|
Set-Content -Path "database.py" -Value $dbContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add database module" | Out-Null
|
||||||
|
|
||||||
|
$dbContent = @"
|
||||||
|
# database.py - Database operations
|
||||||
|
|
||||||
|
class Database:
|
||||||
|
def __init__(self):
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def connect(self):
|
||||||
|
"""Connect to database."""
|
||||||
|
print("Connecting to database...")
|
||||||
|
self.connection = True
|
||||||
|
|
||||||
|
def disconnect(self):
|
||||||
|
"""Disconnect from database."""
|
||||||
|
print("Disconnecting from database...")
|
||||||
|
self.connection = None
|
||||||
|
|
||||||
|
def query(self, sql):
|
||||||
|
"""Execute SQL query."""
|
||||||
|
print(f"Executing: {sql}")
|
||||||
|
return []
|
||||||
|
"@
|
||||||
|
Set-Content -Path "database.py" -Value $dbContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add disconnect method" | Out-Null
|
||||||
|
|
||||||
|
# Switch to main branch and add another commit (to create divergent history)
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
|
||||||
|
$configContent = @"
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"port": 3000,
|
||||||
|
"debug": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "config.json" -Value $configContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add configuration file" | Out-Null
|
||||||
|
|
||||||
|
# Merge feature-database (will be three-way merge since main diverged)
|
||||||
|
Write-Host "Merging feature-database into $mainBranch..." -ForegroundColor Green
|
||||||
|
git merge feature-database --no-edit | Out-Null
|
||||||
|
|
||||||
|
# Final update on main
|
||||||
|
$readmeContent = @"
|
||||||
|
# My Application
|
||||||
|
|
||||||
|
A sample application for learning Git branching and merging.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- User authentication
|
||||||
|
- API endpoints
|
||||||
|
- Database operations
|
||||||
|
- Main application logic
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Run: python app.py
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Python 3.6+
|
||||||
|
"@
|
||||||
|
Set-Content -Path "README.md" -Value $readmeContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Update README with all features" | Out-Null
|
||||||
|
|
||||||
|
# Return to module directory
|
||||||
|
Set-Location ..
|
||||||
|
|
||||||
|
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||||
|
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
||||||
|
Write-Host "`nThe repository contains a realistic project history:" -ForegroundColor Yellow
|
||||||
|
Write-Host " - Multiple feature branches (login, api, database)" -ForegroundColor White
|
||||||
|
Write-Host " - All branches have been merged into $mainBranch" -ForegroundColor White
|
||||||
|
Write-Host " - View the history: git log --oneline --graph --all" -ForegroundColor White
|
||||||
|
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White
|
||||||
|
Write-Host " 2. cd challenge" -ForegroundColor White
|
||||||
|
Write-Host " 3. Explore the repository history: git log --oneline --graph --all" -ForegroundColor White
|
||||||
|
Write-Host " 4. Create your own branches and practice merging!" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
177
01-essentials/03-branching-and-merging/verify.ps1
Executable file
177
01-essentials/03-branching-and-merging/verify.ps1
Executable file
@@ -0,0 +1,177 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Verifies the Module 03 challenge solution.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script checks that you've practiced branching and merging by:
|
||||||
|
- Creating at least one new branch (beyond the example branches)
|
||||||
|
- Making commits on your branch
|
||||||
|
- Merging your branch into main
|
||||||
|
#>
|
||||||
|
|
||||||
|
$script:allChecksPassed = $true
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Helper Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function Write-Pass {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[PASS] $Message" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Fail {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
||||||
|
$script:allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Hint {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Info {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check challenge directory exists
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
if (-not (Test-Path "challenge")) {
|
||||||
|
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
||||||
|
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location "challenge"
|
||||||
|
|
||||||
|
if (-not (Test-Path ".git")) {
|
||||||
|
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
||||||
|
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n=== Verifying Module 03: Branching and Merging ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Detect the main branch name (could be main, master, etc.)
|
||||||
|
# ============================================================================
|
||||||
|
# Try to get the default branch from remote origin first
|
||||||
|
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Fallback: try to detect from local branches
|
||||||
|
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||||
|
if ($allBranches -contains "main") {
|
||||||
|
$mainBranch = "main"
|
||||||
|
} elseif ($allBranches -contains "master") {
|
||||||
|
$mainBranch = "master"
|
||||||
|
} else {
|
||||||
|
# Get the default branch from git config
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Ultimate fallback: use the first branch
|
||||||
|
$mainBranch = $allBranches | Select-Object -First 1
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Count initial setup commits (should be 15 commits from setup)
|
||||||
|
# ============================================================================
|
||||||
|
$initialCommitCount = 15
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check for new commits beyond setup
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "`nChecking your work..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$totalCommits = [int](git rev-list --count HEAD 2>$null)
|
||||||
|
|
||||||
|
if ($totalCommits -gt $initialCommitCount) {
|
||||||
|
$newCommits = $totalCommits - $initialCommitCount
|
||||||
|
Write-Pass "Found $newCommits new commit(s) beyond the initial setup"
|
||||||
|
} else {
|
||||||
|
Write-Fail "No new commits found"
|
||||||
|
Write-Hint "Create a branch, make some commits, and merge it into main"
|
||||||
|
Write-Hint "Example: git switch -c my-feature"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check for branches (excluding the example branches)
|
||||||
|
# ============================================================================
|
||||||
|
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||||
|
$exampleBranches = @($mainBranch, 'feature-login', 'feature-api', 'feature-database')
|
||||||
|
$studentBranches = $allBranches | Where-Object { $_ -notin $exampleBranches }
|
||||||
|
|
||||||
|
if ($studentBranches.Count -gt 0) {
|
||||||
|
Write-Pass "Created $($studentBranches.Count) new branch(es): $($studentBranches -join ', ')"
|
||||||
|
} else {
|
||||||
|
Write-Info "No new branches found (it's OK if you deleted them after merging)"
|
||||||
|
Write-Hint "To practice: git switch -c your-branch-name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check for merge commits by the student
|
||||||
|
# ============================================================================
|
||||||
|
$setupUser = "Workshop Student"
|
||||||
|
$mergeCommits = git log --merges --format="%s" 2>$null
|
||||||
|
|
||||||
|
# Count how many merge commits exist beyond the initial 3
|
||||||
|
$totalMerges = ($mergeCommits | Measure-Object).Count
|
||||||
|
$setupMerges = 3 # feature-login, feature-api, feature-database
|
||||||
|
|
||||||
|
if ($totalMerges -gt $setupMerges) {
|
||||||
|
$studentMerges = $totalMerges - $setupMerges
|
||||||
|
Write-Pass "Performed $studentMerges merge(s) of your own work"
|
||||||
|
} else {
|
||||||
|
Write-Fail "No merge commits found beyond the example merges"
|
||||||
|
Write-Hint "Create a branch, add commits, then merge it: git merge your-branch-name"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check current branch
|
||||||
|
# ============================================================================
|
||||||
|
$currentBranch = git branch --show-current 2>$null
|
||||||
|
if ($currentBranch -eq $mainBranch) {
|
||||||
|
Write-Pass "Currently on $mainBranch branch"
|
||||||
|
} else {
|
||||||
|
Write-Info "Currently on '$currentBranch' branch"
|
||||||
|
Write-Hint "Typically you merge feature branches INTO $mainBranch"
|
||||||
|
}
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Final summary
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host ""
|
||||||
|
if ($script:allChecksPassed) {
|
||||||
|
Write-Host "=========================================" -ForegroundColor Green
|
||||||
|
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
|
||||||
|
Write-Host "=========================================" -ForegroundColor Green
|
||||||
|
Write-Host "`nYou've successfully practiced:" -ForegroundColor Cyan
|
||||||
|
Write-Host " ✓ Creating branches" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Making commits on branches" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Merging branches together" -ForegroundColor White
|
||||||
|
Write-Host "`nReady for the next module!" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Quick guide:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. Create a branch: git switch -c my-feature" -ForegroundColor White
|
||||||
|
Write-Host " 2. Make changes and commit them" -ForegroundColor White
|
||||||
|
Write-Host " 3. Switch to $mainBranch : git switch $mainBranch" -ForegroundColor White
|
||||||
|
Write-Host " 4. Merge your branch: git merge my-feature" -ForegroundColor White
|
||||||
|
Write-Host " 5. Run this verify script again" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
487
01-essentials/04-merge-conflict/README.md
Normal file
487
01-essentials/04-merge-conflict/README.md
Normal file
@@ -0,0 +1,487 @@
|
|||||||
|
# Module 04: Merge Conflicts
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By the end of this module, you will:
|
||||||
|
- Understand what merge conflicts are and why they occur
|
||||||
|
- Use `git diff` to discover changes between branches
|
||||||
|
- Identify merge conflicts in your repository
|
||||||
|
- Read and interpret conflict markers (`<<<<<<<`, `=======`, `>>>>>>>`)
|
||||||
|
- Resolve merge conflicts manually
|
||||||
|
- Complete a merge after resolving conflicts
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Create the challenge environment:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a repository with two feature branches that have conflicting changes.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
A **merge conflict** occurs when Git cannot automatically combine changes because both branches modified the same part of the same file in different ways.
|
||||||
|
|
||||||
|
**When do conflicts happen?**
|
||||||
|
- ✅ Two branches modify the same lines in a file
|
||||||
|
- ✅ One branch deletes a file that another branch modifies
|
||||||
|
- ✅ Complex changes Git can't merge automatically
|
||||||
|
- ❌ Different files are changed (no conflict!)
|
||||||
|
- ❌ Different parts of the same file are changed (no conflict!)
|
||||||
|
|
||||||
|
**Don't fear conflicts!** They're a normal part of collaborative development. Git just needs your help to decide what the final code should look like.
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
### Part 1: Discover the Changes
|
||||||
|
|
||||||
|
Before merging, it's good practice to see what each branch changed:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd challenge
|
||||||
|
|
||||||
|
# Check which branch you're on
|
||||||
|
git branch
|
||||||
|
|
||||||
|
# View all branches
|
||||||
|
git branch --all
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see three branches: `main`, `add-timeout`, and `add-debug`.
|
||||||
|
|
||||||
|
**Discover what each branch changed:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Compare main with add-timeout
|
||||||
|
git diff main add-timeout
|
||||||
|
|
||||||
|
# Compare main with add-debug
|
||||||
|
git diff main add-debug
|
||||||
|
|
||||||
|
# Compare the two feature branches directly
|
||||||
|
git diff add-timeout add-debug
|
||||||
|
```
|
||||||
|
|
||||||
|
**What did you discover?**
|
||||||
|
- Both branches modified `config.json`
|
||||||
|
- They both added a line in the same location (after `"port": 3000`)
|
||||||
|
- One adds `"timeout": 5000`
|
||||||
|
- The other adds `"debug": true`
|
||||||
|
|
||||||
|
This is a recipe for a conflict!
|
||||||
|
|
||||||
|
### Part 2: Merge the First Branch (No Conflict)
|
||||||
|
|
||||||
|
Let's merge `add-timeout` first:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Make sure you're on main
|
||||||
|
git switch main
|
||||||
|
|
||||||
|
# Merge the first branch
|
||||||
|
git merge add-timeout
|
||||||
|
```
|
||||||
|
|
||||||
|
✅ **Success!** This merge works because main hasn't changed since add-timeout was created.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View the updated config
|
||||||
|
cat config.json
|
||||||
|
|
||||||
|
# Check the history
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part 3: Try to Merge the Second Branch (Conflict!)
|
||||||
|
|
||||||
|
Now let's try to merge `add-debug`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Still on main
|
||||||
|
git merge add-debug
|
||||||
|
```
|
||||||
|
|
||||||
|
💥 **Boom!** You'll see:
|
||||||
|
|
||||||
|
```
|
||||||
|
Auto-merging config.json
|
||||||
|
CONFLICT (content): Merge conflict in config.json
|
||||||
|
Automatic merge failed; fix conflicts and then commit the result.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Don't panic!** This is expected. Git is asking for your help.
|
||||||
|
|
||||||
|
### Part 4: Check the Status
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see:
|
||||||
|
|
||||||
|
```
|
||||||
|
On branch main
|
||||||
|
You have unmerged paths.
|
||||||
|
(fix conflicts and run "git commit")
|
||||||
|
(use "git merge --abort" to abort the merge)
|
||||||
|
|
||||||
|
Unmerged paths:
|
||||||
|
(use "git add <file>..." to mark resolution)
|
||||||
|
both modified: config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
This tells you that `config.json` needs your attention!
|
||||||
|
|
||||||
|
### Part 5: Open and Examine the Conflicted File
|
||||||
|
|
||||||
|
Open `config.json` in your text editor:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# On Windows
|
||||||
|
notepad config.json
|
||||||
|
|
||||||
|
# Or use VS Code
|
||||||
|
code config.json
|
||||||
|
```
|
||||||
|
|
||||||
|
You'll see special **conflict markers**:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"timeout": 5000
|
||||||
|
=======
|
||||||
|
"debug": true
|
||||||
|
>>>>>>> add-debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part 6: Understand the Conflict Markers
|
||||||
|
|
||||||
|
Let's break down what you're seeing:
|
||||||
|
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"timeout": 5000 ← Your current branch (main, which has add-timeout merged)
|
||||||
|
=======
|
||||||
|
"debug": true ← The branch you're merging (add-debug)
|
||||||
|
>>>>>>> add-debug
|
||||||
|
```
|
||||||
|
|
||||||
|
**What each marker means:**
|
||||||
|
- `<<<<<<< HEAD` - Start of your changes (current branch)
|
||||||
|
- `=======` - Separator between the two versions
|
||||||
|
- `>>>>>>> add-debug` - End of their changes (branch being merged)
|
||||||
|
|
||||||
|
### Part 7: Resolve the Conflict
|
||||||
|
|
||||||
|
You have three options:
|
||||||
|
|
||||||
|
**Option 1: Keep ONLY your changes (timeout)**
|
||||||
|
```json
|
||||||
|
"timeout": 5000
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 2: Keep ONLY their changes (debug)**
|
||||||
|
```json
|
||||||
|
"debug": true
|
||||||
|
```
|
||||||
|
|
||||||
|
**Option 3: Keep BOTH changes** ← **Do this!**
|
||||||
|
```json
|
||||||
|
"timeout": 5000,
|
||||||
|
"debug": true
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part 8: Edit the File
|
||||||
|
|
||||||
|
For this challenge, we want **both settings**, so:
|
||||||
|
|
||||||
|
1. Delete ALL the conflict markers:
|
||||||
|
- Remove `<<<<<<< HEAD`
|
||||||
|
- Remove `=======`
|
||||||
|
- Remove `>>>>>>> add-debug`
|
||||||
|
|
||||||
|
2. Keep both settings:
|
||||||
|
|
||||||
|
**Before (with conflict markers):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
<<<<<<< HEAD
|
||||||
|
"timeout": 5000
|
||||||
|
=======
|
||||||
|
"debug": true
|
||||||
|
>>>>>>> add-debug
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**After (resolved):**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
"timeout": 5000,
|
||||||
|
"debug": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:**
|
||||||
|
- Remove ALL markers
|
||||||
|
- Add a comma after `"timeout": 5000` (for valid JSON)
|
||||||
|
- Ensure the file is valid JSON
|
||||||
|
|
||||||
|
3. Save the file
|
||||||
|
|
||||||
|
### Part 9: Mark the Conflict as Resolved
|
||||||
|
|
||||||
|
Tell Git you've resolved the conflict:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Stage the resolved file
|
||||||
|
git add config.json
|
||||||
|
|
||||||
|
# Check status
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
|
||||||
|
```
|
||||||
|
On branch main
|
||||||
|
All conflicts fixed but you are still merging.
|
||||||
|
(use "git commit" to conclude merge)
|
||||||
|
```
|
||||||
|
|
||||||
|
Perfect! Git confirms the conflict is resolved.
|
||||||
|
|
||||||
|
### Part 10: Complete the Merge
|
||||||
|
|
||||||
|
Commit the merge:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git commit
|
||||||
|
```
|
||||||
|
|
||||||
|
Git will open an editor with a default merge message. You can accept it or customize it, then save and close.
|
||||||
|
|
||||||
|
**Done!** Your merge is complete!
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# View the final result
|
||||||
|
cat config.json
|
||||||
|
|
||||||
|
# View the history
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see both `timeout` and `debug` in the config!
|
||||||
|
|
||||||
|
### Part 11: Verify Your Solution
|
||||||
|
|
||||||
|
From the module directory (not inside challenge/):
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Understanding Conflict Markers
|
||||||
|
|
||||||
|
### Anatomy of a Conflict
|
||||||
|
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD ← Marker: Start of your version
|
||||||
|
Your changes here
|
||||||
|
======= ← Marker: Separator
|
||||||
|
Their changes here
|
||||||
|
>>>>>>> branch-name ← Marker: End of their version
|
||||||
|
```
|
||||||
|
|
||||||
|
### Common Conflict Patterns
|
||||||
|
|
||||||
|
**Simple conflict:**
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD
|
||||||
|
print("Hello")
|
||||||
|
=======
|
||||||
|
print("Hi")
|
||||||
|
>>>>>>> feature
|
||||||
|
```
|
||||||
|
Decision: Which greeting do you want?
|
||||||
|
|
||||||
|
**Both are needed:**
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD
|
||||||
|
timeout: 5000
|
||||||
|
=======
|
||||||
|
debug: true
|
||||||
|
>>>>>>> feature
|
||||||
|
```
|
||||||
|
Decision: Keep both (add comma)!
|
||||||
|
|
||||||
|
**Deletion conflict:**
|
||||||
|
```
|
||||||
|
<<<<<<< HEAD
|
||||||
|
# Function deleted on your branch
|
||||||
|
=======
|
||||||
|
def old_function():
|
||||||
|
pass
|
||||||
|
>>>>>>> feature
|
||||||
|
```
|
||||||
|
Decision: Delete or keep the function?
|
||||||
|
|
||||||
|
## Common Mistakes to Avoid
|
||||||
|
|
||||||
|
❌ **Forgetting to remove conflict markers**
|
||||||
|
```json
|
||||||
|
<<<<<<< HEAD ← Don't leave these in!
|
||||||
|
"timeout": 5000,
|
||||||
|
"debug": true
|
||||||
|
>>>>>>> add-debug ← Don't leave these in!
|
||||||
|
```
|
||||||
|
This breaks your code! Always remove ALL markers.
|
||||||
|
|
||||||
|
❌ **Committing without staging**
|
||||||
|
```bash
|
||||||
|
git commit # Error! You didn't add the file
|
||||||
|
```
|
||||||
|
Always `git add` the resolved file first!
|
||||||
|
|
||||||
|
❌ **Keeping only one side when both are needed**
|
||||||
|
If you delete one setting, you lose that work!
|
||||||
|
|
||||||
|
❌ **Breaking syntax**
|
||||||
|
```json
|
||||||
|
"timeout": 5000 ← Missing comma!
|
||||||
|
"debug": true
|
||||||
|
```
|
||||||
|
Always verify your file is valid after resolving!
|
||||||
|
|
||||||
|
❌ **Not testing the result**
|
||||||
|
Always check that your resolved code works!
|
||||||
|
|
||||||
|
## Aborting a Merge
|
||||||
|
|
||||||
|
Changed your mind? You can abort the merge anytime before committing:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git merge --abort
|
||||||
|
```
|
||||||
|
|
||||||
|
This returns your repository to the state before you started the merge. No harm done!
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Discover changes before merging
|
||||||
|
git diff branch1 branch2
|
||||||
|
|
||||||
|
# Attempt a merge
|
||||||
|
git merge <branch-name>
|
||||||
|
|
||||||
|
# Check which files have conflicts
|
||||||
|
git status
|
||||||
|
|
||||||
|
# Abort the merge and start over
|
||||||
|
git merge --abort
|
||||||
|
|
||||||
|
# After resolving conflicts:
|
||||||
|
git add <resolved-file>
|
||||||
|
git commit
|
||||||
|
|
||||||
|
# View conflicts in a different style
|
||||||
|
git diff --ours # Your changes
|
||||||
|
git diff --theirs # Their changes
|
||||||
|
git diff --base # Original version
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pro Tips
|
||||||
|
|
||||||
|
💡 **Use `git diff` first**
|
||||||
|
Always compare branches before merging:
|
||||||
|
```bash
|
||||||
|
git diff main..feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
💡 **Prevent conflicts**
|
||||||
|
- Pull changes frequently
|
||||||
|
- Communicate with your team about who's working on what
|
||||||
|
- Keep branches short-lived and merge often
|
||||||
|
|
||||||
|
💡 **Make conflicts easier**
|
||||||
|
- Work on different files when possible
|
||||||
|
- Make small, focused commits
|
||||||
|
- If editing the same file, coordinate with teammates
|
||||||
|
|
||||||
|
💡 **When stuck**
|
||||||
|
- Read the conflict markers carefully
|
||||||
|
- Look at `git log` to understand what each side changed
|
||||||
|
- Use `git diff` to see the changes
|
||||||
|
- Ask a teammate to review your resolution
|
||||||
|
- Use a merge tool: `git mergetool`
|
||||||
|
|
||||||
|
## Merge Tools
|
||||||
|
|
||||||
|
Git supports visual merge tools that make resolving conflicts easier:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Configure a merge tool (one-time setup)
|
||||||
|
git config --global merge.tool vscode # or meld, kdiff3, etc.
|
||||||
|
|
||||||
|
# Use the merge tool during a conflict
|
||||||
|
git mergetool
|
||||||
|
```
|
||||||
|
|
||||||
|
This opens a visual interface showing both versions side-by-side.
|
||||||
|
|
||||||
|
## Real-World Scenario
|
||||||
|
|
||||||
|
This exercise simulates a common real-world situation:
|
||||||
|
|
||||||
|
**Scenario:** Two developers working on the same file
|
||||||
|
- Alice adds a timeout configuration
|
||||||
|
- Bob adds debug mode configuration
|
||||||
|
- Both push their changes
|
||||||
|
- When Bob tries to merge, he gets a conflict
|
||||||
|
- Bob resolves it by keeping both changes
|
||||||
|
- Everyone's work is preserved!
|
||||||
|
|
||||||
|
This happens all the time in team development. Conflicts are normal!
|
||||||
|
|
||||||
|
## What You've Learned
|
||||||
|
|
||||||
|
After completing this module, you understand:
|
||||||
|
|
||||||
|
- ✅ Merge conflicts happen when the same lines are changed differently
|
||||||
|
- ✅ `git diff` helps you discover changes before merging
|
||||||
|
- ✅ Conflict markers show both versions
|
||||||
|
- ✅ You decide what the final code should look like
|
||||||
|
- ✅ Remove all markers before committing
|
||||||
|
- ✅ Test your resolution to ensure it works
|
||||||
|
- ✅ Conflicts are normal and easy to resolve with practice
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to continue? The next module covers **cherry-picking** - selectively applying specific commits from one branch to another.
|
||||||
|
|
||||||
|
To start over:
|
||||||
|
```bash
|
||||||
|
.\reset.ps1
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Need help?** Review the steps above, or run `git status` to see what Git suggests!
|
||||||
23
01-essentials/04-merge-conflict/reset.ps1
Normal file
23
01-essentials/04-merge-conflict/reset.ps1
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Resets the Module 04 challenge environment.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script removes the challenge directory, allowing you to start fresh.
|
||||||
|
Run setup.ps1 again after resetting to recreate the environment.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Resetting Module 04 Challenge ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
if (Test-Path "challenge") {
|
||||||
|
Write-Host "Removing challenge directory..." -ForegroundColor Yellow
|
||||||
|
Remove-Item -Recurse -Force "challenge"
|
||||||
|
Write-Host "`n[SUCCESS] Challenge environment reset complete!" -ForegroundColor Green
|
||||||
|
Write-Host "`nRun .\setup.ps1 to create a fresh challenge environment." -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
} else {
|
||||||
|
Write-Host "`n[INFO] No challenge directory found. Nothing to reset." -ForegroundColor Yellow
|
||||||
|
Write-Host "Run .\setup.ps1 to create the challenge environment." -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
138
01-essentials/04-merge-conflict/setup.ps1
Normal file
138
01-essentials/04-merge-conflict/setup.ps1
Normal file
@@ -0,0 +1,138 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets up the Module 04 challenge environment for merge conflicts.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script creates a challenge directory with a Git repository containing
|
||||||
|
two feature branches that have conflicting changes to the same file.
|
||||||
|
Students will learn to identify, understand, and resolve merge conflicts.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Setting up Module 04: Merge Conflicts ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Remove existing challenge directory if it exists
|
||||||
|
if (Test-Path "challenge") {
|
||||||
|
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
||||||
|
Remove-Item -Recurse -Force "challenge"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create fresh challenge directory
|
||||||
|
Write-Host "Creating challenge directory..." -ForegroundColor Green
|
||||||
|
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
||||||
|
Set-Location "challenge"
|
||||||
|
|
||||||
|
# Initialize Git repository
|
||||||
|
Write-Host "Initializing Git repository..." -ForegroundColor Green
|
||||||
|
git init | Out-Null
|
||||||
|
|
||||||
|
# Configure git for this repository
|
||||||
|
git config user.name "Workshop Student"
|
||||||
|
git config user.email "student@example.com"
|
||||||
|
|
||||||
|
# Detect the default branch name (could be main, master, etc.)
|
||||||
|
# First commit creates the branch, so we detect it after that
|
||||||
|
$mainBranch = git branch --show-current
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Fallback: Get default branch name from git config
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Ultimate fallback: use "main"
|
||||||
|
$mainBranch = "main"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Create base project
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating base project..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Initial commit with config file
|
||||||
|
$configContent = @"
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "config.json" -Value $configContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial commit with config" | Out-Null
|
||||||
|
|
||||||
|
# Add README
|
||||||
|
$readmeContent = @"
|
||||||
|
# My Application
|
||||||
|
|
||||||
|
A simple application for learning merge conflicts.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Edit `config.json` to configure the application.
|
||||||
|
"@
|
||||||
|
Set-Content -Path "README.md" -Value $readmeContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add README" | Out-Null
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Branch 1: add-timeout (adds timeout setting)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating add-timeout branch..." -ForegroundColor Cyan
|
||||||
|
git switch -c add-timeout | Out-Null
|
||||||
|
|
||||||
|
$timeoutConfig = @"
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
"timeout": 5000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "config.json" -Value $timeoutConfig
|
||||||
|
git add .
|
||||||
|
git commit -m "Add timeout configuration" | Out-Null
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Branch 2: add-debug (adds debug setting - CONFLICTS with timeout!)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "Creating add-debug branch..." -ForegroundColor Cyan
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
git switch -c add-debug | Out-Null
|
||||||
|
|
||||||
|
$debugConfig = @"
|
||||||
|
{
|
||||||
|
"app": {
|
||||||
|
"name": "MyApp",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"port": 3000,
|
||||||
|
"debug": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"@
|
||||||
|
Set-Content -Path "config.json" -Value $debugConfig
|
||||||
|
git add .
|
||||||
|
git commit -m "Add debug mode configuration" | Out-Null
|
||||||
|
|
||||||
|
# Switch back to main branch
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
|
||||||
|
# Return to module directory
|
||||||
|
Set-Location ..
|
||||||
|
|
||||||
|
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||||
|
Write-Host "`nYour challenge environment is ready in the 'challenge/' directory." -ForegroundColor Cyan
|
||||||
|
Write-Host "`nThe repository contains:" -ForegroundColor Yellow
|
||||||
|
Write-Host " - $mainBranch branch: base configuration" -ForegroundColor White
|
||||||
|
Write-Host " - add-timeout branch: adds timeout setting" -ForegroundColor White
|
||||||
|
Write-Host " - add-debug branch: adds debug setting" -ForegroundColor White
|
||||||
|
Write-Host "`nBoth branches modify the same part of config.json!" -ForegroundColor Red
|
||||||
|
Write-Host "This will cause a merge conflict when you try to merge both." -ForegroundColor Red
|
||||||
|
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. Read the README.md for detailed instructions" -ForegroundColor White
|
||||||
|
Write-Host " 2. cd challenge" -ForegroundColor White
|
||||||
|
Write-Host " 3. Follow the guide to discover and resolve the conflict" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
232
01-essentials/04-merge-conflict/verify.ps1
Normal file
232
01-essentials/04-merge-conflict/verify.ps1
Normal file
@@ -0,0 +1,232 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Verifies the Module 04 challenge solution.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script checks that you've successfully resolved the merge conflict by:
|
||||||
|
- Merging both branches into main
|
||||||
|
- Resolving the conflict in config.json
|
||||||
|
- Keeping both timeout and debug settings
|
||||||
|
- Ensuring valid JSON syntax
|
||||||
|
#>
|
||||||
|
|
||||||
|
$script:allChecksPassed = $true
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Helper Functions
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
function Write-Pass {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[PASS] $Message" -ForegroundColor Green
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Fail {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[FAIL] $Message" -ForegroundColor Red
|
||||||
|
$script:allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Hint {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[HINT] $Message" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Info {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-Host "[INFO] $Message" -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check challenge directory exists
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
if (-not (Test-Path "challenge")) {
|
||||||
|
Write-Host "[ERROR] Challenge directory not found." -ForegroundColor Red
|
||||||
|
Write-Host "Run .\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Push-Location "challenge"
|
||||||
|
|
||||||
|
if (-not (Test-Path ".git")) {
|
||||||
|
Write-Host "[ERROR] Not a git repository." -ForegroundColor Red
|
||||||
|
Write-Host "Run ..\setup.ps1 first to create the challenge environment." -ForegroundColor Yellow
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "`n=== Verifying Module 04: Merge Conflicts ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Detect the main branch name (could be main, master, etc.)
|
||||||
|
# ============================================================================
|
||||||
|
# Try to get the default branch from remote origin first
|
||||||
|
$mainBranch = git symbolic-ref refs/remotes/origin/HEAD 2>$null | Split-Path -Leaf
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Fallback: try to detect from local branches
|
||||||
|
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||||
|
if ($allBranches -contains "main") {
|
||||||
|
$mainBranch = "main"
|
||||||
|
} elseif ($allBranches -contains "master") {
|
||||||
|
$mainBranch = "master"
|
||||||
|
} else {
|
||||||
|
# Get the default branch from git config
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
# Ultimate fallback: use the first branch
|
||||||
|
$mainBranch = $allBranches | Select-Object -First 1
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check current branch
|
||||||
|
# ============================================================================
|
||||||
|
$currentBranch = git branch --show-current 2>$null
|
||||||
|
if ($currentBranch -eq $mainBranch) {
|
||||||
|
Write-Pass "Currently on $mainBranch branch"
|
||||||
|
} else {
|
||||||
|
Write-Fail "Should be on $mainBranch branch (currently on: $currentBranch)"
|
||||||
|
Write-Hint "Switch to $mainBranch with: git switch $mainBranch"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check that merge is not in progress
|
||||||
|
# ============================================================================
|
||||||
|
if (Test-Path ".git/MERGE_HEAD") {
|
||||||
|
Write-Fail "Merge is still in progress (conflicts not resolved)"
|
||||||
|
Write-Hint "Resolve conflicts in config.json, then: git add config.json && git commit"
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Pass "No merge in progress (conflicts resolved)"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check if config.json exists
|
||||||
|
# ============================================================================
|
||||||
|
if (-not (Test-Path "config.json")) {
|
||||||
|
Write-Fail "File 'config.json' not found"
|
||||||
|
Write-Hint "The config.json file should exist"
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Verify config.json is valid JSON
|
||||||
|
# ============================================================================
|
||||||
|
try {
|
||||||
|
$configContent = Get-Content "config.json" -Raw
|
||||||
|
$config = $configContent | ConvertFrom-Json -ErrorAction Stop
|
||||||
|
Write-Pass "File 'config.json' is valid JSON"
|
||||||
|
} catch {
|
||||||
|
Write-Fail "File 'config.json' is not valid JSON"
|
||||||
|
Write-Hint "Make sure you removed all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||||
|
Write-Hint "Check for missing commas or brackets"
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check for conflict markers
|
||||||
|
# ============================================================================
|
||||||
|
if ($configContent -match '<<<<<<<|=======|>>>>>>>') {
|
||||||
|
Write-Fail "Conflict markers still present in config.json"
|
||||||
|
Write-Hint "Remove all conflict markers (<<<<<<<, =======, >>>>>>>)"
|
||||||
|
Pop-Location
|
||||||
|
exit 1
|
||||||
|
} else {
|
||||||
|
Write-Pass "No conflict markers in config.json"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Verify both settings are present (timeout and debug)
|
||||||
|
# ============================================================================
|
||||||
|
if ($config.app.timeout -eq 5000) {
|
||||||
|
Write-Pass "Timeout setting preserved (5000)"
|
||||||
|
} else {
|
||||||
|
Write-Fail "Timeout setting missing or incorrect"
|
||||||
|
Write-Hint "Keep the timeout: 5000 setting from add-timeout branch"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($config.app.debug -eq $true) {
|
||||||
|
Write-Pass "Debug setting preserved (true)"
|
||||||
|
} else {
|
||||||
|
Write-Fail "Debug setting missing or incorrect"
|
||||||
|
Write-Hint "Keep the debug: true setting from add-debug branch"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Verify both branches were merged
|
||||||
|
# ============================================================================
|
||||||
|
$addTimeoutMerged = git log --oneline --grep="add-timeout" 2>$null | Select-String "Merge"
|
||||||
|
$addDebugMerged = git log --oneline --grep="add-debug" 2>$null | Select-String "Merge"
|
||||||
|
|
||||||
|
if ($addTimeoutMerged) {
|
||||||
|
Write-Pass "add-timeout branch has been merged"
|
||||||
|
} else {
|
||||||
|
# Check if it was a fast-forward merge (commits exist but no merge commit)
|
||||||
|
$timeoutCommit = git log --oneline --grep="Add timeout configuration" 2>$null
|
||||||
|
if ($timeoutCommit) {
|
||||||
|
Write-Pass "add-timeout branch changes are in main"
|
||||||
|
} else {
|
||||||
|
Write-Fail "add-timeout branch not merged"
|
||||||
|
Write-Hint "Merge with: git merge add-timeout"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($addDebugMerged) {
|
||||||
|
Write-Pass "add-debug branch has been merged"
|
||||||
|
} else {
|
||||||
|
Write-Fail "add-debug branch not merged"
|
||||||
|
Write-Hint "Merge with: git merge add-debug"
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Check commit count (should have both merges)
|
||||||
|
# ============================================================================
|
||||||
|
$totalCommits = [int](git rev-list --count HEAD 2>$null)
|
||||||
|
if ($totalCommits -ge 5) {
|
||||||
|
Write-Pass "Repository has $totalCommits commits (all merges complete)"
|
||||||
|
} else {
|
||||||
|
Write-Info "Repository has $totalCommits commits"
|
||||||
|
Write-Hint "Make sure both branches are merged"
|
||||||
|
}
|
||||||
|
|
||||||
|
Pop-Location
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Final summary
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host ""
|
||||||
|
if ($script:allChecksPassed) {
|
||||||
|
Write-Host "=========================================" -ForegroundColor Green
|
||||||
|
Write-Host " CONGRATULATIONS! CHALLENGE PASSED!" -ForegroundColor Green
|
||||||
|
Write-Host "=========================================" -ForegroundColor Green
|
||||||
|
Write-Host "`nYou've successfully:" -ForegroundColor Cyan
|
||||||
|
Write-Host " ✓ Discovered changes using git diff" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Merged the first branch" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Encountered a merge conflict" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Resolved the conflict by keeping both changes" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Completed the merge" -ForegroundColor White
|
||||||
|
Write-Host "`nYou're now ready to handle merge conflicts in real projects!" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
exit 0
|
||||||
|
} else {
|
||||||
|
Write-Host "[SUMMARY] Some checks failed. Review the hints above." -ForegroundColor Red
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Quick guide:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. Make sure you're on $mainBranch : git switch $mainBranch" -ForegroundColor White
|
||||||
|
Write-Host " 2. Merge first branch: git merge add-timeout" -ForegroundColor White
|
||||||
|
Write-Host " 3. Merge second branch: git merge add-debug" -ForegroundColor White
|
||||||
|
Write-Host " 4. Resolve conflict: edit config.json, remove markers, keep both settings" -ForegroundColor White
|
||||||
|
Write-Host " 5. Stage resolved file: git add config.json" -ForegroundColor White
|
||||||
|
Write-Host " 6. Complete merge: git commit" -ForegroundColor White
|
||||||
|
Write-Host " 7. Run this verify script again" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
363
01-essentials/05-cherry-pick/README.md
Normal file
363
01-essentials/05-cherry-pick/README.md
Normal file
@@ -0,0 +1,363 @@
|
|||||||
|
# Module 05: Cherry-Pick
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By the end of this module, you will:
|
||||||
|
- Understand what cherry-picking is and how it works
|
||||||
|
- Know when to use cherry-pick vs merge or rebase
|
||||||
|
- Apply specific commits from one branch to another
|
||||||
|
- Understand common use cases for cherry-picking
|
||||||
|
- Learn how cherry-pick creates new commits with different hashes
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Create the challenge environment:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a repository with a `development` branch containing both bug fixes and experimental features.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
**Cherry-pick** allows you to copy specific commits from one branch to another. Unlike merging (which brings ALL commits), cherry-pick lets you be surgical about exactly which changes you want to apply.
|
||||||
|
|
||||||
|
Think of it like picking cherries from a tree - you select only the ripe ones you want, leaving the rest behind.
|
||||||
|
|
||||||
|
### Why Use Cherry-Pick?
|
||||||
|
|
||||||
|
- **Selective deployment** - Apply critical bug fixes without merging unfinished features
|
||||||
|
- **Hotfixes** - Quickly move a fix from development to production
|
||||||
|
- **Backporting** - Apply fixes to older release branches
|
||||||
|
- **Wrong branch** - Move commits you accidentally made on the wrong branch
|
||||||
|
- **Duplicate commits** - Apply the same fix across multiple branches
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
### The Scenario
|
||||||
|
|
||||||
|
You're working on a project where:
|
||||||
|
- The `main` branch is stable and in production
|
||||||
|
- The `development` branch has new features being tested
|
||||||
|
- Development has critical bug fixes that need to go to production NOW
|
||||||
|
- But development also has experimental features that aren't ready yet
|
||||||
|
|
||||||
|
You need to cherry-pick ONLY the bug fixes to main, leaving the experimental features behind.
|
||||||
|
|
||||||
|
### Part 1: Explore the Development Branch
|
||||||
|
|
||||||
|
First, see what commits are on the development branch:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
cd challenge
|
||||||
|
|
||||||
|
# View all commits on development branch
|
||||||
|
git log --oneline development
|
||||||
|
|
||||||
|
# View the full commit graph
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
**Study the commits:**
|
||||||
|
- Look for commits with "Fix" in the message (these are bug fixes)
|
||||||
|
- Look for commits with "experimental" or "beta" (these should stay on development)
|
||||||
|
- Note the commit hashes (the 7-character codes like `abc1234`)
|
||||||
|
|
||||||
|
**Inspect specific commits:**
|
||||||
|
```pwsh
|
||||||
|
# See what files a commit changed
|
||||||
|
git show <commit-hash>
|
||||||
|
|
||||||
|
# Example:
|
||||||
|
# git show abc1234
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see:
|
||||||
|
- 2 commits that fix bugs (security and performance)
|
||||||
|
- 2 commits that add experimental features
|
||||||
|
|
||||||
|
### Part 2: Switch to Main Branch
|
||||||
|
|
||||||
|
Before cherry-picking, you need to be on the target branch (main):
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Switch to main branch
|
||||||
|
git switch main
|
||||||
|
|
||||||
|
# Verify you're on main
|
||||||
|
git branch
|
||||||
|
```
|
||||||
|
|
||||||
|
The `*` should be next to `main`.
|
||||||
|
|
||||||
|
**Check what's currently on main:**
|
||||||
|
```pwsh
|
||||||
|
# See main's commits
|
||||||
|
git log --oneline
|
||||||
|
|
||||||
|
# See what files exist
|
||||||
|
ls
|
||||||
|
```
|
||||||
|
|
||||||
|
Main should only have the initial app and README - no bug fixes yet, no experimental features.
|
||||||
|
|
||||||
|
### Part 3: Cherry-Pick the Bug Fixes
|
||||||
|
|
||||||
|
Now copy the bug fix commits from development to main:
|
||||||
|
|
||||||
|
1. Find the security fix commit hash by looking at your earlier `git log --oneline --graph --all`
|
||||||
|
- Look for a commit message like "Fix security vulnerability in input validation"
|
||||||
|
- Note its hash (first 7 characters)
|
||||||
|
|
||||||
|
2. Cherry-pick the security fix:
|
||||||
|
```pwsh
|
||||||
|
git cherry-pick <security-fix-hash>
|
||||||
|
|
||||||
|
# Example if the hash is abc1234:
|
||||||
|
# git cherry-pick abc1234
|
||||||
|
```
|
||||||
|
3. Verify it worked: Check that security.py, with `ls` or check your file explorer in VSCode, now exists and check that the commit has been added to the main branch with `git log --oneline --graph --all`
|
||||||
|
4. Find the performance fix commit hash
|
||||||
|
- Look for "Fix performance issue with data caching"
|
||||||
|
- Note its hash
|
||||||
|
|
||||||
|
5. Cherry-pick the performance fix:
|
||||||
|
```pwsh
|
||||||
|
git cherry-pick <performance-fix-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. Verify both fixes are now on main:
|
||||||
|
```pwsh
|
||||||
|
# You should see both security.py and cache.py
|
||||||
|
ls
|
||||||
|
|
||||||
|
# View the graph showing both branches
|
||||||
|
git log --oneline --graph --all
|
||||||
|
```
|
||||||
|
|
||||||
|
### Part 4: Verify Your Solution
|
||||||
|
|
||||||
|
Check that you completed the challenge correctly:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# From inside the module directory
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The verification checks:
|
||||||
|
- ✅ You're on the main branch
|
||||||
|
- ✅ Security fix is applied to main
|
||||||
|
- ✅ Performance fix is applied to main
|
||||||
|
- ✅ Experimental features are NOT on main
|
||||||
|
- ✅ Development branch still has all commits
|
||||||
|
|
||||||
|
## Understanding Cherry-Pick
|
||||||
|
|
||||||
|
### What Actually Happens?
|
||||||
|
|
||||||
|
When you cherry-pick a commit, Git:
|
||||||
|
1. Looks at what changed in that specific commit
|
||||||
|
2. Applies those same changes to your current branch
|
||||||
|
3. Creates a NEW commit with those changes
|
||||||
|
|
||||||
|
```
|
||||||
|
Before cherry-pick:
|
||||||
|
|
||||||
|
development: A---B---C---D
|
||||||
|
/
|
||||||
|
main: E---F
|
||||||
|
|
||||||
|
After: git switch main && git cherry-pick C
|
||||||
|
|
||||||
|
development: A---B---C---D
|
||||||
|
/
|
||||||
|
main: E---F---C'
|
||||||
|
```
|
||||||
|
|
||||||
|
Notice:
|
||||||
|
- `C'` is a NEW commit (different hash than original `C`)
|
||||||
|
- Original `C` still exists on development
|
||||||
|
- Main now has the changes from C, but not B or D
|
||||||
|
|
||||||
|
### Cherry-Pick vs Merge
|
||||||
|
|
||||||
|
**Merge brings everything:**
|
||||||
|
```pwsh
|
||||||
|
git switch main
|
||||||
|
git merge development
|
||||||
|
# Result: A, B, C, and D all come to main
|
||||||
|
```
|
||||||
|
|
||||||
|
**Cherry-pick is selective:**
|
||||||
|
```pwsh
|
||||||
|
git switch main
|
||||||
|
git cherry-pick C
|
||||||
|
# Result: Only C comes to main (as C')
|
||||||
|
```
|
||||||
|
|
||||||
|
### Important: New Commits, New Hashes
|
||||||
|
|
||||||
|
Cherry-picked commits are COPIES, not moves:
|
||||||
|
- Original commit stays on source branch
|
||||||
|
- New commit created on target branch
|
||||||
|
- Different commit hash (because different parent)
|
||||||
|
- Same changes, same message, different identity
|
||||||
|
|
||||||
|
## Key Commands
|
||||||
|
|
||||||
|
### Viewing Commits
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# See commits on another branch
|
||||||
|
git log branch-name --oneline
|
||||||
|
|
||||||
|
# See what a specific commit changed
|
||||||
|
git show <commit-hash>
|
||||||
|
|
||||||
|
# See commit graph
|
||||||
|
git log --oneline --graph --all
|
||||||
|
|
||||||
|
# See only commit message (not changes)
|
||||||
|
git log --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
### Cherry-Picking
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Cherry-pick a single commit
|
||||||
|
git cherry-pick <commit-hash>
|
||||||
|
|
||||||
|
# Cherry-pick multiple commits (in order)
|
||||||
|
git cherry-pick <hash1> <hash2> <hash3>
|
||||||
|
|
||||||
|
# Cherry-pick a range of commits
|
||||||
|
git cherry-pick <start-hash>..<end-hash>
|
||||||
|
|
||||||
|
# Abort a cherry-pick if something goes wrong
|
||||||
|
git cherry-pick --abort
|
||||||
|
```
|
||||||
|
|
||||||
|
### After Cherry-Pick
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Verify the commit was added
|
||||||
|
git log --oneline
|
||||||
|
|
||||||
|
# See what files changed
|
||||||
|
git show HEAD
|
||||||
|
|
||||||
|
# Compare branches
|
||||||
|
git log main..development --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Workflows
|
||||||
|
|
||||||
|
### Hotfix to Production
|
||||||
|
|
||||||
|
Critical bug found in production:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# You're on feature-new-ui branch
|
||||||
|
# You just committed a critical security fix
|
||||||
|
|
||||||
|
git log --oneline
|
||||||
|
# Note the hash of your fix commit
|
||||||
|
|
||||||
|
# Switch to production branch
|
||||||
|
git switch production
|
||||||
|
|
||||||
|
# Apply just that fix
|
||||||
|
git cherry-pick <fix-commit-hash>
|
||||||
|
|
||||||
|
# Deploy to production
|
||||||
|
# Your fix is live, but new UI stays in development
|
||||||
|
```
|
||||||
|
|
||||||
|
### Backporting to Old Versions
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# You fixed a bug on main
|
||||||
|
git switch main
|
||||||
|
git log --oneline
|
||||||
|
# Note the fix commit hash
|
||||||
|
|
||||||
|
# Apply to older release branch
|
||||||
|
git switch release-2.5
|
||||||
|
git cherry-pick <fix-commit-hash>
|
||||||
|
|
||||||
|
# Apply to even older release
|
||||||
|
git switch release-2.0
|
||||||
|
git cherry-pick <fix-commit-hash>
|
||||||
|
|
||||||
|
# Same fix now on three branches!
|
||||||
|
```
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "I can't remember the commit hash!"
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# See commits on the source branch
|
||||||
|
git log development --oneline
|
||||||
|
|
||||||
|
# Search for specific text in commit messages
|
||||||
|
git log development --oneline --grep="security"
|
||||||
|
|
||||||
|
# See recent commits with more detail
|
||||||
|
git log development --oneline -n 10
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I cherry-picked in the wrong order!"
|
||||||
|
|
||||||
|
Order matters! If commit B depends on commit A, cherry-pick A first:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Wrong order might cause issues
|
||||||
|
git cherry-pick B # Might fail if it needs changes from A
|
||||||
|
|
||||||
|
# Correct order
|
||||||
|
git cherry-pick A
|
||||||
|
git cherry-pick B
|
||||||
|
```
|
||||||
|
|
||||||
|
### "How do I see what will change before cherry-picking?"
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# See what changes are in a commit
|
||||||
|
git show <commit-hash>
|
||||||
|
|
||||||
|
# Compare your current branch with a commit
|
||||||
|
git diff HEAD <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
💡 **Copy the commit hashes** - Write them down before switching branches
|
||||||
|
💡 **Cherry-pick oldest first** - Apply commits in chronological order
|
||||||
|
💡 **Check your branch** - Always verify you're on the target branch first with `git branch`
|
||||||
|
💡 **Verify after each pick** - Run `git log --oneline` to confirm it worked
|
||||||
|
💡 **Use the graph** - `git log --oneline --graph --all` shows the full picture
|
||||||
|
💡 **Original stays put** - Cherry-pick copies, doesn't move commits
|
||||||
|
|
||||||
|
## What You've Learned
|
||||||
|
|
||||||
|
After completing this module, you understand:
|
||||||
|
|
||||||
|
- ✅ Cherry-pick copies specific commits between branches
|
||||||
|
- ✅ `git cherry-pick <hash>` applies a commit to current branch
|
||||||
|
- ✅ Cherry-picked commits get new hashes but same changes
|
||||||
|
- ✅ Use cherry-pick for selective deployment of changes
|
||||||
|
- ✅ Cherry-pick is different from merge (selective vs all)
|
||||||
|
- ✅ Original commit stays on source branch
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to continue? Cherry-pick is a powerful tool for selective change management. Next modules will cover more advanced Git operations.
|
||||||
|
|
||||||
|
To start over:
|
||||||
|
```pwsh
|
||||||
|
.\reset.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
**Need help?** Run `git status` to see what Git suggests, or `git log --oneline --graph --all` to see the full picture!
|
||||||
@@ -26,7 +26,8 @@ git init | Out-Null
|
|||||||
git config user.name "Workshop User" | Out-Null
|
git config user.name "Workshop User" | Out-Null
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
git config user.email "user@workshop.local" | Out-Null
|
||||||
|
|
||||||
# Create initial commits on main branch
|
# Detect the default branch name (could be main, master, etc.)
|
||||||
|
# First commit creates the branch, so we detect it after the first commit below
|
||||||
$app = @"
|
$app = @"
|
||||||
class App:
|
class App:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
@@ -40,6 +41,14 @@ Set-Content -Path "app.py" -Value $app
|
|||||||
git add app.py
|
git add app.py
|
||||||
git commit -m "Initial app implementation" | Out-Null
|
git commit -m "Initial app implementation" | Out-Null
|
||||||
|
|
||||||
|
# Detect the main branch name after first commit
|
||||||
|
$mainBranch = git branch --show-current
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
$readme = @"
|
$readme = @"
|
||||||
# Application
|
# Application
|
||||||
|
|
||||||
@@ -119,40 +128,29 @@ git add app.py
|
|||||||
git commit -m "Add beta features framework" | Out-Null
|
git commit -m "Add beta features framework" | Out-Null
|
||||||
|
|
||||||
# Commit 4: Performance bug fix (SHOULD be cherry-picked)
|
# Commit 4: Performance bug fix (SHOULD be cherry-picked)
|
||||||
$appWithPerformance = @"
|
# This commit adds caching to the existing file to fix performance
|
||||||
class App:
|
# It should apply cleanly to main since it doesn't depend on experimental features
|
||||||
|
$performanceCode = @"
|
||||||
|
|
||||||
|
class DataCache:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.version = '1.0.0'
|
|
||||||
self.experimental_mode = False
|
|
||||||
self.beta_features = []
|
|
||||||
self.cache = {}
|
self.cache = {}
|
||||||
|
|
||||||
def start(self):
|
def get(self, key):
|
||||||
print('App started')
|
|
||||||
if self.experimental_mode:
|
|
||||||
self.enable_experimental_features()
|
|
||||||
|
|
||||||
def enable_experimental_features(self):
|
|
||||||
print('Experimental features enabled')
|
|
||||||
|
|
||||||
def add_beta_feature(self, feature):
|
|
||||||
self.beta_features.append(feature)
|
|
||||||
|
|
||||||
def get_data(self, key):
|
|
||||||
# Use cache to improve performance
|
# Use cache to improve performance
|
||||||
if key in self.cache:
|
if key in self.cache:
|
||||||
return self.cache[key]
|
return self.cache[key]
|
||||||
data = self.fetch_data(key)
|
return None
|
||||||
self.cache[key] = data
|
|
||||||
return data
|
|
||||||
|
|
||||||
def fetch_data(self, key):
|
def set(self, key, value):
|
||||||
# Simulate data fetching
|
self.cache[key] = value
|
||||||
return {'key': key, 'value': 'data'}
|
|
||||||
|
def clear(self):
|
||||||
|
self.cache.clear()
|
||||||
"@
|
"@
|
||||||
|
|
||||||
Set-Content -Path "app.py" -Value $appWithPerformance
|
Set-Content -Path "cache.py" -Value $performanceCode
|
||||||
git add app.py
|
git add cache.py
|
||||||
git commit -m "Fix performance issue with data caching" | Out-Null
|
git commit -m "Fix performance issue with data caching" | Out-Null
|
||||||
|
|
||||||
# Return to module directory
|
# Return to module directory
|
||||||
@@ -164,12 +162,13 @@ Write-Host "========================================" -ForegroundColor Green
|
|||||||
Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan
|
Write-Host "`nYou are on the 'development' branch with multiple commits:" -ForegroundColor Cyan
|
||||||
Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow
|
Write-Host "- Experimental features (not ready for production)" -ForegroundColor Yellow
|
||||||
Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green
|
Write-Host "- Critical bug fixes (needed in production NOW)" -ForegroundColor Green
|
||||||
|
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
Write-Host "`nYour task:" -ForegroundColor Yellow
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
||||||
Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White
|
Write-Host "2. View the development branch commits: git log --oneline" -ForegroundColor White
|
||||||
Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White
|
Write-Host "3. Identify which commits are bug fixes (look for 'Fix' in messages)" -ForegroundColor White
|
||||||
Write-Host "4. Switch to main branch: git checkout main" -ForegroundColor White
|
Write-Host "4. Switch to $mainBranch branch: git checkout $mainBranch" -ForegroundColor White
|
||||||
Write-Host "5. Cherry-pick ONLY the bug fix commits to main" -ForegroundColor White
|
Write-Host "5. Cherry-pick ONLY the bug fix commits to $mainBranch" -ForegroundColor White
|
||||||
Write-Host "6. Do NOT bring the experimental features to main" -ForegroundColor White
|
Write-Host "6. Do NOT bring the experimental features to $mainBranch" -ForegroundColor White
|
||||||
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
|
Write-Host "`nHint: Look for commits mentioning 'security' and 'performance' fixes" -ForegroundColor Cyan
|
||||||
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
Write-Host "Run '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
||||||
@@ -32,12 +32,27 @@ if (-not (Test-Path ".git")) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Detect the main branch name
|
||||||
|
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||||
|
if ($allBranches -contains "main") {
|
||||||
|
$mainBranch = "main"
|
||||||
|
} elseif ($allBranches -contains "master") {
|
||||||
|
$mainBranch = "master"
|
||||||
|
} else {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
$mainBranch = $allBranches | Select-Object -First 1
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check current branch
|
# Check current branch
|
||||||
$currentBranch = git branch --show-current 2>$null
|
$currentBranch = git branch --show-current 2>$null
|
||||||
if ($currentBranch -ne "main") {
|
if ($currentBranch -ne $mainBranch) {
|
||||||
Write-Host "[FAIL] You should be on the 'main' branch." -ForegroundColor Red
|
Write-Host "[FAIL] You should be on the '$mainBranch' branch." -ForegroundColor Red
|
||||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
||||||
Write-Host "Hint: Use 'git checkout main' to switch to main branch" -ForegroundColor Yellow
|
Write-Host "Hint: Use 'git checkout $mainBranch' to switch to $mainBranch branch" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
@@ -54,15 +69,15 @@ if (Test-Path ".git/CHERRY_PICK_HEAD") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
|
# Check commit count on main (should be 4: 2 initial + 2 cherry-picked)
|
||||||
$mainCommitCount = (git rev-list --count main 2>$null)
|
$mainCommitCount = (git rev-list --count $mainBranch 2>$null)
|
||||||
if ($mainCommitCount -ne 4) {
|
if ($mainCommitCount -ne 4) {
|
||||||
Write-Host "[FAIL] Expected 4 commits on main branch, found $mainCommitCount" -ForegroundColor Red
|
Write-Host "[FAIL] Expected 4 commits on $mainBranch branch, found $mainCommitCount" -ForegroundColor Red
|
||||||
if ($mainCommitCount -lt 4) {
|
if ($mainCommitCount -lt 4) {
|
||||||
Write-Host "Hint: You should cherry-pick 2 bug fix commits to main" -ForegroundColor Yellow
|
Write-Host "Hint: You should cherry-pick 2 bug fix commits to $mainBranch" -ForegroundColor Yellow
|
||||||
} else {
|
} else {
|
||||||
Write-Host "Hint: You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
|
Write-Host "Hint: You should cherry-pick ONLY the 2 bug fix commits, not all commits" -ForegroundColor Yellow
|
||||||
}
|
}
|
||||||
Write-Host "`nExpected commits on main:" -ForegroundColor Yellow
|
Write-Host "`nExpected commits on ${mainBranch}:" -ForegroundColor Yellow
|
||||||
Write-Host " 1. Initial app implementation" -ForegroundColor White
|
Write-Host " 1. Initial app implementation" -ForegroundColor White
|
||||||
Write-Host " 2. Add README" -ForegroundColor White
|
Write-Host " 2. Add README" -ForegroundColor White
|
||||||
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
|
Write-Host " 3. Fix security vulnerability in input validation (cherry-picked)" -ForegroundColor White
|
||||||
@@ -72,9 +87,9 @@ if ($mainCommitCount -ne 4) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
|
# Check for merge commits (should be none - cherry-pick doesn't create merge commits)
|
||||||
$mergeCommits = git log --merges --oneline main 2>$null
|
$mergeCommits = git log --merges --oneline $mainBranch 2>$null
|
||||||
if ($mergeCommits) {
|
if ($mergeCommits) {
|
||||||
Write-Host "[FAIL] Found merge commits on main. You should use cherry-pick, not merge." -ForegroundColor Red
|
Write-Host "[FAIL] Found merge commits on $mainBranch. You should use cherry-pick, not merge." -ForegroundColor Red
|
||||||
Write-Host "Hint: Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
|
Write-Host "Hint: Use 'git cherry-pick <commit-hash>' instead of 'git merge'" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -82,7 +97,7 @@ if ($mergeCommits) {
|
|||||||
|
|
||||||
# Check that security.py exists (from the security fix commit)
|
# Check that security.py exists (from the security fix commit)
|
||||||
if (-not (Test-Path "security.py")) {
|
if (-not (Test-Path "security.py")) {
|
||||||
Write-Host "[FAIL] security.py not found on main branch." -ForegroundColor Red
|
Write-Host "[FAIL] security.py not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
|
Write-Host "Hint: You need to cherry-pick the 'Fix security vulnerability' commit" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -110,23 +125,32 @@ if (-not (Test-Path "app.py")) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
# Check that app.py has the performance fix (cache) but NOT experimental features
|
# Check that cache.py exists (from performance fix)
|
||||||
$appContent = Get-Content "app.py" -Raw
|
if (-not (Test-Path "cache.py")) {
|
||||||
|
Write-Host "[FAIL] cache.py not found on $mainBranch branch." -ForegroundColor Red
|
||||||
# Should have cache (from performance fix)
|
|
||||||
if ($appContent -notmatch "cache") {
|
|
||||||
Write-Host "[FAIL] app.py is missing the performance fix (cache)." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: You need to cherry-pick the 'Fix performance issue' commit" -ForegroundColor Yellow
|
Write-Host "Hint: You need to cherry-pick the 'Fix performance issue' commit" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($appContent -notmatch "get_data") {
|
# Check that cache.py has the DataCache class
|
||||||
Write-Host "[FAIL] app.py is missing the get_data method from performance fix." -ForegroundColor Red
|
$cacheContent = Get-Content "cache.py" -Raw
|
||||||
|
|
||||||
|
if ($cacheContent -notmatch "DataCache") {
|
||||||
|
Write-Host "[FAIL] cache.py is missing the DataCache class." -ForegroundColor Red
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if ($cacheContent -notmatch "def get\(") {
|
||||||
|
Write-Host "[FAIL] cache.py is missing the get method." -ForegroundColor Red
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that app.py does NOT have experimental features
|
||||||
|
$appContent = Get-Content "app.py" -Raw
|
||||||
|
|
||||||
# Should NOT have experimental features
|
# Should NOT have experimental features
|
||||||
if ($appContent -match "experimental_mode") {
|
if ($appContent -match "experimental_mode") {
|
||||||
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -ForegroundColor Red
|
Write-Host "[FAIL] app.py contains experimental features (experimental_mode)." -ForegroundColor Red
|
||||||
@@ -151,7 +175,7 @@ if ($appContent -match "enable_experimental_features") {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Check commit messages to verify cherry-picks
|
# Check commit messages to verify cherry-picks
|
||||||
$commits = git log --pretty=format:"%s" main 2>$null
|
$commits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||||
$commitArray = $commits -split "`n"
|
$commitArray = $commits -split "`n"
|
||||||
|
|
||||||
$hasSecurityFix = $false
|
$hasSecurityFix = $false
|
||||||
@@ -167,14 +191,14 @@ foreach ($commit in $commitArray) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasSecurityFix) {
|
if (-not $hasSecurityFix) {
|
||||||
Write-Host "[FAIL] Security fix commit not found on main branch." -ForegroundColor Red
|
Write-Host "[FAIL] Security fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
|
Write-Host "Hint: Cherry-pick the 'Fix security vulnerability' commit from development" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
if (-not $hasPerformanceFix) {
|
if (-not $hasPerformanceFix) {
|
||||||
Write-Host "[FAIL] Performance fix commit not found on main branch." -ForegroundColor Red
|
Write-Host "[FAIL] Performance fix commit not found on $mainBranch branch." -ForegroundColor Red
|
||||||
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
|
Write-Host "Hint: Cherry-pick the 'Fix performance issue' commit from development" -ForegroundColor Yellow
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -194,8 +218,8 @@ Write-Host "`n========================================" -ForegroundColor Green
|
|||||||
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
||||||
Write-Host "========================================" -ForegroundColor Green
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
||||||
Write-Host "- Cherry-picked the security vulnerability fix to main" -ForegroundColor White
|
Write-Host "- Cherry-picked the security vulnerability fix to $mainBranch" -ForegroundColor White
|
||||||
Write-Host "- Cherry-picked the performance issue fix to main" -ForegroundColor White
|
Write-Host "- Cherry-picked the performance issue fix to $mainBranch" -ForegroundColor White
|
||||||
Write-Host "- Left experimental features on development branch only" -ForegroundColor White
|
Write-Host "- Left experimental features on development branch only" -ForegroundColor White
|
||||||
Write-Host "- Kept development branch intact with all commits" -ForegroundColor White
|
Write-Host "- Kept development branch intact with all commits" -ForegroundColor White
|
||||||
Write-Host "`nPerfect use of cherry-pick!" -ForegroundColor Green
|
Write-Host "`nPerfect use of cherry-pick!" -ForegroundColor Green
|
||||||
400
01-essentials/06-revert/README.md
Normal file
400
01-essentials/06-revert/README.md
Normal file
@@ -0,0 +1,400 @@
|
|||||||
|
# Module 06: Git Revert - Safe Undoing
|
||||||
|
|
||||||
|
## About This Module
|
||||||
|
|
||||||
|
Welcome to Module 06, where you'll learn the **safe, team-friendly way to undo changes** in Git. Unlike destructive commands that erase history, `git revert` creates new commits that undo previous changes while preserving the complete project history.
|
||||||
|
|
||||||
|
**Why revert is important:**
|
||||||
|
- ✅ Safe for shared/pushed commits
|
||||||
|
- ✅ Preserves complete history and audit trail
|
||||||
|
- ✅ Transparent to your team
|
||||||
|
- ✅ Can be undone itself if needed
|
||||||
|
- ✅ Works with any commit in history
|
||||||
|
|
||||||
|
**Key principle:** Revert doesn't erase mistakes—it documents how you fixed them.
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By completing this module, you will:
|
||||||
|
|
||||||
|
1. Revert commits safely while preserving surrounding changes
|
||||||
|
2. Understand how revert creates new commits instead of erasing history
|
||||||
|
3. Revert multiple commits at once
|
||||||
|
4. Know when to use revert vs. other undo strategies
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before starting this module, you should be comfortable with:
|
||||||
|
- Creating commits (`git commit`)
|
||||||
|
- Viewing commit history (`git log`)
|
||||||
|
- Understanding branches (Module 03)
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Run the setup script to create the challenge environment:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `challenge/` directory with two branches demonstrating different revert scenarios:
|
||||||
|
- `regular-revert` - Basic commit reversion
|
||||||
|
- `multi-revert` - Multiple commit reversion
|
||||||
|
|
||||||
|
## Challenge 1: Reverting a Regular Commit
|
||||||
|
|
||||||
|
### Scenario
|
||||||
|
|
||||||
|
You're working on a calculator application. A developer added a `divide` function that crashes when dividing by zero. The bug was discovered after subsequent commits were made, so you can't just delete it—you need to revert it while keeping the commits that came after.
|
||||||
|
|
||||||
|
### Your Task
|
||||||
|
|
||||||
|
1. **Navigate to the challenge directory:**
|
||||||
|
```pwsh
|
||||||
|
cd challenge
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check which branch you're on** (you should be on `regular-revert`):
|
||||||
|
```pwsh
|
||||||
|
git branch
|
||||||
|
```
|
||||||
|
The `*` should be next to `regular-revert`.
|
||||||
|
|
||||||
|
3. **View the commit history:**
|
||||||
|
```pwsh
|
||||||
|
git log --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Find the commit with message:** "Add broken divide function - needs to be reverted!"
|
||||||
|
- Note the commit hash (the 7-character code at the start, like `a1b2c3d`)
|
||||||
|
- Write it down or copy it
|
||||||
|
|
||||||
|
5. **Revert that specific commit** (replace `<commit-hash>` with the actual hash):
|
||||||
|
```pwsh
|
||||||
|
git revert <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Visual Studio Code will open** with the revert commit message:
|
||||||
|
- The default message is fine (it says "Revert 'Add broken divide function...'")
|
||||||
|
- Close the editor window to accept the commit message
|
||||||
|
- Git will create the revert commit
|
||||||
|
|
||||||
|
### What to Observe
|
||||||
|
|
||||||
|
After reverting, check your work:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# View the new revert commit in history
|
||||||
|
git log --oneline
|
||||||
|
|
||||||
|
# Check that divide.py file is gone (reverted)
|
||||||
|
ls
|
||||||
|
# You should see calculator.py but NOT divide.py
|
||||||
|
|
||||||
|
# Check that modulo function still exists in calculator.py (it came after the bad commit)
|
||||||
|
cat calculator.py
|
||||||
|
# You should see def modulo
|
||||||
|
|
||||||
|
# Check that multiply function still exists (it came before the bad commit)
|
||||||
|
# (You already see it when you cat the file above)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key insight:** Revert creates a NEW commit that undoes the changes from the target commit, but leaves all other commits intact.
|
||||||
|
|
||||||
|
### Understanding the Timeline
|
||||||
|
|
||||||
|
```
|
||||||
|
Before revert:
|
||||||
|
main.py (initial) → multiply (good) → divide (BAD) → modulo (good)
|
||||||
|
↑
|
||||||
|
We want to undo THIS
|
||||||
|
|
||||||
|
After revert:
|
||||||
|
main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → revert divide (new commit)
|
||||||
|
↑
|
||||||
|
Removes divide, keeps modulo
|
||||||
|
```
|
||||||
|
|
||||||
|
The revert commit adds a new point in history that undoes the divide changes.
|
||||||
|
|
||||||
|
## Challenge 2: Reverting Multiple Commits
|
||||||
|
|
||||||
|
### Scenario
|
||||||
|
|
||||||
|
Two separate commits added broken mathematical functions (`square_root` and `logarithm`). Both have critical bugs and need to be removed. You can revert multiple commits at once.
|
||||||
|
|
||||||
|
### Your Task
|
||||||
|
|
||||||
|
1. **Switch to the multi-revert branch:**
|
||||||
|
```pwsh
|
||||||
|
git switch multi-revert
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **View the commit history:**
|
||||||
|
```pwsh
|
||||||
|
git log --oneline
|
||||||
|
```
|
||||||
|
|
||||||
|
Find the two bad commits:
|
||||||
|
- "Add broken square_root - REVERT THIS!"
|
||||||
|
- "Add broken logarithm - REVERT THIS TOO!"
|
||||||
|
|
||||||
|
Note both commit hashes (write them down)
|
||||||
|
|
||||||
|
3. **Revert both commits in one command** (replace with actual hashes):
|
||||||
|
```pwsh
|
||||||
|
git revert <commit-hash-1> <commit-hash-2>
|
||||||
|
```
|
||||||
|
|
||||||
|
**Important:** List commits from **oldest to newest** for cleanest history (square_root first, then logarithm).
|
||||||
|
|
||||||
|
**Alternatively**, revert them one at a time:
|
||||||
|
```pwsh
|
||||||
|
git revert <commit-hash-1>
|
||||||
|
git revert <commit-hash-2>
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Visual Studio Code will open TWICE** (once for each revert):
|
||||||
|
- Close the editor each time to accept the default commit message
|
||||||
|
- Git will create two revert commits
|
||||||
|
|
||||||
|
5. **Verify the result:**
|
||||||
|
```pwsh
|
||||||
|
# View files - sqrt.py and logarithm.py should be gone
|
||||||
|
ls
|
||||||
|
# You should see calculator.py but NOT sqrt.py or logarithm.py
|
||||||
|
|
||||||
|
# Check that good functions remain in calculator.py
|
||||||
|
cat calculator.py
|
||||||
|
# You should see def power and def absolute
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multi-Revert Strategies
|
||||||
|
|
||||||
|
**Reverting a range of commits:**
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Revert commits from A to B (inclusive)
|
||||||
|
git revert A^..B
|
||||||
|
|
||||||
|
# Example: Revert last 3 commits
|
||||||
|
git revert HEAD~3..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
**Reverting without auto-commit:**
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Stage revert changes without committing
|
||||||
|
git revert --no-commit <commit-hash>
|
||||||
|
|
||||||
|
# Review changes
|
||||||
|
git diff --staged
|
||||||
|
|
||||||
|
# Commit when ready
|
||||||
|
git commit
|
||||||
|
```
|
||||||
|
|
||||||
|
This is useful when reverting multiple commits and you want one combined revert commit.
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
After completing both challenges, verify your solutions:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
cd .. # Return to module directory (if you're in challenge/)
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from inside the challenge directory:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
..\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The script checks that:
|
||||||
|
- ✅ Revert commits were created (not destructive deletion)
|
||||||
|
- ✅ Bad code is removed
|
||||||
|
- ✅ Good code before and after is preserved
|
||||||
|
|
||||||
|
## Command Reference
|
||||||
|
|
||||||
|
### Basic Revert
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Revert a specific commit
|
||||||
|
git revert <commit-hash>
|
||||||
|
|
||||||
|
# Revert the most recent commit
|
||||||
|
git revert HEAD
|
||||||
|
|
||||||
|
# Revert the second-to-last commit
|
||||||
|
git revert HEAD~1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Reverting Old Commits
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Revert a specific commit from any point in history
|
||||||
|
git revert <commit-hash>
|
||||||
|
|
||||||
|
# Revert a commit from 5 commits ago
|
||||||
|
git revert HEAD~5
|
||||||
|
|
||||||
|
# View what a commit changed before reverting
|
||||||
|
git show <commit-hash>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Multiple Commits
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Revert multiple specific commits
|
||||||
|
git revert <hash1> <hash2> <hash3>
|
||||||
|
|
||||||
|
# Revert a range of commits (oldest^..newest)
|
||||||
|
git revert <oldest-hash>^..<newest-hash>
|
||||||
|
|
||||||
|
# Revert last 3 commits
|
||||||
|
git revert HEAD~3..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
### Revert Options
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Revert but don't commit automatically
|
||||||
|
git revert --no-commit <commit-hash>
|
||||||
|
|
||||||
|
# Revert and edit the commit message
|
||||||
|
git revert --edit <commit-hash>
|
||||||
|
|
||||||
|
# Revert without opening editor (use default message)
|
||||||
|
git revert --no-edit <commit-hash>
|
||||||
|
|
||||||
|
# Abort a revert in progress (if conflicts)
|
||||||
|
git revert --abort
|
||||||
|
|
||||||
|
# Continue revert after resolving conflicts
|
||||||
|
git revert --continue
|
||||||
|
```
|
||||||
|
|
||||||
|
## When to Use Git Revert
|
||||||
|
|
||||||
|
Use `git revert` when:
|
||||||
|
|
||||||
|
- ✅ **Commits are already pushed** - Safe for shared history
|
||||||
|
- ✅ **Working in a team** - Transparent to everyone
|
||||||
|
- ✅ **Need audit trail** - Shows what was undone and why
|
||||||
|
- ✅ **Public repositories** - Can't rewrite public history
|
||||||
|
- ✅ **Undoing old commits** - Can revert commits from weeks ago
|
||||||
|
- ✅ **Production hotfixes** - Safe emergency rollback
|
||||||
|
|
||||||
|
**Golden Rule:** If others might have your commits, use revert.
|
||||||
|
|
||||||
|
## When NOT to Use Git Revert
|
||||||
|
|
||||||
|
Consider alternatives when:
|
||||||
|
|
||||||
|
- ❌ **Commits are still local** - Use `git reset` instead (advanced module)
|
||||||
|
- ❌ **Just want to edit a commit** - Use `git commit --amend`
|
||||||
|
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup, but more dangerous, stick to revert if in doubt
|
||||||
|
- ❌ **Need to combine commits** - Use interactive rebase IF nothing has been pushed to cloud
|
||||||
|
- ❌ **Reverting creates complex conflicts** - Might need manual fix forward
|
||||||
|
|
||||||
|
## Revert vs. Reset vs. Rebase
|
||||||
|
|
||||||
|
| Command | History | Safety | Use Case |
|
||||||
|
|---------|---------|--------|----------|
|
||||||
|
| **revert** | Preserves | ✅ Safe | Undo pushed commits |
|
||||||
|
| **reset** | Erases | ⚠️ Dangerous | Clean up local commits |
|
||||||
|
| **rebase** | Rewrites | ⚠️ Dangerous | Polish commit history |
|
||||||
|
|
||||||
|
**This module teaches revert.** You'll learn reset in Module 06.
|
||||||
|
|
||||||
|
## Handling Revert Conflicts
|
||||||
|
|
||||||
|
Sometimes reverting causes conflicts if subsequent changes touched the same code:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Start revert
|
||||||
|
git revert <commit-hash>
|
||||||
|
|
||||||
|
# If conflicts occur:
|
||||||
|
# Conflict in calculator.py
|
||||||
|
# CONFLICT (content): Merge conflict in calculator.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**To resolve:**
|
||||||
|
|
||||||
|
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
|
||||||
|
2. Stage resolved files:
|
||||||
|
```pwsh
|
||||||
|
git add <resolved-files>
|
||||||
|
```
|
||||||
|
3. Continue the revert:
|
||||||
|
```pwsh
|
||||||
|
git revert --continue
|
||||||
|
```
|
||||||
|
|
||||||
|
Or abort if you change your mind:
|
||||||
|
```pwsh
|
||||||
|
git revert --abort
|
||||||
|
```
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
### 1. Using Reset on Pushed Commits
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# ❌ NEVER do this with pushed commits. Or at least try your best to avoid it.
|
||||||
|
git reset --hard HEAD~3
|
||||||
|
|
||||||
|
# ✅ Do this instead
|
||||||
|
git revert HEAD~3..HEAD
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Reverting Commits in Wrong Order
|
||||||
|
|
||||||
|
When reverting multiple related commits, revert from newest to oldest:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# If you have: A → B → C (and C depends on B)
|
||||||
|
|
||||||
|
# ✅ Correct order
|
||||||
|
git revert C
|
||||||
|
git revert B
|
||||||
|
|
||||||
|
# ❌ Wrong order (may cause conflicts)
|
||||||
|
git revert B # Conflict! C still references B
|
||||||
|
git revert C
|
||||||
|
```
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Write clear revert messages:**
|
||||||
|
```pwsh
|
||||||
|
git revert <hash> -m "Revert authentication - security issue #1234"
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Link to issue tracking:**
|
||||||
|
```
|
||||||
|
Revert "Add new payment system"
|
||||||
|
|
||||||
|
This reverts commit abc123.
|
||||||
|
|
||||||
|
Critical bug in payment processing.
|
||||||
|
See bug tracker: ISSUE-1234
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Test after reverting:**
|
||||||
|
- Run your test suite
|
||||||
|
- Verify the application still works
|
||||||
|
- Check no unintended changes occurred
|
||||||
|
|
||||||
|
4. **Communicate with team:**
|
||||||
|
- Announce reverts in team chat
|
||||||
|
- Explain why the revert was necessary
|
||||||
|
- Provide timeline for re-introducing the feature
|
||||||
|
|
||||||
|
5. **Keep reverts focused:**
|
||||||
|
- Revert the minimum necessary
|
||||||
|
- Don't bundle multiple unrelated reverts
|
||||||
|
- One problem = one revert commit
|
||||||
24
01-essentials/06-revert/reset.ps1
Normal file
24
01-essentials/06-revert/reset.ps1
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/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 Revert 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"
|
||||||
199
01-essentials/06-revert/setup.ps1
Normal file
199
01-essentials/06-revert/setup.ps1
Normal file
@@ -0,0 +1,199 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets up the Module 06 challenge environment for learning git revert.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script creates a challenge directory with two branches demonstrating
|
||||||
|
different revert scenarios:
|
||||||
|
- regular-revert: Basic revert of a single bad commit
|
||||||
|
- multi-revert: Reverting multiple commits at once
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Setting up Module 06: Git Revert 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"
|
||||||
|
|
||||||
|
# Detect the default branch name after first commit (created below)
|
||||||
|
# Will be detected after the initial commit in SCENARIO 1
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SCENARIO 1: Regular Revert (Basic)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "`nScenario 1: Creating regular-revert branch..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Initial commit
|
||||||
|
$calcContent = @"
|
||||||
|
# calculator.py - Simple calculator
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
"""Add two numbers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def subtract(a, b):
|
||||||
|
"""Subtract b from a."""
|
||||||
|
return a - b
|
||||||
|
"@
|
||||||
|
Set-Content -Path "calculator.py" -Value $calcContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Initial calculator implementation" | Out-Null
|
||||||
|
|
||||||
|
# Detect the main branch name after first commit
|
||||||
|
$mainBranch = git branch --show-current
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Create regular-revert branch
|
||||||
|
git switch -c regular-revert | Out-Null
|
||||||
|
|
||||||
|
# Good commit: Add multiply using append
|
||||||
|
$multiplyFunc = @"
|
||||||
|
|
||||||
|
def multiply(a, b):
|
||||||
|
"""Multiply two numbers."""
|
||||||
|
return a * b
|
||||||
|
"@
|
||||||
|
Add-Content -Path "calculator.py" -Value $multiplyFunc
|
||||||
|
git add .
|
||||||
|
git commit -m "Add multiply function" | Out-Null
|
||||||
|
|
||||||
|
# BAD commit: Add broken divide function using separate file
|
||||||
|
$divideContent = @"
|
||||||
|
# divide.py - Division functionality
|
||||||
|
|
||||||
|
def divide(a, b):
|
||||||
|
"""Divide a by b - BROKEN: doesn't handle division by zero!"""
|
||||||
|
return a / b # This will crash if b is 0!
|
||||||
|
"@
|
||||||
|
Set-Content -Path "divide.py" -Value $divideContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add broken divide function - needs to be reverted!" | Out-Null
|
||||||
|
|
||||||
|
# Good commit: Add modulo (after bad commit) using append
|
||||||
|
$moduloFunc = @"
|
||||||
|
|
||||||
|
def modulo(a, b):
|
||||||
|
"""Return remainder of a divided by b."""
|
||||||
|
return a % b
|
||||||
|
"@
|
||||||
|
Add-Content -Path "calculator.py" -Value $moduloFunc
|
||||||
|
git add .
|
||||||
|
git commit -m "Add modulo function" | Out-Null
|
||||||
|
|
||||||
|
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SCENARIO 2: Multi Revert (Multiple Bad Commits)
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "`nScenario 2: Creating multi-revert branch..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Switch back to main
|
||||||
|
git switch $mainBranch | Out-Null
|
||||||
|
|
||||||
|
# Create multi-revert branch
|
||||||
|
git switch -c multi-revert | Out-Null
|
||||||
|
|
||||||
|
# Reset calculator to simple version
|
||||||
|
$calcContent = @"
|
||||||
|
# calculator.py - Simple calculator
|
||||||
|
|
||||||
|
def add(a, b):
|
||||||
|
"""Add two numbers."""
|
||||||
|
return a + b
|
||||||
|
|
||||||
|
def subtract(a, b):
|
||||||
|
"""Subtract b from a."""
|
||||||
|
return a - b
|
||||||
|
"@
|
||||||
|
Set-Content -Path "calculator.py" -Value $calcContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Reset to basic calculator" | Out-Null
|
||||||
|
|
||||||
|
# Good commit: Add power function using append
|
||||||
|
$powerFunc = @"
|
||||||
|
|
||||||
|
def power(a, b):
|
||||||
|
"""Raise a to the power of b."""
|
||||||
|
return a ** b
|
||||||
|
"@
|
||||||
|
Add-Content -Path "calculator.py" -Value $powerFunc
|
||||||
|
git add .
|
||||||
|
git commit -m "Add power function" | Out-Null
|
||||||
|
|
||||||
|
# BAD commit 1: Add broken square_root in separate file
|
||||||
|
$sqrtContent = @"
|
||||||
|
# sqrt.py - Square root functionality
|
||||||
|
|
||||||
|
def square_root(a):
|
||||||
|
"""BROKEN: Returns wrong result for negative numbers!"""
|
||||||
|
return a ** 0.5 # This returns NaN for negative numbers!
|
||||||
|
"@
|
||||||
|
Set-Content -Path "sqrt.py" -Value $sqrtContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add broken square_root - REVERT THIS!" | Out-Null
|
||||||
|
|
||||||
|
# BAD commit 2: Add broken logarithm in separate file
|
||||||
|
$logContent = @"
|
||||||
|
# logarithm.py - Logarithm functionality
|
||||||
|
|
||||||
|
def logarithm(a):
|
||||||
|
"""BROKEN: Doesn't handle zero or negative numbers!"""
|
||||||
|
import math
|
||||||
|
return math.log(a) # This crashes for a <= 0!
|
||||||
|
"@
|
||||||
|
Set-Content -Path "logarithm.py" -Value $logContent
|
||||||
|
git add .
|
||||||
|
git commit -m "Add broken logarithm - REVERT THIS TOO!" | Out-Null
|
||||||
|
|
||||||
|
# Good commit: Add absolute value (after bad commits) using append
|
||||||
|
$absoluteFunc = @"
|
||||||
|
|
||||||
|
def absolute(a):
|
||||||
|
"""Return absolute value of a."""
|
||||||
|
return abs(a)
|
||||||
|
"@
|
||||||
|
Add-Content -Path "calculator.py" -Value $absoluteFunc
|
||||||
|
git add .
|
||||||
|
git commit -m "Add absolute value function" | Out-Null
|
||||||
|
|
||||||
|
Write-Host "[CREATED] multi-revert branch with two bad commits to revert" -ForegroundColor Green
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Return to regular-revert to start
|
||||||
|
# ============================================================================
|
||||||
|
git switch regular-revert | Out-Null
|
||||||
|
|
||||||
|
# Return to module directory
|
||||||
|
Set-Location ..
|
||||||
|
|
||||||
|
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
|
||||||
|
Write-Host "`nTwo revert scenarios have been created:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. regular-revert - Revert a single bad commit" -ForegroundColor White
|
||||||
|
Write-Host " 2. multi-revert - Revert multiple bad commits" -ForegroundColor White
|
||||||
|
Write-Host "`nYou are currently on the 'regular-revert' branch." -ForegroundColor Cyan
|
||||||
|
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
||||||
|
Write-Host " 1. cd challenge" -ForegroundColor White
|
||||||
|
Write-Host " 2. Read the README.md for detailed instructions" -ForegroundColor White
|
||||||
|
Write-Host " 3. Complete each revert challenge" -ForegroundColor White
|
||||||
|
Write-Host " 4. Run '..\verify.ps1' to check your solutions" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
173
01-essentials/06-revert/verify.ps1
Normal file
173
01-essentials/06-revert/verify.ps1
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Verifies the Module 06 challenge solutions.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Checks that both revert scenarios have been completed correctly:
|
||||||
|
- regular-revert: Single commit reverted
|
||||||
|
- multi-revert: Multiple commits reverted
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$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: Regular Revert Verification
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "`n=== Scenario 1: Regular Revert ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
git switch regular-revert 2>&1 | Out-Null
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "[FAIL] regular-revert branch not found" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
} else {
|
||||||
|
# Check that a revert commit exists
|
||||||
|
$revertCommit = git log --oneline --grep="Revert" 2>$null
|
||||||
|
if ($revertCommit) {
|
||||||
|
Write-Host "[PASS] Revert commit found" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] No revert commit found" -ForegroundColor Red
|
||||||
|
Write-Host "[HINT] Use: git revert <commit-hash>" -ForegroundColor Yellow
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that divide.py is removed (was reverted)
|
||||||
|
if (-not (Test-Path "divide.py")) {
|
||||||
|
Write-Host "[PASS] Broken divide.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] divide.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
|
Write-Host "[HINT] The bad commit should be reverted, removing divide.py" -ForegroundColor Yellow
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that calculator.py exists and has correct content
|
||||||
|
if (Test-Path "calculator.py") {
|
||||||
|
$calcContent = Get-Content "calculator.py" -Raw
|
||||||
|
|
||||||
|
# Check that modulo function still exists (should be preserved)
|
||||||
|
if ($calcContent -match "def modulo") {
|
||||||
|
Write-Host "[PASS] modulo function preserved (good commit after bad one)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[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
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that multiply function exists (should be preserved)
|
||||||
|
if ($calcContent -match "def multiply") {
|
||||||
|
Write-Host "[PASS] multiply function preserved (good commit before bad one)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] multiply function missing" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] calculator.py not found" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# SCENARIO 2: Multi Revert Verification
|
||||||
|
# ============================================================================
|
||||||
|
Write-Host "`n=== Scenario 2: Multi Revert ===" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
git switch multi-revert 2>&1 | Out-Null
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -ne 0) {
|
||||||
|
Write-Host "[FAIL] multi-revert branch not found" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
} else {
|
||||||
|
# Count revert commits
|
||||||
|
$revertCommits = git log --oneline --grep="Revert" 2>$null
|
||||||
|
$revertCount = ($revertCommits | Measure-Object).Count
|
||||||
|
|
||||||
|
if ($revertCount -ge 2) {
|
||||||
|
Write-Host "[PASS] Found $revertCount revert commits (expected at least 2)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[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
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that sqrt.py is removed (reverted)
|
||||||
|
if (-not (Test-Path "sqrt.py")) {
|
||||||
|
Write-Host "[PASS] Broken sqrt.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] sqrt.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that logarithm.py is removed (reverted)
|
||||||
|
if (-not (Test-Path "logarithm.py")) {
|
||||||
|
Write-Host "[PASS] Broken logarithm.py successfully reverted (file removed)" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] logarithm.py still exists (should be reverted)" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check calculator.py content
|
||||||
|
if (Test-Path "calculator.py") {
|
||||||
|
$calcContent = Get-Content "calculator.py" -Raw
|
||||||
|
|
||||||
|
# Check that power function still exists (good commit before bad ones)
|
||||||
|
if ($calcContent -match "def power") {
|
||||||
|
Write-Host "[PASS] power function preserved" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] power function missing (should still exist)" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that absolute function still exists (good commit after bad ones)
|
||||||
|
if ($calcContent -match "def absolute") {
|
||||||
|
Write-Host "[PASS] absolute function preserved" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] absolute function missing (should still exist)" -ForegroundColor Red
|
||||||
|
$allChecksPassed = $false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host "[FAIL] calculator.py not found" -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 revert!" -ForegroundColor Cyan
|
||||||
|
Write-Host "You now understand:" -ForegroundColor Cyan
|
||||||
|
Write-Host " ✓ Reverting commits safely without erasing history" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
|
||||||
|
Write-Host " ✓ Preserving all other commits while undoing specific changes" -ForegroundColor White
|
||||||
|
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
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 ""
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
387
01-essentials/07-stash/README.md
Normal file
387
01-essentials/07-stash/README.md
Normal file
@@ -0,0 +1,387 @@
|
|||||||
|
# Module 07: Git Stash - Temporary Storage
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By the end of this module, you will:
|
||||||
|
- Understand what git stash is and when to use it
|
||||||
|
- Temporarily save work without committing
|
||||||
|
- Switch between branches without losing uncommitted changes
|
||||||
|
- Manage multiple stashes
|
||||||
|
- Apply and remove stashed changes
|
||||||
|
- Understand the difference between stash pop and stash apply
|
||||||
|
|
||||||
|
## Challenge Description
|
||||||
|
|
||||||
|
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
|
||||||
|
|
||||||
|
Your task is to:
|
||||||
|
1. Stash your incomplete work
|
||||||
|
2. Switch to the main branch
|
||||||
|
3. Fix the urgent bug and commit it
|
||||||
|
4. Return to your feature branch
|
||||||
|
5. Restore your stashed changes
|
||||||
|
6. Complete your feature and commit it
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### What is Git Stash?
|
||||||
|
|
||||||
|
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
|
||||||
|
|
||||||
|
### When to Use Stash
|
||||||
|
|
||||||
|
Use stash when you need to:
|
||||||
|
- Switch branches but have uncommitted changes
|
||||||
|
- Pull updates from remote but have local modifications
|
||||||
|
- Quickly test something on a clean working directory
|
||||||
|
- Save work temporarily without creating a commit
|
||||||
|
- Context-switch between tasks
|
||||||
|
|
||||||
|
### Stash vs Commit
|
||||||
|
|
||||||
|
**Stash:**
|
||||||
|
- Temporary storage
|
||||||
|
- Not part of project history
|
||||||
|
- Can be applied to different branches
|
||||||
|
- Easy to discard if not needed
|
||||||
|
- Local only (not pushed to remote)
|
||||||
|
|
||||||
|
**Commit:**
|
||||||
|
- Permanent part of history
|
||||||
|
- Creates a snapshot in the project timeline
|
||||||
|
- Associated with a specific branch
|
||||||
|
- Should be meaningful and complete
|
||||||
|
- Can be pushed to remote
|
||||||
|
|
||||||
|
### The Stash Stack
|
||||||
|
|
||||||
|
Git stash works like a stack (LIFO - Last In, First Out):
|
||||||
|
```
|
||||||
|
stash@{0} <- Most recent stash (top of stack)
|
||||||
|
stash@{1}
|
||||||
|
stash@{2} <- Oldest stash
|
||||||
|
```
|
||||||
|
|
||||||
|
You can have multiple stashes and apply any of them.
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
Run the setup script to create the challenge environment:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
.\setup.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a `challenge/` directory where you're working on a login feature with uncommitted changes, and a critical bug needs fixing on main.
|
||||||
|
|
||||||
|
## Your Task
|
||||||
|
|
||||||
|
### The Scenario
|
||||||
|
|
||||||
|
You're working on a login feature on the `feature-login` branch. Your work is incomplete (has TODOs), so it's not ready to commit.
|
||||||
|
|
||||||
|
Suddenly, your teammate reports a **critical security bug** in production! You need to:
|
||||||
|
1. Temporarily save your incomplete work
|
||||||
|
2. Switch to the main branch
|
||||||
|
3. Fix the urgent bug
|
||||||
|
4. Return to your feature and continue working
|
||||||
|
|
||||||
|
### Step-by-Step Instructions
|
||||||
|
|
||||||
|
1. **Navigate to the challenge directory:**
|
||||||
|
```pwsh
|
||||||
|
cd challenge
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Check your current status:**
|
||||||
|
```pwsh
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
You should see modified `login.py` (uncommitted changes)
|
||||||
|
|
||||||
|
3. **Stash your work with a message:**
|
||||||
|
```pwsh
|
||||||
|
git stash save "WIP: login feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Verify working directory is clean:**
|
||||||
|
```pwsh
|
||||||
|
git status
|
||||||
|
```
|
||||||
|
Should say "nothing to commit, working tree clean"
|
||||||
|
|
||||||
|
5. **Switch to main branch:**
|
||||||
|
```pwsh
|
||||||
|
git switch main
|
||||||
|
```
|
||||||
|
|
||||||
|
6. **Open app.py and find the bug:**
|
||||||
|
```pwsh
|
||||||
|
cat app.py
|
||||||
|
```
|
||||||
|
Look for the comment "# BUG: This allows unauthenticated access!"
|
||||||
|
|
||||||
|
7. **Fix the bug** by editing app.py:
|
||||||
|
- Remove the buggy comment line
|
||||||
|
- You can leave the implementation as-is or improve it
|
||||||
|
- The important thing is removing the comment that says "allows unauthenticated access"
|
||||||
|
|
||||||
|
8. **Commit the fix:**
|
||||||
|
```pwsh
|
||||||
|
git add app.py
|
||||||
|
git commit -m "Fix critical security bug"
|
||||||
|
```
|
||||||
|
|
||||||
|
9. **Switch back to your feature branch:**
|
||||||
|
```pwsh
|
||||||
|
git switch feature-login
|
||||||
|
```
|
||||||
|
|
||||||
|
10. **Restore your stashed work:**
|
||||||
|
```pwsh
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
This applies the stash and removes it from the stash stack
|
||||||
|
|
||||||
|
11. **Complete the TODOs in login.py:**
|
||||||
|
- Open login.py in your editor
|
||||||
|
- Complete the login method (verify password and return session)
|
||||||
|
- Add a logout method
|
||||||
|
- Remove all TODO comments
|
||||||
|
|
||||||
|
12. **Commit your completed feature:**
|
||||||
|
```pwsh
|
||||||
|
git add login.py
|
||||||
|
git commit -m "Complete login feature"
|
||||||
|
```
|
||||||
|
|
||||||
|
13. **Verify your solution:**
|
||||||
|
```pwsh
|
||||||
|
..\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Stash Commands
|
||||||
|
|
||||||
|
### Basic Operations
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Stash current changes with a message
|
||||||
|
git stash save "description"
|
||||||
|
|
||||||
|
# Stash without a message (not recommended)
|
||||||
|
git stash
|
||||||
|
|
||||||
|
# Stash including untracked files
|
||||||
|
git stash -u
|
||||||
|
|
||||||
|
# List all stashes
|
||||||
|
git stash list
|
||||||
|
|
||||||
|
# Show what's in the most recent stash
|
||||||
|
git stash show
|
||||||
|
|
||||||
|
# Show full diff of stash
|
||||||
|
git stash show -p
|
||||||
|
```
|
||||||
|
|
||||||
|
### Applying Stashes
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Apply most recent stash and remove it (RECOMMENDED)
|
||||||
|
git stash pop
|
||||||
|
|
||||||
|
# Apply most recent stash but keep it in stack
|
||||||
|
git stash apply
|
||||||
|
|
||||||
|
# Apply a specific stash
|
||||||
|
git stash apply stash@{1}
|
||||||
|
|
||||||
|
# Apply a specific stash by number
|
||||||
|
git stash apply 1
|
||||||
|
```
|
||||||
|
|
||||||
|
### Managing Stashes
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
# Drop (delete) the most recent stash
|
||||||
|
git stash drop
|
||||||
|
|
||||||
|
# Drop a specific stash
|
||||||
|
git stash drop stash@{1}
|
||||||
|
|
||||||
|
# Clear all stashes
|
||||||
|
git stash clear
|
||||||
|
|
||||||
|
# Create a new branch from a stash
|
||||||
|
git stash branch new-branch-name
|
||||||
|
```
|
||||||
|
|
||||||
|
## Understanding Stash vs Pop vs Apply
|
||||||
|
|
||||||
|
### Stash Pop (Recommended)
|
||||||
|
```pwsh
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
- Applies the stash to your working directory
|
||||||
|
- **Removes** the stash from the stack
|
||||||
|
- Use this most of the time
|
||||||
|
|
||||||
|
### Stash Apply (Keep Stash)
|
||||||
|
```pwsh
|
||||||
|
git stash apply
|
||||||
|
```
|
||||||
|
- Applies the stash to your working directory
|
||||||
|
- **Keeps** the stash in the stack
|
||||||
|
- Useful if you want to apply the same changes to multiple branches
|
||||||
|
|
||||||
|
### When to Use Which
|
||||||
|
|
||||||
|
**Use `pop` when:**
|
||||||
|
- You're done with the stash and won't need it again (99% of the time)
|
||||||
|
- You want to keep your stash list clean
|
||||||
|
|
||||||
|
**Use `apply` when:**
|
||||||
|
- You want to test the same changes on different branches
|
||||||
|
- You're not sure if you want to keep the stash yet
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Run the verification script to check your solution:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
..\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
Or from the module directory:
|
||||||
|
|
||||||
|
```pwsh
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The verification will check that:
|
||||||
|
- ✅ The bug fix commit exists on main
|
||||||
|
- ✅ Your feature is completed on the feature-login branch
|
||||||
|
- ✅ All TODOs are removed from login.py
|
||||||
|
- ✅ No uncommitted changes remain
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
### "Cannot switch branches - you have uncommitted changes"
|
||||||
|
|
||||||
|
**Problem:** Git won't let you switch branches with uncommitted changes.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```pwsh
|
||||||
|
# Stash your changes first
|
||||||
|
git stash save "work in progress"
|
||||||
|
|
||||||
|
# Now you can switch
|
||||||
|
git switch other-branch
|
||||||
|
|
||||||
|
# When you come back, restore your work
|
||||||
|
git switch original-branch
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
### "I don't remember what's in my stash"
|
||||||
|
|
||||||
|
**Problem:** You stashed something but forgot what it was.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
```pwsh
|
||||||
|
# List all stashes
|
||||||
|
git stash list
|
||||||
|
|
||||||
|
# Show summary of what changed
|
||||||
|
git stash show stash@{0}
|
||||||
|
|
||||||
|
# Show full diff
|
||||||
|
git stash show -p stash@{0}
|
||||||
|
```
|
||||||
|
|
||||||
|
### "Stash conflicts when I apply"
|
||||||
|
|
||||||
|
**Problem:** Applying a stash causes merge conflicts.
|
||||||
|
|
||||||
|
**Solution:**
|
||||||
|
1. Git marks conflicts in your files with `<<<<<<<` markers
|
||||||
|
2. Open the files and resolve conflicts manually
|
||||||
|
3. Stage the resolved files: `git add <file>`
|
||||||
|
4. If you used `pop`, the stash is automatically dropped
|
||||||
|
5. If you used `apply`, manually drop it: `git stash drop`
|
||||||
|
|
||||||
|
### "I accidentally cleared my stash"
|
||||||
|
|
||||||
|
**Problem:** You deleted a stash you still needed.
|
||||||
|
|
||||||
|
**Unfortunately:** Stashes are hard to recover once deleted. Lessons learned:
|
||||||
|
- Use `git stash pop` instead of `git stash drop` when you're unsure
|
||||||
|
- Use `git stash show -p` to preview before dropping
|
||||||
|
- Consider committing work instead of stashing for important changes
|
||||||
|
|
||||||
|
## Tips for Success
|
||||||
|
|
||||||
|
💡 **Always add a message** - `git stash save "your message"` helps you remember what you stashed
|
||||||
|
💡 **Use pop, not apply** - Pop removes the stash automatically, keeping your stash list clean
|
||||||
|
💡 **Stash before pulling** - Avoid merge conflicts when pulling updates
|
||||||
|
💡 **Preview before applying** - Use `git stash show -p` to see what's in a stash
|
||||||
|
💡 **Stashes are local** - They don't get pushed to remote repositories
|
||||||
|
💡 **Clean working directory** - Always verify with `git status` after stashing
|
||||||
|
|
||||||
|
## Common Use Cases
|
||||||
|
|
||||||
|
### Quick Branch Switch
|
||||||
|
```pwsh
|
||||||
|
# You're working on feature-A
|
||||||
|
git stash save "feature A progress"
|
||||||
|
git switch hotfix-branch
|
||||||
|
# Fix the issue, commit
|
||||||
|
git switch feature-A
|
||||||
|
git stash pop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pull with Local Changes
|
||||||
|
```pwsh
|
||||||
|
# You have uncommitted changes
|
||||||
|
git stash save "local changes"
|
||||||
|
git pull
|
||||||
|
git stash pop
|
||||||
|
# Resolve conflicts if any
|
||||||
|
```
|
||||||
|
|
||||||
|
### Test Clean State
|
||||||
|
```pwsh
|
||||||
|
# Stash changes to test on clean code
|
||||||
|
git stash save "testing clean state"
|
||||||
|
# Run tests
|
||||||
|
git stash pop # Restore your changes
|
||||||
|
```
|
||||||
|
|
||||||
|
## What You've Learned
|
||||||
|
|
||||||
|
After completing this module, you understand:
|
||||||
|
|
||||||
|
- ✅ Stash temporarily saves uncommitted changes
|
||||||
|
- ✅ Stash lets you switch contexts without committing
|
||||||
|
- ✅ `git stash pop` applies and removes the stash
|
||||||
|
- ✅ `git stash apply` applies but keeps the stash
|
||||||
|
- ✅ Stashes are local and not pushed to remote
|
||||||
|
- ✅ Stash is essential for handling interruptions and urgent fixes
|
||||||
|
|
||||||
|
**Key Takeaway:** Stash is your "temporary clipboard" for incomplete work. It helps you stay productive when you need to context-switch without making messy "WIP" commits.
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Ready to continue? You've now mastered the essential Git commands for daily development:
|
||||||
|
- Committing and history
|
||||||
|
- Branching and merging
|
||||||
|
- Cherry-picking specific changes
|
||||||
|
- Safely reverting commits
|
||||||
|
- Temporarily stashing work
|
||||||
|
|
||||||
|
Move on to **Module 08: Multiplayer Git** to practice collaborating with others!
|
||||||
|
|
||||||
|
To start over:
|
||||||
|
```pwsh
|
||||||
|
.\reset.ps1
|
||||||
|
```
|
||||||
@@ -25,6 +25,9 @@ git init | Out-Null
|
|||||||
git config user.name "Workshop User" | Out-Null
|
git config user.name "Workshop User" | Out-Null
|
||||||
git config user.email "user@workshop.local" | Out-Null
|
git config user.email "user@workshop.local" | Out-Null
|
||||||
|
|
||||||
|
# Detect the default branch name after first commit (created below)
|
||||||
|
# Will be detected after the initial commit
|
||||||
|
|
||||||
# Create initial application on main
|
# Create initial application on main
|
||||||
$app = @"
|
$app = @"
|
||||||
class Application:
|
class Application:
|
||||||
@@ -45,6 +48,14 @@ Set-Content -Path "app.py" -Value $app
|
|||||||
git add app.py
|
git add app.py
|
||||||
git commit -m "Initial application" | Out-Null
|
git commit -m "Initial application" | Out-Null
|
||||||
|
|
||||||
|
# Detect the main branch name after first commit
|
||||||
|
$mainBranch = git branch --show-current
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
Write-Host "Default branch detected: $mainBranch" -ForegroundColor Yellow
|
||||||
|
|
||||||
$readme = @"
|
$readme = @"
|
||||||
# MyApp
|
# MyApp
|
||||||
|
|
||||||
@@ -78,7 +89,7 @@ git add login.py
|
|||||||
git commit -m "Start login service implementation" | Out-Null
|
git commit -m "Start login service implementation" | Out-Null
|
||||||
|
|
||||||
# Add a critical bug to main branch (simulating a bug that was introduced)
|
# Add a critical bug to main branch (simulating a bug that was introduced)
|
||||||
git checkout main | Out-Null
|
git checkout $mainBranch | Out-Null
|
||||||
|
|
||||||
$appWithBug = @"
|
$appWithBug = @"
|
||||||
class Application:
|
class Application:
|
||||||
@@ -140,16 +151,17 @@ Write-Host "========================================" -ForegroundColor Green
|
|||||||
Write-Host "`nSituation:" -ForegroundColor Cyan
|
Write-Host "`nSituation:" -ForegroundColor Cyan
|
||||||
Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White
|
Write-Host "You're working on the login feature (feature-login branch)" -ForegroundColor White
|
||||||
Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow
|
Write-Host "You have uncommitted changes - the feature is NOT complete yet" -ForegroundColor Yellow
|
||||||
Write-Host "`nUrgent: A critical security bug was found in production (main branch)!" -ForegroundColor Red
|
Write-Host "`nUrgent: A critical security bug was found in production ($mainBranch branch)!" -ForegroundColor Red
|
||||||
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
|
Write-Host "You need to fix it immediately, but your current work isn't ready to commit." -ForegroundColor Red
|
||||||
|
Write-Host "`nDetected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
Write-Host "`nYour task:" -ForegroundColor Yellow
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
||||||
Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White
|
Write-Host "2. Check your status: git status (see uncommitted changes)" -ForegroundColor White
|
||||||
Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White
|
Write-Host "3. Stash your work: git stash save 'WIP: login feature'" -ForegroundColor White
|
||||||
Write-Host "4. Switch to main: git checkout main" -ForegroundColor White
|
Write-Host "4. Switch to ${mainBranch}: git switch $mainBranch" -ForegroundColor White
|
||||||
Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White
|
Write-Host "5. Fix the security bug in app.py (remove the comment and fix the auth)" -ForegroundColor White
|
||||||
Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White
|
Write-Host "6. Commit the fix: git add app.py && git commit -m 'Fix critical security bug'" -ForegroundColor White
|
||||||
Write-Host "7. Switch back: git checkout feature-login" -ForegroundColor White
|
Write-Host "7. Switch back: git switch feature-login" -ForegroundColor White
|
||||||
Write-Host "8. Restore your work: git stash pop" -ForegroundColor White
|
Write-Host "8. Restore your work: git stash pop" -ForegroundColor White
|
||||||
Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White
|
Write-Host "9. Complete the TODOs in login.py" -ForegroundColor White
|
||||||
Write-Host "10. Commit your completed feature" -ForegroundColor White
|
Write-Host "10. Commit your completed feature" -ForegroundColor White
|
||||||
@@ -32,6 +32,21 @@ if (-not (Test-Path ".git")) {
|
|||||||
exit 1
|
exit 1
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Detect the main branch name
|
||||||
|
$allBranches = git branch --list 2>$null | ForEach-Object { $_.Trim('* ') }
|
||||||
|
if ($allBranches -contains "main") {
|
||||||
|
$mainBranch = "main"
|
||||||
|
} elseif ($allBranches -contains "master") {
|
||||||
|
$mainBranch = "master"
|
||||||
|
} else {
|
||||||
|
$mainBranch = git config --get init.defaultBranch
|
||||||
|
if (-not $mainBranch) {
|
||||||
|
$mainBranch = $allBranches | Select-Object -First 1
|
||||||
|
if (-not $mainBranch) { $mainBranch = "main" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write-Host "Detected main branch: $mainBranch" -ForegroundColor Cyan
|
||||||
|
|
||||||
# Check current branch
|
# Check current branch
|
||||||
$currentBranch = git branch --show-current 2>$null
|
$currentBranch = git branch --show-current 2>$null
|
||||||
if ($currentBranch -ne "feature-login") {
|
if ($currentBranch -ne "feature-login") {
|
||||||
@@ -53,14 +68,22 @@ if ($status) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# Verify main branch has the security fix
|
# Verify main branch has the security fix
|
||||||
Write-Host "`nChecking main branch for bug fix..." -ForegroundColor Cyan
|
Write-Host "`nChecking $mainBranch branch for bug fix..." -ForegroundColor Cyan
|
||||||
git checkout main 2>$null | Out-Null
|
git checkout $mainBranch 2>$null | Out-Null
|
||||||
|
|
||||||
# Check for bug fix commit
|
# Check for bug fix commit
|
||||||
$mainCommits = git log --pretty=format:"%s" main 2>$null
|
$mainCommits = git log --pretty=format:"%s" $mainBranch 2>$null
|
||||||
if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
|
$hasSecurityFix = $false
|
||||||
Write-Host "[FAIL] No security bug fix commit found on main branch." -ForegroundColor Red
|
foreach ($commit in $mainCommits) {
|
||||||
Write-Host "Hint: After stashing, switch to main and commit a bug fix" -ForegroundColor Yellow
|
if ($commit -match "security|Fix.*bug") {
|
||||||
|
$hasSecurityFix = $true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $hasSecurityFix) {
|
||||||
|
Write-Host "[FAIL] No security bug fix commit found on $mainBranch branch." -ForegroundColor Red
|
||||||
|
Write-Host "Hint: After stashing, switch to $mainBranch and commit a bug fix" -ForegroundColor Yellow
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git checkout feature-login 2>$null | Out-Null
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -68,7 +91,7 @@ if ($mainCommits -notmatch "security bug|Fix.*bug|security fix") {
|
|||||||
|
|
||||||
# Check that app.py has been fixed
|
# Check that app.py has been fixed
|
||||||
if (-not (Test-Path "app.py")) {
|
if (-not (Test-Path "app.py")) {
|
||||||
Write-Host "[FAIL] app.py not found on main branch." -ForegroundColor Red
|
Write-Host "[FAIL] app.py not found on $mainBranch branch." -ForegroundColor Red
|
||||||
git checkout feature-login 2>$null | Out-Null
|
git checkout feature-login 2>$null | Out-Null
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
@@ -113,7 +136,7 @@ if (-not (Test-Path "login.py")) {
|
|||||||
$loginContent = Get-Content "login.py" -Raw
|
$loginContent = Get-Content "login.py" -Raw
|
||||||
|
|
||||||
# Check that login method exists and is implemented
|
# Check that login method exists and is implemented
|
||||||
if ($loginContent -notmatch "login\(username, password\)") {
|
if ($loginContent -notmatch "def login") {
|
||||||
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
|
Write-Host "[FAIL] login.py should have a login method." -ForegroundColor Red
|
||||||
Set-Location ..
|
Set-Location ..
|
||||||
exit 1
|
exit 1
|
||||||
239
01-essentials/08-multiplayer/01_FACILITATOR.md
Normal file
239
01-essentials/08-multiplayer/01_FACILITATOR.md
Normal file
@@ -0,0 +1,239 @@
|
|||||||
|
# Facilitator Setup Guide
|
||||||
|
|
||||||
|
This guide helps workshop facilitators set up the cloud-based multiplayer Git module using Azure DevOps.
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
The Number Challenge is a collaborative Git exercise where students work together on a shared repository hosted on **Azure DevOps**.
|
||||||
|
|
||||||
|
**What participants will do:**
|
||||||
|
- Clone a real repository from Azure DevOps
|
||||||
|
- Collaborate to sort numbers 0-20 into the correct order
|
||||||
|
- Experience push/pull workflow and merge conflicts
|
||||||
|
- Learn to communicate and coordinate with teammates
|
||||||
|
- Use SSH keys for secure authentication
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
### Azure DevOps Setup
|
||||||
|
|
||||||
|
You need:
|
||||||
|
- **Azure DevOps Organization** - Free tier is sufficient
|
||||||
|
- Sign up at [dev.azure.com](https://dev.azure.com)
|
||||||
|
- **Project created** within your organization
|
||||||
|
- **Admin access** to create repositories and manage users
|
||||||
|
|
||||||
|
### Workshop Materials
|
||||||
|
|
||||||
|
Participants need:
|
||||||
|
- Git installed (version 2.23+)
|
||||||
|
- VS Code (or any text editor)
|
||||||
|
- SSH keys configured
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pre-Workshop Setup
|
||||||
|
|
||||||
|
### Step 1: Add User Accounts
|
||||||
|
|
||||||
|
Add workshop participants to your Azure DevOps organization.
|
||||||
|
|
||||||
|
1. Navigate to **Organization Settings** → **Users**
|
||||||
|
2. Click **Add users**
|
||||||
|
3. Enter participant email addresses (Microsoft accounts)
|
||||||
|
4. Select your workshop project
|
||||||
|
5. Select **Access level**: Stakeholder (free) or Basic
|
||||||
|
6. Click **Add**
|
||||||
|
|
||||||
|
### Step 2: Create the Repository
|
||||||
|
|
||||||
|
Create the shared repository: **number-challenge**
|
||||||
|
|
||||||
|
1. Sign in to Azure DevOps at [dev.azure.com](https://dev.azure.com)
|
||||||
|
2. Navigate to your **Project**
|
||||||
|
3. Click **Repos** in the left navigation
|
||||||
|
4. Click the repo dropdown → **New repository**
|
||||||
|
5. Fill in details:
|
||||||
|
- **Name:** `number-challenge`
|
||||||
|
- **Add a README:** Checked
|
||||||
|
6. Click **Create**
|
||||||
|
|
||||||
|
### Step 3: Add the Starter File
|
||||||
|
|
||||||
|
Create `numbers.txt` with numbers 0-20 in random order.
|
||||||
|
|
||||||
|
**Option A: Via Azure DevOps web UI**
|
||||||
|
|
||||||
|
1. In your repository, click **+ New** → **File**
|
||||||
|
2. Name it `numbers.txt`
|
||||||
|
3. Add this content (numbers 0-20 shuffled):
|
||||||
|
|
||||||
|
```
|
||||||
|
17
|
||||||
|
3
|
||||||
|
12
|
||||||
|
8
|
||||||
|
19
|
||||||
|
1
|
||||||
|
14
|
||||||
|
6
|
||||||
|
11
|
||||||
|
0
|
||||||
|
20
|
||||||
|
9
|
||||||
|
4
|
||||||
|
16
|
||||||
|
2
|
||||||
|
18
|
||||||
|
7
|
||||||
|
13
|
||||||
|
5
|
||||||
|
15
|
||||||
|
10
|
||||||
|
```
|
||||||
|
|
||||||
|
4. Click **Commit**
|
||||||
|
|
||||||
|
**Option B: Via command line**
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone git@ssh.dev.azure.com:v3/{organization}/{project}/number-challenge
|
||||||
|
cd number-challenge
|
||||||
|
|
||||||
|
# Create numbers.txt with shuffled numbers
|
||||||
|
@"
|
||||||
|
17
|
||||||
|
3
|
||||||
|
12
|
||||||
|
8
|
||||||
|
19
|
||||||
|
1
|
||||||
|
14
|
||||||
|
6
|
||||||
|
11
|
||||||
|
0
|
||||||
|
20
|
||||||
|
9
|
||||||
|
4
|
||||||
|
16
|
||||||
|
2
|
||||||
|
18
|
||||||
|
7
|
||||||
|
13
|
||||||
|
5
|
||||||
|
15
|
||||||
|
10
|
||||||
|
"@ | Out-File -FilePath numbers.txt -Encoding UTF8
|
||||||
|
|
||||||
|
git add numbers.txt
|
||||||
|
git commit -m "feat: add shuffled numbers for challenge"
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4: Verify Student Access
|
||||||
|
|
||||||
|
Students added to the project automatically have access. Verify:
|
||||||
|
|
||||||
|
1. Go to **Project Settings** → **Repositories** → **number-challenge**
|
||||||
|
2. Click **Security** tab
|
||||||
|
3. Verify project team has **Contribute** permission
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## During the Workshop
|
||||||
|
|
||||||
|
### Getting Started
|
||||||
|
|
||||||
|
1. Ensure all students have cloned the repository
|
||||||
|
2. Have everyone open `numbers.txt` to see the shuffled numbers
|
||||||
|
3. Explain the goal: sort numbers 0-20 into correct order
|
||||||
|
|
||||||
|
### The Exercise Flow
|
||||||
|
|
||||||
|
1. **Students pull** the latest changes
|
||||||
|
2. **One person** moves a number to its correct position
|
||||||
|
3. **They commit and push**
|
||||||
|
4. **Others pull** and see the change
|
||||||
|
5. **Repeat** until sorted
|
||||||
|
|
||||||
|
### Creating Conflicts (The Learning Moment)
|
||||||
|
|
||||||
|
Conflicts happen naturally when multiple people edit at once. You can encourage this:
|
||||||
|
|
||||||
|
- Have two students deliberately edit at the same time
|
||||||
|
- Watch them experience the push rejection
|
||||||
|
- Guide them through pulling and resolving the conflict
|
||||||
|
|
||||||
|
### Monitoring Progress
|
||||||
|
|
||||||
|
Check progress in Azure DevOps:
|
||||||
|
|
||||||
|
- **Repos → Commits**: See who's contributing
|
||||||
|
- **Repos → Files → numbers.txt**: See current state
|
||||||
|
|
||||||
|
### Common Issues
|
||||||
|
|
||||||
|
**"I can't push!"**
|
||||||
|
- Did they pull first? Run `git pull`
|
||||||
|
- Is SSH set up? Check with `ssh -T git@ssh.dev.azure.com`
|
||||||
|
|
||||||
|
**"Merge conflict!"**
|
||||||
|
- Walk them through removing conflict markers
|
||||||
|
- Help them understand both sides of the conflict
|
||||||
|
|
||||||
|
**"Numbers are duplicated/missing!"**
|
||||||
|
- Someone resolved a conflict incorrectly
|
||||||
|
- Have the team review and fix together
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success
|
||||||
|
|
||||||
|
When complete, `numbers.txt` should contain:
|
||||||
|
|
||||||
|
```
|
||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
|
11
|
||||||
|
12
|
||||||
|
13
|
||||||
|
14
|
||||||
|
15
|
||||||
|
16
|
||||||
|
17
|
||||||
|
18
|
||||||
|
19
|
||||||
|
20
|
||||||
|
```
|
||||||
|
|
||||||
|
Celebrate the team's success!
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Post-Workshop Cleanup
|
||||||
|
|
||||||
|
To reuse the repository:
|
||||||
|
|
||||||
|
1. Reset `numbers.txt` to shuffled state
|
||||||
|
2. Or delete and recreate the repository
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- **Keep groups small** (2 people per repository) for more interaction
|
||||||
|
- **Encourage communication** - the exercise works best when people talk
|
||||||
|
- **Let conflicts happen** - they're the best learning opportunity
|
||||||
|
- **Walk the room** - help students who get stuck
|
||||||
|
- **Point students to 03_TASKS.md** - Simple explanations of clone, push, pull, and fetch for beginners
|
||||||
264
01-essentials/08-multiplayer/02_AZURE-DEVOPS-SSH-SETUP.md
Normal file
264
01-essentials/08-multiplayer/02_AZURE-DEVOPS-SSH-SETUP.md
Normal file
@@ -0,0 +1,264 @@
|
|||||||
|
# Azure DevOps SSH Setup - Best Practices Guide
|
||||||
|
|
||||||
|
This guide provides comprehensive instructions for setting up SSH authentication with Azure DevOps. SSH is the recommended authentication method for secure Git operations.
|
||||||
|
|
||||||
|
## Why SSH is Best Practice
|
||||||
|
|
||||||
|
SSH (Secure Shell) keys provide a secure way to authenticate with Azure DevOps without exposing passwords or tokens. Here's why SSH is the security best practice:
|
||||||
|
|
||||||
|
**Security Benefits:**
|
||||||
|
- **No Password Exposure**: Your credentials never travel over the network
|
||||||
|
- **Strong Encryption**: Uses RSA cryptographic algorithms
|
||||||
|
- **No Credential Prompts**: Seamless authentication after initial setup
|
||||||
|
- **Revocable**: Individual keys can be removed without changing passwords
|
||||||
|
- **Auditable**: Track which key was used for each operation
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
Before starting, ensure you have:
|
||||||
|
|
||||||
|
- **Git 2.23 or higher** installed
|
||||||
|
```powershell
|
||||||
|
git --version
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Azure DevOps account** with access to your organization/project
|
||||||
|
- If you don't have one, create a free account at [dev.azure.com](https://dev.azure.com)
|
||||||
|
|
||||||
|
- **PowerShell 7+ or Bash terminal** for running commands
|
||||||
|
```powershell
|
||||||
|
pwsh --version
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Generate SSH Key Pair
|
||||||
|
|
||||||
|
SSH authentication uses a key pair: a private key (stays on your computer) and a public key (uploaded to Azure DevOps).
|
||||||
|
|
||||||
|
### Generate RSA Key
|
||||||
|
|
||||||
|
Open your terminal and run:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
ssh-keygen -t rsa
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note about RSA:** Azure DevOps currently only supports RSA SSH keys. While newer algorithms like Ed25519 offer better security and performance, they are not yet supported by Azure DevOps. See the note at the end of this guide for more information.
|
||||||
|
|
||||||
|
### Save Location
|
||||||
|
|
||||||
|
When prompted for the file location, press `Enter` to accept the default:
|
||||||
|
|
||||||
|
```
|
||||||
|
Enter file in which to save the key (/Users/yourname/.ssh/id_rsa):
|
||||||
|
```
|
||||||
|
|
||||||
|
**Default locations:**
|
||||||
|
- **Windows**: `C:\Users\YourName\.ssh\id_rsa` and `C:\Users\YourName\.ssh\id_rsa.pub`
|
||||||
|
|
||||||
|
### Passphrase (Optional but Recommended)
|
||||||
|
|
||||||
|
You'll be prompted to enter a passphrase, just press `Enter` no password is needed:
|
||||||
|
|
||||||
|
```
|
||||||
|
Enter passphrase (empty for no passphrase):
|
||||||
|
Enter same passphrase again:
|
||||||
|
```
|
||||||
|
|
||||||
|
### Verify Key Generation
|
||||||
|
|
||||||
|
Check that your keys were created:
|
||||||
|
|
||||||
|
**Linux/Mac:**
|
||||||
|
**Windows PowerShell:**
|
||||||
|
```powershell
|
||||||
|
dir $HOME\.ssh\
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see two files:
|
||||||
|
- `id_rsa` - Private key (NEVER share this)
|
||||||
|
- `id_rsa.pub` - Public key (safe to share for upload to Azure DevOps)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Add SSH Public Key to Azure DevOps
|
||||||
|
|
||||||
|
Now you'll upload your public key to Azure DevOps.
|
||||||
|
|
||||||
|
### Navigate to SSH Public Keys Settings
|
||||||
|
|
||||||
|
1. Sign in to Azure DevOps at [https://dev.azure.com](https://dev.azure.com)
|
||||||
|
2. Click your **profile icon** in the top-right corner
|
||||||
|
3. Select **User settings** from the dropdown menu
|
||||||
|
4. Click **SSH Public Keys**
|
||||||
|
|
||||||
|

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

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

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

|
||||||
|
*Select SSH from the clone dialog to get your repository's SSH URL*
|
||||||
|
|
||||||
|
### Convert Existing HTTPS Repository to SSH
|
||||||
|
|
||||||
|
If you already cloned a repository using HTTPS, you can switch it to SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /path/to/your/repository
|
||||||
|
git remote set-url origin git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Verify the change:**
|
||||||
|
```bash
|
||||||
|
git remote -v
|
||||||
|
```
|
||||||
|
|
||||||
|
You should see SSH URLs:
|
||||||
|
```
|
||||||
|
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (fetch)
|
||||||
|
origin git@ssh.dev.azure.com:v3/myorg/git-workshop/great-print-project (push)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Daily Git Operations
|
||||||
|
|
||||||
|
All standard Git commands now work seamlessly with SSH:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Pull latest changes
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Push your commits
|
||||||
|
git push
|
||||||
|
|
||||||
|
# Fetch from remote
|
||||||
|
git fetch
|
||||||
|
|
||||||
|
# Push a new branch
|
||||||
|
git push -u origin feature-branch
|
||||||
|
```
|
||||||
|
|
||||||
|
**No more credential prompts!** SSH authentication happens automatically.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Resources
|
||||||
|
|
||||||
|
- **Azure DevOps SSH Documentation**: [https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate](https://docs.microsoft.com/en-us/azure/devops/repos/git/use-ssh-keys-to-authenticate)
|
||||||
|
- **SSH Key Best Practices**: [https://security.stackexchange.com/questions/tagged/ssh-keys](https://security.stackexchange.com/questions/tagged/ssh-keys)
|
||||||
|
- **Git with SSH**: [https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key](https://git-scm.com/book/en/v2/Git-on-the-Server-Generating-Your-SSH-Public-Key)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Common Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Generate RSA key
|
||||||
|
ssh-keygen -t
|
||||||
|
|
||||||
|
# Display public key (Linux/Mac)
|
||||||
|
cat ~/.ssh/id_rsa.pub
|
||||||
|
|
||||||
|
# Display public key (Windows)
|
||||||
|
type $HOME\.ssh\id_rsa.pub
|
||||||
|
|
||||||
|
# Test SSH connection
|
||||||
|
ssh -T git@ssh.dev.azure.com
|
||||||
|
|
||||||
|
# Clone with SSH
|
||||||
|
git clone git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
||||||
|
|
||||||
|
# Convert HTTPS to SSH
|
||||||
|
git remote set-url origin git@ssh.dev.azure.com:v3/{org}/{project}/{repo}
|
||||||
|
|
||||||
|
# Check remote URL
|
||||||
|
git remote -v
|
||||||
|
```
|
||||||
|
|
||||||
|
### SSH URL Format
|
||||||
|
|
||||||
|
```
|
||||||
|
git@ssh.dev.azure.com:v3/{organization}/{project}/{repository}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```
|
||||||
|
git@ssh.dev.azure.com:v3/mycompany/git-workshop/great-print-project
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**You're all set!** SSH authentication with RSA keys is now configured for secure, passwordless Git operations with Azure DevOps.
|
||||||
167
01-essentials/08-multiplayer/03_README.md
Normal file
167
01-essentials/08-multiplayer/03_README.md
Normal file
@@ -0,0 +1,167 @@
|
|||||||
|
# Multiplayer Git
|
||||||
|
|
||||||
|
Work with others using branches and pull requests.
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Learn to collaborate on a shared repository using:
|
||||||
|
- **Branches** - work independently without breaking main
|
||||||
|
- **Pull Requests** - review and merge changes safely
|
||||||
|
|
||||||
|
## The Workflow
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Create branch → 2. Make changes → 3. Push branch
|
||||||
|
↓
|
||||||
|
6. Delete branch ← 5. Merge PR ← 4. Create PR
|
||||||
|
```
|
||||||
|
|
||||||
|
This is how professional teams work together on code.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 1: Clone the Repository
|
||||||
|
|
||||||
|
Get the repository URL from your facilitator, then:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git clone <repository-url>
|
||||||
|
code <repository-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 2: Create a Branch
|
||||||
|
|
||||||
|
Never work directly on `main`. Create your own branch:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch -c <branch>
|
||||||
|
```
|
||||||
|
|
||||||
|
This creates a new branch and switches to it.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Make Changes
|
||||||
|
|
||||||
|
1. Open `numbers.txt` in VS Code
|
||||||
|
2. Move one number to its correct position
|
||||||
|
3. Save the file (`Ctrl+S`)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: Commit and Push
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: move 7 to correct position"
|
||||||
|
git push <branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
Your branch is now on Azure DevOps.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 5: Create a Pull Request
|
||||||
|
|
||||||
|
[Detailed guide](https://learn.microsoft.com/en-us/azure/devops/repos/git/pull-requests?view=azure-devops&tabs=browser#create-a-pull-request)
|
||||||
|
|
||||||
|
1. Go to Azure DevOps in your browser
|
||||||
|
2. Navigate to **Repos** → **Pull Requests** 3. Click **New Pull Request**
|
||||||
|
4. Set:
|
||||||
|
- **Source branch:** `<branch-name>`
|
||||||
|
- **Target branch:** `main`
|
||||||
|
5. Add a title describing your change
|
||||||
|
6. Click **Create**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Review and Merge
|
||||||
|
|
||||||
|
1. Review the changes shown in the PR (Person B)
|
||||||
|
2. If everything looks good, click **Complete**
|
||||||
|
3. Select **Complete merge**
|
||||||
|
4. Your changes are now in `main`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7: Update Your Local Main
|
||||||
|
|
||||||
|
After merging, update your local copy:
|
||||||
|
|
||||||
|
```powershell
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 8: Repeat
|
||||||
|
|
||||||
|
1. Create a new branch for your next change
|
||||||
|
2. Make changes, commit, push
|
||||||
|
3. Create another PR
|
||||||
|
4. Continue until all numbers are sorted
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 9: Create a merge conflict
|
||||||
|
|
||||||
|
1. Both people should create a branch with changes to `feature-1` and `feature-2`, you task is to change the position of number 5. Where you place it is up to you.
|
||||||
|
2. Now both people should push their respective branch `git push <the-branch>`
|
||||||
|
3. Now merge `feature-1` branch first, going throught the Pull Request flow.
|
||||||
|
4. Then merge `feature-2` branch second, and notice you'll get a MERGE CONFLICT.
|
||||||
|
5. It is not the owner of `feature-2` branch to resolve the conflict. This is done by merge the `main` branch into `feature-2` locally and so the owner of `feature-2` has to do the following
|
||||||
|
```pwsh
|
||||||
|
# First get the latest changes on main
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Then go back to the branch you can from
|
||||||
|
git switch feature-2
|
||||||
|
|
||||||
|
# Now we resolve the merge. We're merging the main branch INTO the feature-2 branch.
|
||||||
|
git merge main
|
||||||
|
# Resolve the merge conflict in numbers.txt
|
||||||
|
# Once resolved
|
||||||
|
git add numbers.txt
|
||||||
|
git commit
|
||||||
|
# VSCode will open up with a default message of "Merge main into feature-2"
|
||||||
|
# finish the commit. And push the changes
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
6. Now the owner of `feature-2` can checkout the pull request on azure again and see that the merge conflict has been resolved and can therefore "Complete" the merge request, using the button in the top right corner with the name "Complete"
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
| Command | What It Does |
|
||||||
|
|---------|--------------|
|
||||||
|
| `git switch -c <name>` | Create and switch to new branch |
|
||||||
|
| `git push -u origin <branch>` | Push branch to Azure DevOps |
|
||||||
|
| `git switch main` | Switch to main branch |
|
||||||
|
| `git pull` | Get latest changes from remote |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Issues
|
||||||
|
|
||||||
|
### "My PR has conflicts"
|
||||||
|
1. Update your branch with latest main:
|
||||||
|
```powershell
|
||||||
|
git switch main
|
||||||
|
git pull
|
||||||
|
git switch <branch-name>
|
||||||
|
git merge main
|
||||||
|
```
|
||||||
|
2. Resolve conflicts in VS Code
|
||||||
|
3. Commit and push again
|
||||||
|
|
||||||
|
### "I need to make more changes to my PR"
|
||||||
|
|
||||||
|
Just commit and push to the same branch - the PR updates automatically:
|
||||||
|
```powershell
|
||||||
|
git add .
|
||||||
|
git commit -m "fix: address review feedback"
|
||||||
|
git push
|
||||||
|
```
|
||||||
BIN
01-essentials/08-multiplayer/images/01_settings.png
Normal file
BIN
01-essentials/08-multiplayer/images/01_settings.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 33 KiB |
BIN
01-essentials/08-multiplayer/images/02_ssh_option.png
Normal file
BIN
01-essentials/08-multiplayer/images/02_ssh_option.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 91 KiB |
BIN
01-essentials/08-multiplayer/images/03_add_new_key.png
Normal file
BIN
01-essentials/08-multiplayer/images/03_add_new_key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 75 KiB |
BIN
01-essentials/08-multiplayer/images/04_copy_paste_key.png
Normal file
BIN
01-essentials/08-multiplayer/images/04_copy_paste_key.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 170 KiB |
11
01-essentials/08-multiplayer/numbers.txt
Normal file
11
01-essentials/08-multiplayer/numbers.txt
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
0
|
||||||
|
1
|
||||||
|
2
|
||||||
|
3
|
||||||
|
4
|
||||||
|
5
|
||||||
|
6
|
||||||
|
7
|
||||||
|
8
|
||||||
|
9
|
||||||
|
10
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
# Module 03: Branching Basics
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
In this module, you will:
|
|
||||||
- Understand what a branch is in Git
|
|
||||||
- Create new branches using `git branch` or `git switch -c`
|
|
||||||
- Switch between branches using `git switch`
|
|
||||||
- View all branches with `git branch`
|
|
||||||
- Understand that branches allow parallel development
|
|
||||||
|
|
||||||
## 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 some initial commits on the main branch.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
Your goal is to create a feature branch, make commits on it, and understand how branches work independently.
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. Create a new branch called `feature-login`
|
|
||||||
2. Switch to the new branch
|
|
||||||
3. Create a new file `login.py` with some login functionality
|
|
||||||
4. Commit the new file to your feature branch
|
|
||||||
5. Make another change to `login.py` and commit it
|
|
||||||
6. Switch back to the main branch and observe that `login.py` doesn't exist there
|
|
||||||
|
|
||||||
**Suggested Approach:**
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory: `cd challenge`
|
|
||||||
2. View existing branches: `git branch`
|
|
||||||
3. Create and switch to new branch: `git switch -c feature-login`
|
|
||||||
4. Create `login.py` with any content you like
|
|
||||||
5. Stage and commit: `git add login.py` and `git commit -m "Add login functionality"`
|
|
||||||
6. Modify `login.py`, then commit again
|
|
||||||
7. Switch back to main: `git switch main`
|
|
||||||
8. Run `ls` (or check your file explorer) and notice that `login.py` doesn't exist on main!
|
|
||||||
9. Switch back to feature-login: `git switch feature-login`
|
|
||||||
10. Run `ls` (or check your file explorer) again and see that `login.py` is back!
|
|
||||||
|
|
||||||
> **Important Notes:**
|
|
||||||
> - Use `git switch` to change branches
|
|
||||||
> - `git switch -c <name>` creates and switches in one command
|
|
||||||
> - Branches are independent - files in one branch don't affect another until you merge
|
|
||||||
> - You can switch between branches as many times as you want
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
- **Branch**: A lightweight movable pointer to a commit. Branches allow you to work on different features independently.
|
|
||||||
- **HEAD**: A pointer to the current branch you're working on. When you switch branches, HEAD moves.
|
|
||||||
- **main/master**: The default branch name in Git (main is the modern convention, master is older).
|
|
||||||
- **Feature Branch**: A branch created to develop a specific feature, separate from the main codebase.
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git branch # List all branches (* shows current)
|
|
||||||
git branch <name> # Create a new branch
|
|
||||||
git switch <branch> # Switch to an existing branch
|
|
||||||
git switch -c <name> # Create and switch to new branch
|
|
||||||
git switch - # Switch back to previous branch
|
|
||||||
git branch -d <name> # Delete a branch (we won't use this yet)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Once you've completed the challenge, verify your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification script will check that you've created the branch, made commits, and that the branches are independent.
|
|
||||||
|
|
||||||
## 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,26 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the Module 03 challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script removes the existing challenge directory and runs
|
|
||||||
the setup script again to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Resetting Module 03 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"
|
|
||||||
} else {
|
|
||||||
Write-Host "No existing challenge directory found." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "----------------------------------------" -ForegroundColor Cyan
|
|
||||||
Write-Host ""
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
& "$PSScriptRoot\setup.ps1"
|
|
||||||
@@ -1,85 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the Module 03 challenge environment for learning about branches.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script creates a challenge directory with a Git repository that
|
|
||||||
contains a couple of commits on the main branch. Students will create
|
|
||||||
a feature branch and make commits on it.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Setting up Module 03 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"
|
|
||||||
|
|
||||||
# Commit 1: Initial commit
|
|
||||||
Write-Host "Creating initial project..." -ForegroundColor Green
|
|
||||||
$mainContent = @"
|
|
||||||
# main.py - Main application file
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to the Application!")
|
|
||||||
print("This is the main branch")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "main.py" -Value $mainContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial commit" | Out-Null
|
|
||||||
|
|
||||||
# Commit 2: Add main functionality
|
|
||||||
Write-Host "Adding main functionality..." -ForegroundColor Green
|
|
||||||
$mainContent = @"
|
|
||||||
# main.py - Main application file
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to the Application!")
|
|
||||||
print("This is the main branch")
|
|
||||||
run_application()
|
|
||||||
|
|
||||||
def run_application():
|
|
||||||
print("Application is running...")
|
|
||||||
print("Ready for new features!")
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "main.py" -Value $mainContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add main functionality" | Out-Null
|
|
||||||
|
|
||||||
# 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 "`nNext steps:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
|
||||||
Write-Host " 2. Create a new branch: git switch -c feature-login" -ForegroundColor White
|
|
||||||
Write-Host " 3. Create login.py and commit it" -ForegroundColor White
|
|
||||||
Write-Host " 4. Make another commit on the feature branch" -ForegroundColor White
|
|
||||||
Write-Host " 5. Switch back to main: git switch main" -ForegroundColor White
|
|
||||||
Write-Host " 6. Observe that login.py doesn't exist on main!" -ForegroundColor White
|
|
||||||
Write-Host " 7. Run '..\verify.ps1' to check your solution" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the Module 03 challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script checks that:
|
|
||||||
- The challenge directory exists
|
|
||||||
- A Git repository exists
|
|
||||||
- The feature-login branch exists
|
|
||||||
- The branch has at least 2 new commits
|
|
||||||
- login.py exists in the feature branch but not in main
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 03 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
|
|
||||||
}
|
|
||||||
|
|
||||||
# Save current branch
|
|
||||||
$originalBranch = git branch --show-current 2>$null
|
|
||||||
|
|
||||||
# Check if feature-login branch exists
|
|
||||||
$branchExists = git branch --list "feature-login" 2>$null
|
|
||||||
if ($branchExists) {
|
|
||||||
Write-Host "[PASS] Branch 'feature-login' exists" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Branch 'feature-login' not found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Create the branch with: git switch -c feature-login" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if feature-login has commits beyond main
|
|
||||||
$commitCount = git rev-list main..feature-login --count 2>$null
|
|
||||||
if ($commitCount -ge 2) {
|
|
||||||
Write-Host "[PASS] Branch 'feature-login' has $commitCount new commits" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] Branch 'feature-login' needs at least 2 new commits (found: $commitCount)" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Make sure you've committed login.py and made at least one more commit" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Switch to feature-login and check for login.py
|
|
||||||
git checkout feature-login 2>$null | Out-Null
|
|
||||||
if (Test-Path "login.py") {
|
|
||||||
Write-Host "[PASS] File 'login.py' exists in feature-login branch" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] File 'login.py' not found in feature-login branch" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Create login.py and commit it to the feature-login branch" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Switch to main and verify login.py doesn't exist
|
|
||||||
git switch main 2>$null | Out-Null
|
|
||||||
if (-not (Test-Path "login.py")) {
|
|
||||||
Write-Host "[PASS] File 'login.py' does NOT exist in main branch (branches are independent!)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] File 'login.py' should not exist in main branch" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Make sure you created login.py only on the feature-login branch" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Switch back to original branch
|
|
||||||
if ($originalBranch) {
|
|
||||||
git switch $originalBranch 2>$null | Out-Null
|
|
||||||
}
|
|
||||||
|
|
||||||
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 successfully learned about Git branches!" -ForegroundColor Cyan
|
|
||||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
|
||||||
Write-Host " - How to create branches with git switch -c" -ForegroundColor White
|
|
||||||
Write-Host " - How to switch between branches" -ForegroundColor White
|
|
||||||
Write-Host " - That branches are independent lines of development" -ForegroundColor White
|
|
||||||
Write-Host " - That files in one branch don't affect another" -ForegroundColor White
|
|
||||||
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,229 +0,0 @@
|
|||||||
# Module 04: Merging Branches
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
In this module, you will:
|
|
||||||
- Understand what merging means in Git
|
|
||||||
- Merge a feature branch back into main
|
|
||||||
- Use `git merge` to combine branches
|
|
||||||
- Visualize merged branches with `git log --graph`
|
|
||||||
|
|
||||||
## 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 main branch and a feature branch.
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
You've been working on a new login feature in a separate branch. Now it's time to merge your work back into the main branch!
|
|
||||||
|
|
||||||
**Scenario:**
|
|
||||||
- You created a `feature-login` branch to add login functionality
|
|
||||||
- You made some commits on that branch
|
|
||||||
- Meanwhile, your teammate updated the README on main
|
|
||||||
- Now you need to bring your login feature back into main
|
|
||||||
|
|
||||||
**Steps:**
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory: `cd challenge`
|
|
||||||
2. Check which branch you're on: `git branch`
|
|
||||||
3. View all branches: `git log --oneline --graph --all`
|
|
||||||
4. Merge the feature-login branch into main: `git merge feature-login`
|
|
||||||
5. View the result: `git log --oneline --graph --all`
|
|
||||||
|
|
||||||
> **That's it!** Merging is how you bring work from one branch into another.
|
|
||||||
|
|
||||||
## What is Merging?
|
|
||||||
|
|
||||||
**Merging** is the process of taking changes from one branch and bringing them into another branch.
|
|
||||||
|
|
||||||
Think of it like combining two streams into one river - all the water (code) flows together.
|
|
||||||
|
|
||||||
### Before Merging
|
|
||||||
|
|
||||||
You have two branches with different work:
|
|
||||||
|
|
||||||
```
|
|
||||||
main: A---B---C
|
|
||||||
\
|
|
||||||
feature-login: D---E
|
|
||||||
```
|
|
||||||
|
|
||||||
- Main branch has commits A, B, and C
|
|
||||||
- Feature-login branch has commits D and E
|
|
||||||
- They split apart at commit B
|
|
||||||
|
|
||||||
### After Merging
|
|
||||||
|
|
||||||
You bring the feature branch into main:
|
|
||||||
|
|
||||||
```
|
|
||||||
main: A---B---C---M
|
|
||||||
\ /
|
|
||||||
feature-login: D-E
|
|
||||||
```
|
|
||||||
|
|
||||||
- Commit M is a **merge commit** - it combines both branches
|
|
||||||
- Main now has all the work from both branches
|
|
||||||
- Your login feature is now part of main!
|
|
||||||
|
|
||||||
## How to Merge
|
|
||||||
|
|
||||||
Merging is simple - just two steps:
|
|
||||||
|
|
||||||
**1. Switch to the branch you want to merge INTO**
|
|
||||||
```bash
|
|
||||||
git switch main
|
|
||||||
```
|
|
||||||
This is the branch that will receive the changes.
|
|
||||||
|
|
||||||
**2. Merge the other branch**
|
|
||||||
```bash
|
|
||||||
git merge feature-login
|
|
||||||
```
|
|
||||||
This brings changes from feature-login into main.
|
|
||||||
|
|
||||||
**That's it!** Git does the work of combining the changes.
|
|
||||||
|
|
||||||
## Understanding the Merge Commit
|
|
||||||
|
|
||||||
When you merge, Git creates a special commit called a **merge commit**.
|
|
||||||
|
|
||||||
**What makes it special?**
|
|
||||||
- It has TWO parent commits (one from each branch)
|
|
||||||
- It represents the point where branches came back together
|
|
||||||
- Git writes a message like "Merge branch 'feature-login'"
|
|
||||||
|
|
||||||
You can view the merge commit just like any other commit:
|
|
||||||
```bash
|
|
||||||
git show HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
## Visualizing Merges
|
|
||||||
|
|
||||||
Use `git log --graph` to see how branches merged:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --graph --all
|
|
||||||
```
|
|
||||||
|
|
||||||
**Example output:**
|
|
||||||
```
|
|
||||||
* a1b2c3d (HEAD -> main) Merge branch 'feature-login'
|
|
||||||
|\
|
|
||||||
| * e5f6g7h (feature-login) Implement login validation
|
|
||||||
| * h8i9j0k Add login form
|
|
||||||
* | k1l2m3n Update README with setup instructions
|
|
||||||
|/
|
|
||||||
* n4o5p6q Initial project structure
|
|
||||||
```
|
|
||||||
|
|
||||||
**Reading the graph:**
|
|
||||||
- `*` = A commit
|
|
||||||
- `|` = Branch line
|
|
||||||
- `/` and `\` = Branches splitting or joining
|
|
||||||
- `(HEAD -> main)` = You are here
|
|
||||||
|
|
||||||
The graph shows how the branches split and came back together!
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Merging
|
|
||||||
git merge <branch-name> # Merge a branch into current branch
|
|
||||||
|
|
||||||
# Viewing
|
|
||||||
git log --oneline --graph # See branch history visually
|
|
||||||
git log --oneline --graph --all # Include all branches
|
|
||||||
|
|
||||||
# Branch management
|
|
||||||
git branch # List branches
|
|
||||||
git switch <branch-name> # Switch to a branch
|
|
||||||
git branch -d <branch-name> # Delete a branch (after merging)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Common Questions
|
|
||||||
|
|
||||||
### "What if I'm on the wrong branch when I merge?"
|
|
||||||
|
|
||||||
Don't worry! The branch you're currently on is the one that receives the changes.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
git switch main # Go to main
|
|
||||||
git merge feature-login # Bring feature-login INTO main
|
|
||||||
```
|
|
||||||
|
|
||||||
Always switch to the destination branch first!
|
|
||||||
|
|
||||||
### "Can I undo a merge?"
|
|
||||||
|
|
||||||
Yes! Before you push to a remote:
|
|
||||||
```bash
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
This removes the merge commit. (We'll cover this more in later modules)
|
|
||||||
|
|
||||||
### "What happens to the feature branch after merging?"
|
|
||||||
|
|
||||||
The feature branch still exists! The merge just copies its commits into main.
|
|
||||||
|
|
||||||
You can delete it if you're done:
|
|
||||||
```bash
|
|
||||||
git branch -d feature-login # Safe delete (only if merged)
|
|
||||||
```
|
|
||||||
|
|
||||||
The commits are still in history - you're just removing the branch label.
|
|
||||||
|
|
||||||
### "What if Git can't merge automatically?"
|
|
||||||
|
|
||||||
Sometimes Git needs your help when the same lines were changed in both branches. This is called a **merge conflict**.
|
|
||||||
|
|
||||||
Don't worry - we'll learn how to handle conflicts in the next module!
|
|
||||||
|
|
||||||
If you encounter a conflict now and want to cancel:
|
|
||||||
```bash
|
|
||||||
git merge --abort
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Once you've merged the feature-login branch, verify your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification script will check that you've successfully merged.
|
|
||||||
|
|
||||||
## Need to Start Over?
|
|
||||||
|
|
||||||
If you want to reset the challenge and start fresh:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
## What's Next?
|
|
||||||
|
|
||||||
**Next module:** Merge Conflicts - Learn what to do when Git can't automatically merge changes.
|
|
||||||
|
|
||||||
**Later:** In the advanced modules, you'll learn about different merging strategies and when to use them. For now, understanding basic merging is all you need!
|
|
||||||
|
|
||||||
## Quick Summary
|
|
||||||
|
|
||||||
✅ **Merging** combines work from one branch into another
|
|
||||||
✅ Switch to the destination branch, then run `git merge <source-branch>`
|
|
||||||
✅ Git creates a **merge commit** to record the merge
|
|
||||||
✅ Use `git log --graph` to visualize how branches merged
|
|
||||||
✅ The feature branch still exists after merging - you can delete it if you want
|
|
||||||
|
|
||||||
That's all there is to basic merging! 🎉
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the Module 04 challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script removes the existing challenge directory and runs
|
|
||||||
the setup script again to create a fresh challenge environment.
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Resetting Module 04 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"
|
|
||||||
} else {
|
|
||||||
Write-Host "No existing challenge directory found." -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host ""
|
|
||||||
Write-Host "----------------------------------------" -ForegroundColor Cyan
|
|
||||||
Write-Host ""
|
|
||||||
|
|
||||||
# Run setup script
|
|
||||||
& "$PSScriptRoot\setup.ps1"
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the Module 04 challenge environment for learning about merging.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script creates a challenge directory with a Git repository that
|
|
||||||
contains two divergent branches ready to merge (three-way merge scenario).
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Setting up Module 04 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"
|
|
||||||
|
|
||||||
# Commit 1: Initial project structure on main
|
|
||||||
Write-Host "Creating initial project structure..." -ForegroundColor Green
|
|
||||||
$readmeContent = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A simple web application project.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
Coming soon...
|
|
||||||
"@
|
|
||||||
Set-Content -Path "README.md" -Value $readmeContent
|
|
||||||
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
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 configuration file on main
|
|
||||||
Write-Host "Adding configuration..." -ForegroundColor Green
|
|
||||||
$configContent = @"
|
|
||||||
# config.py - Configuration settings
|
|
||||||
|
|
||||||
APP_NAME = "My Project"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
DEBUG = False
|
|
||||||
"@
|
|
||||||
Set-Content -Path "config.py" -Value $configContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add basic configuration" | Out-Null
|
|
||||||
|
|
||||||
# Commit 3: Add database utilities on main
|
|
||||||
Write-Host "Adding database utilities..." -ForegroundColor Green
|
|
||||||
$dbContent = @"
|
|
||||||
# database.py - Database utilities
|
|
||||||
|
|
||||||
def connect():
|
|
||||||
"""Connect to database."""
|
|
||||||
print("Connecting to database...")
|
|
||||||
return True
|
|
||||||
|
|
||||||
def disconnect():
|
|
||||||
"""Disconnect from database."""
|
|
||||||
print("Disconnecting...")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "database.py" -Value $dbContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add database utilities" | Out-Null
|
|
||||||
|
|
||||||
# Create feature-login branch
|
|
||||||
Write-Host "Creating feature-login branch..." -ForegroundColor Green
|
|
||||||
git switch -c feature-login | Out-Null
|
|
||||||
|
|
||||||
# Commit 4: Add login module on feature-login
|
|
||||||
Write-Host "Adding login functionality on feature-login..." -ForegroundColor Green
|
|
||||||
$loginContent = @"
|
|
||||||
# login.py - User login module
|
|
||||||
|
|
||||||
def login(username, password):
|
|
||||||
"""Authenticate a user."""
|
|
||||||
print(f"Authenticating user: {username}")
|
|
||||||
# TODO: Add actual authentication logic
|
|
||||||
return True
|
|
||||||
|
|
||||||
def logout(username):
|
|
||||||
"""Log out a user."""
|
|
||||||
print(f"Logging out user: {username}")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "login.py" -Value $loginContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add login module" | Out-Null
|
|
||||||
|
|
||||||
# Commit 5: Add password validation on feature-login
|
|
||||||
$loginContent = @"
|
|
||||||
# login.py - User login module
|
|
||||||
|
|
||||||
def validate_password(password):
|
|
||||||
"""Validate password strength."""
|
|
||||||
if len(password) < 8:
|
|
||||||
return False
|
|
||||||
return True
|
|
||||||
|
|
||||||
def login(username, password):
|
|
||||||
"""Authenticate a user."""
|
|
||||||
if not validate_password(password):
|
|
||||||
print("Password too weak!")
|
|
||||||
return False
|
|
||||||
print(f"Authenticating user: {username}")
|
|
||||||
# TODO: Add actual authentication logic
|
|
||||||
return True
|
|
||||||
|
|
||||||
def logout(username):
|
|
||||||
"""Log out a user."""
|
|
||||||
print(f"Logging out user: {username}")
|
|
||||||
return True
|
|
||||||
"@
|
|
||||||
Set-Content -Path "login.py" -Value $loginContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add password validation" | Out-Null
|
|
||||||
|
|
||||||
# Commit 6: Integrate login with app on feature-login
|
|
||||||
$appContent = @"
|
|
||||||
# app.py - Main application file
|
|
||||||
from login import login, logout
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("Welcome to My App!")
|
|
||||||
# Add login integration
|
|
||||||
if login("testuser", "password123"):
|
|
||||||
print("Login successful!")
|
|
||||||
pass
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
"@
|
|
||||||
Set-Content -Path "app.py" -Value $appContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Integrate login with main app" | Out-Null
|
|
||||||
|
|
||||||
# Commit 7: Add session management on feature-login
|
|
||||||
$sessionContent = @"
|
|
||||||
# session.py - Session management
|
|
||||||
|
|
||||||
class Session:
|
|
||||||
def __init__(self, username):
|
|
||||||
self.username = username
|
|
||||||
self.active = True
|
|
||||||
|
|
||||||
def end(self):
|
|
||||||
"""End the session."""
|
|
||||||
self.active = False
|
|
||||||
print(f"Session ended for {self.username}")
|
|
||||||
"@
|
|
||||||
Set-Content -Path "session.py" -Value $sessionContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add session management" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main branch
|
|
||||||
Write-Host "Switching back to main branch..." -ForegroundColor Green
|
|
||||||
git switch main | Out-Null
|
|
||||||
|
|
||||||
# Commit 8: Update README on main (creates divergence)
|
|
||||||
Write-Host "Adding documentation on main (creates divergence)..." -ForegroundColor Green
|
|
||||||
$readmeContent = @"
|
|
||||||
# My Project
|
|
||||||
|
|
||||||
A simple web application project.
|
|
||||||
|
|
||||||
## Setup
|
|
||||||
|
|
||||||
1. Install Python 3.8 or higher
|
|
||||||
2. Run: python app.py
|
|
||||||
|
|
||||||
## Features
|
|
||||||
|
|
||||||
- User authentication (coming soon)
|
|
||||||
- Data management (coming soon)
|
|
||||||
|
|
||||||
## Contributing
|
|
||||||
|
|
||||||
Please follow our coding standards when contributing.
|
|
||||||
"@
|
|
||||||
Set-Content -Path "README.md" -Value $readmeContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Update README with setup instructions" | Out-Null
|
|
||||||
|
|
||||||
# Commit 9: Update configuration on main
|
|
||||||
$configContent = @"
|
|
||||||
# config.py - Configuration settings
|
|
||||||
|
|
||||||
APP_NAME = "My Project"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
DEBUG = False
|
|
||||||
DATABASE_PATH = "./data/app.db"
|
|
||||||
LOG_LEVEL = "INFO"
|
|
||||||
"@
|
|
||||||
Set-Content -Path "config.py" -Value $configContent
|
|
||||||
|
|
||||||
git add .
|
|
||||||
git commit -m "Add database path to config" | 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 "`nScenario: You have two divergent branches!" -ForegroundColor Yellow
|
|
||||||
Write-Host " - main: Has 5 commits (config, database utils, README updates)" -ForegroundColor White
|
|
||||||
Write-Host " - feature-login: Has 4 commits (login, validation, session)" -ForegroundColor White
|
|
||||||
Write-Host "`nNext steps:" -ForegroundColor Cyan
|
|
||||||
Write-Host " 1. cd challenge" -ForegroundColor White
|
|
||||||
Write-Host " 2. View the branch structure: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host " 3. Merge feature-login into main: git merge feature-login" -ForegroundColor White
|
|
||||||
Write-Host " 4. View the merge result: git log --oneline --graph --all" -ForegroundColor White
|
|
||||||
Write-Host " 5. Run '..\verify.ps1' to check your solution" -ForegroundColor White
|
|
||||||
Write-Host ""
|
|
||||||
@@ -1,122 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the Module 04 challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
This script checks that:
|
|
||||||
- The challenge directory exists
|
|
||||||
- A Git repository exists
|
|
||||||
- Currently on main branch
|
|
||||||
- feature-login has been merged into main
|
|
||||||
- A merge commit exists (three-way merge)
|
|
||||||
- Login functionality is present on main
|
|
||||||
#>
|
|
||||||
|
|
||||||
Write-Host "`n=== Verifying Module 04 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 if feature-login branch exists
|
|
||||||
$featureLoginBranch = git branch --list "feature-login" 2>$null
|
|
||||||
if ($featureLoginBranch) {
|
|
||||||
Write-Host "[PASS] Branch 'feature-login' exists" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[INFO] Branch 'feature-login' not found (may have been deleted after merge)" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if login.py exists (should be on main after merge)
|
|
||||||
if (Test-Path "login.py") {
|
|
||||||
Write-Host "[PASS] File 'login.py' exists on main (from feature-login merge)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] File 'login.py' not found on main" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] This file should appear after merging feature-login into main" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if app.py contains login integration
|
|
||||||
if (Test-Path "app.py") {
|
|
||||||
$appContent = Get-Content "app.py" -Raw
|
|
||||||
if ($appContent -match "login") {
|
|
||||||
Write-Host "[PASS] app.py contains login integration" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] app.py doesn't contain login integration" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] After merging, app.py should import and use the login module" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] app.py not found" -ForegroundColor Red
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for merge commit (indicates three-way merge happened)
|
|
||||||
$mergeCommits = git log --merges --oneline 2>$null
|
|
||||||
if ($mergeCommits) {
|
|
||||||
Write-Host "[PASS] Merge commit found (three-way merge completed)" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Get the merge commit message
|
|
||||||
$mergeMessage = git log --merges --format=%s -1 2>$null
|
|
||||||
Write-Host "[INFO] Merge commit message: '$mergeMessage'" -ForegroundColor Cyan
|
|
||||||
} else {
|
|
||||||
Write-Host "[FAIL] No merge commit found" -ForegroundColor Red
|
|
||||||
Write-Host "[HINT] Merge feature-login into main with: git merge feature-login" -ForegroundColor Yellow
|
|
||||||
$allChecksPassed = $false
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if both branches contributed commits (true three-way merge)
|
|
||||||
$totalCommits = git rev-list --count HEAD 2>$null
|
|
||||||
if ($totalCommits -ge 4) {
|
|
||||||
Write-Host "[PASS] Repository has $totalCommits commits (branches diverged properly)" -ForegroundColor Green
|
|
||||||
} else {
|
|
||||||
Write-Host "[WARN] Repository has only $totalCommits commits (expected at least 4)" -ForegroundColor Yellow
|
|
||||||
Write-Host "[INFO] This might still be correct if you deleted commits" -ForegroundColor Cyan
|
|
||||||
}
|
|
||||||
|
|
||||||
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 successfully completed your first branch merge!" -ForegroundColor Cyan
|
|
||||||
Write-Host "You now understand:" -ForegroundColor Cyan
|
|
||||||
Write-Host " - How to merge branches with git merge" -ForegroundColor White
|
|
||||||
Write-Host " - What a merge commit is and why it's created" -ForegroundColor White
|
|
||||||
Write-Host " - How divergent branches are combined" -ForegroundColor White
|
|
||||||
Write-Host " - How to visualize merges with git log --graph" -ForegroundColor White
|
|
||||||
Write-Host "`nNext up: Module 05 - Merge Conflicts!" -ForegroundColor Yellow
|
|
||||||
Write-Host "You'll learn what happens when Git can't automatically merge." -ForegroundColor Cyan
|
|
||||||
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,481 +0,0 @@
|
|||||||
# Module 05: Merge Conflicts
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what merge conflicts are and why they occur
|
|
||||||
- Identify merge conflicts in your repository
|
|
||||||
- Read and interpret conflict markers
|
|
||||||
- Resolve merge conflicts manually step-by-step
|
|
||||||
- Complete a merge after resolving conflicts
|
|
||||||
|
|
||||||
## Challenge
|
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
Run the setup script to create your challenge environment:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
This will create a `challenge/` directory with a repository that has a merge conflict waiting to happen!
|
|
||||||
|
|
||||||
### Your Task
|
|
||||||
|
|
||||||
You have a repository with a main branch and a feature branch called `update-config`. Both branches have modified the same configuration file in different ways, creating a merge conflict.
|
|
||||||
|
|
||||||
**Your mission:**
|
|
||||||
1. Attempt to merge the `update-config` branch into `main`
|
|
||||||
2. Git will tell you there's a conflict - don't panic!
|
|
||||||
3. Resolve the conflict by keeping BOTH settings (timeout AND debug)
|
|
||||||
4. Complete the merge
|
|
||||||
|
|
||||||
**Steps to follow:**
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory: `cd challenge`
|
|
||||||
2. Make sure you're on main: `git branch`
|
|
||||||
3. Try to merge: `git merge update-config`
|
|
||||||
4. Git will report a conflict!
|
|
||||||
5. Open `config.py` in your text editor
|
|
||||||
6. Follow the step-by-step guide below to resolve it
|
|
||||||
7. Save the file, stage it, and commit
|
|
||||||
|
|
||||||
## What Are Merge Conflicts?
|
|
||||||
|
|
||||||
A **merge conflict** occurs when Git cannot automatically combine changes because both branches modified the same lines in the same file.
|
|
||||||
|
|
||||||
**Example scenario:**
|
|
||||||
```
|
|
||||||
main branch changes line 5: TIMEOUT = 30
|
|
||||||
feature branch changes line 5: DEBUG = True
|
|
||||||
```
|
|
||||||
|
|
||||||
Git doesn't know which one you want! So it asks you to decide.
|
|
||||||
|
|
||||||
**When do conflicts happen?**
|
|
||||||
- ✅ Two branches modify the same line(s)
|
|
||||||
- ✅ One branch deletes a file that another branch modifies
|
|
||||||
- ❌ Different files are changed (no conflict!)
|
|
||||||
- ❌ Different lines in the same file are changed (no conflict!)
|
|
||||||
|
|
||||||
## Step-by-Step: Resolving Your First Conflict
|
|
||||||
|
|
||||||
### Step 1: Attempt the Merge
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd challenge
|
|
||||||
git merge update-config
|
|
||||||
```
|
|
||||||
|
|
||||||
**You'll see:**
|
|
||||||
```
|
|
||||||
Auto-merging config.py
|
|
||||||
CONFLICT (content): Merge conflict in config.py
|
|
||||||
Automatic merge failed; fix conflicts and then commit the result.
|
|
||||||
```
|
|
||||||
|
|
||||||
**Don't panic!** This is normal. Git is just asking for your help.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 2: Check What Happened
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git status
|
|
||||||
```
|
|
||||||
|
|
||||||
**You'll see:**
|
|
||||||
```
|
|
||||||
On branch main
|
|
||||||
You have unmerged paths.
|
|
||||||
(fix conflicts and run "git commit")
|
|
||||||
(use "git merge --abort" to abort the merge)
|
|
||||||
|
|
||||||
Unmerged paths:
|
|
||||||
(use "git add <file>..." to mark resolution)
|
|
||||||
both modified: config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
This tells you that `config.py` needs your attention!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
### Step 3: Open the Conflicted File
|
|
||||||
|
|
||||||
Open `config.py` in your text editor. You'll see something like this:
|
|
||||||
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
<<<<<<< HEAD
|
|
||||||
TIMEOUT = 30
|
|
||||||
=======
|
|
||||||
DEBUG = True
|
|
||||||
>>>>>>> update-config
|
|
||||||
```
|
|
||||||
|
|
||||||
**Let's break down what you're seeing:**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Understanding Conflict Markers
|
|
||||||
|
|
||||||
Git has added special markers to show you both versions:
|
|
||||||
|
|
||||||
```python
|
|
||||||
<<<<<<< HEAD ← Start of YOUR version (current branch)
|
|
||||||
TIMEOUT = 30 ← What YOU have on main
|
|
||||||
======= ← Divider between versions
|
|
||||||
DEBUG = True ← What THEY have on update-config
|
|
||||||
>>>>>>> update-config ← End of THEIR version
|
|
||||||
```
|
|
||||||
|
|
||||||
**The three parts:**
|
|
||||||
|
|
||||||
1. **`<<<<<<< HEAD`** - Everything between here and `=======` is YOUR current branch's version
|
|
||||||
2. **`=======`** - This separates the two versions
|
|
||||||
3. **`>>>>>>> update-config`** - Everything between `=======` and here is the INCOMING branch's version
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Decide What to Keep
|
|
||||||
|
|
||||||
You have three options:
|
|
||||||
|
|
||||||
**Option A: Keep YOUR version (main)**
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
TIMEOUT = 30
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option B: Keep THEIR version (update-config)**
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
DEBUG = True
|
|
||||||
```
|
|
||||||
|
|
||||||
**Option C: Keep BOTH (this is what we want!)**
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
TIMEOUT = 30
|
|
||||||
DEBUG = True
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Edit the File to Resolve the Conflict
|
|
||||||
|
|
||||||
**What the file looks like NOW (with conflict markers):**
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
<<<<<<< HEAD
|
|
||||||
TIMEOUT = 30
|
|
||||||
=======
|
|
||||||
DEBUG = True
|
|
||||||
>>>>>>> update-config
|
|
||||||
```
|
|
||||||
|
|
||||||
**What you need to do:**
|
|
||||||
|
|
||||||
1. **Delete** the line `<<<<<<< HEAD`
|
|
||||||
2. **Keep** the line `TIMEOUT = 30`
|
|
||||||
3. **Delete** the line `=======`
|
|
||||||
4. **Keep** the line `DEBUG = True`
|
|
||||||
5. **Delete** the line `>>>>>>> update-config`
|
|
||||||
|
|
||||||
**What the file should look like AFTER (conflict resolved):**
|
|
||||||
```python
|
|
||||||
# config.py - Application configuration
|
|
||||||
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
TIMEOUT = 30
|
|
||||||
DEBUG = True
|
|
||||||
```
|
|
||||||
|
|
||||||
**Save the file!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 6: Mark the Conflict as Resolved
|
|
||||||
|
|
||||||
Tell Git you've fixed the conflict by staging the file:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
This tells Git: "I've resolved the conflict in this file, it's ready to be committed."
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 7: Check Your Status
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git status
|
|
||||||
```
|
|
||||||
|
|
||||||
**You should see:**
|
|
||||||
```
|
|
||||||
On branch main
|
|
||||||
All conflicts fixed but you are still merging.
|
|
||||||
(use "git commit" to conclude merge)
|
|
||||||
|
|
||||||
Changes to be committed:
|
|
||||||
modified: config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Great! Git knows the conflict is resolved and is ready for you to complete the merge.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 8: Complete the Merge
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git commit
|
|
||||||
```
|
|
||||||
|
|
||||||
Git will open your editor with a default merge message:
|
|
||||||
```
|
|
||||||
Merge branch 'update-config'
|
|
||||||
|
|
||||||
# Conflicts:
|
|
||||||
# config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
You can keep this message or customize it. Save and close the editor.
|
|
||||||
|
|
||||||
**Or use a one-liner:**
|
|
||||||
```bash
|
|
||||||
git commit -m "Merge update-config: resolve config conflict"
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 9: Verify the Merge
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --graph
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see your merge commit!
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cat config.py
|
|
||||||
```
|
|
||||||
|
|
||||||
Verify the file has BOTH settings and NO conflict markers!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Visual Summary: Before and After
|
|
||||||
|
|
||||||
### Before Resolution (WRONG ❌)
|
|
||||||
```python
|
|
||||||
# config.py
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
<<<<<<< HEAD
|
|
||||||
TIMEOUT = 30
|
|
||||||
=======
|
|
||||||
DEBUG = True
|
|
||||||
>>>>>>> update-config
|
|
||||||
```
|
|
||||||
**This will NOT run! Conflict markers are syntax errors!**
|
|
||||||
|
|
||||||
### After Resolution (CORRECT ✅)
|
|
||||||
```python
|
|
||||||
# config.py
|
|
||||||
APP_NAME = "My Application"
|
|
||||||
VERSION = "1.0.0"
|
|
||||||
TIMEOUT = 30
|
|
||||||
DEBUG = True
|
|
||||||
```
|
|
||||||
**Clean code, no markers, both settings preserved!**
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Common Mistakes to Avoid
|
|
||||||
|
|
||||||
### ❌ Mistake 1: Forgetting to Remove Conflict Markers
|
|
||||||
```python
|
|
||||||
TIMEOUT = 30
|
|
||||||
======= ← Still has conflict marker!
|
|
||||||
DEBUG = True
|
|
||||||
```
|
|
||||||
**This is invalid Python code and will crash!**
|
|
||||||
|
|
||||||
### ❌ Mistake 2: Committing Without Staging
|
|
||||||
```bash
|
|
||||||
# Wrong order:
|
|
||||||
git commit # Error! File not staged
|
|
||||||
|
|
||||||
# Correct order:
|
|
||||||
git add config.py # Stage first
|
|
||||||
git commit # Then commit
|
|
||||||
```
|
|
||||||
|
|
||||||
### ❌ Mistake 3: Only Keeping One Side When You Need Both
|
|
||||||
```python
|
|
||||||
# If the task asks for BOTH settings, this is wrong:
|
|
||||||
TIMEOUT = 30
|
|
||||||
# Missing DEBUG = True
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Useful Commands Reference
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# During a conflict
|
|
||||||
git status # See which files have conflicts
|
|
||||||
git diff # See the conflict in detail
|
|
||||||
|
|
||||||
# Resolving
|
|
||||||
git add <file> # Mark file as resolved
|
|
||||||
git commit # Complete the merge
|
|
||||||
|
|
||||||
# If you want to start over
|
|
||||||
git merge --abort # Cancel the merge, go back to before
|
|
||||||
|
|
||||||
# After resolving
|
|
||||||
git log --oneline --graph # See the merge commit
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Real-World Conflict Resolution Workflow
|
|
||||||
|
|
||||||
**Here's what you'll do every time you have a conflict:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 1. Attempt the merge
|
|
||||||
git merge feature-branch
|
|
||||||
# → Git says: "CONFLICT! Fix it!"
|
|
||||||
|
|
||||||
# 2. Check what's wrong
|
|
||||||
git status
|
|
||||||
# → Shows which files have conflicts
|
|
||||||
|
|
||||||
# 3. Open the file, find the markers
|
|
||||||
# <<<<<<< HEAD
|
|
||||||
# your version
|
|
||||||
# =======
|
|
||||||
# their version
|
|
||||||
# >>>>>>> branch
|
|
||||||
|
|
||||||
# 4. Edit the file to resolve
|
|
||||||
# Remove the markers, keep what you need
|
|
||||||
|
|
||||||
# 5. Stage the resolved file
|
|
||||||
git add config.py
|
|
||||||
|
|
||||||
# 6. Complete the merge
|
|
||||||
git commit -m "Resolve conflict in config.py"
|
|
||||||
|
|
||||||
# 7. Celebrate! 🎉
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What If You Get Stuck?
|
|
||||||
|
|
||||||
### Option 1: Abort the Merge
|
|
||||||
```bash
|
|
||||||
git merge --abort
|
|
||||||
```
|
|
||||||
This puts everything back to how it was before the merge. You can try again!
|
|
||||||
|
|
||||||
### Option 2: Ask for Help
|
|
||||||
```bash
|
|
||||||
git status # See what's going on
|
|
||||||
git diff # See the conflict details
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pro Tips
|
|
||||||
|
|
||||||
✅ **Use `git status` constantly** - It tells you exactly what to do next
|
|
||||||
|
|
||||||
✅ **Search for `<<<<<<<`** - Your editor's search function can find all conflicts quickly
|
|
||||||
|
|
||||||
✅ **Test after resolving** - Make sure your code still works!
|
|
||||||
|
|
||||||
✅ **Read both sides carefully** - Sometimes you need parts from each version
|
|
||||||
|
|
||||||
✅ **Take your time** - Conflicts are not a race. Understand what changed and why.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Once you've resolved the conflict and completed the merge, verify your solution:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- ✅ The merge conflict was resolved
|
|
||||||
- ✅ The merge was completed successfully
|
|
||||||
- ✅ BOTH settings are in the config file (TIMEOUT and DEBUG)
|
|
||||||
- ✅ NO conflict markers remain
|
|
||||||
- ✅ The merge commit exists in history
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Need to Start Over?
|
|
||||||
|
|
||||||
If you want to reset the challenge and start fresh:
|
|
||||||
|
|
||||||
```powershell
|
|
||||||
.\reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## What You've Learned
|
|
||||||
|
|
||||||
🎉 **Congratulations!** You can now:
|
|
||||||
- Recognize when a merge conflict occurs
|
|
||||||
- Read and understand conflict markers
|
|
||||||
- Edit files to resolve conflicts
|
|
||||||
- Stage resolved files with `git add`
|
|
||||||
- Complete a merge with `git commit`
|
|
||||||
|
|
||||||
**This is a critical skill!** Every developer encounters merge conflicts. Now you know exactly what to do when they happen.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Quick Reference Card
|
|
||||||
|
|
||||||
**When you see a conflict:**
|
|
||||||
|
|
||||||
1. **Don't panic** ✅
|
|
||||||
2. **Run `git status`** to see which files
|
|
||||||
3. **Open the file** in your editor
|
|
||||||
4. **Find the markers** (`<<<<<<<`, `=======`, `>>>>>>>`)
|
|
||||||
5. **Decide what to keep** (one side, other side, or both)
|
|
||||||
6. **Remove ALL markers** (the `<<<<<<<`, `=======`, `>>>>>>>` lines)
|
|
||||||
7. **Save the file**
|
|
||||||
8. **Stage it** (`git add filename`)
|
|
||||||
9. **Commit** (`git commit`)
|
|
||||||
10. **Verify** (`git status`, `git log --oneline --graph`)
|
|
||||||
|
|
||||||
**Remember:** The conflict markers are NOT valid code - they MUST be removed!
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the merge conflicts 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,97 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the merge conflicts challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with a merge conflict scenario involving
|
|
||||||
a configuration file that has been modified differently on two branches.
|
|
||||||
#>
|
|
||||||
|
|
||||||
# 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 config.json file
|
|
||||||
$initialConfig = @"
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"name": "MyApp",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"port": 3000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "config.json" -Value $initialConfig
|
|
||||||
git add config.json
|
|
||||||
git commit -m "Initial configuration" | Out-Null
|
|
||||||
|
|
||||||
# Create feature branch
|
|
||||||
git branch update-config | Out-Null
|
|
||||||
|
|
||||||
# On main branch: Add timeout setting
|
|
||||||
$mainConfig = @"
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"name": "MyApp",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"port": 3000,
|
|
||||||
"timeout": 5000
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "config.json" -Value $mainConfig
|
|
||||||
git add config.json
|
|
||||||
git commit -m "Add timeout configuration" | Out-Null
|
|
||||||
|
|
||||||
# Switch to feature branch: Add debug setting (conflicting change)
|
|
||||||
git switch update-config | Out-Null
|
|
||||||
|
|
||||||
$featureConfig = @"
|
|
||||||
{
|
|
||||||
"app": {
|
|
||||||
"name": "MyApp",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"port": 3000,
|
|
||||||
"debug": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "config.json" -Value $featureConfig
|
|
||||||
git add config.json
|
|
||||||
git commit -m "Add debug mode configuration" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main branch
|
|
||||||
git switch main | 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 'main' branch." -ForegroundColor Cyan
|
|
||||||
Write-Host "There is a branch called 'update-config' with conflicting changes." -ForegroundColor Cyan
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
|
||||||
Write-Host "2. Try to merge the 'update-config' branch into 'main'" -ForegroundColor White
|
|
||||||
Write-Host "3. Resolve the merge conflict in config.json" -ForegroundColor White
|
|
||||||
Write-Host "4. Keep BOTH the timeout setting AND the debug setting" -ForegroundColor White
|
|
||||||
Write-Host "5. Complete the merge" -ForegroundColor White
|
|
||||||
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
|
||||||
@@ -1,151 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Verifies the merge conflicts challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user successfully resolved the merge conflict,
|
|
||||||
kept both configuration settings, and completed the merge.
|
|
||||||
#>
|
|
||||||
|
|
||||||
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 "main") {
|
|
||||||
Write-Host "[FAIL] You should be on the 'main' branch." -ForegroundColor Red
|
|
||||||
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
|
||||||
Write-Host "Hint: Use 'git checkout main' to switch to main branch" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if there's an ongoing merge
|
|
||||||
if (Test-Path ".git/MERGE_HEAD") {
|
|
||||||
Write-Host "[FAIL] Merge is not complete. There are still unresolved conflicts." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: After resolving conflicts in config.json, use:" -ForegroundColor Yellow
|
|
||||||
Write-Host " git add config.json" -ForegroundColor White
|
|
||||||
Write-Host " git commit" -ForegroundColor White
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if config.json exists
|
|
||||||
if (-not (Test-Path "config.json")) {
|
|
||||||
Write-Host "[FAIL] config.json file not found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Read and parse config.json
|
|
||||||
try {
|
|
||||||
$configContent = Get-Content "config.json" -Raw
|
|
||||||
$config = $configContent | ConvertFrom-Json
|
|
||||||
} catch {
|
|
||||||
Write-Host "[FAIL] config.json is not valid JSON." -ForegroundColor Red
|
|
||||||
Write-Host "Make sure you removed all conflict markers (<<<<<<, ======, >>>>>>)" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check for conflict markers (in case user forgot to remove them)
|
|
||||||
if ($configContent -match "<<<<<<|======|>>>>>>") {
|
|
||||||
Write-Host "[FAIL] Conflict markers still present in config.json" -ForegroundColor Red
|
|
||||||
Write-Host "Remove all lines containing: <<<<<<, ======, >>>>>>" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if both timeout and debug settings exist
|
|
||||||
$hasTimeout = $null -ne $config.app.timeout
|
|
||||||
$hasDebug = $null -ne $config.app.debug
|
|
||||||
|
|
||||||
if (-not $hasTimeout) {
|
|
||||||
Write-Host "[FAIL] The 'timeout' setting is missing from config.json" -ForegroundColor Red
|
|
||||||
Write-Host "Hint: The resolved file should include both timeout AND debug settings" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if (-not $hasDebug) {
|
|
||||||
Write-Host "[FAIL] The 'debug' setting is missing from config.json" -ForegroundColor Red
|
|
||||||
Write-Host "Hint: The resolved file should include both timeout AND debug settings" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Validate the values
|
|
||||||
if ($config.app.timeout -ne 5000) {
|
|
||||||
Write-Host "[FAIL] The 'timeout' value should be 5000" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
if ($config.app.debug -ne $true) {
|
|
||||||
Write-Host "[FAIL] The 'debug' value should be true" -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that merge commit exists
|
|
||||||
$commitCount = (git rev-list --count HEAD 2>$null)
|
|
||||||
if ($commitCount -lt 4) {
|
|
||||||
Write-Host "[FAIL] Not enough commits. Expected at least 4 commits (including merge commit)." -ForegroundColor Red
|
|
||||||
Write-Host "Current commits: $commitCount" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that the latest commit is a merge commit (has 2 parents)
|
|
||||||
$parentCount = (git rev-list --parents -n 1 HEAD 2>$null).Split(' ').Count - 1
|
|
||||||
if ($parentCount -ne 2) {
|
|
||||||
Write-Host "[FAIL] The latest commit should be a merge commit." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Complete the merge with 'git commit' after resolving conflicts" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that both branches are merged
|
|
||||||
$branches = git branch --merged update-config 2>$null
|
|
||||||
Write-Host "$branches $($branches -notmatch 'update-config')"
|
|
||||||
if ($branches -notmatch "update-config") {
|
|
||||||
Write-Host "[FAIL] The 'update-config' branch was not merged." -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 "- Identified the merge conflict" -ForegroundColor White
|
|
||||||
Write-Host "- Resolved the conflict by keeping both settings" -ForegroundColor White
|
|
||||||
Write-Host "- Completed the merge with a proper merge commit" -ForegroundColor White
|
|
||||||
Write-Host "`nYou now understand how to handle merge conflicts!" -ForegroundColor Green
|
|
||||||
Write-Host "This is a critical skill for collaborative development.`n" -ForegroundColor Green
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
# Module 09: Cherry-Pick
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what cherry-picking is and how it works
|
|
||||||
- Know when to use cherry-pick vs merge or rebase
|
|
||||||
- Apply specific commits from one branch to another
|
|
||||||
- Handle cherry-pick conflicts if they occur
|
|
||||||
- Understand common use cases for cherry-picking
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You have a `development` branch with several commits. Some of these commits are bug fixes that need to be applied to the `main` branch immediately, but other commits are experimental features that shouldn't be merged yet.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Review the commits on the development branch
|
|
||||||
2. Identify which commits are bug fixes
|
|
||||||
3. Cherry-pick only the bug fix commits to the main branch
|
|
||||||
4. Verify that main has the bug fixes but not the experimental features
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What is Cherry-Pick?
|
|
||||||
|
|
||||||
Cherry-pick allows you to apply a specific commit from one branch to another. Instead of merging an entire branch, you can selectively choose individual commits.
|
|
||||||
|
|
||||||
```
|
|
||||||
A---B---C---D development
|
|
||||||
/
|
|
||||||
E---F main
|
|
||||||
```
|
|
||||||
|
|
||||||
After cherry-picking commit C:
|
|
||||||
```
|
|
||||||
A---B---C---D development
|
|
||||||
/
|
|
||||||
E---F---C' main
|
|
||||||
```
|
|
||||||
|
|
||||||
Note that C' is a new commit with the same changes as C but a different commit hash.
|
|
||||||
|
|
||||||
### Cherry-Pick vs Merge vs Rebase
|
|
||||||
|
|
||||||
- **Merge**: Brings all commits from another branch and creates a merge commit
|
|
||||||
- **Rebase**: Replays all commits from your branch on top of another branch
|
|
||||||
- **Cherry-Pick**: Applies one or more specific commits to your current branch
|
|
||||||
|
|
||||||
### When to Use Cherry-Pick
|
|
||||||
|
|
||||||
Cherry-pick is useful when you:
|
|
||||||
- Need a bug fix from a feature branch but can't merge the whole branch yet
|
|
||||||
- Want to apply a specific commit to a release branch
|
|
||||||
- Need to backport a fix to an older version
|
|
||||||
- Made a commit on the wrong branch and need to move it
|
|
||||||
- Want to duplicate a commit across multiple branches
|
|
||||||
|
|
||||||
### Cherry-Pick Creates New Commits
|
|
||||||
|
|
||||||
Important: Cherry-picked commits are new commits with different hashes. The original commit remains on the source branch, and a copy is created on the target branch with the same changes but a different commit ID.
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# View commits on another branch
|
|
||||||
git log <branch-name> --oneline
|
|
||||||
|
|
||||||
# View a specific commit's details
|
|
||||||
git show <commit-hash>
|
|
||||||
|
|
||||||
# Cherry-pick a single commit
|
|
||||||
git cherry-pick <commit-hash>
|
|
||||||
|
|
||||||
# Cherry-pick multiple commits
|
|
||||||
git cherry-pick <commit-hash1> <commit-hash2>
|
|
||||||
|
|
||||||
# Cherry-pick a range of commits
|
|
||||||
git cherry-pick <start-hash>..<end-hash>
|
|
||||||
|
|
||||||
# If conflicts occur during cherry-pick:
|
|
||||||
# 1. Resolve conflicts in files
|
|
||||||
# 2. Stage the resolved files
|
|
||||||
git add <file>
|
|
||||||
# 3. Continue the cherry-pick
|
|
||||||
git cherry-pick --continue
|
|
||||||
|
|
||||||
# Abort a cherry-pick if something goes wrong
|
|
||||||
git cherry-pick --abort
|
|
||||||
|
|
||||||
# View commit history graph
|
|
||||||
git log --oneline --graph --all
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- You're on the main branch
|
|
||||||
- The security bug fix commit has been applied to main
|
|
||||||
- The performance bug fix commit has been applied to main
|
|
||||||
- The experimental features are NOT on main
|
|
||||||
- The commits were cherry-picked (not merged)
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. You're currently on the development branch
|
|
||||||
3. View the commits: `git log --oneline`
|
|
||||||
4. You'll see several commits - identify the bug fixes
|
|
||||||
5. Switch to main branch: `git switch main`
|
|
||||||
6. Cherry-pick the bug fix commits (you'll need their commit hashes)
|
|
||||||
7. Verify the result with `git log --oneline`
|
|
||||||
8. Run the verification script
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Use `git log development --oneline` to see commits on the development branch
|
|
||||||
- Use `git show <hash>` to view details of a specific commit
|
|
||||||
- You can cherry-pick by commit hash - you only need the first 7 characters
|
|
||||||
- Cherry-pick commits in chronological order (oldest first) to avoid conflicts
|
|
||||||
- If you make a mistake, use `.\reset.ps1` to start over
|
|
||||||
- The commit message will be preserved when cherry-picking
|
|
||||||
|
|
||||||
## Common Cherry-Pick Scenarios
|
|
||||||
|
|
||||||
### Hotfix to Production
|
|
||||||
You have a critical bug fix on a development branch that needs to go to production immediately:
|
|
||||||
```bash
|
|
||||||
git switch production
|
|
||||||
git cherry-pick <bugfix-commit-hash>
|
|
||||||
```
|
|
||||||
|
|
||||||
### Wrong Branch
|
|
||||||
You accidentally committed on the wrong branch:
|
|
||||||
```bash
|
|
||||||
# On wrong branch, note the commit hash
|
|
||||||
git log --oneline
|
|
||||||
# Switch to correct branch
|
|
||||||
git switch correct-branch
|
|
||||||
git cherry-pick <commit-hash>
|
|
||||||
# Go back and remove from wrong branch
|
|
||||||
git switch wrong-branch
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
```
|
|
||||||
|
|
||||||
### Backporting
|
|
||||||
You need to apply a fix to an older release branch:
|
|
||||||
```bash
|
|
||||||
git switch release-2.0
|
|
||||||
git cherry-pick <fix-from-main>
|
|
||||||
```
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Cherry-pick is a surgical tool in your Git toolbox. While merge and rebase work with entire branches, cherry-pick lets you be selective about which changes to apply. This is invaluable for managing hotfixes, maintaining multiple release branches, and handling situations where you need specific changes without bringing along everything else. Understanding when to use cherry-pick versus other Git operations is a mark of Git expertise.
|
|
||||||
@@ -1,198 +0,0 @@
|
|||||||
# Module 10: Reset vs Revert
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand the difference between `git reset` and `git revert`
|
|
||||||
- Know when to use reset vs revert
|
|
||||||
- Understand the three modes of reset (--soft, --mixed, --hard)
|
|
||||||
- Safely undo commits in both local and shared branches
|
|
||||||
- Understand the risks of rewriting history
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You have two branches with problematic commits:
|
|
||||||
1. **local-feature**: A private branch with bad commits that you haven't shared with anyone
|
|
||||||
2. **shared-feature**: A branch that has been pushed and others might be using
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Use `git reset` to remove the bad commit from the local-feature branch (safe because it's not shared)
|
|
||||||
2. Use `git revert` to undo the bad commit from the shared-feature branch (safe because it preserves history)
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### Git Reset: Rewriting History
|
|
||||||
|
|
||||||
`git reset` moves the branch pointer backward, effectively erasing commits from history. It has three modes:
|
|
||||||
|
|
||||||
**--soft**: Moves HEAD, keeps changes staged
|
|
||||||
```bash
|
|
||||||
git reset --soft HEAD~1
|
|
||||||
# Commit is gone, but changes are staged and ready to commit again
|
|
||||||
```
|
|
||||||
|
|
||||||
**--mixed** (default): Moves HEAD, keeps changes unstaged
|
|
||||||
```bash
|
|
||||||
git reset HEAD~1
|
|
||||||
# Commit is gone, changes are in working directory but not staged
|
|
||||||
```
|
|
||||||
|
|
||||||
**--hard**: Moves HEAD, discards all changes
|
|
||||||
```bash
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
# Commit is gone, changes are PERMANENTLY DELETED
|
|
||||||
```
|
|
||||||
|
|
||||||
### Git Revert: Safe Undo
|
|
||||||
|
|
||||||
`git revert` creates a NEW commit that undoes the changes from a previous commit. History is preserved.
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git revert <commit-hash>
|
|
||||||
# Creates a new commit that reverses the specified commit
|
|
||||||
```
|
|
||||||
|
|
||||||
### Visual Comparison
|
|
||||||
|
|
||||||
**Before (both branches):**
|
|
||||||
```
|
|
||||||
A---B---C---D (D is the bad commit)
|
|
||||||
```
|
|
||||||
|
|
||||||
**After Reset (rewrites history):**
|
|
||||||
```
|
|
||||||
A---B---C
|
|
||||||
```
|
|
||||||
Commit D is gone. If anyone else had D, they'll have problems.
|
|
||||||
|
|
||||||
**After Revert (preserves history):**
|
|
||||||
```
|
|
||||||
A---B---C---D---E
|
|
||||||
```
|
|
||||||
E is a new commit that undoes D. Everyone can pull E safely.
|
|
||||||
|
|
||||||
### When to Use Each
|
|
||||||
|
|
||||||
**Use Reset when:**
|
|
||||||
- The commits haven't been pushed to a shared repository
|
|
||||||
- You're cleaning up local commits before pushing
|
|
||||||
- You made a mistake locally and want to start over
|
|
||||||
- You're working alone on a branch
|
|
||||||
|
|
||||||
**Use Revert when:**
|
|
||||||
- The commits have been pushed to a shared repository
|
|
||||||
- Others might have based work on these commits
|
|
||||||
- You want to preserve the complete history
|
|
||||||
- You need a safe, reversible undo operation
|
|
||||||
|
|
||||||
### The Golden Rule
|
|
||||||
|
|
||||||
**Never use `git reset` on commits that have been pushed to a shared branch!**
|
|
||||||
|
|
||||||
This will cause problems for anyone who has pulled those commits. Use `git revert` instead.
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Reset (for local-only commits)
|
|
||||||
git reset --soft HEAD~1 # Undo commit, keep changes staged
|
|
||||||
git reset HEAD~1 # Undo commit, keep changes unstaged
|
|
||||||
git reset --hard HEAD~1 # Undo commit, discard changes (DANGEROUS!)
|
|
||||||
|
|
||||||
# Reset to a specific commit
|
|
||||||
git reset --hard <commit-hash>
|
|
||||||
|
|
||||||
# Revert (for shared commits)
|
|
||||||
git revert <commit-hash>
|
|
||||||
git revert HEAD # Revert the last commit
|
|
||||||
|
|
||||||
# See what would be affected before resetting
|
|
||||||
git log --oneline
|
|
||||||
git diff HEAD~1
|
|
||||||
|
|
||||||
# If you reset by mistake, you can sometimes recover with reflog
|
|
||||||
git reflog
|
|
||||||
git reset --hard <commit-hash-from-reflog>
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- local-feature branch has the bad commit removed via reset
|
|
||||||
- shared-feature branch has the bad commit undone via revert
|
|
||||||
- shared-feature has a revert commit in the history
|
|
||||||
- All good commits are preserved
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. You're on the local-feature branch with a bad commit
|
|
||||||
3. View commits: `git log --oneline`
|
|
||||||
4. Use `git reset --hard HEAD~1` to remove the bad commit
|
|
||||||
5. Switch to shared-feature: `git switch shared-feature`
|
|
||||||
6. View commits: `git log --oneline`
|
|
||||||
7. Find the hash of the "Add broken feature" commit
|
|
||||||
8. Use `git revert <commit-hash>` to undo it safely
|
|
||||||
9. Run the verification script
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- `HEAD~1` means "one commit before HEAD"
|
|
||||||
- `HEAD~2` means "two commits before HEAD"
|
|
||||||
- Always check `git log` before and after reset/revert
|
|
||||||
- `git reset --hard` is DANGEROUS - it permanently deletes uncommitted changes
|
|
||||||
- If you're unsure, use `git reset --soft` instead of `--hard`
|
|
||||||
- Revert will open an editor for the commit message - you can accept the default
|
|
||||||
- You can always use `.\reset.ps1` to start over if you make a mistake
|
|
||||||
|
|
||||||
## Common Mistakes to Avoid
|
|
||||||
|
|
||||||
### Mistake 1: Using Reset on Pushed Commits
|
|
||||||
```bash
|
|
||||||
# DON'T DO THIS if the commit was pushed!
|
|
||||||
git reset --hard HEAD~1
|
|
||||||
git push --force # This will cause problems for others
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mistake 2: Using --hard Without Checking
|
|
||||||
```bash
|
|
||||||
# This DELETES your work permanently!
|
|
||||||
git reset --hard HEAD~1 # Uncommitted changes are GONE
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mistake 3: Reverting the Wrong Commit
|
|
||||||
```bash
|
|
||||||
# Always double-check the commit hash
|
|
||||||
git log --oneline
|
|
||||||
git show <commit-hash> # Verify it's the right commit
|
|
||||||
git revert <commit-hash> # Now revert it
|
|
||||||
```
|
|
||||||
|
|
||||||
## Recovery from Mistakes
|
|
||||||
|
|
||||||
If you reset by accident, Git keeps a reflog:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# See recent HEAD movements
|
|
||||||
git reflog
|
|
||||||
|
|
||||||
# Find the commit you want to restore
|
|
||||||
# Output looks like:
|
|
||||||
# abc1234 HEAD@{0}: reset: moving to HEAD~1
|
|
||||||
# def5678 HEAD@{1}: commit: The commit you just lost
|
|
||||||
|
|
||||||
# Restore it
|
|
||||||
git reset --hard def5678
|
|
||||||
```
|
|
||||||
|
|
||||||
The reflog is your safety net, but it only keeps history for about 30 days.
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Understanding when to use reset versus revert is crucial for safe Git usage. Reset is powerful but dangerous when used on shared commits, while revert is always safe but creates additional history. Mastering both commands and knowing which to use in different situations is a hallmark of Git expertise. The rule is simple: if in doubt, use revert - it's always safe.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Resets the reset vs revert 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,190 +0,0 @@
|
|||||||
#!/usr/bin/env pwsh
|
|
||||||
|
|
||||||
<#
|
|
||||||
.SYNOPSIS
|
|
||||||
Sets up the reset vs revert challenge environment.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Creates a Git repository with two branches:
|
|
||||||
- local-feature: A private branch where reset should be used
|
|
||||||
- shared-feature: A pushed branch where revert should be used
|
|
||||||
#>
|
|
||||||
|
|
||||||
# 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 commits on main
|
|
||||||
$app = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $app
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Initial calculator implementation" | Out-Null
|
|
||||||
|
|
||||||
$readme = @"
|
|
||||||
# Calculator App
|
|
||||||
|
|
||||||
A simple calculator application.
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "README.md" -Value $readme
|
|
||||||
git add README.md
|
|
||||||
git commit -m "Add README" | Out-Null
|
|
||||||
|
|
||||||
# Create local-feature branch (private, not shared)
|
|
||||||
git checkout -b local-feature | Out-Null
|
|
||||||
|
|
||||||
$appWithMultiply = @"
|
|
||||||
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 $appWithMultiply
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add multiply function" | Out-Null
|
|
||||||
|
|
||||||
# Add a bad commit that should be removed with reset
|
|
||||||
$appWithBadCode = @"
|
|
||||||
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: This is broken and should never have been committed!
|
|
||||||
def divide(self, a, b):
|
|
||||||
# Forgot to check for division by zero
|
|
||||||
return a / b # This will raise ZeroDivisionError for zero!
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $appWithBadCode
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add broken divide function - DO NOT KEEP" | Out-Null
|
|
||||||
|
|
||||||
# Switch back to main for shared-feature branch
|
|
||||||
git checkout main | Out-Null
|
|
||||||
|
|
||||||
# Create shared-feature branch (simulating a pushed/shared branch)
|
|
||||||
git checkout -b shared-feature | Out-Null
|
|
||||||
|
|
||||||
$appWithPower = @"
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $appWithPower
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add power function" | Out-Null
|
|
||||||
|
|
||||||
# Add a bad commit that should be reverted (not reset)
|
|
||||||
$appWithBrokenFeature = @"
|
|
||||||
import math
|
|
||||||
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
# BUG: This breaks the calculator!
|
|
||||||
def square_root(self, a):
|
|
||||||
# This implementation is wrong for negative numbers
|
|
||||||
return math.sqrt(a) # Raises ValueError for negative numbers without warning!
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $appWithBrokenFeature
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add broken feature" | Out-Null
|
|
||||||
|
|
||||||
# Add another good commit after the bad one (to show that revert preserves subsequent commits)
|
|
||||||
$appWithMoreFeatures = @"
|
|
||||||
import math
|
|
||||||
|
|
||||||
class Calculator:
|
|
||||||
def add(self, a, b):
|
|
||||||
return a + b
|
|
||||||
|
|
||||||
def subtract(self, a, b):
|
|
||||||
return a - b
|
|
||||||
|
|
||||||
def power(self, a, b):
|
|
||||||
return a ** b
|
|
||||||
|
|
||||||
# BUG: This breaks the calculator!
|
|
||||||
def square_root(self, a):
|
|
||||||
# This implementation is wrong for negative numbers
|
|
||||||
return math.sqrt(a) # Raises ValueError for negative numbers without warning!
|
|
||||||
|
|
||||||
def modulo(self, a, b):
|
|
||||||
return a % b
|
|
||||||
"@
|
|
||||||
|
|
||||||
Set-Content -Path "calculator.py" -Value $appWithMoreFeatures
|
|
||||||
git add calculator.py
|
|
||||||
git commit -m "Add modulo function" | Out-Null
|
|
||||||
|
|
||||||
# Switch to local-feature for the challenge start
|
|
||||||
git checkout local-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 have two branches with bad commits:" -ForegroundColor Cyan
|
|
||||||
Write-Host "`n1. local-feature (PRIVATE - not shared):" -ForegroundColor Yellow
|
|
||||||
Write-Host " - Has a broken divide function commit" -ForegroundColor White
|
|
||||||
Write-Host " - Safe to use 'git reset' to remove it" -ForegroundColor Green
|
|
||||||
Write-Host "`n2. shared-feature (PUBLIC - shared with team):" -ForegroundColor Yellow
|
|
||||||
Write-Host " - Has a broken feature commit" -ForegroundColor White
|
|
||||||
Write-Host " - Must use 'git revert' to undo it safely" -ForegroundColor Green
|
|
||||||
Write-Host "`nYour task:" -ForegroundColor Yellow
|
|
||||||
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
|
||||||
Write-Host "2. You're on local-feature - view commits: git log --oneline" -ForegroundColor White
|
|
||||||
Write-Host "3. Remove the bad commit with: git reset --hard HEAD~1" -ForegroundColor White
|
|
||||||
Write-Host "4. Switch to shared-feature: git checkout shared-feature" -ForegroundColor White
|
|
||||||
Write-Host "5. Find the 'Add broken feature' commit hash: git log --oneline" -ForegroundColor White
|
|
||||||
Write-Host "6. Revert it with: git revert <commit-hash>" -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 reset vs revert challenge solution.
|
|
||||||
|
|
||||||
.DESCRIPTION
|
|
||||||
Checks that the user correctly used reset on the local branch
|
|
||||||
and revert on the shared branch.
|
|
||||||
#>
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify local-feature branch
|
|
||||||
Write-Host "`nChecking local-feature branch..." -ForegroundColor Cyan
|
|
||||||
git checkout local-feature 2>$null | Out-Null
|
|
||||||
|
|
||||||
# Check commit count on local-feature (should be 3: initial + README + multiply)
|
|
||||||
$localCommitCount = (git rev-list --count local-feature 2>$null)
|
|
||||||
if ($localCommitCount -ne 3) {
|
|
||||||
Write-Host "[FAIL] local-feature should have 3 commits, found $localCommitCount" -ForegroundColor Red
|
|
||||||
if ($localCommitCount -gt 3) {
|
|
||||||
Write-Host "Hint: The bad commit should be removed. Use 'git reset --hard HEAD~1'" -ForegroundColor Yellow
|
|
||||||
} else {
|
|
||||||
Write-Host "Hint: You may have reset too far. Run ../reset.ps1 to start over." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that calculator.py exists
|
|
||||||
if (-not (Test-Path "calculator.py")) {
|
|
||||||
Write-Host "[FAIL] calculator.py not found." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check calculator.py on local-feature
|
|
||||||
$localCalcContent = Get-Content "calculator.py" -Raw
|
|
||||||
|
|
||||||
# Should have multiply function
|
|
||||||
if ($localCalcContent -notmatch "multiply") {
|
|
||||||
Write-Host "[FAIL] calculator.py should have the multiply function." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Should NOT have divide function (it was in the bad commit that should be reset)
|
|
||||||
if ($localCalcContent -match "divide") {
|
|
||||||
Write-Host "[FAIL] calculator.py should NOT have the divide function." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Use 'git reset --hard HEAD~1' to remove the bad commit" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check commit messages on local-feature
|
|
||||||
$localCommits = git log --pretty=format:"%s" local-feature 2>$null
|
|
||||||
if ($localCommits -match "broken divide") {
|
|
||||||
Write-Host "[FAIL] The 'broken divide' commit should be removed from local-feature." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Use 'git reset --hard HEAD~1' to remove it" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[PASS] local-feature branch correctly reset!" -ForegroundColor Green
|
|
||||||
|
|
||||||
# Verify shared-feature branch
|
|
||||||
Write-Host "`nChecking shared-feature branch..." -ForegroundColor Cyan
|
|
||||||
git checkout shared-feature 2>$null | Out-Null
|
|
||||||
|
|
||||||
# Check commit count on shared-feature
|
|
||||||
# Should be 6: initial + README + power + broken feature + modulo + revert
|
|
||||||
$sharedCommitCount = (git rev-list --count shared-feature 2>$null)
|
|
||||||
if ($sharedCommitCount -ne 6) {
|
|
||||||
Write-Host "[FAIL] shared-feature should have 6 commits, found $sharedCommitCount" -ForegroundColor Red
|
|
||||||
if ($sharedCommitCount -lt 6) {
|
|
||||||
Write-Host "Hint: You should REVERT the bad commit, not reset it." -ForegroundColor Yellow
|
|
||||||
Write-Host " Revert creates a new commit that undoes the bad one." -ForegroundColor Yellow
|
|
||||||
Write-Host " Use: git revert <commit-hash>" -ForegroundColor Yellow
|
|
||||||
} else {
|
|
||||||
Write-Host "Hint: You should have exactly 6 commits after reverting." -ForegroundColor Yellow
|
|
||||||
}
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check that there's a revert commit
|
|
||||||
$sharedCommits = git log --pretty=format:"%s" shared-feature 2>$null
|
|
||||||
if ($sharedCommits -notmatch "Revert") {
|
|
||||||
Write-Host "[FAIL] No revert commit found on shared-feature." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Use 'git revert <commit-hash>' to undo the bad commit" -ForegroundColor Yellow
|
|
||||||
Write-Host " Find the hash with: git log --oneline" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check calculator.py on shared-feature
|
|
||||||
$sharedCalcContent = Get-Content "calculator.py" -Raw
|
|
||||||
|
|
||||||
# Should have power function
|
|
||||||
if ($sharedCalcContent -notmatch "power") {
|
|
||||||
Write-Host "[FAIL] calculator.py should have the power function." -ForegroundColor Red
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Should have modulo function (commits after the reverted one should be preserved)
|
|
||||||
if ($sharedCalcContent -notmatch "modulo") {
|
|
||||||
Write-Host "[FAIL] calculator.py should have the modulo function." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Reverting should preserve commits made after the bad one" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Should NOT have square_root function (it was in the bad commit that should be reverted)
|
|
||||||
if ($sharedCalcContent -match "square_root") {
|
|
||||||
Write-Host "[FAIL] calculator.py should NOT have the square_root function." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: The 'Add broken feature' commit should be reverted" -ForegroundColor Yellow
|
|
||||||
Write-Host " Use: git revert <commit-hash-of-broken-feature>" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
# Verify the revert commit specifically reverted the "Add broken feature" commit
|
|
||||||
$revertCommitMessage = git log --grep="Revert" --pretty=format:"%s" -n 1 2>$null
|
|
||||||
if ($revertCommitMessage -notmatch "broken feature") {
|
|
||||||
Write-Host "[FAIL] The revert commit should mention 'broken feature'." -ForegroundColor Red
|
|
||||||
Write-Host "Hint: Make sure you reverted the correct commit (the one that added square_root)" -ForegroundColor Yellow
|
|
||||||
Set-Location ..
|
|
||||||
exit 1
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Host "[PASS] shared-feature branch correctly reverted!" -ForegroundColor Green
|
|
||||||
|
|
||||||
# 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 reset' on local-feature (private branch)" -ForegroundColor White
|
|
||||||
Write-Host " Removed the bad commit completely from history" -ForegroundColor White
|
|
||||||
Write-Host "- Used 'git revert' on shared-feature (public branch)" -ForegroundColor White
|
|
||||||
Write-Host " Created a new commit that undoes the bad one" -ForegroundColor White
|
|
||||||
Write-Host " Preserved all history and subsequent commits" -ForegroundColor White
|
|
||||||
Write-Host "`nYou now understand when to use reset vs revert!" -ForegroundColor Green
|
|
||||||
Write-Host "`nKey takeaway:" -ForegroundColor Yellow
|
|
||||||
Write-Host "- Reset rewrites history (use only on private commits)" -ForegroundColor White
|
|
||||||
Write-Host "- Revert preserves history (safe for shared commits)`n" -ForegroundColor White
|
|
||||||
|
|
||||||
Set-Location ..
|
|
||||||
exit 0
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
# Module 11: Stash
|
|
||||||
|
|
||||||
## Learning Objectives
|
|
||||||
|
|
||||||
By the end of this module, you will:
|
|
||||||
- Understand what git stash is and when to use it
|
|
||||||
- Temporarily save work without committing
|
|
||||||
- Switch between branches without losing uncommitted changes
|
|
||||||
- Manage multiple stashes
|
|
||||||
- Apply and remove stashed changes
|
|
||||||
- Understand the difference between stash pop and stash apply
|
|
||||||
|
|
||||||
## Challenge Description
|
|
||||||
|
|
||||||
You're working on a feature when your teammate reports a critical bug in production. You need to switch to the main branch to fix it immediately, but your current work is incomplete and not ready to commit.
|
|
||||||
|
|
||||||
Your task is to:
|
|
||||||
1. Stash your incomplete work
|
|
||||||
2. Switch to the main branch
|
|
||||||
3. Fix the urgent bug and commit it
|
|
||||||
4. Return to your feature branch
|
|
||||||
5. Restore your stashed changes
|
|
||||||
6. Complete your feature and commit it
|
|
||||||
|
|
||||||
## Key Concepts
|
|
||||||
|
|
||||||
### What is Git Stash?
|
|
||||||
|
|
||||||
Git stash temporarily saves your uncommitted changes (both staged and unstaged) and reverts your working directory to match the HEAD commit. Think of it as a clipboard for your changes.
|
|
||||||
|
|
||||||
### When to Use Stash
|
|
||||||
|
|
||||||
Use stash when you need to:
|
|
||||||
- Switch branches but have uncommitted changes
|
|
||||||
- Pull updates from remote but have local modifications
|
|
||||||
- Quickly test something on a clean working directory
|
|
||||||
- Save work temporarily without creating a commit
|
|
||||||
- Context-switch between tasks
|
|
||||||
|
|
||||||
### Stash vs Commit
|
|
||||||
|
|
||||||
**Stash:**
|
|
||||||
- Temporary storage
|
|
||||||
- Not part of project history
|
|
||||||
- Can be applied to different branches
|
|
||||||
- Easy to discard if not needed
|
|
||||||
- Local only (not pushed to remote)
|
|
||||||
|
|
||||||
**Commit:**
|
|
||||||
- Permanent part of history
|
|
||||||
- Creates a snapshot in the project timeline
|
|
||||||
- Associated with a specific branch
|
|
||||||
- Should be meaningful and complete
|
|
||||||
- Can be pushed to remote
|
|
||||||
|
|
||||||
### The Stash Stack
|
|
||||||
|
|
||||||
Git stash works like a stack (LIFO - Last In, First Out):
|
|
||||||
```
|
|
||||||
stash@{0} <- Most recent stash (top of stack)
|
|
||||||
stash@{1}
|
|
||||||
stash@{2} <- Oldest stash
|
|
||||||
```
|
|
||||||
|
|
||||||
You can have multiple stashes and apply any of them.
|
|
||||||
|
|
||||||
## Useful Commands
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Stash current changes
|
|
||||||
git stash
|
|
||||||
git stash save "description" # With a descriptive message
|
|
||||||
|
|
||||||
# Stash including untracked files
|
|
||||||
git stash -u
|
|
||||||
|
|
||||||
# List all stashes
|
|
||||||
git stash list
|
|
||||||
|
|
||||||
# Apply most recent stash and remove it from stack
|
|
||||||
git stash pop
|
|
||||||
|
|
||||||
# Apply most recent stash but keep it in stack
|
|
||||||
git stash apply
|
|
||||||
|
|
||||||
# Apply a specific stash
|
|
||||||
git stash apply stash@{1}
|
|
||||||
|
|
||||||
# Show what's in a stash
|
|
||||||
git stash show
|
|
||||||
git stash show -p # Show full diff
|
|
||||||
|
|
||||||
# Drop (delete) a stash
|
|
||||||
git stash drop stash@{0}
|
|
||||||
|
|
||||||
# Clear all stashes
|
|
||||||
git stash clear
|
|
||||||
|
|
||||||
# Create a branch from a stash
|
|
||||||
git stash branch new-branch-name
|
|
||||||
```
|
|
||||||
|
|
||||||
## Verification
|
|
||||||
|
|
||||||
Run the verification script to check your solution:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
.\verify.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
The verification will check that:
|
|
||||||
- The bug fix commit exists on main
|
|
||||||
- Your feature is completed on the feature branch
|
|
||||||
- Changes were properly stashed and restored
|
|
||||||
- No uncommitted changes remain
|
|
||||||
|
|
||||||
## Challenge Steps
|
|
||||||
|
|
||||||
1. Navigate to the challenge directory
|
|
||||||
2. You're on feature-login with uncommitted changes
|
|
||||||
3. Check status: `git status` (you'll see modified files)
|
|
||||||
4. Stash your changes: `git stash save "WIP: login feature"`
|
|
||||||
5. Verify working directory is clean: `git status`
|
|
||||||
6. Switch to main: `git switch main`
|
|
||||||
7. View the bug in app.js and fix it (remove the incorrect line)
|
|
||||||
8. Commit the fix: `git add app.js && git commit -m "Fix critical security bug"`
|
|
||||||
9. Switch back to feature: `git switch feature-login`
|
|
||||||
10. Restore your work: `git stash pop`
|
|
||||||
11. Complete the feature (the TODOs in login.js)
|
|
||||||
12. Commit your completed feature
|
|
||||||
13. Run verification
|
|
||||||
|
|
||||||
## Tips
|
|
||||||
|
|
||||||
- Always use `git stash save "message"` to describe what you're stashing
|
|
||||||
- Use `git stash list` to see all your stashes
|
|
||||||
- `git stash pop` applies and removes the stash (use this most often)
|
|
||||||
- `git stash apply` keeps the stash (useful if you want to apply it to multiple branches)
|
|
||||||
- Stashes are local - they don't get pushed to remote repositories
|
|
||||||
- You can stash even if you have changes to different files
|
|
||||||
- Stash before pulling to avoid merge conflicts
|
|
||||||
- Use `git stash show -p` to preview what's in a stash before applying
|
|
||||||
|
|
||||||
## Common Stash Scenarios
|
|
||||||
|
|
||||||
### Scenario 1: Quick Branch Switch
|
|
||||||
```bash
|
|
||||||
# Working on feature, need to switch to main
|
|
||||||
git stash
|
|
||||||
git switch main
|
|
||||||
# Do work on main
|
|
||||||
git switch feature
|
|
||||||
git stash pop
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 2: Pull with Local Changes
|
|
||||||
```bash
|
|
||||||
# You have local changes but need to pull
|
|
||||||
git stash
|
|
||||||
git pull
|
|
||||||
git stash pop
|
|
||||||
# Resolve any conflicts
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 3: Experimental Changes
|
|
||||||
```bash
|
|
||||||
# Try something experimental
|
|
||||||
git stash # Save current work
|
|
||||||
# Make experimental changes
|
|
||||||
# Decide you don't like it
|
|
||||||
git restore . # Discard experiment
|
|
||||||
git stash pop # Restore original work
|
|
||||||
```
|
|
||||||
|
|
||||||
### Scenario 4: Apply to Multiple Branches
|
|
||||||
```bash
|
|
||||||
# Same fix needed on multiple branches
|
|
||||||
git stash
|
|
||||||
git switch branch1
|
|
||||||
git stash apply
|
|
||||||
git commit -am "Apply fix"
|
|
||||||
git switch branch2
|
|
||||||
git stash apply
|
|
||||||
git commit -am "Apply fix"
|
|
||||||
git stash drop # Clean up when done
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stash Conflicts
|
|
||||||
|
|
||||||
If applying a stash causes conflicts:
|
|
||||||
1. Git will mark the conflicts in your files
|
|
||||||
2. Resolve conflicts manually (like merge conflicts)
|
|
||||||
3. Stage the resolved files: `git add <file>`
|
|
||||||
4. The stash is automatically dropped after successful pop
|
|
||||||
5. If you used `apply`, manually drop it: `git stash drop`
|
|
||||||
|
|
||||||
## What You'll Learn
|
|
||||||
|
|
||||||
Git stash is an essential tool for managing context switches in your daily workflow. It lets you maintain a clean working directory while preserving incomplete work, making it easy to handle interruptions, urgent fixes, and quick branch switches. Mastering stash makes you more efficient and helps avoid the temptation to make "WIP" commits just to switch branches. Think of stash as your temporary workspace that follows you around.
|
|
||||||
@@ -1,904 +0,0 @@
|
|||||||
# Facilitator Setup Guide - The Great Print Project
|
|
||||||
|
|
||||||
This guide helps workshop facilitators set up the cloud-based multiplayer Git module.
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
The Great Print Project is a collaborative Git exercise where pairs of students work together on a shared repository hosted on your Gitea server at **https://git.frod.dk/multiplayer**.
|
|
||||||
|
|
||||||
**What participants will do:**
|
|
||||||
- Clone a real repository from your server
|
|
||||||
- Collaborate with partners on shared branches
|
|
||||||
- Deliberately create and resolve merge conflicts
|
|
||||||
- Create pull requests and review code
|
|
||||||
- Experience the full collaborative Git workflow
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
### Gitea Server Setup
|
|
||||||
|
|
||||||
You should have:
|
|
||||||
- Gitea running at https://git.frod.dk/multiplayer
|
|
||||||
- Admin access to create repositories and users
|
|
||||||
- HTTPS or SSH access enabled for Git operations
|
|
||||||
|
|
||||||
**Need to set up Gitea?** See the main workshop's `GITEA-SETUP.md` for Docker + Cloudflare Tunnel instructions.
|
|
||||||
|
|
||||||
### Workshop Materials
|
|
||||||
|
|
||||||
Participants need:
|
|
||||||
- Access to the module README in `01_essentials/09-multiplayer/README.md`
|
|
||||||
- Git installed (version 2.23+)
|
|
||||||
- Python 3.6+ (to run the print project)
|
|
||||||
- Text editor
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Pre-Workshop Setup
|
|
||||||
|
|
||||||
### Step 1: Create User Accounts
|
|
||||||
|
|
||||||
Create individual Gitea accounts for each participant.
|
|
||||||
|
|
||||||
**Recommended naming:**
|
|
||||||
- `student01`, `student02`, `student03`, etc.
|
|
||||||
- Or use their real names/emails if preferred
|
|
||||||
|
|
||||||
**Two approaches:**
|
|
||||||
|
|
||||||
**Option A: Manual account creation**
|
|
||||||
1. Go to Gitea admin panel
|
|
||||||
2. Create users one by one
|
|
||||||
3. Set initial passwords (students can change later)
|
|
||||||
4. Provide credentials to students
|
|
||||||
|
|
||||||
**Option B: Self-registration** (if you trust your network)
|
|
||||||
1. Enable self-registration in Gitea settings
|
|
||||||
2. Provide registration URL to students
|
|
||||||
3. They create their own accounts
|
|
||||||
4. You verify and approve accounts
|
|
||||||
|
|
||||||
**Access tokens (recommended for HTTPS):**
|
|
||||||
- Have students create personal access tokens after logging in
|
|
||||||
- Settings → Applications → Generate New Token
|
|
||||||
- Token needs `repo` scope
|
|
||||||
- Students use token as password when pushing/pulling
|
|
||||||
|
|
||||||
### Step 2: Create the Repository
|
|
||||||
|
|
||||||
Create the shared repository: **great-print-project**
|
|
||||||
|
|
||||||
**Via Gitea web UI:**
|
|
||||||
1. Log in as admin or organization account
|
|
||||||
2. Click "+" → "New Repository"
|
|
||||||
3. **Name:** `great-print-project`
|
|
||||||
4. **Owner:** `multiplayer` (organization) or your admin account
|
|
||||||
5. **Visibility:** Private (only visible to students you add)
|
|
||||||
6. **Initialize:** Check "Initialize this repository with selected files"
|
|
||||||
7. **README:** Yes
|
|
||||||
8. **License:** None
|
|
||||||
9. **.gitignore:** Python
|
|
||||||
10. Click "Create Repository"
|
|
||||||
|
|
||||||
**Via command line (alternative):**
|
|
||||||
```bash
|
|
||||||
# Create local directory
|
|
||||||
mkdir great-print-project
|
|
||||||
cd great-print-project
|
|
||||||
|
|
||||||
# Initialize git
|
|
||||||
git init
|
|
||||||
|
|
||||||
# Add files (see Step 3)
|
|
||||||
git add .
|
|
||||||
git commit -m "Initial commit: The Great Print Project"
|
|
||||||
|
|
||||||
# Create bare repo on server
|
|
||||||
ssh user@git.frod.dk
|
|
||||||
cd /path/to/gitea/repositories/multiplayer
|
|
||||||
git init --bare great-print-project.git
|
|
||||||
exit
|
|
||||||
|
|
||||||
# Push to server
|
|
||||||
git remote add origin git@git.frod.dk:multiplayer/great-print-project.git
|
|
||||||
git push -u origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Add Starter Code to Repository
|
|
||||||
|
|
||||||
Commit these four files to the repository:
|
|
||||||
|
|
||||||
#### File 1: main.py
|
|
||||||
|
|
||||||
```python
|
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""
|
|
||||||
The Great Print Project
|
|
||||||
A collaborative Git exercise
|
|
||||||
|
|
||||||
When everyone completes their assigned functions,
|
|
||||||
this program will print the complete alphabet and numbers!
|
|
||||||
"""
|
|
||||||
|
|
||||||
from letters import print_letters
|
|
||||||
from numbers import print_numbers
|
|
||||||
|
|
||||||
def main():
|
|
||||||
print("=" * 50)
|
|
||||||
print(" THE GREAT PRINT PROJECT")
|
|
||||||
print("=" * 50)
|
|
||||||
print("\nLetters:")
|
|
||||||
print_letters()
|
|
||||||
print() # New line after letters
|
|
||||||
|
|
||||||
print("\nNumbers:")
|
|
||||||
print_numbers()
|
|
||||||
print() # New line after numbers
|
|
||||||
|
|
||||||
print("\n" + "=" * 50)
|
|
||||||
print(" PROJECT COMPLETE!")
|
|
||||||
print("=" * 50)
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
main()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### File 2: letters.py
|
|
||||||
|
|
||||||
```python
|
|
||||||
"""
|
|
||||||
Letter Printing Functions
|
|
||||||
Each pair completes their assigned functions.
|
|
||||||
|
|
||||||
Expected output: A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
|
||||||
"""
|
|
||||||
|
|
||||||
def print_a():
|
|
||||||
"""Print letter A - EXAMPLE (already completed)"""
|
|
||||||
print("A", end=" ")
|
|
||||||
|
|
||||||
def print_b():
|
|
||||||
# TODO: Pair 1 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_c():
|
|
||||||
# TODO: Pair 1 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_d():
|
|
||||||
# TODO: Pair 1 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_e():
|
|
||||||
# TODO: Pair 2 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_f():
|
|
||||||
# TODO: Pair 2 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_g():
|
|
||||||
# TODO: Pair 2 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_h():
|
|
||||||
# TODO: Pair 3 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_i():
|
|
||||||
# TODO: Pair 3 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_j():
|
|
||||||
# TODO: Pair 3 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_k():
|
|
||||||
# TODO: Pair 4 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_l():
|
|
||||||
# TODO: Pair 4 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_m():
|
|
||||||
# TODO: Pair 4 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_n():
|
|
||||||
# TODO: Pair 5 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_o():
|
|
||||||
# TODO: Pair 5 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_p():
|
|
||||||
# TODO: Pair 5 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_q():
|
|
||||||
# TODO: Pair 6 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_r():
|
|
||||||
# TODO: Pair 6 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_s():
|
|
||||||
# TODO: Pair 6 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_t():
|
|
||||||
# TODO: Pair 7 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_u():
|
|
||||||
# TODO: Pair 7 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_v():
|
|
||||||
# TODO: Pair 7 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_w():
|
|
||||||
# TODO: Pair 8 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_x():
|
|
||||||
# TODO: Pair 8 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_y():
|
|
||||||
# TODO: Pair 8 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_z():
|
|
||||||
# TODO: Pair 9 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def print_letters():
|
|
||||||
"""Print all letters A-Z"""
|
|
||||||
print_a()
|
|
||||||
print_b()
|
|
||||||
print_c()
|
|
||||||
print_d()
|
|
||||||
print_e()
|
|
||||||
print_f()
|
|
||||||
print_g()
|
|
||||||
print_h()
|
|
||||||
print_i()
|
|
||||||
print_j()
|
|
||||||
print_k()
|
|
||||||
print_l()
|
|
||||||
print_m()
|
|
||||||
print_n()
|
|
||||||
print_o()
|
|
||||||
print_p()
|
|
||||||
print_q()
|
|
||||||
print_r()
|
|
||||||
print_s()
|
|
||||||
print_t()
|
|
||||||
print_u()
|
|
||||||
print_v()
|
|
||||||
print_w()
|
|
||||||
print_x()
|
|
||||||
print_y()
|
|
||||||
print_z()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### File 3: numbers.py
|
|
||||||
|
|
||||||
```python
|
|
||||||
"""
|
|
||||||
Number Printing Functions
|
|
||||||
Each pair completes their assigned functions.
|
|
||||||
|
|
||||||
Expected output: 0 1 2 3 4 5 6 7 8 9
|
|
||||||
"""
|
|
||||||
|
|
||||||
def print_0():
|
|
||||||
# TODO: Pair 9 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_1():
|
|
||||||
# TODO: Pair 10 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_2():
|
|
||||||
# TODO: Pair 10 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_3():
|
|
||||||
# TODO: Pair 10 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_4():
|
|
||||||
# TODO: Pair 11 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_5():
|
|
||||||
# TODO: Pair 11 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_6():
|
|
||||||
# TODO: Pair 11 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_7():
|
|
||||||
# TODO: Pair 12 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_8():
|
|
||||||
# TODO: Pair 12 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
def print_9():
|
|
||||||
# TODO: Pair 12 - implement this function
|
|
||||||
pass
|
|
||||||
|
|
||||||
|
|
||||||
def print_numbers():
|
|
||||||
"""Print all numbers 0-9"""
|
|
||||||
print_0()
|
|
||||||
print_1()
|
|
||||||
print_2()
|
|
||||||
print_3()
|
|
||||||
print_4()
|
|
||||||
print_5()
|
|
||||||
print_6()
|
|
||||||
print_7()
|
|
||||||
print_8()
|
|
||||||
print_9()
|
|
||||||
```
|
|
||||||
|
|
||||||
#### File 4: assignments.md
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Pair Assignments
|
|
||||||
|
|
||||||
## How This Works
|
|
||||||
|
|
||||||
Each pair is assigned 3 functions to implement. You'll work together on a shared branch.
|
|
||||||
|
|
||||||
**Important:** Check with your facilitator for your pair number and assignment!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Assignments
|
|
||||||
|
|
||||||
### Pair 1
|
|
||||||
- **Functions:** `print_b()`, `print_c()`, `print_d()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-1-bcd`
|
|
||||||
|
|
||||||
### Pair 2
|
|
||||||
- **Functions:** `print_e()`, `print_f()`, `print_g()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-2-efg`
|
|
||||||
|
|
||||||
### Pair 3
|
|
||||||
- **Functions:** `print_h()`, `print_i()`, `print_j()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-3-hij`
|
|
||||||
|
|
||||||
### Pair 4
|
|
||||||
- **Functions:** `print_k()`, `print_l()`, `print_m()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-4-klm`
|
|
||||||
|
|
||||||
### Pair 5
|
|
||||||
- **Functions:** `print_n()`, `print_o()`, `print_p()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-5-nop`
|
|
||||||
|
|
||||||
### Pair 6
|
|
||||||
- **Functions:** `print_q()`, `print_r()`, `print_s()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-6-qrs`
|
|
||||||
|
|
||||||
### Pair 7
|
|
||||||
- **Functions:** `print_t()`, `print_u()`, `print_v()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-7-tuv`
|
|
||||||
|
|
||||||
### Pair 8
|
|
||||||
- **Functions:** `print_w()`, `print_x()`, `print_y()`
|
|
||||||
- **File:** `letters.py`
|
|
||||||
- **Branch:** `pair-8-wxy`
|
|
||||||
|
|
||||||
### Pair 9
|
|
||||||
- **Functions:** `print_z()`, `print_0()`, `print_1()`
|
|
||||||
- **Files:** `letters.py`, `numbers.py`
|
|
||||||
- **Branch:** `pair-9-z01`
|
|
||||||
|
|
||||||
### Pair 10
|
|
||||||
- **Functions:** `print_2()`, `print_3()`, `print_4()`
|
|
||||||
- **File:** `numbers.py`
|
|
||||||
- **Branch:** `pair-10-234`
|
|
||||||
|
|
||||||
### Pair 11
|
|
||||||
- **Functions:** `print_5()`, `print_6()`, `print_7()`
|
|
||||||
- **File:** `numbers.py`
|
|
||||||
- **Branch:** `pair-11-567`
|
|
||||||
|
|
||||||
### Pair 12
|
|
||||||
- **Functions:** `print_8()`, `print_9()`
|
|
||||||
- **File:** `numbers.py`
|
|
||||||
- **Branch:** `pair-12-89`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Example Implementation
|
|
||||||
|
|
||||||
```python
|
|
||||||
def print_a():
|
|
||||||
"""Print letter A - EXAMPLE (already completed)"""
|
|
||||||
print("A", end=" ")
|
|
||||||
```
|
|
||||||
|
|
||||||
**Your functions should follow the same pattern:**
|
|
||||||
|
|
||||||
```python
|
|
||||||
def print_x():
|
|
||||||
"""Print letter/number X"""
|
|
||||||
print("X", end=" ")
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Testing
|
|
||||||
|
|
||||||
After implementing your functions, test with:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
You should see your letters/numbers in the output!
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Questions?
|
|
||||||
|
|
||||||
Ask your facilitator for:
|
|
||||||
- Your pair number
|
|
||||||
- Your Gitea credentials
|
|
||||||
- Help with authentication setup
|
|
||||||
- Any Git or Python issues
|
|
||||||
```
|
|
||||||
|
|
||||||
#### File 5: README.md (in repository)
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# The Great Print Project 🎯
|
|
||||||
|
|
||||||
A collaborative Git exercise for learning teamwork with version control!
|
|
||||||
|
|
||||||
## Goal
|
|
||||||
|
|
||||||
When everyone completes their assigned functions, running `python main.py` will print:
|
|
||||||
|
|
||||||
```
|
|
||||||
==================================================
|
|
||||||
THE GREAT PRINT PROJECT
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
Letters:
|
|
||||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
|
||||||
|
|
||||||
Numbers:
|
|
||||||
0 1 2 3 4 5 6 7 8 9
|
|
||||||
|
|
||||||
==================================================
|
|
||||||
PROJECT COMPLETE!
|
|
||||||
==================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
## Your Mission
|
|
||||||
|
|
||||||
1. Find your pair assignment in `assignments.md`
|
|
||||||
2. Clone this repository
|
|
||||||
3. Create your feature branch
|
|
||||||
4. Implement your assigned functions
|
|
||||||
5. Practice collaboration: push, pull, resolve conflicts
|
|
||||||
6. Create a pull request
|
|
||||||
7. Celebrate when your code is merged!
|
|
||||||
|
|
||||||
## Quick Start
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone the repository
|
|
||||||
git clone https://git.frod.dk/multiplayer/great-print-project.git
|
|
||||||
cd great-print-project
|
|
||||||
|
|
||||||
# Check your assignment
|
|
||||||
cat assignments.md
|
|
||||||
|
|
||||||
# Create your branch (replace X with your pair number)
|
|
||||||
git switch -c pair-X-feature
|
|
||||||
|
|
||||||
# Edit your file (letters.py or numbers.py)
|
|
||||||
# Implement your functions
|
|
||||||
|
|
||||||
# Test it
|
|
||||||
python main.py
|
|
||||||
|
|
||||||
# Commit and push
|
|
||||||
git add .
|
|
||||||
git commit -m "Implement print_x() functions"
|
|
||||||
git push -u origin pair-X-feature
|
|
||||||
```
|
|
||||||
|
|
||||||
## Files
|
|
||||||
|
|
||||||
- **main.py** - Orchestrator (runs the whole program)
|
|
||||||
- **letters.py** - Functions for printing A-Z
|
|
||||||
- **numbers.py** - Functions for printing 0-9
|
|
||||||
- **assignments.md** - See which functions your pair should implement
|
|
||||||
|
|
||||||
## Need Help?
|
|
||||||
|
|
||||||
See the module README in the workshop repository for detailed step-by-step instructions!
|
|
||||||
|
|
||||||
**Happy Collaborating! 🚀**
|
|
||||||
```
|
|
||||||
|
|
||||||
**Commit these files:**
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git add main.py letters.py numbers.py assignments.md README.md
|
|
||||||
git commit -m "Add Great Print Project starter code"
|
|
||||||
git push origin main
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Grant Student Access
|
|
||||||
|
|
||||||
Add students as collaborators with write access:
|
|
||||||
|
|
||||||
**Via Gitea web UI:**
|
|
||||||
1. Go to repository → Settings → Collaborators
|
|
||||||
2. Add each student account
|
|
||||||
3. Set permission level: **Write** (allows push, pull, branch creation)
|
|
||||||
|
|
||||||
**Important:** Students need **Write** access to:
|
|
||||||
- Create branches
|
|
||||||
- Push commits
|
|
||||||
- Create pull requests
|
|
||||||
|
|
||||||
### Step 5: Configure Branch Protection (Optional)
|
|
||||||
|
|
||||||
To prevent accidental pushes to main:
|
|
||||||
|
|
||||||
1. Repository → Settings → Branches
|
|
||||||
2. Add protection rule for `main` branch
|
|
||||||
3. Settings:
|
|
||||||
- **Block direct pushes:** Yes (requires pull requests)
|
|
||||||
- **Require PR reviews:** Optional (you can review PRs yourself)
|
|
||||||
- **Auto-merge:** Disabled (you merge manually or students do)
|
|
||||||
|
|
||||||
This ensures students:
|
|
||||||
- MUST use feature branches
|
|
||||||
- MUST create pull requests
|
|
||||||
- Can't accidentally break main
|
|
||||||
|
|
||||||
**For beginners:** Consider allowing students to merge their own PRs after approval to complete the full workflow.
|
|
||||||
|
|
||||||
### Step 6: Test the Setup
|
|
||||||
|
|
||||||
Before the workshop, test as a student would:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Clone as a test student
|
|
||||||
git clone https://git.frod.dk/multiplayer/great-print-project.git
|
|
||||||
cd great-print-project
|
|
||||||
|
|
||||||
# Run the program
|
|
||||||
python main.py
|
|
||||||
# Should show only "A" with missing letters/numbers
|
|
||||||
|
|
||||||
# Create test branch
|
|
||||||
git switch -c test-branch
|
|
||||||
|
|
||||||
# Edit letters.py, add print_b()
|
|
||||||
def print_b():
|
|
||||||
print("B", end=" ")
|
|
||||||
|
|
||||||
# Commit and push
|
|
||||||
git add letters.py
|
|
||||||
git commit -m "Test commit"
|
|
||||||
git push -u origin test-branch
|
|
||||||
|
|
||||||
# Create test pull request
|
|
||||||
# (Do this via web UI)
|
|
||||||
|
|
||||||
# Clean up test branch after
|
|
||||||
git push origin --delete test-branch
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## During the Workshop
|
|
||||||
|
|
||||||
### Pairing Students
|
|
||||||
|
|
||||||
**Strategies for assigning pairs:**
|
|
||||||
|
|
||||||
**Option 1: Random pairing**
|
|
||||||
- Use a random number generator
|
|
||||||
- Pair students as they arrive
|
|
||||||
|
|
||||||
**Option 2: Skill-based pairing**
|
|
||||||
- Mix experienced and beginner students
|
|
||||||
- Balance pair capabilities
|
|
||||||
|
|
||||||
**Option 3: Let them choose**
|
|
||||||
- Students pick their own partners
|
|
||||||
- Good for building team dynamics
|
|
||||||
|
|
||||||
**Announce pairs clearly:**
|
|
||||||
- Write on board/screen: "Pair 1: Alice & Bob"
|
|
||||||
- Provide printed assignment sheet
|
|
||||||
- Update `assignments.md` in repo if needed
|
|
||||||
|
|
||||||
### Timeline
|
|
||||||
|
|
||||||
**Suggested schedule for 2-hour session:**
|
|
||||||
|
|
||||||
- **0:00-0:10** (10 min): Introduction, distribute credentials
|
|
||||||
- **0:10-0:20** (10 min): Students clone repo, verify access
|
|
||||||
- **0:20-0:35** (15 min): Part 1 - Getting Started
|
|
||||||
- **0:35-0:55** (20 min): Part 2 - First Contribution
|
|
||||||
- **0:55-1:25** (30 min): Part 3 - Conflict Exercise (key learning!)
|
|
||||||
- **1:25-1:45** (20 min): Part 4 - Pull Requests
|
|
||||||
- **1:45-2:00** (15 min): Part 5 - Syncing, Q&A, wrap-up
|
|
||||||
|
|
||||||
### Monitoring Progress
|
|
||||||
|
|
||||||
**Use Gitea to track:**
|
|
||||||
|
|
||||||
1. **Branches created:** Repository → Branches
|
|
||||||
- Should see `pair-1-bcd`, `pair-2-efg`, etc.
|
|
||||||
|
|
||||||
2. **Commits:** Repository → Commits
|
|
||||||
- Each pair should have multiple commits
|
|
||||||
|
|
||||||
3. **Pull requests:** Repository → Pull Requests
|
|
||||||
- Should see one PR per pair
|
|
||||||
|
|
||||||
**Walk around the room:**
|
|
||||||
- Check screens for conflict markers
|
|
||||||
- Ask pairs how they're resolving conflicts
|
|
||||||
- Ensure both partners are engaged
|
|
||||||
|
|
||||||
**Common issues to watch for:**
|
|
||||||
- Partners not using the same branch name
|
|
||||||
- Forgetting to pull before pushing
|
|
||||||
- Not removing conflict markers completely
|
|
||||||
- Committing to main instead of feature branch
|
|
||||||
|
|
||||||
### Managing Pull Requests
|
|
||||||
|
|
||||||
**Your role:**
|
|
||||||
|
|
||||||
**Option A: Review and merge yourself**
|
|
||||||
- Teaches students what good reviews look like
|
|
||||||
- Ensures quality before merging
|
|
||||||
- More facilitator work
|
|
||||||
|
|
||||||
**Option B: Students merge their own**
|
|
||||||
- More autonomous learning
|
|
||||||
- Students experience complete workflow
|
|
||||||
- Risk of messy main branch
|
|
||||||
|
|
||||||
**Recommended approach:**
|
|
||||||
1. First 2-3 PRs: You review and merge (demonstrate good practices)
|
|
||||||
2. Remaining PRs: Students review each other, you approve
|
|
||||||
3. Students can merge after approval
|
|
||||||
|
|
||||||
**What to check in PR reviews:**
|
|
||||||
- Functions implemented correctly
|
|
||||||
- No conflict markers in code
|
|
||||||
- Code follows pattern (e.g., `print("X", end=" ")`)
|
|
||||||
- Meaningful commit messages
|
|
||||||
|
|
||||||
### Handling Problems
|
|
||||||
|
|
||||||
**Common issues and solutions:**
|
|
||||||
|
|
||||||
**Problem: "I can't push!"**
|
|
||||||
- Check they're authenticated (token or SSH key)
|
|
||||||
- Check they pulled latest changes first
|
|
||||||
- Check branch name matches their partner's
|
|
||||||
|
|
||||||
**Problem: "Merge conflict won't resolve!"**
|
|
||||||
- Walk through Part 3 step-by-step with them
|
|
||||||
- Show them the conflict markers
|
|
||||||
- Verify they removed ALL markers
|
|
||||||
- Run `python main.py` together to test
|
|
||||||
|
|
||||||
**Problem: "We both committed to main!"**
|
|
||||||
- Have them create proper feature branch
|
|
||||||
- Use `git cherry-pick` to move commits
|
|
||||||
- Reset main to origin/main
|
|
||||||
|
|
||||||
**Problem: "GitHub Desktop / GUI tool shows something different"**
|
|
||||||
- Recommend command line for this exercise
|
|
||||||
- GUIs can hide important details during conflicts
|
|
||||||
|
|
||||||
### Celebrating Success
|
|
||||||
|
|
||||||
When all pairs have merged:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git pull origin main
|
|
||||||
python main.py
|
|
||||||
```
|
|
||||||
|
|
||||||
**Everyone should see:**
|
|
||||||
```
|
|
||||||
==================================================
|
|
||||||
THE GREAT PRINT PROJECT
|
|
||||||
==================================================
|
|
||||||
|
|
||||||
Letters:
|
|
||||||
A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
|
|
||||||
|
|
||||||
Numbers:
|
|
||||||
0 1 2 3 4 5 6 7 8 9
|
|
||||||
|
|
||||||
==================================================
|
|
||||||
PROJECT COMPLETE!
|
|
||||||
==================================================
|
|
||||||
```
|
|
||||||
|
|
||||||
**Take a screenshot!** Share it with the class. This is a genuine collaborative achievement.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Post-Workshop
|
|
||||||
|
|
||||||
### Cleanup (Optional)
|
|
||||||
|
|
||||||
**Keep the repository for future workshops:**
|
|
||||||
- Delete all feature branches: `git push origin --delete pair-1-bcd` (etc.)
|
|
||||||
- Reset main to initial state
|
|
||||||
- Reuse for next cohort
|
|
||||||
|
|
||||||
**Archive the session:**
|
|
||||||
- Export final repository state
|
|
||||||
- Take screenshots of successful PRs
|
|
||||||
- Save for portfolio/examples
|
|
||||||
|
|
||||||
### Student Takeaways
|
|
||||||
|
|
||||||
Provide students:
|
|
||||||
- Link to repository (they can clone for reference)
|
|
||||||
- Completion certificate (if applicable)
|
|
||||||
- Next steps: contributing to open source, Git resources
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Troubleshooting
|
|
||||||
|
|
||||||
### Gitea Server Issues
|
|
||||||
|
|
||||||
**Problem: Server unreachable**
|
|
||||||
- Check Cloudflare Tunnel is running: `cloudflared tunnel info`
|
|
||||||
- Verify Gitea container is up: `docker ps`
|
|
||||||
- Check firewall rules
|
|
||||||
|
|
||||||
**Problem: SSH not working**
|
|
||||||
- Verify SSH port is exposed in docker-compose.yml
|
|
||||||
- Check Cloudflare Tunnel config includes SSH
|
|
||||||
- Test: `ssh -T git@git.frod.dk`
|
|
||||||
|
|
||||||
**Problem: HTTPS clone fails**
|
|
||||||
- Check certificate validity
|
|
||||||
- Try `GIT_SSL_NO_VERIFY=true git clone ...` (temporary workaround)
|
|
||||||
- Configure Gitea to use proper HTTPS certificates
|
|
||||||
|
|
||||||
### Authentication Issues
|
|
||||||
|
|
||||||
**Problem: Students can't log in**
|
|
||||||
- Verify accounts created and active
|
|
||||||
- Reset passwords if needed
|
|
||||||
- Check email verification isn't blocking (disable for workshop)
|
|
||||||
|
|
||||||
**Problem: Push fails with authentication error**
|
|
||||||
- HTTPS: Ensure students use access token, not password
|
|
||||||
- SSH: Verify keys added to Gitea account
|
|
||||||
- Check repo permissions (must be Write, not Read)
|
|
||||||
|
|
||||||
### Git Workflow Issues
|
|
||||||
|
|
||||||
**Problem: Students create PR but can't merge**
|
|
||||||
- Check branch protection rules
|
|
||||||
- Verify they have Write access
|
|
||||||
- Ensure PR doesn't have conflicts
|
|
||||||
|
|
||||||
**Problem: Main branch gets messy**
|
|
||||||
- Reset to last good commit: `git reset --hard <commit>`
|
|
||||||
- Force push: `git push --force origin main` (CAREFUL!)
|
|
||||||
- Or start fresh: delete repo, recreate with starter code
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Tips for Success
|
|
||||||
|
|
||||||
### Before Workshop
|
|
||||||
|
|
||||||
- Test the entire flow yourself as a student
|
|
||||||
- Prepare credential sheets for each student
|
|
||||||
- Have backup plan if server goes down (local git exercise)
|
|
||||||
- Prepare slides explaining merge conflicts visually
|
|
||||||
|
|
||||||
### During Workshop
|
|
||||||
|
|
||||||
- **Start on time** - respect everyone's schedule
|
|
||||||
- **Pair programming** - ensure both partners engage
|
|
||||||
- **Encourage talking** - best conflicts are resolved by discussion
|
|
||||||
- **Celebrate small wins** - first push, first conflict resolution
|
|
||||||
- **Walk the room** - see screens, answer questions live
|
|
||||||
|
|
||||||
### After Workshop
|
|
||||||
|
|
||||||
- Gather feedback - what worked, what didn't
|
|
||||||
- Note timing - were parts too rushed or too slow?
|
|
||||||
- Archive successful PRs as examples
|
|
||||||
- Plan improvements for next session
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Scaling Considerations
|
|
||||||
|
|
||||||
### Small Groups (4-8 students, 2-4 pairs)
|
|
||||||
|
|
||||||
- More hands-on facilitator time
|
|
||||||
- Can review all PRs in detail
|
|
||||||
- Easier to monitor progress
|
|
||||||
|
|
||||||
### Medium Groups (10-20 students, 5-10 pairs)
|
|
||||||
|
|
||||||
- Recommended size
|
|
||||||
- Good mix of collaboration and individual attention
|
|
||||||
- Helps if you have a teaching assistant
|
|
||||||
|
|
||||||
### Large Groups (20+ students, 10+ pairs)
|
|
||||||
|
|
||||||
- Consider multiple repositories (split into groups of 12 pairs max)
|
|
||||||
- Recruit teaching assistants to help monitor
|
|
||||||
- Use breakout rooms (if online)
|
|
||||||
- Automate more (less PR review, more self-merging)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Additional Resources
|
|
||||||
|
|
||||||
### For You (Facilitator)
|
|
||||||
|
|
||||||
- Gitea documentation: https://docs.gitea.io/
|
|
||||||
- Pro Git book (free): https://git-scm.com/book/en/v2
|
|
||||||
- Teaching Git: https://git-scm.com/doc
|
|
||||||
|
|
||||||
### For Students
|
|
||||||
|
|
||||||
- Git cheatsheet (included in workshop repo)
|
|
||||||
- Interactive Git tutorial: https://learngitbranching.js.org/
|
|
||||||
- Oh Shit Git: https://ohshitgit.com/ (recovering from mistakes)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Questions or Issues?
|
|
||||||
|
|
||||||
This guide should cover most scenarios. If you encounter issues not listed here:
|
|
||||||
|
|
||||||
1. Check Gitea logs: `docker logs gitea-container-name`
|
|
||||||
2. Test with minimal setup (single test student)
|
|
||||||
3. Consult Gitea documentation
|
|
||||||
4. Reach out to workshop repository maintainers
|
|
||||||
|
|
||||||
**Good luck with your workshop! The multiplayer module is where Git skills really come alive.**
|
|
||||||
File diff suppressed because it is too large
Load Diff
717
02-advanced/01-reset/README.md
Normal file
717
02-advanced/01-reset/README.md
Normal file
@@ -0,0 +1,717 @@
|
|||||||
|
# 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!**
|
||||||
24
02-advanced/01-reset/reset.ps1
Normal file
24
02-advanced/01-reset/reset.ps1
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#!/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"
|
||||||
359
02-advanced/01-reset/setup.ps1
Normal file
359
02-advanced/01-reset/setup.ps1
Normal file
@@ -0,0 +1,359 @@
|
|||||||
|
#!/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 ""
|
||||||
231
02-advanced/01-reset/verify.ps1
Normal file
231
02-advanced/01-reset/verify.ps1
Normal file
@@ -0,0 +1,231 @@
|
|||||||
|
#!/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
|
||||||
|
}
|
||||||
@@ -74,7 +74,7 @@ Use bisect when:
|
|||||||
|
|
||||||
## Useful Commands
|
## Useful Commands
|
||||||
|
|
||||||
```bash
|
```powershell
|
||||||
# Start bisect session
|
# Start bisect session
|
||||||
git bisect start
|
git bisect start
|
||||||
|
|
||||||
@@ -112,7 +112,7 @@ git bisect log
|
|||||||
|
|
||||||
Run the verification script to check your solution:
|
Run the verification script to check your solution:
|
||||||
|
|
||||||
```bash
|
```powershell
|
||||||
.\verify.ps1
|
.\verify.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -152,7 +152,7 @@ The verification will check that:
|
|||||||
## Manual vs Automated Bisect
|
## Manual vs Automated Bisect
|
||||||
|
|
||||||
### Manual Bisect
|
### Manual Bisect
|
||||||
```bash
|
```powershell
|
||||||
git bisect start
|
git bisect start
|
||||||
git bisect bad
|
git bisect bad
|
||||||
git bisect good HEAD~20
|
git bisect good HEAD~20
|
||||||
@@ -165,7 +165,7 @@ git bisect reset
|
|||||||
```
|
```
|
||||||
|
|
||||||
### Automated Bisect
|
### Automated Bisect
|
||||||
```bash
|
```powershell
|
||||||
git bisect start
|
git bisect start
|
||||||
git bisect bad
|
git bisect bad
|
||||||
git bisect good HEAD~20
|
git bisect good HEAD~20
|
||||||
@@ -182,22 +182,28 @@ The test script should exit with:
|
|||||||
## Bisect Workflow Example
|
## Bisect Workflow Example
|
||||||
|
|
||||||
### Finding a Performance Regression
|
### Finding a Performance Regression
|
||||||
```bash
|
```powershell
|
||||||
# App is slow now, was fast 50 commits ago
|
# App is slow now, was fast 50 commits ago
|
||||||
git bisect start
|
git bisect start
|
||||||
git bisect bad
|
git bisect bad
|
||||||
git bisect good HEAD~50
|
git bisect good HEAD~50
|
||||||
|
|
||||||
# Create test script
|
# Create test script
|
||||||
echo '#!/bin/bash\ntime npm start | grep "Started in"' > test.sh
|
@'
|
||||||
chmod +x test.sh
|
$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 ./test.sh
|
git bisect run pwsh test.ps1
|
||||||
# Git finds the commit that made it slow
|
# Git finds the commit that made it slow
|
||||||
```
|
```
|
||||||
|
|
||||||
### Finding When a Feature Broke
|
### Finding When a Feature Broke
|
||||||
```bash
|
```powershell
|
||||||
git bisect start
|
git bisect start
|
||||||
git bisect bad
|
git bisect bad
|
||||||
git bisect good v1.0.0 # Last known good version
|
git bisect good v1.0.0 # Last known good version
|
||||||
146
BEST-PRACTICES.md
Normal file
146
BEST-PRACTICES.md
Normal file
@@ -0,0 +1,146 @@
|
|||||||
|
# Best Practices for Cloud-Based Git Collaboration
|
||||||
|
|
||||||
|
When multiple people work on the same project, merge conflicts are inevitable. But with good habits, you can dramatically reduce how often they happen and how painful they are to resolve.
|
||||||
|
|
||||||
|
## Pull Early, Pull Often
|
||||||
|
|
||||||
|
The most common cause of merge conflicts is working on outdated code. The longer you work without syncing, the more your code drifts from what others are doing.
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
```bash
|
||||||
|
# Start every work session by pulling
|
||||||
|
git pull
|
||||||
|
|
||||||
|
# Pull again before pushing
|
||||||
|
git pull
|
||||||
|
git push
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works:** Small, frequent syncs mean small, manageable conflicts. A conflict in 3 lines is easy to fix. A conflict in 300 lines is a nightmare.
|
||||||
|
|
||||||
|
## Keep Commits Small and Focused
|
||||||
|
|
||||||
|
Large commits that touch many files are conflict magnets.
|
||||||
|
|
||||||
|
**Instead of this:**
|
||||||
|
```
|
||||||
|
"Implemented user authentication, fixed navbar, updated styles, refactored database"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
```
|
||||||
|
"Add login form to auth page"
|
||||||
|
"Add password validation"
|
||||||
|
"Connect login form to auth API"
|
||||||
|
"Add logout button to navbar"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works:**
|
||||||
|
- Smaller changes = smaller chance of overlap with others
|
||||||
|
- If a conflict does happen, it's easier to understand and resolve
|
||||||
|
- Easier to review, revert, or cherry-pick specific changes
|
||||||
|
|
||||||
|
## Communicate About Shared Files
|
||||||
|
|
||||||
|
Some files are conflict hotspots because everyone needs to edit them:
|
||||||
|
- Configuration files
|
||||||
|
- Route definitions
|
||||||
|
- Database schemas
|
||||||
|
- Shared constants or types
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
- Tell your team when you're editing shared files
|
||||||
|
- Make those changes in dedicated commits
|
||||||
|
- Push changes to shared files quickly - don't let them sit
|
||||||
|
|
||||||
|
## Use Short-Lived Branches
|
||||||
|
|
||||||
|
Long-running branches drift further from the main branch every day.
|
||||||
|
|
||||||
|
```
|
||||||
|
main: A───B───C───D───E───F───G───H
|
||||||
|
\
|
||||||
|
feature: X───────────────────────Y
|
||||||
|
(2 weeks of drift = painful merge)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Better approach:**
|
||||||
|
```
|
||||||
|
main: A───B───C───D───E───F───G───H
|
||||||
|
\ \ \
|
||||||
|
feature: X───────Y───────Z
|
||||||
|
(merge main frequently)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
- Merge main into your branch regularly (daily if active)
|
||||||
|
- Keep features small enough to complete in days, not weeks
|
||||||
|
- Break large features into smaller incremental changes
|
||||||
|
|
||||||
|
## Organize Code to Minimize Overlap
|
||||||
|
|
||||||
|
How you structure your code affects how often people collide.
|
||||||
|
|
||||||
|
**Conflict-prone structure:**
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
app.py # 2000 lines, everyone edits this
|
||||||
|
utils.py # 500 lines of mixed utilities
|
||||||
|
```
|
||||||
|
|
||||||
|
**Better structure:**
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
auth/
|
||||||
|
login.py
|
||||||
|
logout.py
|
||||||
|
users/
|
||||||
|
profile.py
|
||||||
|
settings.py
|
||||||
|
utils/
|
||||||
|
dates.py
|
||||||
|
strings.py
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why it works:** When each file has a clear purpose, different team members naturally work in different files.
|
||||||
|
|
||||||
|
## Avoid Reformatting Wars
|
||||||
|
|
||||||
|
Nothing creates unnecessary conflicts like two people reformatting the same file differently.
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
- Agree on code formatting standards as a team
|
||||||
|
- Use automatic formatters (Prettier, Black, etc.)
|
||||||
|
- Configure your editor to format on save
|
||||||
|
- Run formatters before committing
|
||||||
|
|
||||||
|
**Important:** If you need to reformat a file, do it in a dedicated commit with no other changes. This keeps the reformatting separate from your actual work.
|
||||||
|
|
||||||
|
## Coordinate Large Refactors
|
||||||
|
|
||||||
|
Renaming a widely-used function or moving files around will conflict with almost everyone's work.
|
||||||
|
|
||||||
|
**Do this:**
|
||||||
|
- Announce refactors to the team before starting
|
||||||
|
- Do them quickly and push immediately
|
||||||
|
- Consider doing them when others aren't actively working
|
||||||
|
- Keep refactoring commits separate from feature work
|
||||||
|
|
||||||
|
## The Golden Rules
|
||||||
|
|
||||||
|
1. **Sync frequently** - Pull before you start, pull before you push
|
||||||
|
2. **Commit small** - Many small commits beat one large commit
|
||||||
|
3. **Talk to your team** - A quick message prevents hours of conflict resolution
|
||||||
|
4. **Stay focused** - One branch = one purpose
|
||||||
|
5. **Push promptly** - Don't sit on finished work
|
||||||
|
|
||||||
|
## When Conflicts Do Happen
|
||||||
|
|
||||||
|
Even with best practices, conflicts will occur. When they do:
|
||||||
|
|
||||||
|
1. **Don't panic** - Conflicts are normal, not failures
|
||||||
|
2. **Read carefully** - Understand both sides before choosing
|
||||||
|
3. **Test after resolving** - Make sure the merged code actually works
|
||||||
|
4. **Ask if unsure** - If you don't understand the other person's code, ask them
|
||||||
|
|
||||||
|
Remember: merge conflicts are a communication problem as much as a technical one. The best tool for reducing conflicts is talking to your team.
|
||||||
123
COMMIT-MESSAGES.md
Normal file
123
COMMIT-MESSAGES.md
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
# Writing Good Commit Messages
|
||||||
|
|
||||||
|
A good commit message explains **what** the change does and **why** it matters. Your future self (and your teammates) will thank you.
|
||||||
|
|
||||||
|
## The Golden Rule: Write the Intent
|
||||||
|
|
||||||
|
Write your message as a command - what will this commit **do** when applied?
|
||||||
|
|
||||||
|
**Good (imperative, present tense):**
|
||||||
|
```
|
||||||
|
Add login button to navbar
|
||||||
|
Fix crash when username is empty
|
||||||
|
Remove unused database connection
|
||||||
|
```
|
||||||
|
|
||||||
|
**Avoid (past tense, describing what you did):**
|
||||||
|
```
|
||||||
|
Added login button to navbar
|
||||||
|
Fixed crash when username is empty
|
||||||
|
Removed unused database connection
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why?** Think of it as completing this sentence:
|
||||||
|
> "If applied, this commit will... **add login button to navbar**"
|
||||||
|
|
||||||
|
## Use Prefixes to Categorize
|
||||||
|
|
||||||
|
Start your message with a prefix that tells readers what kind of change this is:
|
||||||
|
|
||||||
|
| Prefix | Use For | Example |
|
||||||
|
|--------|---------|---------|
|
||||||
|
| `feat:` | New features | `feat: add password reset flow` |
|
||||||
|
| `fix:` | Bug fixes | `fix: prevent duplicate form submission` |
|
||||||
|
| `docs:` | Documentation only | `docs: add API examples to README` |
|
||||||
|
| `style:` | Formatting, no code change | `style: fix indentation in auth module` |
|
||||||
|
| `refactor:` | Code change that doesn't fix or add | `refactor: extract validation logic` |
|
||||||
|
| `test:` | Adding or fixing tests | `test: add unit tests for login` |
|
||||||
|
| `chore:` | Maintenance, dependencies | `chore: update pytest to 8.0` |
|
||||||
|
|
||||||
|
## Keep It Short
|
||||||
|
|
||||||
|
The first line should be **50 characters or less**. This ensures it displays properly in:
|
||||||
|
- Git log output
|
||||||
|
- GitHub/Azure DevOps commit lists
|
||||||
|
- Email notifications
|
||||||
|
|
||||||
|
```
|
||||||
|
fix: resolve memory leak in image processing
|
||||||
|
│ │
|
||||||
|
└──────────── 45 characters ─────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Add Details When Needed
|
||||||
|
|
||||||
|
For complex changes, add a blank line and then more context:
|
||||||
|
|
||||||
|
```
|
||||||
|
fix: prevent crash when user uploads empty file
|
||||||
|
|
||||||
|
The application crashed because we tried to read the first byte
|
||||||
|
of an empty file. Now we check file size before processing.
|
||||||
|
|
||||||
|
Closes #142
|
||||||
|
```
|
||||||
|
|
||||||
|
Use the body to explain:
|
||||||
|
- **Why** this change was necessary
|
||||||
|
- **What** was the problem or context
|
||||||
|
- **How** does this approach solve it (if not obvious)
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
**Simple bug fix:**
|
||||||
|
```
|
||||||
|
fix: correct tax calculation for EU customers
|
||||||
|
```
|
||||||
|
|
||||||
|
**New feature:**
|
||||||
|
```
|
||||||
|
feat: add dark mode toggle to settings
|
||||||
|
```
|
||||||
|
|
||||||
|
**With context:**
|
||||||
|
```
|
||||||
|
refactor: split user service into smaller modules
|
||||||
|
|
||||||
|
The user service had grown to 800 lines and was handling
|
||||||
|
authentication, profile management, and notifications.
|
||||||
|
Split into three focused modules for maintainability.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Documentation:**
|
||||||
|
```
|
||||||
|
docs: add setup instructions for Windows
|
||||||
|
```
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
```
|
||||||
|
<prefix>: <what this commit does>
|
||||||
|
|
||||||
|
[optional body: why and how]
|
||||||
|
|
||||||
|
[optional footer: references issues]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Checklist:**
|
||||||
|
- [ ] Starts with a prefix (`feat:`, `fix:`, etc.)
|
||||||
|
- [ ] Uses imperative mood ("add" not "added")
|
||||||
|
- [ ] First line under 50 characters
|
||||||
|
- [ ] Explains why, not just what (for complex changes)
|
||||||
|
|
||||||
|
## Common Mistakes
|
||||||
|
|
||||||
|
| Instead of... | Write... |
|
||||||
|
|---------------|----------|
|
||||||
|
| `fixed bug` | `fix: prevent null pointer in search` |
|
||||||
|
| `updates` | `feat: add email notifications` |
|
||||||
|
| `WIP` | `feat: add basic form validation` |
|
||||||
|
| `stuff` | `chore: clean up unused imports` |
|
||||||
|
| `fix: fix the bug` | `fix: handle empty input in calculator` |
|
||||||
|
|
||||||
|
Your commit history tells the story of your project. Make it a story worth reading.
|
||||||
@@ -247,6 +247,16 @@ git revert --abort
|
|||||||
```
|
```
|
||||||
Abort a revert in progress.
|
Abort a revert in progress.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git revert -m 1 <merge-commit>
|
||||||
|
```
|
||||||
|
Revert a merge commit (requires -m flag to specify which parent to keep). Use `-m 1` to keep the branch you merged into (most common).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git revert -m 2 <merge-commit>
|
||||||
|
```
|
||||||
|
Revert a merge commit but keep the branch that was merged in (rare).
|
||||||
|
|
||||||
### Cherry-Pick
|
### Cherry-Pick
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
337
INSTALLATION.md
Normal file
337
INSTALLATION.md
Normal file
@@ -0,0 +1,337 @@
|
|||||||
|
# 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!
|
||||||
448
README.md
448
README.md
@@ -1,35 +1,42 @@
|
|||||||
# Git Workshop
|
# Git Workshop
|
||||||
|
|
||||||
A comprehensive, hands-on Git workshop with 13 progressive modules covering everything from basic commits to advanced debugging techniques. Each module presents a real-world scenario that you must solve using Git commands, with automated verification to confirm your solution.
|
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, bisecting, worktrees, and more.
|
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
|
## Workshop Structure
|
||||||
|
|
||||||
Each module is a self-contained challenge that teaches specific git concepts:
|
The workshop is organized into two tracks:
|
||||||
|
|
||||||
### Beginner Level
|
### 01 Essentials - Core Git Skills (8 modules)
|
||||||
- **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 Basics - Create, switch, and manage branches
|
|
||||||
|
|
||||||
### Intermediate Level
|
Master fundamental Git concepts and collaborative workflows:
|
||||||
- **Module 04**: Merging - Combine branches with fast-forward and three-way merges
|
|
||||||
- **Module 05**: Merge Conflicts - Identify, understand, and resolve merge conflicts
|
|
||||||
- **Module 06**: Rebasing - Rebase branches to create linear history
|
|
||||||
- **Module 07**: Interactive Rebase - Clean up commit history with reset and recommit
|
|
||||||
- **Module 08**: Cherry-Pick - Apply specific commits from one branch to another
|
|
||||||
|
|
||||||
### Advanced Level
|
- **Module 01: Git Basics** - Initialize repositories, stage changes, make commits
|
||||||
- **Module 09**: Reset vs Revert - Understand when to rewrite history vs create new commits
|
- **Module 02: Viewing History** - Use git log and git diff to explore project history
|
||||||
- **Module 10**: Stash - Temporarily save work without committing
|
- **Module 03: Branching and Merging** - Create branches, merge them, and resolve conflicts (checkpoint-based)
|
||||||
- **Module 11**: Working with Remotes - Clone, push, pull, and fetch from remote repositories
|
- **Module 04: Cherry-Pick** - Apply specific commits from one branch to another
|
||||||
- **Module 12**: Worktrees - Work on multiple branches simultaneously
|
- **Module 05: Git Revert** - Safe undoing - preserve history while reversing changes (includes merge commit reversion)
|
||||||
- **Module 13**: Bisect - Use binary search to find bugs in commit history
|
- **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
|
## How to Use This Workshop
|
||||||
|
|
||||||
1. Navigate to a module directory (e.g., `module-01-basics`)
|
### 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
|
2. Read the `README.md` to understand the challenge
|
||||||
3. Run `./setup.ps1` to create the challenge environment
|
3. Run `./setup.ps1` to create the challenge environment
|
||||||
4. Complete the challenge using git commands
|
4. Complete the challenge using git commands
|
||||||
@@ -38,11 +45,23 @@ Each module is a self-contained challenge that teaches specific git concepts:
|
|||||||
|
|
||||||
**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!
|
**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
|
## Running PowerShell Scripts
|
||||||
|
|
||||||
Each module includes setup, verification, and reset scripts. Here's how to run them:
|
Most modules include setup, verification, and reset scripts.
|
||||||
|
|
||||||
### Windows
|
|
||||||
|
|
||||||
If you encounter an "execution policy" error when running scripts, open PowerShell as Administrator and run:
|
If you encounter an "execution policy" error when running scripts, open PowerShell as Administrator and run:
|
||||||
|
|
||||||
@@ -53,69 +72,86 @@ Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|||||||
Then run scripts using:
|
Then run scripts using:
|
||||||
```powershell
|
```powershell
|
||||||
.\setup.ps1
|
.\setup.ps1
|
||||||
|
.\verify.ps1
|
||||||
|
.\reset.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
### macOS/Linux
|
|
||||||
|
|
||||||
First, make sure scripts are executable (only needed once):
|
|
||||||
```bash
|
|
||||||
chmod +x module-*/setup.ps1 module-*/verify.ps1 module-*/reset.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Then run scripts using:
|
|
||||||
```bash
|
|
||||||
./setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
Or explicitly with PowerShell:
|
|
||||||
```bash
|
|
||||||
pwsh setup.ps1
|
|
||||||
```
|
|
||||||
|
|
||||||
**Note:** All examples in this workshop use Windows syntax (`.\script.ps1`). macOS/Linux users should use `./script.ps1` instead.
|
|
||||||
|
|
||||||
## Requirements
|
## Requirements
|
||||||
|
|
||||||
### Git
|
### Prerequisites
|
||||||
|
|
||||||
You need Git version 2.23 or later (for `git switch` and `git restore` commands).
|
Install these tools before starting:
|
||||||
|
|
||||||
**Check if Git is installed:**
|
**PowerShell 7+**
|
||||||
```powershell
|
```powershell
|
||||||
git --version
|
winget install Microsoft.PowerShell
|
||||||
```
|
```
|
||||||
|
|
||||||
If you see a version number (e.g., "git version 2.34.1"), you're all set!
|
**Git 2.23+**
|
||||||
|
```powershell
|
||||||
|
winget install Git.Git
|
||||||
|
```
|
||||||
|
|
||||||
If not, download and install from: https://git-scm.com/downloads
|
**Visual Studio Code**
|
||||||
|
```powershell
|
||||||
|
winget install Microsoft.VisualStudioCode
|
||||||
|
```
|
||||||
|
|
||||||
**Configure Git (Required - First Time Only):**
|
### Quick Start
|
||||||
|
|
||||||
Before making your first commit, tell Git who you are:
|
**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
|
```powershell
|
||||||
git config --global user.name "Your Name"
|
git config --global user.name "Your Name"
|
||||||
git config --global user.email "your.email@example.com"
|
git config --global user.email "your.email@example.com"
|
||||||
```
|
```
|
||||||
|
|
||||||
**Verify your configuration:**
|
Verify your configuration:
|
||||||
```powershell
|
```powershell
|
||||||
git config --global user.name
|
git config --global user.name
|
||||||
git config --global user.email
|
git config --global user.email
|
||||||
```
|
```
|
||||||
|
|
||||||
You should see your name and email printed. This is required to make commits in Git.
|
|
||||||
|
|
||||||
### PowerShell
|
|
||||||
|
|
||||||
- **Windows**: PowerShell 5.1+ (already included in Windows 10/11)
|
|
||||||
- **macOS/Linux**: Install PowerShell Core 7+ from https://github.com/PowerShell/PowerShell
|
|
||||||
|
|
||||||
**Check PowerShell version:**
|
|
||||||
```powershell
|
|
||||||
$PSVersionTable.PSVersion
|
|
||||||
```
|
|
||||||
|
|
||||||
### Basic Command Line Knowledge
|
### Basic Command Line Knowledge
|
||||||
|
|
||||||
You should know how to:
|
You should know how to:
|
||||||
@@ -136,11 +172,7 @@ This workshop includes many markdown files with instructions. You can install `g
|
|||||||
After installation, read markdown files with:
|
After installation, read markdown files with:
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
# Windows
|
|
||||||
.\bin\glow.exe README.md
|
.\bin\glow.exe README.md
|
||||||
|
|
||||||
# macOS/Linux
|
|
||||||
./bin/glow README.md
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Pro tip**: Use `glow -p` for pager mode on longer files!
|
**Pro tip**: Use `glow -p` for pager mode on longer files!
|
||||||
@@ -159,16 +191,20 @@ New to Git? Here are the key terms you'll encounter:
|
|||||||
- **HEAD**: A pointer showing which commit you're currently working from
|
- **HEAD**: A pointer showing which commit you're currently working from
|
||||||
- **main/master**: The primary branch in a repository (main is the modern convention)
|
- **main/master**: The primary branch in a repository (main is the modern convention)
|
||||||
- **Merge**: Combining changes from different branches
|
- **Merge**: Combining changes from different branches
|
||||||
- **Remote**: A version of your repository hosted elsewhere (like GitHub)
|
- **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!
|
Don't worry if these don't make sense yet - you'll learn them hands-on as you progress!
|
||||||
|
|
||||||
## Getting Started
|
## Getting Started
|
||||||
|
|
||||||
Start with Module 01:
|
**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
|
```powershell
|
||||||
cd module-01-basics
|
cd 01-essentials\01-basics
|
||||||
.\setup.ps1
|
.\setup.ps1
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -177,54 +213,99 @@ Follow the instructions in each module's README.md file.
|
|||||||
## What Makes This Workshop Different
|
## What Makes This Workshop Different
|
||||||
|
|
||||||
- **Hands-On Practice**: Each module creates a real Git scenario you must solve
|
- **Hands-On Practice**: Each module creates a real Git scenario you must solve
|
||||||
- **Automated Verification**: Scripts check your solution instantly
|
- **Automated Verification**: Scripts check your solution instantly (modules 01-08)
|
||||||
- **Progressive Difficulty**: Builds from basics to advanced Git techniques
|
- **Progressive Difficulty**: Builds from basics to advanced Git techniques
|
||||||
- **Reset Anytime**: Each module includes a reset script for a fresh start
|
- **Reset Anytime**: Each local module includes a reset script for a fresh start
|
||||||
- **Self-Paced**: Learn at your own speed with detailed README guides
|
- **Self-Paced**: Learn at your own speed with detailed README guides
|
||||||
- **No Internet Required**: All modules work offline with local repositories
|
- **Real Collaboration**: Module 08 uses an actual Git server for authentic teamwork
|
||||||
|
- **Comprehensive Coverage**: From `git init` to advanced rebasing and bisecting
|
||||||
|
|
||||||
## Learning Path
|
## Learning Path
|
||||||
|
|
||||||
The modules are designed to build on each other:
|
The modules are designed to build on each other:
|
||||||
|
|
||||||
1. **Start Here** (Modules 1-3): Core Git fundamentals everyone needs
|
### Recommended Progression
|
||||||
2. **Collaboration** (Modules 4-5): Working with branches and resolving conflicts
|
|
||||||
3. **History Management** (Modules 6-8): Rewriting and organizing commits
|
**Phase 1: Core Fundamentals (Essentials 01-02)**
|
||||||
4. **Advanced Workflows** (Modules 9-11): Professional Git techniques
|
- Git Basics, History
|
||||||
5. **Power User** (Modules 12-13): Advanced tools for productivity and debugging
|
- **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
|
## Tips for Success
|
||||||
|
|
||||||
- Don't skip modules - each builds on previous concepts
|
- **Don't skip modules** - each builds on previous concepts
|
||||||
- Read the README.md thoroughly before starting each challenge
|
- **Read the README.md thoroughly** before starting each challenge
|
||||||
- Keep [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) open as a quick reference
|
- **Keep [GIT-CHEATSHEET.md](GIT-CHEATSHEET.md) open** as a quick reference
|
||||||
- Experiment freely - you can always run `./reset.ps1` to start over
|
- **Experiment freely** - you can always run `./reset.ps1` to start over
|
||||||
- Use `git log --oneline --graph --all` frequently to visualize repository state
|
- **Use `git log --oneline --graph --all`** frequently to visualize repository state
|
||||||
- If stuck, check the Key Concepts section in the module's README
|
- **If stuck**, check the Key Concepts section in the module's README
|
||||||
- Consider installing glow for better markdown reading experience
|
- **Consider installing glow** for better markdown reading experience
|
||||||
|
- **For Module 07**, work with a partner - collaboration is the point!
|
||||||
|
|
||||||
## Skills You'll Master
|
## Skills You'll Master
|
||||||
|
|
||||||
By completing this workshop, you'll be able to:
|
By completing this workshop, you'll be able to:
|
||||||
|
|
||||||
|
### From Essentials Track:
|
||||||
- ✅ Create and manage Git repositories with confidence
|
- ✅ Create and manage Git repositories with confidence
|
||||||
- ✅ Navigate project history and understand what changed when
|
- ✅ Navigate project history and understand what changed when
|
||||||
- ✅ Use branches effectively for parallel development
|
- ✅ Use branches effectively for parallel development
|
||||||
- ✅ Merge branches and resolve conflicts like a pro
|
- ✅ Merge branches and resolve conflicts like a pro
|
||||||
- ✅ Rebase to maintain clean, linear history
|
- ✅ Apply specific commits with cherry-pick
|
||||||
- ✅ Clean up messy commits before submitting pull requests
|
|
||||||
- ✅ Cherry-pick specific commits across branches
|
|
||||||
- ✅ Choose correctly between reset and revert
|
- ✅ Choose correctly between reset and revert
|
||||||
- ✅ Use stash to manage work-in-progress without commits
|
- ✅ Use stash to manage work-in-progress without commits
|
||||||
- ✅ Collaborate effectively with remote repositories
|
- ✅ **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
|
- ✅ Work on multiple branches simultaneously with worktrees
|
||||||
- ✅ Debug efficiently by finding bug-introducing commits with bisect
|
- ✅ 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.
|
These are professional-level Git skills used daily by developers at top tech companies.
|
||||||
|
|
||||||
## For Workshop Facilitators
|
## For Workshop Facilitators
|
||||||
|
|
||||||
Before distributing this workshop to attendees:
|
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
|
1. **Delete the root `.git` directory**: This prevents confusion and ensures attendees practice git from scratch
|
||||||
```powershell
|
```powershell
|
||||||
@@ -232,3 +313,190 @@ Before distributing this workshop to attendees:
|
|||||||
```
|
```
|
||||||
2. Each module's `challenge/` directory will become its own independent git repository when attendees run `setup.ps1`
|
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
|
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! 🚀
|
||||||
|
|||||||
124
WHAT-IS-GIT.md
Normal file
124
WHAT-IS-GIT.md
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
# What is Git?
|
||||||
|
|
||||||
|
Git is a tool that tracks changes to your files over time. Think of it as an "undo history" for your entire project that you can browse, search, and share with others.
|
||||||
|
|
||||||
|
## Commits: Snapshots of Your Project
|
||||||
|
|
||||||
|
A **commit** is like taking a photo of your entire project at a specific moment in time.
|
||||||
|
|
||||||
|
Every time you make a commit, Git:
|
||||||
|
1. Records what **all** your files look like right now
|
||||||
|
2. Adds a message describing what changed
|
||||||
|
3. Notes who made the change and when
|
||||||
|
4. Links back to the previous commit
|
||||||
|
|
||||||
|
### Every Commit Contains Everything
|
||||||
|
|
||||||
|
This is important: each commit is a complete snapshot of **all** files in your project - not just the files you changed. You can check out any commit and see the entire project exactly as it was.
|
||||||
|
|
||||||
|
But wait - doesn't that waste a lot of space? No! Git is clever about this.
|
||||||
|
|
||||||
|
### Unchanged Files Are Reused
|
||||||
|
|
||||||
|
If a file hasn't changed since the last commit, Git doesn't store a new copy. Instead, it simply points to the version it already has:
|
||||||
|
|
||||||
|
```
|
||||||
|
Commit 1 Commit 2 Commit 3
|
||||||
|
───────── ───────── ─────────
|
||||||
|
README.md ─────────────────────────────> (same)
|
||||||
|
app.py ────────> app.py (v2) ───────> (same)
|
||||||
|
config.json ──────> (same) ────────────> config.json (v3)
|
||||||
|
```
|
||||||
|
|
||||||
|
In this example:
|
||||||
|
- `README.md` never changed - all three commits refer to the same stored version
|
||||||
|
- `app.py` changed in Commit 2, so a new version was stored
|
||||||
|
- `config.json` changed in Commit 3, so a new version was stored
|
||||||
|
|
||||||
|
This means:
|
||||||
|
- Every commit gives you the **complete picture** of your project
|
||||||
|
- Git only stores **new content** when files actually change
|
||||||
|
- Going back to any point in history is instant - no need to "replay" changes
|
||||||
|
|
||||||
|
```
|
||||||
|
Commit 3 Commit 2 Commit 1
|
||||||
|
| | |
|
||||||
|
v v v
|
||||||
|
[Add login] <-- [Fix bug] <-- [First version]
|
||||||
|
```
|
||||||
|
|
||||||
|
Each commit points back to its parent, creating a chain of history. You can always go back and see exactly what your project looked like at any point.
|
||||||
|
|
||||||
|
## The Magic of Checksums
|
||||||
|
|
||||||
|
Here's where Git gets clever. Every commit gets a unique ID called a **checksum** (or "hash"). It looks like this:
|
||||||
|
|
||||||
|
```
|
||||||
|
a1b2c3d4e5f6g7h8i9j0...
|
||||||
|
```
|
||||||
|
|
||||||
|
This ID is calculated from the **contents** of the commit - the files, the message, the author, and the parent commit's ID.
|
||||||
|
|
||||||
|
Why does this matter?
|
||||||
|
|
||||||
|
### Verification
|
||||||
|
|
||||||
|
If even one character changes in a file, the checksum becomes completely different. This means:
|
||||||
|
- Git instantly knows if something has been corrupted or tampered with
|
||||||
|
- You can trust that what you downloaded is exactly what was uploaded
|
||||||
|
|
||||||
|
### Finding Differences
|
||||||
|
|
||||||
|
When you connect to another copy of the repository, Git compares checksums:
|
||||||
|
|
||||||
|
```
|
||||||
|
Your computer: Server:
|
||||||
|
Commit A Commit A (same checksum = identical)
|
||||||
|
Commit B Commit B (same checksum = identical)
|
||||||
|
Commit C (missing) (you have something new!)
|
||||||
|
```
|
||||||
|
|
||||||
|
Git doesn't need to compare every file. It just compares the short checksums to instantly know what's different.
|
||||||
|
|
||||||
|
## Distributed: Everyone Has a Full Copy
|
||||||
|
|
||||||
|
Unlike older systems where one central server held all the history, Git is **distributed**. This means:
|
||||||
|
|
||||||
|
- Every person has a complete copy of the entire project history
|
||||||
|
- You can work offline - commit, browse history, create branches
|
||||||
|
- If the server disappears, anyone's copy can restore everything
|
||||||
|
- You sync with others by exchanging commits
|
||||||
|
|
||||||
|
```
|
||||||
|
[Alice's Computer] [Bob's Computer]
|
||||||
|
| |
|
||||||
|
Full history Full history
|
||||||
|
All branches All branches
|
||||||
|
| |
|
||||||
|
+---- [Shared Server] ------+
|
||||||
|
|
|
||||||
|
Full history
|
||||||
|
All branches
|
||||||
|
```
|
||||||
|
|
||||||
|
When Alice pushes her new commits to the server, Bob can pull them down. The checksums ensure nothing gets lost or corrupted in transit.
|
||||||
|
|
||||||
|
## Putting It All Together
|
||||||
|
|
||||||
|
1. **You work** - Edit files, create new ones, delete old ones
|
||||||
|
2. **You commit** - Take a snapshot with a descriptive message
|
||||||
|
3. **Git calculates** - Creates a unique checksum for this commit
|
||||||
|
4. **You push** - Send your commits to a shared server
|
||||||
|
5. **Others pull** - Download your commits using checksums to verify
|
||||||
|
6. **History grows** - The chain of commits gets longer
|
||||||
|
|
||||||
|
That's it! Git is essentially a distributed database of snapshots, connected together and verified by checksums. Everything else - branches, merges, rebasing - builds on these simple ideas.
|
||||||
|
|
||||||
|
## Key Takeaways
|
||||||
|
|
||||||
|
- **Commit** = A snapshot of your project at one moment
|
||||||
|
- **Checksum** = A unique fingerprint calculated from the content
|
||||||
|
- **Distributed** = Everyone has a full copy, not just the server
|
||||||
|
- **History** = A chain of commits, each pointing to its parent
|
||||||
|
|
||||||
|
You don't need to understand every detail to use Git effectively. Just remember: commit often, write clear messages, and sync with your team regularly.
|
||||||
960
install.ps1
Normal file
960
install.ps1
Normal file
@@ -0,0 +1,960 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Installs all prerequisites for Git Workshop using winget and clones the repository.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
This script automates the installation of required tools for the Git Workshop:
|
||||||
|
- PowerShell 7 (cross-platform PowerShell)
|
||||||
|
- Git 2.23+ (version control system)
|
||||||
|
- Visual Studio Code (code editor with Git integration)
|
||||||
|
|
||||||
|
Optional tools (with user prompts):
|
||||||
|
- Windows Terminal (modern terminal experience)
|
||||||
|
|
||||||
|
The script checks for existing installations, shows clear progress, and verifies
|
||||||
|
each installation succeeded. At the end, it clones the repository and can
|
||||||
|
open it in VSCode for immediate workshop access.
|
||||||
|
|
||||||
|
One-shot installation:
|
||||||
|
Invoke-RestMethod -Uri https://git.frod.dk/floppydiscen/git-workshop/raw/branch/main/install.ps1 | Invoke-Expression
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
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.
|
||||||
|
|
||||||
|
.EXAMPLE
|
||||||
|
PS> .\install.ps1
|
||||||
|
Runs the installation script with interactive prompts.
|
||||||
|
|
||||||
|
.NOTES
|
||||||
|
Requires Windows 11 with winget (App Installer) available.
|
||||||
|
Some installations may require administrator privileges.
|
||||||
|
#>
|
||||||
|
|
||||||
|
[CmdletBinding()]
|
||||||
|
param()
|
||||||
|
|
||||||
|
Set-StrictMode -Version Latest
|
||||||
|
$ErrorActionPreference = 'Continue' # Continue on errors to show all results
|
||||||
|
|
||||||
|
#region Helper Functions
|
||||||
|
|
||||||
|
function Write-ColorMessage {
|
||||||
|
param(
|
||||||
|
[string]$Message,
|
||||||
|
[string]$Color = 'White'
|
||||||
|
)
|
||||||
|
Write-Host $Message -ForegroundColor $Color
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Step {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColorMessage "`n=== $Message ===" -Color Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Success {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColorMessage " ✓ $Message" -Color Green
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Warning {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColorMessage " ⚠ $Message" -Color Yellow
|
||||||
|
}
|
||||||
|
|
||||||
|
function Write-Error {
|
||||||
|
param([string]$Message)
|
||||||
|
Write-ColorMessage " ✗ $Message" -Color Red
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-CommandExists {
|
||||||
|
param([string]$Command)
|
||||||
|
|
||||||
|
$oldPreference = $ErrorActionPreference
|
||||||
|
$ErrorActionPreference = 'SilentlyContinue'
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (Get-Command $Command -ErrorAction SilentlyContinue) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
finally {
|
||||||
|
$ErrorActionPreference = $oldPreference
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-InstalledVersion {
|
||||||
|
param(
|
||||||
|
[string]$Command,
|
||||||
|
[string]$VersionArg = '--version'
|
||||||
|
)
|
||||||
|
|
||||||
|
try {
|
||||||
|
$output = & $Command $VersionArg 2>&1 | Select-Object -First 1
|
||||||
|
if ($null -ne $output) {
|
||||||
|
return $output.ToString().Trim()
|
||||||
|
}
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
return $null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-WingetAvailable {
|
||||||
|
if (-not (Test-CommandExists 'winget')) {
|
||||||
|
Write-Error "winget is not available on this system."
|
||||||
|
Write-Host "`nTo fix this:" -ForegroundColor Yellow
|
||||||
|
Write-Host " 1. Update Windows 11 to the latest version (Settings → Windows Update)" -ForegroundColor White
|
||||||
|
Write-Host " 2. Install 'App Installer' from the Microsoft Store" -ForegroundColor White
|
||||||
|
Write-Host " 3. Restart your computer and run this script again" -ForegroundColor White
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-Package {
|
||||||
|
param(
|
||||||
|
[string]$Name,
|
||||||
|
[string]$WingetId,
|
||||||
|
[string]$CheckCommand,
|
||||||
|
[string]$MinVersion = $null,
|
||||||
|
[string]$AdditionalArgs = ''
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Step "Installing $Name"
|
||||||
|
|
||||||
|
# Check if already installed
|
||||||
|
if (Test-CommandExists $CheckCommand) {
|
||||||
|
$version = Get-InstalledVersion $CheckCommand
|
||||||
|
Write-Success "$Name is already installed: $version"
|
||||||
|
|
||||||
|
if ($MinVersion -and $version) {
|
||||||
|
# Extract semantic version numbers only - stop before any non-digit/non-dot characters
|
||||||
|
# This extracts "2.52.0" from "2.52.0.windows.1"
|
||||||
|
if ($version -match '(\d+)(?:\.(\d+))?(?:\.(\d+))?') {
|
||||||
|
$major = $matches[1]
|
||||||
|
$minor = if ($matches[2]) { $matches[2] } else { "0" }
|
||||||
|
$patch = if ($matches[3]) { $matches[3] } else { "0" }
|
||||||
|
$installedVersion = "$major.$minor.$patch"
|
||||||
|
try {
|
||||||
|
if ([version]$installedVersion -lt [version]$MinVersion) {
|
||||||
|
Write-Warning "Version $installedVersion is below minimum required version $MinVersion"
|
||||||
|
Write-Host " Attempting to upgrade..." -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Version comparison failed - assuming sufficient version"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "Could not parse version from: $version"
|
||||||
|
Write-Host " Assuming installed version is sufficient..." -ForegroundColor Cyan
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install using winget
|
||||||
|
Write-Host " Installing via winget: $WingetId" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
$installCmd = "winget install --id $WingetId --source winget --silent $AdditionalArgs".Trim()
|
||||||
|
Write-Host " Running: $installCmd" -ForegroundColor Gray
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Show progress during installation
|
||||||
|
Write-Progress -Activity "Installing $Name" -Status "Downloading and installing..." -PercentComplete 25
|
||||||
|
|
||||||
|
$result = Invoke-Expression $installCmd 2>&1
|
||||||
|
|
||||||
|
Write-Progress -Activity "Installing $Name" -Status "Verifying installation..." -PercentComplete 75
|
||||||
|
|
||||||
|
# Check if installation succeeded
|
||||||
|
Start-Sleep -Seconds 2 # Give the system time to register the new command
|
||||||
|
|
||||||
|
# Refresh environment variables in current session
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||||
|
|
||||||
|
if (Test-CommandExists $CheckCommand) {
|
||||||
|
$version = Get-InstalledVersion $CheckCommand
|
||||||
|
Write-Success "$Name installed successfully: $version"
|
||||||
|
Write-Progress -Activity "Installing $Name" -Completed
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "$Name installation completed, but command '$CheckCommand' not found."
|
||||||
|
Write-Host " You may need to restart your terminal or computer." -ForegroundColor Yellow
|
||||||
|
Write-Progress -Activity "Installing $Name" -Completed
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Error "Failed to install $Name`: $_"
|
||||||
|
Write-Progress -Activity "Installing $Name" -Completed
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Test-GitVersion {
|
||||||
|
if (-not (Test-CommandExists 'git')) {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
$version = Get-InstalledVersion 'git'
|
||||||
|
|
||||||
|
# Parse Git version from various formats:
|
||||||
|
# "git version 2.52.0", "git version 2.52.0.windows.1", etc.
|
||||||
|
if ($version -match 'git version (\d+)(?:\.(\d+))?(?:\.(\d+))?') {
|
||||||
|
$majorVersion = [int]$matches[1]
|
||||||
|
$minorVersion = [int]$matches[2]
|
||||||
|
|
||||||
|
# Check if version is 2.23 or higher
|
||||||
|
if ($majorVersion -gt 2 -or ($majorVersion -eq 2 -and $minorVersion -ge 23)) {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "Git version $majorVersion.$minorVersion is below required version 2.23"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Warning "Could not parse Git version from: $version"
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-UserConfirmation {
|
||||||
|
param([string]$Prompt)
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
$response = Read-Host "$Prompt (y/n)"
|
||||||
|
$response = $response.Trim().ToLower()
|
||||||
|
|
||||||
|
if ($response -eq 'y' -or $response -eq 'yes') {
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
elseif ($response -eq 'n' -or $response -eq 'no') {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host "Please enter 'y' or 'n'" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Prerequisites Check Function
|
||||||
|
|
||||||
|
function Get-SystemPrerequisites {
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Checks which required tools are installed and their versions.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Scans the system for PowerShell 7, Git 2.23+, and Visual Studio Code.
|
||||||
|
Returns a hashtable with installation status and version information.
|
||||||
|
#>
|
||||||
|
|
||||||
|
$prereqs = @{
|
||||||
|
PowerShell = @{
|
||||||
|
Installed = $false
|
||||||
|
Version = $null
|
||||||
|
Command = "pwsh"
|
||||||
|
MinVersion = "7.0"
|
||||||
|
Name = "PowerShell 7"
|
||||||
|
Sufficient = $false
|
||||||
|
}
|
||||||
|
Git = @{
|
||||||
|
Installed = $false
|
||||||
|
Version = $null
|
||||||
|
Command = "git"
|
||||||
|
MinVersion = "2.23"
|
||||||
|
Name = "Git"
|
||||||
|
Sufficient = $false
|
||||||
|
}
|
||||||
|
VSCode = @{
|
||||||
|
Installed = $false
|
||||||
|
Version = $null
|
||||||
|
Command = "code"
|
||||||
|
MinVersion = $null
|
||||||
|
Name = "Visual Studio Code"
|
||||||
|
Sufficient = $false
|
||||||
|
}
|
||||||
|
WindowsTerminal = @{
|
||||||
|
Installed = $false
|
||||||
|
Version = $null
|
||||||
|
Command = "wt"
|
||||||
|
MinVersion = $null
|
||||||
|
Name = "Windows Terminal"
|
||||||
|
Sufficient = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Checking system for installed tools..." -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
foreach ($key in $prereqs.Keys) {
|
||||||
|
$tool = $prereqs[$key]
|
||||||
|
|
||||||
|
if (Test-CommandExists $tool.Command) {
|
||||||
|
$tool.Installed = $true
|
||||||
|
$tool.Version = Get-InstalledVersion $tool.Command
|
||||||
|
|
||||||
|
if ($tool.MinVersion -and $tool.Version) {
|
||||||
|
# Extract semantic version for comparison
|
||||||
|
if ($tool.Version -match '(\d+)(?:\.(\d+))?(?:\.(\d+))?') {
|
||||||
|
$major = [int]$matches[1]
|
||||||
|
$minor = if ($matches[2]) { [int]$matches[2] } else { 0 }
|
||||||
|
$patch = if ($matches[3]) { [int]$matches[3] } else { 0 }
|
||||||
|
$installedVersion = "$major.$minor.$patch"
|
||||||
|
|
||||||
|
$minParts = $tool.MinVersion.Split('.')
|
||||||
|
$minMajor = [int]$minParts[0]
|
||||||
|
$minMinor = if ($minParts.Length -gt 1) { [int]$minParts[1] } else { 0 }
|
||||||
|
$minPatch = if ($minParts.Length -gt 2) { [int]$minParts[2] } else { 0 }
|
||||||
|
$minVersion = "$minMajor.$minMinor.$minPatch"
|
||||||
|
|
||||||
|
if ([version]$installedVersion -lt [version]$minVersion) {
|
||||||
|
Write-Host " ✓ $($tool.Name): $($tool.Version) (⚠ below required $($tool.MinVersion))" -ForegroundColor Yellow
|
||||||
|
$tool.Sufficient = $false
|
||||||
|
} else {
|
||||||
|
Write-Host " ✓ $($tool.Name): $($tool.Version)" -ForegroundColor Green
|
||||||
|
$tool.Sufficient = $true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " ✓ $($tool.Name): $($tool.Version) (⚠ cannot parse version)" -ForegroundColor Yellow
|
||||||
|
$tool.Sufficient = $true # Assume sufficient if we can't parse
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " ✓ $($tool.Name): $($tool.Version)" -ForegroundColor Green
|
||||||
|
$tool.Sufficient = $true
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " ✗ $($tool.Name): Not installed" -ForegroundColor Red
|
||||||
|
$tool.Sufficient = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $prereqs
|
||||||
|
}
|
||||||
|
|
||||||
|
function Show-PrerequisitesSummary {
|
||||||
|
param([hashtable]$Prereqs)
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Step "Prerequisites Summary"
|
||||||
|
|
||||||
|
$requiredTools = @("PowerShell", "Git", "VSCode")
|
||||||
|
$allRequiredInstalled = $true
|
||||||
|
$allRequiredSufficient = $true
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Required Tools:" -ForegroundColor White
|
||||||
|
|
||||||
|
foreach ($toolName in $requiredTools) {
|
||||||
|
$tool = $Prereqs[$toolName]
|
||||||
|
if ($tool.Installed -and $tool.Sufficient) {
|
||||||
|
Write-Success " $($tool.Name): $($tool.Version)"
|
||||||
|
} elseif ($tool.Installed) {
|
||||||
|
Write-Warning " $($tool.Name): $($tool.Version) (needs upgrade)"
|
||||||
|
$allRequiredSufficient = $false
|
||||||
|
} else {
|
||||||
|
Write-Error " $($tool.Name): Not installed"
|
||||||
|
$allRequiredInstalled = $false
|
||||||
|
$allRequiredSufficient = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Optional Tools:" -ForegroundColor White
|
||||||
|
|
||||||
|
$wt = $Prereqs.WindowsTerminal
|
||||||
|
if ($wt.Installed) {
|
||||||
|
Write-Success " $($wt.Name): $($wt.Version)"
|
||||||
|
} else {
|
||||||
|
Write-Host " $($wt.Name): Not installed" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
|
||||||
|
return @{
|
||||||
|
AllRequiredInstalled = $allRequiredInstalled -and $allRequiredSufficient
|
||||||
|
AnyMissing = -not $allRequiredInstalled
|
||||||
|
AnyInsufficient = -not $allRequiredSufficient
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Get-UserChoice {
|
||||||
|
param([hashtable]$PrereqStatus)
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Step "Choose Your Action"
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "What would you like to do?" -ForegroundColor White
|
||||||
|
Write-Host " 1) Install/update tools and clone workshop repository" -ForegroundColor Cyan
|
||||||
|
Write-Host " 2) Just clone the workshop repository (skip tool installation)" -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if ($PrereqStatus.AllRequiredInstalled) {
|
||||||
|
Write-Host "Note: All required tools are already installed" -ForegroundColor Green
|
||||||
|
} else {
|
||||||
|
Write-Host "Note: Some required tools are missing or need updates" -ForegroundColor Yellow
|
||||||
|
}
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
while ($true) {
|
||||||
|
$choice = Read-Host "Enter your choice (1-2)"
|
||||||
|
switch ($choice.Trim()) {
|
||||||
|
"1" { return "InstallTools" }
|
||||||
|
"2" { return "CloneOnly" }
|
||||||
|
default { Write-Host "Please enter 1 or 2" -ForegroundColor Yellow }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Main Script
|
||||||
|
|
||||||
|
Write-Host @"
|
||||||
|
|
||||||
|
╔═══════════════════════════════════════════════════════════╗
|
||||||
|
║ ║
|
||||||
|
║ Git Workshop - Prerequisites Installation Script ║
|
||||||
|
║ ║
|
||||||
|
╚═══════════════════════════════════════════════════════════╝
|
||||||
|
|
||||||
|
"@ -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Check for winget first
|
||||||
|
Write-Step "Checking System Requirements"
|
||||||
|
|
||||||
|
if (-not (Test-WingetAvailable)) {
|
||||||
|
Write-Host "`nInstallation cannot continue without winget." -ForegroundColor Red
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Success "winget is available"
|
||||||
|
|
||||||
|
# Check system prerequisites
|
||||||
|
$prereqs = Get-SystemPrerequisites
|
||||||
|
$prereqStatus = Show-PrerequisitesSummary $prereqs
|
||||||
|
$userChoice = Get-UserChoice $prereqStatus
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Step "User Choice: $userChoice"
|
||||||
|
|
||||||
|
# Track installation results
|
||||||
|
$results = @{
|
||||||
|
PowerShell = $prereqs.PowerShell.Installed -and $prereqs.PowerShell.Sufficient
|
||||||
|
Git = $prereqs.Git.Installed -and $prereqs.Git.Sufficient
|
||||||
|
VSCode = $prereqs.VSCode.Installed -and $prereqs.VSCode.Sufficient
|
||||||
|
VSCodeExtensions = $false
|
||||||
|
VSCodePowerShellIntegration = $null # null = not asked, true = configured, false = skipped/failed
|
||||||
|
WindowsTerminal = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Progress tracking
|
||||||
|
$totalSteps = 4 # Required installations + extensions
|
||||||
|
$currentStep = 0
|
||||||
|
|
||||||
|
function Write-ProgressIndicator {
|
||||||
|
param(
|
||||||
|
[string]$Activity,
|
||||||
|
[string]$Status,
|
||||||
|
[int]$PercentComplete
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Progress -Activity $Activity -Status $Status -PercentComplete $PercentComplete
|
||||||
|
}
|
||||||
|
|
||||||
|
function Should-Install {
|
||||||
|
param(
|
||||||
|
[string]$ToolName,
|
||||||
|
[string]$UserChoice,
|
||||||
|
[hashtable]$Prereqs
|
||||||
|
)
|
||||||
|
|
||||||
|
$tool = $Prereqs[$ToolName]
|
||||||
|
|
||||||
|
switch ($UserChoice) {
|
||||||
|
"CloneOnly" {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
"InstallTools" {
|
||||||
|
# Install only if missing or insufficient (smart install)
|
||||||
|
return -not ($tool.Installed -and $tool.Sufficient)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Install-VSCodeExtension {
|
||||||
|
param(
|
||||||
|
[string]$ExtensionId,
|
||||||
|
[string]$ExtensionName
|
||||||
|
)
|
||||||
|
|
||||||
|
Write-Host " Installing VSCode extension: $ExtensionName" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Refresh environment to ensure code command is available
|
||||||
|
$env:Path = [System.Environment]::GetEnvironmentVariable("Path", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path", "User")
|
||||||
|
|
||||||
|
$result = & code --install-extension $ExtensionId 2>&1
|
||||||
|
|
||||||
|
if ($LASTEXITCODE -eq 0) {
|
||||||
|
Write-Success "VSCode extension '$ExtensionName' installed successfully"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning "Failed to install VSCode extension '$ExtensionName'"
|
||||||
|
Write-Host " You can install it manually later: Ctrl+Shift+X → Search '$ExtensionName'" -ForegroundColor Gray
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not install VSCode extension '$ExtensionName`: $_"
|
||||||
|
Write-Host " You can install it manually later: Ctrl+Shift+X → Search '$ExtensionName'" -ForegroundColor Gray
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function Set-VSCodePowerShellIntegration {
|
||||||
|
# Ask user if they want to set PowerShell 7 as default terminal
|
||||||
|
$setAsDefault = Get-UserConfirmation "Set PowerShell 7 as the default terminal in VSCode? (Recommended for this workshop)"
|
||||||
|
|
||||||
|
if (-not $setAsDefault) {
|
||||||
|
Write-Host " Skipping PowerShell 7 terminal configuration." -ForegroundColor Gray
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Configuring PowerShell 7 integration with VSCode..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
try {
|
||||||
|
# Set PowerShell 7 as the default terminal in VSCode
|
||||||
|
$vscodeSettingsPath = Join-Path $env:APPDATA "Code\User\settings.json"
|
||||||
|
$vscodeSettingsDir = Split-Path $vscodeSettingsPath -Parent
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
if (-not (Test-Path $vscodeSettingsDir)) {
|
||||||
|
New-Item -Path $vscodeSettingsDir -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Read existing settings or create new
|
||||||
|
if (Test-Path $vscodeSettingsPath) {
|
||||||
|
$settings = Get-Content $vscodeSettingsPath -Raw | ConvertFrom-Json
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$settings = @{}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Set PowerShell 7 as default terminal
|
||||||
|
if (-not $settings.PSObject.Properties.Name -contains "terminal.integrated.defaultProfile.windows") {
|
||||||
|
$settings | Add-Member -NotePropertyName "terminal.integrated.defaultProfile.windows" -NotePropertyValue "PowerShell"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
$settings."terminal.integrated.defaultProfile.windows" = "PowerShell"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Add terminal profiles if not present
|
||||||
|
if (-not $settings.PSObject.Properties.Name -contains "terminal.integrated.profiles.windows") {
|
||||||
|
$profiles = @{
|
||||||
|
"PowerShell" = @{
|
||||||
|
"source" = "PowerShell"
|
||||||
|
"icon" = "terminal-powershell"
|
||||||
|
"path" = "pwsh.exe"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$settings | Add-Member -NotePropertyName "terminal.integrated.profiles.windows" -NotePropertyValue $profiles
|
||||||
|
}
|
||||||
|
|
||||||
|
# Save settings
|
||||||
|
$settings | ConvertTo-Json -Depth 10 | Set-Content $vscodeSettingsPath
|
||||||
|
|
||||||
|
Write-Success "VSCode configured to use PowerShell 7 as default terminal"
|
||||||
|
return $true
|
||||||
|
}
|
||||||
|
catch {
|
||||||
|
Write-Warning "Could not configure VSCode PowerShell integration automatically"
|
||||||
|
Write-Host " You can configure it manually in VSCode: Ctrl+Shift+P → Terminal: Select Default Profile → PowerShell" -ForegroundColor Gray
|
||||||
|
return $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#region Installation Based on User Choice
|
||||||
|
|
||||||
|
if ($userChoice -ne "CloneOnly") {
|
||||||
|
Write-Host "`nStarting installation based on your choice..." -ForegroundColor Cyan
|
||||||
|
Write-Host "Note: Some installations may take a few minutes." -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Calculate steps needed
|
||||||
|
$neededSteps = 0
|
||||||
|
if (Should-Install -ToolName "PowerShell" -UserChoice $userChoice -Prereqs $prereqs) { $neededSteps++ }
|
||||||
|
if (Should-Install -ToolName "Git" -UserChoice $userChoice -Prereqs $prereqs) { $neededSteps++ }
|
||||||
|
if (Should-Install -ToolName "VSCode" -UserChoice $userChoice -Prereqs $prereqs) { $neededSteps++ }
|
||||||
|
if (Should-Install -ToolName "WindowsTerminal" -UserChoice $userChoice -Prereqs $prereqs) { $neededSteps++ }
|
||||||
|
if ($neededSteps -eq 0) { $neededSteps = 1 } # At least for progress bar
|
||||||
|
if ($prereqs.VSCode.Installed -or (Should-Install -ToolName "VSCode" -UserChoice $userChoice -Prereqs $prereqs)) { $neededSteps++ } # For VSCode extensions
|
||||||
|
|
||||||
|
$currentStep = 0
|
||||||
|
$totalSteps = $neededSteps
|
||||||
|
|
||||||
|
#region Required Installations
|
||||||
|
|
||||||
|
# Install PowerShell 7
|
||||||
|
if (Should-Install -ToolName "PowerShell" -UserChoice $userChoice -Prereqs $prereqs) {
|
||||||
|
$currentStep++
|
||||||
|
Write-ProgressIndicator -Activity "Installing Tools" -Status "Installing PowerShell 7 ($currentStep/$totalSteps)" -PercentComplete (($currentStep / $totalSteps) * 100)
|
||||||
|
$results.PowerShell = Install-Package `
|
||||||
|
-Name "PowerShell 7" `
|
||||||
|
-WingetId "Microsoft.PowerShell" `
|
||||||
|
-CheckCommand "pwsh"
|
||||||
|
} else {
|
||||||
|
Write-Success "PowerShell 7 already installed and sufficient"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install Git
|
||||||
|
if (Should-Install -ToolName "Git" -UserChoice $userChoice -Prereqs $prereqs) {
|
||||||
|
$currentStep++
|
||||||
|
Write-ProgressIndicator -Activity "Installing Tools" -Status "Installing Git ($currentStep/$totalSteps)" -PercentComplete (($currentStep / $totalSteps) * 100)
|
||||||
|
$results.Git = Install-Package `
|
||||||
|
-Name "Git" `
|
||||||
|
-WingetId "Git.Git" `
|
||||||
|
-CheckCommand "git" `
|
||||||
|
-MinVersion "2.23" `
|
||||||
|
-AdditionalArgs "-e"
|
||||||
|
|
||||||
|
Write-Host " Setting the init.defaultBranch to be 'main'"
|
||||||
|
git config --global init.defaultBranch main
|
||||||
|
|
||||||
|
Write-Host " Setting the default editor to code, to handle merge messages"
|
||||||
|
git config --global core.editor "code --wait"
|
||||||
|
|
||||||
|
Write-Host " Setting vscode at the default code editor for merge conflicts"
|
||||||
|
git config --global merge.tool vscode
|
||||||
|
git config --global mergetool.vscode.cmd 'code --wait --merge $REMOTE $LOCAL $BASE $MERGED'
|
||||||
|
|
||||||
|
Write-Host " Setting vscode as the default code editor for diffs"
|
||||||
|
git config --global diff.tool vscode
|
||||||
|
git config --global difftool.vscode.cmd 'code --wait --diff $LOCAL $REMOTE'
|
||||||
|
|
||||||
|
# Verify Git version specifically
|
||||||
|
if ($results.Git) {
|
||||||
|
if (-not (Test-GitVersion)) {
|
||||||
|
Write-Warning "Git is installed but version may be below 2.23"
|
||||||
|
$results.Git = $false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Success "Git already installed and sufficient"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Install Visual Studio Code
|
||||||
|
if (Should-Install -ToolName "VSCode" -UserChoice $userChoice -Prereqs $prereqs) {
|
||||||
|
$currentStep++
|
||||||
|
Write-ProgressIndicator -Activity "Installing Tools" -Status "Installing Visual Studio Code ($currentStep/$totalSteps)" -PercentComplete (($currentStep / $totalSteps) * 100)
|
||||||
|
$results.VSCode = Install-Package `
|
||||||
|
-Name "Visual Studio Code" `
|
||||||
|
-WingetId "Microsoft.VisualStudioCode" `
|
||||||
|
-CheckCommand "code"
|
||||||
|
} else {
|
||||||
|
Write-Success "Visual Studio Code already installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region VSCode Extensions and Configuration
|
||||||
|
|
||||||
|
if (($prereqs.VSCode.Installed -or $results.VSCode) -and $userChoice -ne "CloneOnly") {
|
||||||
|
$currentStep++
|
||||||
|
Write-ProgressIndicator -Activity "Installing Tools" -Status "Installing VSCode Extensions ($currentStep/$totalSteps)" -PercentComplete (($currentStep / $totalSteps) * 100)
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Step "Configuring VSCode"
|
||||||
|
|
||||||
|
# Install PowerShell extension
|
||||||
|
$powershellExtensionResult = Install-VSCodeExtension -ExtensionId "ms-vscode.PowerShell" -ExtensionName "PowerShell"
|
||||||
|
|
||||||
|
# Configure PowerShell 7 integration (optional but recommended)
|
||||||
|
if ($userChoice -eq "InstallTools") {
|
||||||
|
$powershellIntegrationResult = Set-VSCodePowerShellIntegration
|
||||||
|
$results.VSCodePowerShellIntegration = $powershellIntegrationResult
|
||||||
|
} else {
|
||||||
|
$results.VSCodePowerShellIntegration = $null
|
||||||
|
}
|
||||||
|
|
||||||
|
$results.VSCodeExtensions = $powershellExtensionResult
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clear progress bar
|
||||||
|
Write-Progress -Activity "Installing Tools" -Completed
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Optional Installations
|
||||||
|
|
||||||
|
# Windows Terminal (optional)
|
||||||
|
if (Should-Install -ToolName "WindowsTerminal" -UserChoice $userChoice -Prereqs $prereqs) {
|
||||||
|
Write-Host ""
|
||||||
|
if (Get-UserConfirmation "Do you want to install Windows Terminal? (Highly recommended for better terminal experience)") {
|
||||||
|
Write-ProgressIndicator -Activity "Installing Optional Tools" -Status "Installing Windows Terminal" -PercentComplete 50
|
||||||
|
$results.WindowsTerminal = Install-Package `
|
||||||
|
-Name "Windows Terminal" `
|
||||||
|
-WingetId "Microsoft.WindowsTerminal" `
|
||||||
|
-CheckCommand "wt"
|
||||||
|
Write-Progress -Activity "Installing Optional Tools" -Completed
|
||||||
|
} else {
|
||||||
|
Write-Host " Skipping Windows Terminal installation." -ForegroundColor Gray
|
||||||
|
$results.WindowsTerminal = $null
|
||||||
|
}
|
||||||
|
} elseif ($prereqs.WindowsTerminal.Installed) {
|
||||||
|
Write-Success "Windows Terminal already installed"
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
} else {
|
||||||
|
Write-Host "`nSkipping tool installation as requested." -ForegroundColor Gray
|
||||||
|
Write-Host "Proceeding directly to workshop repository setup..." -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Installation Summary
|
||||||
|
|
||||||
|
Write-Step "Installation Summary"
|
||||||
|
|
||||||
|
# For CloneOnly, check if tools were already installed
|
||||||
|
if ($userChoice -eq "CloneOnly") {
|
||||||
|
$allRequired = $prereqs.PowerShell.Installed -and $prereqs.PowerShell.Sufficient -and
|
||||||
|
$prereqs.Git.Installed -and $prereqs.Git.Sufficient -and
|
||||||
|
$prereqs.VSCode.Installed -and $prereqs.VSCode.Sufficient
|
||||||
|
} else {
|
||||||
|
$allRequired = $results.PowerShell -and $results.Git -and $results.VSCode
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Required Tools:" -ForegroundColor White
|
||||||
|
|
||||||
|
if ($results.PowerShell) {
|
||||||
|
Write-Success "PowerShell 7"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "PowerShell 7 - Installation failed or needs restart"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results.Git) {
|
||||||
|
Write-Success "Git 2.23+"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Git 2.23+ - Installation failed or needs restart"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results.VSCode) {
|
||||||
|
Write-Success "Visual Studio Code"
|
||||||
|
|
||||||
|
if ($results.VSCodeExtensions) {
|
||||||
|
Write-Success " • PowerShell extension"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Warning " • VSCode PowerShell extension may need manual installation"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results.VSCodePowerShellIntegration -eq $true) {
|
||||||
|
Write-Success " • PowerShell 7 terminal integration"
|
||||||
|
}
|
||||||
|
elseif ($results.VSCodePowerShellIntegration -eq $false) {
|
||||||
|
Write-Host " • PowerShell 7 terminal integration: Skipped" -ForegroundColor Gray
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Visual Studio Code - Installation failed or needs restart"
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($results.WindowsTerminal -ne $null) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Optional Tools:" -ForegroundColor White
|
||||||
|
|
||||||
|
if ($results.WindowsTerminal) {
|
||||||
|
Write-Success "Windows Terminal"
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Error "Windows Terminal - Installation failed or needs restart"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endregion
|
||||||
|
|
||||||
|
#region Next Steps
|
||||||
|
|
||||||
|
Write-Step "Next Steps"
|
||||||
|
|
||||||
|
if ($allRequired) {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Success "All required tools installed successfully!"
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "IMPORTANT: Configure Git before your first commit:" -ForegroundColor Yellow
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " git config --global user.name `"Your Name`"" -ForegroundColor White
|
||||||
|
Write-Host " git config --global user.email `"your.email@example.com`"" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "Optional: Set VS Code as Git's default editor:" -ForegroundColor Cyan
|
||||||
|
Write-Host " git config --global core.editor `"code --wait`"" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "Verify your installation:" -ForegroundColor Cyan
|
||||||
|
Write-Host " pwsh --version" -ForegroundColor White
|
||||||
|
Write-Host " git --version" -ForegroundColor White
|
||||||
|
Write-Host " code --version" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "Set PowerShell execution policy (if needed):" -ForegroundColor Cyan
|
||||||
|
Write-Host " Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "Recommended VS Code Extensions:" -ForegroundColor Cyan
|
||||||
|
Write-Host " • PowerShell - Better PowerShell support (from Microsoft)" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host " Install via: Ctrl+Shift+X in VS Code" -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
Write-Host "You're ready to start the workshop!" -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
# Ask user if they want to clone the workshop repository
|
||||||
|
Write-Step "Workshop Setup"
|
||||||
|
|
||||||
|
if (Get-UserConfirmation "Clone the Git Workshop repository to Documents\git-workshop and open in VSCode?") {
|
||||||
|
try {
|
||||||
|
$documentsPath = [System.Environment]::GetFolderPath("MyDocuments")
|
||||||
|
$workshopPath = Join-Path $documentsPath "git-workshop"
|
||||||
|
|
||||||
|
Write-Host " Cloning to: $workshopPath" -ForegroundColor Gray
|
||||||
|
|
||||||
|
# Create directory if it doesn't exist
|
||||||
|
$documentsDir = Split-Path $workshopPath -Parent
|
||||||
|
if (-not (Test-Path $documentsDir)) {
|
||||||
|
New-Item -Path $documentsDir -ItemType Directory -Force | Out-Null
|
||||||
|
}
|
||||||
|
|
||||||
|
# Clone or update the repository
|
||||||
|
if (Test-Path $workshopPath) {
|
||||||
|
Write-Host " Directory already exists. Checking if it's a git repository..." -ForegroundColor Yellow
|
||||||
|
Push-Location $workshopPath
|
||||||
|
if (Get-Command git -ErrorAction SilentlyContinue) {
|
||||||
|
$remoteResult = git remote get-url origin 2>$null
|
||||||
|
if ($LASTEXITCODE -eq 0 -and $remoteResult -like "*git-workshop*") {
|
||||||
|
Write-Host " Repository already exists. Updating..." -ForegroundColor Cyan
|
||||||
|
git pull origin main
|
||||||
|
} else {
|
||||||
|
Write-Warning " Directory exists but is not the git-workshop repository"
|
||||||
|
Write-Host " Please remove the directory manually and run again" -ForegroundColor Yellow
|
||||||
|
Pop-Location
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pop-Location
|
||||||
|
} else {
|
||||||
|
git clone "https://git.frod.dk/floppydiscen/git-workshop.git" $workshopPath
|
||||||
|
Write-Success "Repository cloned successfully!"
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host " Opening in VSCode..." -ForegroundColor Cyan
|
||||||
|
if (Get-Command code -ErrorAction SilentlyContinue) {
|
||||||
|
& code $workshopPath
|
||||||
|
Write-Success "VSCode opened with the workshop repository"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=== IMPORTANT: Terminal Instructions ===" -ForegroundColor Yellow
|
||||||
|
Write-Host "Once VSCode opens, you MUST open the integrated terminal:" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Option 1 (Recommended): Press Ctrl+` (backtick key)" -ForegroundColor Cyan
|
||||||
|
Write-Host "Option 2: Use menu: View → Terminal" -ForegroundColor Cyan
|
||||||
|
Write-Host "Option 3: Use Command Palette: Ctrl+Shift+P → 'Terminal: Create New Terminal'" -ForegroundColor Cyan
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=== Quick Start (run in VSCode terminal) ===" -ForegroundColor Yellow
|
||||||
|
Write-Host " cd 01-essentials\01-basics" -ForegroundColor White
|
||||||
|
Write-Host " .\setup.ps1" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "The terminal should show 'PowerShell' and open to the correct directory." -ForegroundColor Green
|
||||||
|
Write-Host ""
|
||||||
|
} else {
|
||||||
|
Write-Warning "VSCode command not found. Please open manually:"
|
||||||
|
Write-Host " code '$workshopPath'" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "=== Manual Instructions ===" -ForegroundColor Yellow
|
||||||
|
Write-Host "1. Open VSCode manually" -ForegroundColor White
|
||||||
|
Write-Host "2. Open integrated terminal (Ctrl+` or View → Terminal)" -ForegroundColor White
|
||||||
|
Write-Host "3. Navigate: cd 01-essentials\01-basics" -ForegroundColor White
|
||||||
|
Write-Host "4. Run: .\setup.ps1" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Quick start commands (run in VSCode terminal):" -ForegroundColor Cyan
|
||||||
|
Write-Host " cd 01-essentials\01-basics" -ForegroundColor White
|
||||||
|
Write-Host " .\setup.ps1" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
Write-Error "Failed to clone repository: $_"
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "You can clone manually:" -ForegroundColor Yellow
|
||||||
|
Write-Host " git clone https://git.frod.dk/floppydiscen/git-workshop.git ~/Documents/git-workshop" -ForegroundColor White
|
||||||
|
Write-Host " code ~/Documents/git-workshop" -ForegroundColor White
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Write-Host " Skipping repository clone." -ForegroundColor Gray
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Manual setup:" -ForegroundColor Cyan
|
||||||
|
Write-Host " cd path\to\git-workshop" -ForegroundColor White
|
||||||
|
Write-Host " cd 01-essentials\01-basics" -ForegroundColor White
|
||||||
|
Write-Host " .\setup.ps1" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Write-Host ""
|
||||||
|
Write-Warning "Some required installations failed or need verification."
|
||||||
|
Write-Host ""
|
||||||
|
Write-Host "Troubleshooting steps:" -ForegroundColor Yellow
|
||||||
|
Write-Host " 1. Close and reopen your terminal (or restart your computer)" -ForegroundColor White
|
||||||
|
Write-Host " 2. Run this script again: .\install.ps1" -ForegroundColor White
|
||||||
|
Write-Host " 3. If issues persist, try manual installation:" -ForegroundColor White
|
||||||
|
Write-Host " See INSTALLATION.md for detailed instructions" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
|
||||||
|
if (-not $results.Git) {
|
||||||
|
Write-Host "For Git issues:" -ForegroundColor Yellow
|
||||||
|
Write-Host " • Restart terminal after installation (PATH needs to refresh)" -ForegroundColor White
|
||||||
|
Write-Host " • Manual download: https://git-scm.com/downloads" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $results.VSCode) {
|
||||||
|
Write-Host "For VS Code issues:" -ForegroundColor Yellow
|
||||||
|
Write-Host " • Ensure 'Add to PATH' option is enabled during installation" -ForegroundColor White
|
||||||
|
Write-Host " • Manual download: https://code.visualstudio.com/" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $results.PowerShell) {
|
||||||
|
Write-Host "For PowerShell 7 issues:" -ForegroundColor Yellow
|
||||||
|
Write-Host " • Manual download: https://github.com/PowerShell/PowerShell/releases/latest" -ForegroundColor White
|
||||||
|
Write-Host " • Download the file ending in '-win-x64.msi'" -ForegroundColor White
|
||||||
|
Write-Host ""
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user