feat: add module 7 on rebasing
This commit is contained in:
130
module-07-rebasing/README.md
Normal file
130
module-07-rebasing/README.md
Normal file
@@ -0,0 +1,130 @@
|
|||||||
|
# Module 07: Rebasing
|
||||||
|
|
||||||
|
## Learning Objectives
|
||||||
|
|
||||||
|
By the end of this module, you will:
|
||||||
|
- Understand what rebasing is and how it works
|
||||||
|
- Know the difference between merge and rebase
|
||||||
|
- Perform a rebase to integrate changes from one branch to another
|
||||||
|
- Understand when to use rebase vs merge
|
||||||
|
- Know the golden rule of rebasing
|
||||||
|
|
||||||
|
## Challenge Description
|
||||||
|
|
||||||
|
You have a repository with a `main` branch and a `feature` branch. While you were working on the feature branch, new commits were added to `main`. You want to incorporate those changes into your feature branch while maintaining a clean, linear history.
|
||||||
|
|
||||||
|
Your task is to:
|
||||||
|
1. Review the current commit history
|
||||||
|
2. Rebase the `feature` branch onto `main`
|
||||||
|
3. Verify that the history is now linear
|
||||||
|
|
||||||
|
## Key Concepts
|
||||||
|
|
||||||
|
### What is Rebasing?
|
||||||
|
|
||||||
|
Rebasing is the process of moving or combining a sequence of commits to a new base commit. Instead of creating a merge commit like `git merge` does, rebasing rewrites the commit history by replaying your commits on top of another branch.
|
||||||
|
|
||||||
|
### Rebase vs Merge
|
||||||
|
|
||||||
|
**Merge:**
|
||||||
|
```
|
||||||
|
A---B---C feature
|
||||||
|
/ \
|
||||||
|
D---E---F---G main
|
||||||
|
```
|
||||||
|
Creates a merge commit (G) that ties the histories together.
|
||||||
|
|
||||||
|
**Rebase:**
|
||||||
|
```
|
||||||
|
A'--B'--C' feature
|
||||||
|
/
|
||||||
|
D---E---F main
|
||||||
|
```
|
||||||
|
Replays commits A, B, C on top of F, creating new commits A', B', C' with the same changes but different commit hashes.
|
||||||
|
|
||||||
|
### Benefits of Rebasing
|
||||||
|
|
||||||
|
- **Cleaner history**: Linear history is easier to read and understand
|
||||||
|
- **Simpler log**: No merge commits cluttering the history
|
||||||
|
- **Easier bisecting**: Finding bugs with `git bisect` is simpler with linear history
|
||||||
|
|
||||||
|
### The Golden Rule of Rebasing
|
||||||
|
|
||||||
|
**Never rebase commits that have been pushed to a public/shared repository.**
|
||||||
|
|
||||||
|
Why? Because rebasing rewrites history by creating new commits. If others have based work on the original commits, rebasing will cause serious problems for collaborators.
|
||||||
|
|
||||||
|
**Safe to rebase:**
|
||||||
|
- Local commits not yet pushed
|
||||||
|
- Feature branches you're working on alone
|
||||||
|
- Cleaning up your work before creating a pull request
|
||||||
|
|
||||||
|
**Never rebase:**
|
||||||
|
- Commits already pushed to a shared branch (like `main` or `develop`)
|
||||||
|
- Commits that others might have based work on
|
||||||
|
|
||||||
|
## Useful Commands
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Rebase current branch onto another branch
|
||||||
|
git rebase <branch-name>
|
||||||
|
|
||||||
|
# View commit history as a graph
|
||||||
|
git log --oneline --graph --all
|
||||||
|
|
||||||
|
# If conflicts occur during rebase:
|
||||||
|
# 1. Resolve conflicts in files
|
||||||
|
# 2. Stage the resolved files
|
||||||
|
git add <file>
|
||||||
|
# 3. Continue the rebase
|
||||||
|
git rebase --continue
|
||||||
|
|
||||||
|
# Abort a rebase if something goes wrong
|
||||||
|
git rebase --abort
|
||||||
|
|
||||||
|
# Check which branch you're on
|
||||||
|
git branch
|
||||||
|
|
||||||
|
# Switch to a branch
|
||||||
|
git checkout <branch-name>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Verification
|
||||||
|
|
||||||
|
Run the verification script to check your solution:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.\verify.ps1
|
||||||
|
```
|
||||||
|
|
||||||
|
The verification will check that:
|
||||||
|
- You're on the feature branch
|
||||||
|
- The rebase was completed successfully
|
||||||
|
- The history is linear (no merge commits)
|
||||||
|
- All commits from both branches are present
|
||||||
|
|
||||||
|
## Tips
|
||||||
|
|
||||||
|
- Always check your commit graph with `git log --oneline --graph --all` before and after rebasing
|
||||||
|
- If you encounter conflicts during rebase, resolve them just like merge conflicts
|
||||||
|
- Use `git rebase --abort` if you want to cancel the rebase and start over
|
||||||
|
- Rebasing rewrites history, so the commit hashes will change
|
||||||
|
- Only rebase local commits that haven't been shared with others
|
||||||
|
|
||||||
|
## When to Use Rebase vs Merge
|
||||||
|
|
||||||
|
**Use Rebase when:**
|
||||||
|
- You want a clean, linear history
|
||||||
|
- Working on a local feature branch that hasn't been shared
|
||||||
|
- Updating your feature branch with the latest changes from main
|
||||||
|
- You want to clean up commits before submitting a pull request
|
||||||
|
|
||||||
|
**Use Merge when:**
|
||||||
|
- Working on a shared/public branch
|
||||||
|
- You want to preserve the complete history including when branches diverged
|
||||||
|
- You're merging a completed feature into main
|
||||||
|
- You want to be safe and avoid rewriting history
|
||||||
|
|
||||||
|
## What You'll Learn
|
||||||
|
|
||||||
|
Rebasing is a powerful tool for maintaining a clean project history. While merging is safer and preserves exact history, rebasing creates a more readable linear timeline. Understanding both techniques and knowing when to use each is essential for effective Git workflow management.
|
||||||
22
module-07-rebasing/reset.ps1
Normal file
22
module-07-rebasing/reset.ps1
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Resets the rebasing challenge environment.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Removes the existing challenge directory and runs setup.ps1
|
||||||
|
to create a fresh challenge environment.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Write-Host "Resetting challenge environment..." -ForegroundColor Yellow
|
||||||
|
|
||||||
|
# Remove existing challenge directory if present
|
||||||
|
if (Test-Path "challenge") {
|
||||||
|
Remove-Item -Path "challenge" -Recurse -Force
|
||||||
|
Write-Host "Removed existing challenge directory." -ForegroundColor Cyan
|
||||||
|
}
|
||||||
|
|
||||||
|
# Run setup script
|
||||||
|
Write-Host "Running setup script...`n" -ForegroundColor Cyan
|
||||||
|
& ".\setup.ps1"
|
||||||
123
module-07-rebasing/setup.ps1
Normal file
123
module-07-rebasing/setup.ps1
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Sets up the rebasing challenge environment.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Creates a Git repository with diverged branches to practice rebasing.
|
||||||
|
The feature branch needs to be rebased onto main.
|
||||||
|
#>
|
||||||
|
|
||||||
|
# Remove existing challenge directory if present
|
||||||
|
if (Test-Path "challenge") {
|
||||||
|
Write-Host "Removing existing challenge directory..." -ForegroundColor Yellow
|
||||||
|
Remove-Item -Path "challenge" -Recurse -Force
|
||||||
|
}
|
||||||
|
|
||||||
|
# Create challenge directory
|
||||||
|
Write-Host "Creating challenge environment..." -ForegroundColor Cyan
|
||||||
|
New-Item -ItemType Directory -Path "challenge" | Out-Null
|
||||||
|
Set-Location "challenge"
|
||||||
|
|
||||||
|
# Initialize git repository
|
||||||
|
git init | Out-Null
|
||||||
|
git config user.name "Workshop User" | Out-Null
|
||||||
|
git config user.email "user@workshop.local" | Out-Null
|
||||||
|
|
||||||
|
# Create initial file and commit
|
||||||
|
$readme = @"
|
||||||
|
# My Project
|
||||||
|
|
||||||
|
A sample project for learning Git rebasing.
|
||||||
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "README.md" -Value $readme
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Initial commit" | Out-Null
|
||||||
|
|
||||||
|
# Create and switch to feature branch
|
||||||
|
git checkout -b feature | Out-Null
|
||||||
|
|
||||||
|
# Add commits on feature branch
|
||||||
|
$feature1 = @"
|
||||||
|
# My Project
|
||||||
|
|
||||||
|
A sample project for learning Git rebasing.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Feature A: User authentication
|
||||||
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "README.md" -Value $feature1
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Add feature A" | Out-Null
|
||||||
|
|
||||||
|
$feature2 = @"
|
||||||
|
# My Project
|
||||||
|
|
||||||
|
A sample project for learning Git rebasing.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Feature A: User authentication
|
||||||
|
- Feature B: Data validation
|
||||||
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "README.md" -Value $feature2
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Add feature B" | Out-Null
|
||||||
|
|
||||||
|
# Switch back to main and add commits (simulating other work happening on main)
|
||||||
|
git checkout main | Out-Null
|
||||||
|
|
||||||
|
$main1 = @"
|
||||||
|
# My Project
|
||||||
|
|
||||||
|
A sample project for learning Git rebasing.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Run \`npm install\` to install dependencies.
|
||||||
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "README.md" -Value $main1
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Add installation instructions" | Out-Null
|
||||||
|
|
||||||
|
$main2 = @"
|
||||||
|
# My Project
|
||||||
|
|
||||||
|
A sample project for learning Git rebasing.
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
Run \`npm install\` to install dependencies.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Copy \`config.example.json\` to \`config.json\` and update settings.
|
||||||
|
"@
|
||||||
|
|
||||||
|
Set-Content -Path "README.md" -Value $main2
|
||||||
|
git add README.md
|
||||||
|
git commit -m "Add configuration instructions" | Out-Null
|
||||||
|
|
||||||
|
# Switch back to feature branch
|
||||||
|
git checkout feature | Out-Null
|
||||||
|
|
||||||
|
# Return to module directory
|
||||||
|
Set-Location ..
|
||||||
|
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Green
|
||||||
|
Write-Host "Challenge environment created!" -ForegroundColor Green
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host "`nYou are now on the 'feature' branch." -ForegroundColor Cyan
|
||||||
|
Write-Host "The 'main' branch has new commits that aren't in your feature branch." -ForegroundColor Cyan
|
||||||
|
Write-Host "`nYour task:" -ForegroundColor Yellow
|
||||||
|
Write-Host "1. Navigate to the challenge directory: cd challenge" -ForegroundColor White
|
||||||
|
Write-Host "2. View the commit history: git log --oneline --graph --all" -ForegroundColor White
|
||||||
|
Write-Host "3. Rebase the 'feature' branch onto 'main'" -ForegroundColor White
|
||||||
|
Write-Host "4. View the history again to see the linear result" -ForegroundColor White
|
||||||
|
Write-Host "`nRun '../verify.ps1' from the challenge directory to check your solution.`n" -ForegroundColor Cyan
|
||||||
154
module-07-rebasing/verify.ps1
Normal file
154
module-07-rebasing/verify.ps1
Normal file
@@ -0,0 +1,154 @@
|
|||||||
|
#!/usr/bin/env pwsh
|
||||||
|
|
||||||
|
<#
|
||||||
|
.SYNOPSIS
|
||||||
|
Verifies the rebasing challenge solution.
|
||||||
|
|
||||||
|
.DESCRIPTION
|
||||||
|
Checks that the user successfully rebased the feature branch onto main,
|
||||||
|
resulting in a clean, linear history.
|
||||||
|
#>
|
||||||
|
|
||||||
|
Set-Location "challenge" -ErrorAction SilentlyContinue
|
||||||
|
|
||||||
|
# Check if challenge directory exists
|
||||||
|
if (-not (Test-Path "../verify.ps1")) {
|
||||||
|
Write-Host "Error: Please run this script from the module directory" -ForegroundColor Red
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not (Test-Path ".")) {
|
||||||
|
Write-Host "Error: Challenge directory not found. Run setup.ps1 first." -ForegroundColor Red
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
Write-Host "Verifying your solution..." -ForegroundColor Cyan
|
||||||
|
|
||||||
|
# Check if git repository exists
|
||||||
|
if (-not (Test-Path ".git")) {
|
||||||
|
Write-Host "[FAIL] No git repository found." -ForegroundColor Red
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check current branch
|
||||||
|
$currentBranch = git branch --show-current 2>$null
|
||||||
|
if ($currentBranch -ne "feature") {
|
||||||
|
Write-Host "[FAIL] You should be on the 'feature' branch." -ForegroundColor Red
|
||||||
|
Write-Host "Current branch: $currentBranch" -ForegroundColor Yellow
|
||||||
|
Write-Host "Hint: Use 'git checkout feature' to switch to feature branch" -ForegroundColor Yellow
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check if there's an ongoing rebase
|
||||||
|
if (Test-Path ".git/rebase-merge") {
|
||||||
|
Write-Host "[FAIL] Rebase is not complete. There may be unresolved conflicts." -ForegroundColor Red
|
||||||
|
Write-Host "Hint: Resolve any conflicts, then use:" -ForegroundColor Yellow
|
||||||
|
Write-Host " git add <file>" -ForegroundColor White
|
||||||
|
Write-Host " git rebase --continue" -ForegroundColor White
|
||||||
|
Write-Host "Or abort with: git rebase --abort" -ForegroundColor White
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get all commits on feature branch
|
||||||
|
$featureCommits = git log --oneline feature 2>$null
|
||||||
|
if (-not $featureCommits) {
|
||||||
|
Write-Host "[FAIL] No commits found on feature branch." -ForegroundColor Red
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check commit count (should be 5: 1 initial + 2 main + 2 feature)
|
||||||
|
$commitCount = (git rev-list --count feature 2>$null)
|
||||||
|
if ($commitCount -ne 5) {
|
||||||
|
Write-Host "[FAIL] Expected 5 commits on feature branch, found $commitCount" -ForegroundColor Red
|
||||||
|
Write-Host "Hint: The rebased feature branch should contain:" -ForegroundColor Yellow
|
||||||
|
Write-Host " - 1 initial commit" -ForegroundColor White
|
||||||
|
Write-Host " - 2 commits from main" -ForegroundColor White
|
||||||
|
Write-Host " - 2 commits from feature" -ForegroundColor White
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Get commit messages in reverse chronological order (newest first)
|
||||||
|
$commits = git log --pretty=format:"%s" feature 2>$null
|
||||||
|
|
||||||
|
# Convert to array and reverse to get chronological order (oldest first)
|
||||||
|
$commitArray = $commits -split "`n"
|
||||||
|
[array]::Reverse($commitArray)
|
||||||
|
|
||||||
|
# Check that commits are in the expected order after rebase
|
||||||
|
$expectedOrder = @(
|
||||||
|
"Initial commit",
|
||||||
|
"Add installation instructions",
|
||||||
|
"Add configuration instructions",
|
||||||
|
"Add feature A",
|
||||||
|
"Add feature B"
|
||||||
|
)
|
||||||
|
|
||||||
|
$orderCorrect = $true
|
||||||
|
for ($i = 0; $i -lt $expectedOrder.Length; $i++) {
|
||||||
|
if ($commitArray[$i] -ne $expectedOrder[$i]) {
|
||||||
|
$orderCorrect = $false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (-not $orderCorrect) {
|
||||||
|
Write-Host "[FAIL] Commit order is incorrect after rebase." -ForegroundColor Red
|
||||||
|
Write-Host "Expected order (oldest to newest):" -ForegroundColor Yellow
|
||||||
|
$expectedOrder | ForEach-Object { Write-Host " $_" -ForegroundColor White }
|
||||||
|
Write-Host "`nActual order:" -ForegroundColor Yellow
|
||||||
|
$commitArray | ForEach-Object { Write-Host " $_" -ForegroundColor White }
|
||||||
|
Write-Host "`nHint: The feature commits should come AFTER the main commits" -ForegroundColor Yellow
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that main branch still has only 3 commits (initial + 2 main commits)
|
||||||
|
$mainCommitCount = (git rev-list --count main 2>$null)
|
||||||
|
if ($mainCommitCount -ne 3) {
|
||||||
|
Write-Host "[FAIL] Main branch should have 3 commits, found $mainCommitCount" -ForegroundColor Red
|
||||||
|
Write-Host "Hint: You should rebase feature onto main, not the other way around" -ForegroundColor Yellow
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Check that there are no merge commits (parent count should be 1 for all commits)
|
||||||
|
$mergeCommits = git log --merges --oneline feature 2>$null
|
||||||
|
if ($mergeCommits) {
|
||||||
|
Write-Host "[FAIL] Found merge commits in history. Rebasing should create a linear history." -ForegroundColor Red
|
||||||
|
Write-Host "Hint: Use 'git rebase main' instead of 'git merge main'" -ForegroundColor Yellow
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# Verify that feature branch contains all commits from main
|
||||||
|
$mainCommits = git log --pretty=format:"%s" main 2>$null
|
||||||
|
$featureCommitMessages = git log --pretty=format:"%s" feature 2>$null
|
||||||
|
|
||||||
|
foreach ($mainCommit in ($mainCommits -split "`n")) {
|
||||||
|
if ($featureCommitMessages -notcontains $mainCommit) {
|
||||||
|
Write-Host "[FAIL] Feature branch is missing commits from main." -ForegroundColor Red
|
||||||
|
Write-Host "Missing: $mainCommit" -ForegroundColor Yellow
|
||||||
|
Set-Location ..
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
# Success!
|
||||||
|
Write-Host "`n========================================" -ForegroundColor Green
|
||||||
|
Write-Host "SUCCESS! Challenge completed!" -ForegroundColor Green
|
||||||
|
Write-Host "========================================" -ForegroundColor Green
|
||||||
|
Write-Host "`nYou have successfully:" -ForegroundColor Cyan
|
||||||
|
Write-Host "- Rebased the feature branch onto main" -ForegroundColor White
|
||||||
|
Write-Host "- Created a clean, linear commit history" -ForegroundColor White
|
||||||
|
Write-Host "- Preserved all commits from both branches" -ForegroundColor White
|
||||||
|
Write-Host "`nYour feature branch now has a linear history!" -ForegroundColor Green
|
||||||
|
Write-Host "Run 'git log --oneline --graph --all' to see the result.`n" -ForegroundColor Cyan
|
||||||
|
|
||||||
|
Set-Location ..
|
||||||
|
exit 0
|
||||||
Reference in New Issue
Block a user