feat: rebase module 8

This commit is contained in:
Bjarke Sporring
2026-01-04 17:07:43 +01:00
parent b551eee54e
commit 23f0ff511c
4 changed files with 496 additions and 0 deletions

View File

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

View File

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

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the interactive rebase challenge environment.
.DESCRIPTION
Creates a Git repository with messy commit history that needs to be
cleaned up using reset and recommit techniques.
#>
# Remove existing challenge directory if present
if (Test-Path "challenge") {
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
Remove-Item -Path "challenge" -Recurse -Force
}
# Create challenge directory
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
New-Item -ItemType Directory -Path "challenge" | Out-Null
Set-Location "challenge"
# Initialize git repository
git init | Out-Null
git config user.name "Workshop User" | Out-Null
git config user.email "user@workshop.local" | Out-Null
# Create initial commit
$readme = @"
# User Management System
A simple user management application.
"@
Set-Content -Path "README.md" -Value $readme
git add README.md
git commit -m "Initial commit" | Out-Null
# Create messy commits that should be squashed
# Commit 1: WIP user profile
$userProfile = @"
class UserProfile {
constructor(name, email) {
this.name = name;
this.email = email;
}
}
"@
Set-Content -Path "user-profile.js" -Value $userProfile
git add user-profile.js
git commit -m "WIP: user profile" | Out-Null
# Commit 2: Add validation (typo in message)
$userProfileWithValidation = @"
class UserProfile {
constructor(name, email) {
this.name = name;
this.email = email;
}
validate() {
if (!this.name || !this.email) {
throw new Error('Name and email are required');
}
return true;
}
}
"@
Set-Content -Path "user-profile.js" -Value $userProfileWithValidation
git add user-profile.js
git commit -m "add validaton" | Out-Null # Intentional typo
# Commit 3: Fix validation
$userProfileFixed = @"
class UserProfile {
constructor(name, email) {
this.name = name;
this.email = email;
}
validate() {
if (!this.name || !this.email) {
throw new Error('Name and email are required');
}
if (!this.email.includes('@')) {
throw new Error('Invalid email format');
}
return true;
}
}
"@
Set-Content -Path "user-profile.js" -Value $userProfileFixed
git add user-profile.js
git commit -m "fix validation bug" | Out-Null
# Commit 4: Add tests (another WIP commit)
$tests = @"
const assert = require('assert');
const UserProfile = require('./user-profile');
describe('UserProfile', () => {
it('should validate correct user data', () => {
const user = new UserProfile('John', 'john@example.com');
assert.strictEqual(user.validate(), true);
});
it('should reject missing name', () => {
const user = new UserProfile('', 'john@example.com');
assert.throws(() => user.validate());
});
it('should reject invalid email', () => {
const user = new UserProfile('John', 'invalid-email');
assert.throws(() => user.validate());
});
});
"@
Set-Content -Path "user-profile.test.js" -Value $tests
git add user-profile.test.js
git commit -m "WIP tests" | Out-Null
# Return to module directory
Set-Location ..
Write-Host "`n========================================" -ForegroundColor Green
Write-Host "Challenge environment created!" -ForegroundColor Green
Write-Host "========================================" -ForegroundColor Green
Write-Host "`nYou have a repository with messy commit history:" -ForegroundColor Cyan
Write-Host "- WIP commits" -ForegroundColor Yellow
Write-Host "- Typos in commit messages" -ForegroundColor Yellow
Write-Host "- Multiple commits that should be combined" -ForegroundColor Yellow
Write-Host "`nYour task:" -ForegroundColor Yellow
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
Write-Host "2. View the messy history: git log --oneline" -ForegroundColor White
Write-Host "3. Use 'git reset --soft HEAD~4' to uncommit the last 4 commits" -ForegroundColor White
Write-Host "4. Create a single clean commit with a descriptive message" -ForegroundColor White
Write-Host " Example: git commit -m 'Add user profile feature with validation'" -ForegroundColor White
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan

View File

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