refactor: rewrite the merge-revert section

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.
This commit is contained in:
Bjarke Sporring
2026-01-15 16:11:24 +01:00
parent aa24c50b45
commit 575e083f33
4 changed files with 186 additions and 282 deletions

View File

@@ -1,8 +1,8 @@
# Module 05: Git Revert - Safe Undoing
# Module 06: Git Revert - Safe Undoing
## About This Module
Welcome to Module 05, 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.
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
@@ -17,19 +17,18 @@ Welcome to Module 05, where you'll learn the **safe, team-friendly way to undo c
By completing this module, you will:
1. Revert regular commits safely while preserving surrounding changes
2. Revert merge commits using the `-m` flag
3. Understand merge commit parent numbering
4. Handle the re-merge problem that occurs after reverting merges
5. Revert multiple commits at once
6. Know when to use revert vs. other undo strategies
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 and merging (Module 03)
- Understanding branches (Module 03)
## Setup
@@ -41,7 +40,7 @@ Run the setup script to create the challenge environment:
This creates a `challenge/` directory with three branches demonstrating different revert scenarios:
- `regular-revert` - Basic commit reversion
- `merge-revert` - Merge commit reversion
- `middle-revert` - Reverting a commit in the middle of history
- `multi-revert` - Multiple commit reversion
## Challenge 1: Reverting a Regular Commit
@@ -120,140 +119,108 @@ main.py (initial) → multiply (good) → divide (BAD) → modulo (good) → rev
The revert commit adds a new point in history that undoes the divide changes.
## Challenge 2: Reverting a Merge Commit
## Challenge 2: Reverting a Commit in the Middle
### Scenario
Your team merged a `feature-auth` branch that added authentication functionality. After deployment, you discovered the authentication system has critical security issues. You need to revert the entire merge while the security team redesigns the feature.
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)
**This is different from reverting a regular commit!** Merge commits have **two parents**, so you must tell Git which parent to keep.
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).
### Understanding Merge Commit Parents
**This demonstrates an important Git principle:** Revert works on ANY commit in history, not just recent ones!
When you merge a feature branch into main:
### Understanding History Preservation
Here's what the commit history looks like:
```
feature-auth (parent 2)
C---D
/ \
A---B-----M ← Merge commit (has TWO parents)
A (initial) → B (validation) → C (formatter BAD) → D (config)
parent 1 (main)
We want to remove THIS
```
The merge commit `M` has:
- **Parent 1**: The branch you merged INTO (main)
- **Parent 2**: The branch you merged FROM (feature-auth)
When you revert commit C:
When reverting a merge, you must specify which parent to keep using the `-m` flag:
- `-m 1` means "keep parent 1" (main) - **Most common**
- `-m 2` means "keep parent 2" (feature-auth) - Rare
```
A (initial) → B (validation) → C (formatter BAD) → D (config) → E (revert C)
Removes formatter, keeps validation & config
```
**In practice:** You almost always use `-m 1` to keep the main branch and undo the feature branch changes.
**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 merge-revert branch:**
1. **Switch to the middle-revert branch:**
```pwsh
git switch merge-revert
git switch middle-revert
```
2. **View the commit history with the graph:**
2. **View the commit history:**
```pwsh
git log --oneline --graph
git log --oneline
```
Look for the merge commit message: "Merge feature-auth branch"
- Note the commit hash
- Write it down or copy it
You should see three commits after the initial:
- "Add input validation module"
- "Add broken formatter - needs to be reverted!"
- "Add configuration module"
3. **Revert the merge commit using `-m 1`** (replace `<merge-commit-hash>` with actual hash):
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 -m 1 <merge-commit-hash>
git revert <commit-hash>
```
**What `-m 1` means:**
- `-m 1` tells Git to keep parent 1 (the main branch side)
- This undoes all changes from the feature-auth branch
- Creates a new "revert merge" commit
4. **Visual Studio Code will open** with the revert commit message:
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
5. **Check the result:**
6. **Check the result:**
```pwsh
# View files - auth.py should be gone
# View files - formatter.py should be gone
ls
# You should see calculator.py but NOT auth.py
# You should see validation.py and config.py but NOT formatter.py
# Verify calculator.py no longer imports auth
cat calculator.py
# Should NOT see "from auth import" anywhere
# View the history
git log --oneline
# You should see the new revert commit at the top
```
### What Happens Without -m?
### What to Observe
If you try to revert a merge commit without the `-m` flag:
After reverting, notice:
```bash
git revert <merge-commit-hash>
# Error: commit <hash> is a merge but no -m option was given
```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) ❌
```
Git doesn't know which parent you want to keep, so it refuses to proceed.
**Important:** Git successfully removed the bad formatter while keeping everything else!
### The Re-Merge Problem
### Why This Matters
**Important gotcha:** After reverting a merge, you **cannot simply re-merge** the same branch!
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
Here's why:
```
Initial merge:
A---B---M (merged feature-auth)
All changes from feature-auth are now in main
After revert:
A---B---M---R (reverted merge)
Changes removed, but Git remembers they were merged
Attempting to re-merge:
A---B---M---R---M2 (try to merge feature-auth again)
Git thinks: "I already merged these commits,
nothing new to add!" (Empty merge)
```
**Solutions if you need to re-merge:**
1. **Revert the revert** (recommended):
```bash
git revert <revert-commit-hash>
```
This brings back all the feature-auth changes.
2. **Cherry-pick new commits** from the feature branch:
```bash
git cherry-pick <new-commits>
```
3. **Merge with --no-ff** and resolve conflicts manually (advanced).
### When to Revert Merges
Revert merge commits when:
- ✅ Feature causes production issues
- ✅ Need to temporarily remove a feature
- ✅ Discovered critical bugs after merging
- ✅ Security issues require immediate rollback
Don't revert merges when:
- ❌ You just need to fix a small bug (fix it with a new commit instead)
- ❌ You plan to re-merge the same branch soon (use reset if local, or revert-the-revert later)
**Revert is your "undo button" for shared history!**
## Challenge 3: Reverting Multiple Commits
@@ -311,7 +278,7 @@ Two separate commits added broken mathematical functions (`square_root` and `log
**Reverting a range of commits:**
```bash
```pwsh
# Revert commits from A to B (inclusive)
git revert A^..B
@@ -321,7 +288,7 @@ git revert HEAD~3..HEAD
**Reverting without auto-commit:**
```bash
```pwsh
# Stage revert changes without committing
git revert --no-commit <commit-hash>
@@ -353,8 +320,7 @@ The script checks that:
- ✅ Revert commits were created (not destructive deletion)
- ✅ Bad code is removed
- ✅ Good code before and after is preserved
- ✅ Merge commits still exist in history
- ✅ Proper use of `-m` flag for merge reverts
- ✅ Revert works on commits in the middle of history
## Command Reference
@@ -371,14 +337,17 @@ git revert HEAD
git revert HEAD~1
```
### Merge Commit Revert
### Reverting Old Commits
```pwsh
# Revert a merge commit (keep parent 1)
git revert -m 1 <merge-commit-hash>
# Revert a specific commit from any point in history
git revert <commit-hash>
# Revert a merge commit (keep parent 2) - rare
git revert -m 2 <merge-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
@@ -450,7 +419,7 @@ Consider alternatives when:
Sometimes reverting causes conflicts if subsequent changes touched the same code:
```bash
```pwsh
# Start revert
git revert <commit-hash>
@@ -463,47 +432,24 @@ git revert <commit-hash>
1. Open conflicted files and fix conflicts (look for `<<<<<<<` markers)
2. Stage resolved files:
```bash
```pwsh
git add <resolved-files>
```
3. Continue the revert:
```bash
```pwsh
git revert --continue
```
Or abort if you change your mind:
```bash
```pwsh
git revert --abort
```
## Common Mistakes
### 1. Forgetting -m for Merge Commits
### 1. Using Reset on Pushed Commits
```bash
# ❌ Wrong - will fail
git revert <merge-commit>
# ✅ Correct
git revert -m 1 <merge-commit>
```
### 2. Trying to Re-Merge After Revert
```bash
# After reverting a merge:
git revert -m 1 <merge-commit>
# ❌ This won't work as expected
git merge feature-branch # Empty merge!
# ✅ Do this instead
git revert <the-revert-commit> # Revert the revert
```
### 3. Using Reset on Pushed Commits
```bash
```pwsh
# ❌ NEVER do this with pushed commits
git reset --hard HEAD~3
@@ -511,11 +457,11 @@ git reset --hard HEAD~3
git revert HEAD~3..HEAD
```
### 4. Reverting Commits in Wrong Order
### 2. Reverting Commits in Wrong Order
When reverting multiple related commits, revert from newest to oldest:
```bash
```pwsh
# If you have: A → B → C (and C depends on B)
# ✅ Correct order
@@ -530,7 +476,7 @@ git revert C
## Best Practices
1. **Write clear revert messages:**
```bash
```pwsh
git revert <hash> -m "Revert authentication - security issue #1234"
```
@@ -561,15 +507,6 @@ git revert C
## Troubleshooting
### "Commit is a merge but no -m option was given"
**Problem:** Trying to revert a merge commit without `-m`.
**Solution:**
```bash
git revert -m 1 <merge-commit-hash>
```
### "Empty Revert / No Changes"
**Problem:** Revert doesn't seem to do anything.
@@ -580,7 +517,7 @@ git revert -m 1 <merge-commit-hash>
- Wrong commit hash
**Solution:**
```bash
```pwsh
# Check what the commit actually changed
git show <commit-hash>
@@ -601,24 +538,22 @@ git log --grep="Revert"
Or consider fixing forward with a new commit instead of reverting.
### "Can't Re-Merge After Reverting Merge"
### "Reverting Old Commit Breaks Something"
**Problem:** After reverting a merge, re-merging the branch brings no changes.
**Problem:** After reverting an old commit, something else stops working.
**Solution:** Revert the revert commit:
```bash
# Find the revert commit
git log --oneline
**Why:** The old commit might have been a dependency for later commits.
# Revert the revert (brings changes back)
git revert <revert-commit-hash>
```
**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:
```bash
```pwsh
# Revert creates a new commit with inverse changes
git revert <commit-hash>
@@ -645,9 +580,8 @@ You've learned:
- ✅ `git revert` creates new commits that undo previous changes
- ✅ Revert is safe for shared/pushed commits
- ✅ Merge commits require `-m 1` or `-m 2` flag
- ✅ Parent 1 = branch merged into, Parent 2 = branch merged from
- ✅ Can't simply re-merge after reverting a merge
- ✅ 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

View File

@@ -1,14 +1,14 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Resets the Module 05 challenge environment to start fresh.
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 05: Git Revert Challenge ===" -ForegroundColor Cyan
Write-Host "`n=== Resetting Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
# Check if challenge directory exists
if (Test-Path "challenge") {

View File

@@ -1,17 +1,17 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Sets up the Module 05 challenge environment for learning git revert.
Sets up the Module 06 challenge environment for learning git revert.
.DESCRIPTION
This script creates a challenge directory with three branches demonstrating
different revert scenarios:
- regular-revert: Basic revert of a single bad commit
- merge-revert: Reverting a merge commit with -m flag
- regular-revert: Basic revert of a single bad commit at the end
- middle-revert: Reverting a bad commit in the middle of history
- multi-revert: Reverting multiple commits at once
#>
Write-Host "`n=== Setting up Module 05: Git Revert Challenge ===" -ForegroundColor Cyan
Write-Host "`n=== Setting up Module 06: Git Revert Challenge ===" -ForegroundColor Cyan
# Remove existing challenge directory if it exists
if (Test-Path "challenge") {
@@ -104,90 +104,63 @@ git commit -m "Add modulo function" | Out-Null
Write-Host "[CREATED] regular-revert branch with bad divide commit" -ForegroundColor Green
# ============================================================================
# SCENARIO 2: Merge Revert (Merge Commit with -m flag)
# SCENARIO 2: Revert in Middle of History
# ============================================================================
Write-Host "`nScenario 2: Creating merge-revert scenario..." -ForegroundColor Cyan
Write-Host "`nScenario 2: Creating middle-revert scenario..." -ForegroundColor Cyan
# Switch back to main
git switch $mainBranch | Out-Null
# Create merge-revert branch
git switch -c merge-revert | Out-Null
# Create middle-revert branch
git switch -c middle-revert | Out-Null
# Create a feature branch to merge
git switch -c feature-auth | Out-Null
# Good commit: Add validation module
$validationContent = @"
# validation.py - Input validation
# Add auth functionality
$authContent = @"
# auth.py - Authentication module
def login(username, password):
\"\"\"Login user.\"\"\"
print(f"Logging in {username}...")
def validate_number(value):
"""Check if value is a valid number."""
try:
float(value)
return True
def logout(username):
\"\"\"Logout user.\"\"\"
print(f"Logging out {username}...")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
git add .
git commit -m "Add authentication module" | Out-Null
# Add password validation
$authContent = @"
# auth.py - Authentication module
def validate_password(password):
\"\"\"Validate password strength.\"\"\"
return len(password) >= 8
def login(username, password):
\"\"\"Login user.\"\"\"
if not validate_password(password):
print("Password too weak!")
except ValueError:
return False
print(f"Logging in {username}...")
return True
def logout(username):
\"\"\"Logout user.\"\"\"
print(f"Logging out {username}...")
return True
"@
Set-Content -Path "auth.py" -Value $authContent
Set-Content -Path "validation.py" -Value $validationContent
git add .
git commit -m "Add password validation" | Out-Null
git commit -m "Add input validation module" | Out-Null
# Integrate auth into calculator (part of the feature branch)
$calcContent = @"
# calculator.py - Simple calculator
from auth import login
# BAD commit: Add broken formatter
$formatterContent = @"
# formatter.py - Output formatting
def add(a, b):
"""Add two numbers."""
return a + b
def subtract(a, b):
"""Subtract b from a."""
return a - b
def secure_divide(a, b, username):
"""Secure divide - requires authentication."""
if login(username, "password123"):
return a / b
return None
def format_result(result):
"""BROKEN: Doesn't handle None or errors properly!"""
return f"Result: {result.upper()}" # This crashes if result is not a string!
"@
Set-Content -Path "calculator.py" -Value $calcContent
Set-Content -Path "formatter.py" -Value $formatterContent
git add .
git commit -m "Integrate auth into calculator" | Out-Null
git commit -m "Add broken formatter - needs to be reverted!" | Out-Null
# Switch back to merge-revert and merge feature-auth
git switch merge-revert | Out-Null
git merge feature-auth --no-ff -m "Merge feature-auth branch" | Out-Null
# Good commit: Add configuration (depends on validation, not formatter)
$configContent = @"
# config.py - Application configuration
Write-Host "[CREATED] merge-revert branch with merge commit to revert" -ForegroundColor Green
from validation import validate_number
DEFAULT_PRECISION = 2
def set_precision(value):
"""Set calculation precision."""
if validate_number(value):
return int(value)
return DEFAULT_PRECISION
"@
Set-Content -Path "config.py" -Value $configContent
git add .
git commit -m "Add configuration module" | Out-Null
Write-Host "[CREATED] middle-revert branch with bad commit in the middle" -ForegroundColor Green
# ============================================================================
# SCENARIO 3: Multi Revert (Multiple Bad Commits)
@@ -275,8 +248,8 @@ Set-Location ..
Write-Host "`n=== Setup Complete! ===" -ForegroundColor Green
Write-Host "`nThree revert scenarios have been created:" -ForegroundColor Cyan
Write-Host " 1. regular-revert - Revert a single bad commit (basic)" -ForegroundColor White
Write-Host " 2. merge-revert - Revert a merge commit with -m flag" -ForegroundColor White
Write-Host " 1. regular-revert - Revert a bad commit at the end" -ForegroundColor White
Write-Host " 2. middle-revert - Revert a bad commit in the middle of history" -ForegroundColor White
Write-Host " 3. 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

View File

@@ -1,16 +1,16 @@
#!/usr/bin/env pwsh
<#
.SYNOPSIS
Verifies the Module 05 challenge solutions.
Verifies the Module 06 challenge solutions.
.DESCRIPTION
Checks that all three revert scenarios have been completed correctly:
- regular-revert: Single commit reverted
- merge-revert: Merge commit reverted with -m flag
- middle-revert: Commit in middle of history reverted
- multi-revert: Multiple commits reverted
#>
Write-Host "`n=== Verifying Module 05: Git Revert Solutions ===" -ForegroundColor Cyan
Write-Host "`n=== Verifying Module 06: Git Revert Solutions ===" -ForegroundColor Cyan
$allChecksPassed = $true
$originalDir = Get-Location
@@ -87,53 +87,50 @@ if ($LASTEXITCODE -ne 0) {
}
# ============================================================================
# SCENARIO 2: Merge Revert Verification
# SCENARIO 2: Middle Revert Verification
# ============================================================================
Write-Host "`n=== Scenario 2: Merge Revert ===" -ForegroundColor Cyan
Write-Host "`n=== Scenario 2: Middle Revert ===" -ForegroundColor Cyan
git switch merge-revert 2>&1 | Out-Null
git switch middle-revert 2>&1 | Out-Null
if ($LASTEXITCODE -ne 0) {
Write-Host "[FAIL] merge-revert branch not found" -ForegroundColor Red
Write-Host "[FAIL] middle-revert branch not found" -ForegroundColor Red
$allChecksPassed = $false
} else {
# Check that a revert commit for the merge exists
$revertMerge = git log --oneline --grep="Revert.*Merge" 2>$null
if ($revertMerge) {
Write-Host "[PASS] Merge revert commit found" -ForegroundColor Green
# 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 merge revert commit found" -ForegroundColor Red
Write-Host "[HINT] Use: git revert -m 1 <merge-commit-hash>" -ForegroundColor Yellow
Write-Host "[FAIL] No revert commit found" -ForegroundColor Red
Write-Host "[HINT] Use: git revert <commit-hash>" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that the original merge commit still exists (revert doesn't erase it)
$mergeCommit = git log --merges --oneline --grep="Merge feature-auth" 2>$null
if ($mergeCommit) {
Write-Host "[PASS] Original merge commit still in history (not erased)" -ForegroundColor Green
# Check that formatter.py is removed (reverted)
if (-not (Test-Path "formatter.py")) {
Write-Host "[PASS] Broken formatter.py successfully reverted (file removed)" -ForegroundColor Green
} else {
Write-Host "[INFO] Original merge commit not found (this is OK if you used a different approach)" -ForegroundColor Yellow
}
# Check that auth.py no longer exists or its effects are reverted
if (-not (Test-Path "auth.py")) {
Write-Host "[PASS] auth.py removed (merge reverted successfully)" -ForegroundColor Green
} else {
Write-Host "[INFO] auth.py still exists (check if merge was fully reverted)" -ForegroundColor Yellow
}
# Check that calculator.py exists
if (Test-Path "calculator.py") {
$calcContent = Get-Content "calculator.py" -Raw
# After reverting the merge, calculator shouldn't import auth
if ($calcContent -notmatch "from auth import") {
Write-Host "[PASS] Auth integration reverted from calculator.py" -ForegroundColor Green
} else {
Write-Host "[FAIL] calculator.py still imports auth (merge not fully reverted)" -ForegroundColor Red
Write-Host "[HINT] Reverting the merge should remove the auth integration" -ForegroundColor Yellow
Write-Host "[FAIL] formatter.py still exists (should be reverted)" -ForegroundColor Red
Write-Host "[HINT] The bad commit should be reverted, removing formatter.py" -ForegroundColor Yellow
$allChecksPassed = $false
}
# Check that validation.py still exists (good commit before bad one)
if (Test-Path "validation.py") {
Write-Host "[PASS] validation.py preserved (good commit before bad one)" -ForegroundColor Green
} else {
Write-Host "[FAIL] validation.py missing (should still exist)" -ForegroundColor Red
$allChecksPassed = $false
}
# Check that config.py still exists (good commit after bad one)
if (Test-Path "config.py") {
Write-Host "[PASS] config.py preserved (good commit after bad one)" -ForegroundColor Green
} else {
Write-Host "[FAIL] config.py missing (should still exist)" -ForegroundColor Red
Write-Host "[HINT] Only revert the bad commit, not the good ones after it" -ForegroundColor Yellow
$allChecksPassed = $false
}
}
@@ -211,11 +208,11 @@ if ($allChecksPassed) {
Write-Host "=========================================" -ForegroundColor Green
Write-Host "`nYou've mastered git revert!" -ForegroundColor Cyan
Write-Host "You now understand:" -ForegroundColor Cyan
Write-Host " ✓ Reverting regular commits safely" -ForegroundColor White
Write-Host " ✓ Reverting merge commits with -m flag" -ForegroundColor White
Write-Host " ✓ Reverting commits safely without erasing history" -ForegroundColor White
Write-Host " ✓ Reverting old commits in the middle of history" -ForegroundColor White
Write-Host " ✓ Reverting multiple commits at once" -ForegroundColor White
Write-Host " ✓ Preserving history while undoing changes" -ForegroundColor White
Write-Host "`nReady for Module 06: Git Reset!" -ForegroundColor Green
Write-Host " ✓ Preserving commits before and after the reverted one" -ForegroundColor White
Write-Host "`nReady for Module 07: Git Reset!" -ForegroundColor Green
Write-Host ""
exit 0
} else {