It is an advanced and difficult revert to accomplish and should probably be done through a reset instead, which means that we're modifying history which is dangerous and so should be handled by someone who understands these dangers.
604 lines
16 KiB
Markdown
604 lines
16 KiB
Markdown
# 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. Revert old commits in the middle of history
|
|
3. Understand how revert preserves commits before and after the target
|
|
4. Revert multiple commits at once
|
|
5. 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 three branches demonstrating different revert scenarios:
|
|
- `regular-revert` - Basic commit reversion
|
|
- `middle-revert` - Reverting a commit in the middle of history
|
|
- `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 a Commit in the Middle
|
|
|
|
### Scenario
|
|
|
|
You're working on improving your calculator application. Several commits were made in sequence:
|
|
1. Added input validation (good)
|
|
2. Added output formatter (BAD - has bugs!)
|
|
3. Added configuration module (good - but came after the bad commit)
|
|
|
|
The formatter has critical bugs and needs to be removed, but you want to keep both the validation module (added before) and the configuration module (added after).
|
|
|
|
**This demonstrates an important Git principle:** Revert works on ANY commit in history, not just recent ones!
|
|
|
|
### Understanding History Preservation
|
|
|
|
Here's what the commit history looks like:
|
|
|
|
```
|
|
A (initial) → B (validation) → C (formatter BAD) → D (config)
|
|
↑
|
|
We want to remove THIS
|
|
```
|
|
|
|
When you revert commit C:
|
|
|
|
```
|
|
A (initial) → B (validation) → C (formatter BAD) → D (config) → E (revert C)
|
|
↑
|
|
Removes formatter, keeps validation & config
|
|
```
|
|
|
|
**Key insight:** The revert creates a NEW commit (E) that undoes commit C, but leaves B and D completely intact!
|
|
|
|
### Your Task
|
|
|
|
1. **Switch to the middle-revert branch:**
|
|
```pwsh
|
|
git switch middle-revert
|
|
```
|
|
|
|
2. **View the commit history:**
|
|
```pwsh
|
|
git log --oneline
|
|
```
|
|
|
|
You should see three commits after the initial:
|
|
- "Add input validation module"
|
|
- "Add broken formatter - needs to be reverted!"
|
|
- "Add configuration module"
|
|
|
|
3. **Find the broken formatter commit:**
|
|
- Look for the message: "Add broken formatter - needs to be reverted!"
|
|
- Note the commit hash (the 7-character code)
|
|
- Write it down
|
|
|
|
4. **Revert that middle commit** (replace `<commit-hash>` with actual hash):
|
|
```pwsh
|
|
git revert <commit-hash>
|
|
```
|
|
|
|
5. **Visual Studio Code will open** with the revert commit message:
|
|
- The default message is fine
|
|
- Close the editor window to accept it
|
|
- Git will create the revert commit
|
|
|
|
6. **Check the result:**
|
|
```pwsh
|
|
# View files - formatter.py should be gone
|
|
ls
|
|
# You should see validation.py and config.py but NOT formatter.py
|
|
|
|
# View the history
|
|
git log --oneline
|
|
# You should see the new revert commit at the top
|
|
```
|
|
|
|
### What to Observe
|
|
|
|
After reverting, notice:
|
|
|
|
```pwsh
|
|
# Check which files exist
|
|
ls
|
|
|
|
# You should see:
|
|
# - calculator.py (from initial commit)
|
|
# - validation.py (from commit BEFORE bad one) ✅
|
|
# - config.py (from commit AFTER bad one) ✅
|
|
# - formatter.py is GONE (reverted) ❌
|
|
```
|
|
|
|
**Important:** Git successfully removed the bad formatter while keeping everything else!
|
|
|
|
### Why This Matters
|
|
|
|
This scenario demonstrates revert's power in real-world situations:
|
|
- You discover a bug in code committed days or weeks ago
|
|
- Many commits have been made since then
|
|
- You can't just delete the old commit (that would break history)
|
|
- Revert lets you surgically remove just the bad commit
|
|
|
|
**Revert is your "undo button" for shared history!**
|
|
|
|
## Challenge 3: 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 all three 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
|
|
- ✅ Revert works on commits in the middle of history
|
|
|
|
## 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 (Module 06)
|
|
- ❌ **Just want to edit a commit** - Use `git commit --amend`
|
|
- ❌ **Haven't pushed yet** - Reset is cleaner for local cleanup
|
|
- ❌ **Need to combine commits** - Use interactive rebase
|
|
- ❌ **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
|
|
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
|
|
|
|
## Troubleshooting
|
|
|
|
### "Empty Revert / No Changes"
|
|
|
|
**Problem:** Revert doesn't seem to do anything.
|
|
|
|
**Possible causes:**
|
|
- Commit was already reverted
|
|
- Subsequent commits already undid the changes
|
|
- Wrong commit hash
|
|
|
|
**Solution:**
|
|
```pwsh
|
|
# Check what the commit actually changed
|
|
git show <commit-hash>
|
|
|
|
# Check if already reverted
|
|
git log --grep="Revert"
|
|
```
|
|
|
|
### "Conflicts During Revert"
|
|
|
|
**Problem:** Revert causes merge conflicts.
|
|
|
|
**Why:** Subsequent commits modified the same code.
|
|
|
|
**Solution:**
|
|
1. Manually resolve conflicts in affected files
|
|
2. `git add <resolved-files>`
|
|
3. `git revert --continue`
|
|
|
|
Or consider fixing forward with a new commit instead of reverting.
|
|
|
|
### "Reverting Old Commit Breaks Something"
|
|
|
|
**Problem:** After reverting an old commit, something else stops working.
|
|
|
|
**Why:** The old commit might have been a dependency for later commits.
|
|
|
|
**Solution:**
|
|
1. Check what changed: `git diff HEAD~1 HEAD`
|
|
2. Either fix the issue with a new commit, or
|
|
3. Revert the revert if needed: `git revert <revert-commit-hash>`
|
|
|
|
## Advanced: Revert Internals
|
|
|
|
Understanding what revert does under the hood:
|
|
|
|
```pwsh
|
|
# Revert creates a new commit with inverse changes
|
|
git revert <commit-hash>
|
|
|
|
# This is equivalent to:
|
|
git diff <commit-hash>^..<commit-hash> > changes.patch
|
|
patch -R < changes.patch # Apply in reverse
|
|
git add .
|
|
git commit -m "Revert '<original message>'"
|
|
```
|
|
|
|
**Key insight:** Revert computes the diff of the target commit, inverts it, and applies it as a new commit.
|
|
|
|
## Going Further
|
|
|
|
Now that you understand revert, you're ready for:
|
|
|
|
- **Module 06: Git Reset** - Learn the dangerous but powerful local history rewriting
|
|
- **Module 07: Git Stash** - Temporarily set aside uncommitted changes
|
|
- **Module 08: Multiplayer Git** - Collaborate with advanced workflows
|
|
|
|
## Summary
|
|
|
|
You've learned:
|
|
|
|
- ✅ `git revert` creates new commits that undo previous changes
|
|
- ✅ Revert is safe for shared/pushed commits
|
|
- ✅ Revert works on any commit in history, even old ones
|
|
- ✅ Commits before and after the reverted commit are preserved
|
|
- ✅ Multiple commits can be reverted in one command
|
|
- ✅ Revert preserves complete history for audit trails
|
|
|
|
**The Golden Rule of Revert:** Use revert for any commit that might be shared with others.
|
|
|
|
## Next Steps
|
|
|
|
1. Complete all three challenge scenarios
|
|
2. Run `./verify.ps1` to check your solutions
|
|
3. Experiment with reverting different commits
|
|
4. Move on to Module 06: Git Reset (dangerous but powerful!)
|
|
|
|
---
|
|
|
|
**Need Help?**
|
|
- Review the command reference above
|
|
- Check the troubleshooting section
|
|
- Re-run `./setup.ps1` to start fresh
|
|
- Practice reverting in different orders to understand the behavior
|