Rollback System
The Review Bot Automator includes a robust, git-based rollback system that provides automatic recovery when things go wrong. This document explains how the rollback system works, how to use it, and how to troubleshoot issues.
Table of Contents
Overview
The rollback system provides automatic recovery from failures during change application. When enabled (default), it creates a checkpoint before applying changes and automatically restores the previous state if any error occurs.
Key Features
Git-based Checkpointing: Uses
git stashfor reliable state captureAutomatic Recovery: Rolls back on any exception during application
Tracked and Untracked Files: Preserves both tracked changes and untracked files
Context Manager Support: Pythonic API with automatic cleanup
Configurable: Enable/disable per execution
Safe Defaults: Enabled by default for maximum safety
When to Use Rollback
Always Enable (Default):
Production environments
Critical systems
Unfamiliar PRs
Large PRs with many changes
When validation is disabled
Consider Disabling:
When you have external backup systems
Testing environments with disposable state
When performance is absolutely critical
You want to manually inspect failed state
How It Works
The rollback system operates in three phases:
Phase 1: Checkpoint Creation
Before applying any changes, the system:
Checks for uncommitted changes in the working directory
Creates a git stash using
git stash push --include-untrackedCaptures all tracked modifications
Captures untracked files (new files not in .gitignore)
Does NOT capture ignored files (respects .gitignore)
Immediately reapplies changes using
git stash applyRestores working directory to original state
Keeps stash reference for potential rollback
Stores checkpoint ID (
stash@{0}) for later use
Result: Working directory unchanged, but state saved for rollback
Phase 2: Change Application
The resolver applies changes with the checkpoint in place:
Applies resolved changes to files
Monitors for exceptions during application
Tracks success/failure of each change
Phase 3: Commit or Rollback
After change application completes:
On Success (Commit)
Drops the git stash using
git stash drop stash@{0}Clears checkpoint reference
Keeps all applied changes
On Failure (Rollback)
Resets working directory using
git reset --hard HEADRemoves untracked files using
git clean -fdApplies checkpoint state using
git stash apply stash@{0}Drops the stash after successful rollback
Clears checkpoint reference
Result: Working directory restored to pre-application state
Usage
CLI Usage
The rollback system is controlled via the --rollback / --no-rollback flags.
Enable Rollback (Default)
# Rollback enabled by default
pr-resolve apply --pr 123 --owner myorg --repo myrepo
# Explicitly enable rollback
pr-resolve apply --pr 123 --owner myorg --repo myrepo --rollback
Disable Rollback
# Disable rollback (not recommended for production)
pr-resolve apply --pr 123 --owner myorg --repo myrepo --no-rollback
Environment Variable
# Enable via environment variable
export CR_ENABLE_ROLLBACK="true"
pr-resolve apply --pr 123 --owner myorg --repo myrepo
# Disable via environment variable
export CR_ENABLE_ROLLBACK="false"
pr-resolve apply --pr 123 --owner myorg --repo myrepo
Configuration File
# config.yaml
rollback:
enabled: true # Enable rollback
pr-resolve apply --pr 123 --owner myorg --repo myrepo --config config.yaml
Python API Usage
Basic Usage
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager
# Initialize manager
manager = RollbackManager(Path("/path/to/repo"))
# Create checkpoint
checkpoint_id = manager.create_checkpoint()
print(f"Created checkpoint: {checkpoint_id}")
try:
# Apply changes
apply_changes(changes)
# If successful, commit (keep changes)
manager.commit()
print("Changes applied successfully")
except Exception as e:
# If error, rollback (restore previous state)
manager.rollback()
print(f"Error occurred, rolled back: {e}")
raise
Using ConflictResolver
from pr_conflict_resolver import ConflictResolver
from pr_conflict_resolver.config import PresetConfig
# Initialize resolver
resolver = ConflictResolver(config=PresetConfig.BALANCED)
# Resolve with rollback enabled (default)
results = resolver.resolve_pr_conflicts(
owner="myorg",
repo="myrepo",
pr_number=123,
enable_rollback=True # Default: True
)
# Check results
if results.success_rate < 100:
print(f"Some changes failed, but rollback protected working directory")
Context Manager Pattern
The most Pythonic way to use the rollback system:
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager
# Automatic rollback on exception
with RollbackManager(Path("/path/to/repo")) as manager:
# Apply changes here
apply_changes(changes)
# Explicitly commit on success (optional, auto-commits if no exception)
manager.commit()
# If exception occurs, automatically rolls back
# If no exception, automatically commits
Behavior:
On exception: Automatically calls
rollback(), then propagates exceptionOn success (no exception): Automatically calls
commit()Explicit commit: Can call
manager.commit()before block ends
Advanced Context Manager Usage
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager, RollbackError
def apply_changes_safely(changes, repo_path):
"""Apply changes with automatic rollback."""
try:
with RollbackManager(repo_path) as manager:
for change in changes:
# Apply each change
apply_single_change(change)
# All succeeded, commit
manager.commit()
return True
except RollbackError as e:
print(f"Rollback failed: {e}")
return False
except Exception as e:
print(f"Changes failed, rolled back: {e}")
return False
Configuration
Configuration Precedence
Configuration sources (highest to lowest priority):
CLI flags (
--rollback/--no-rollback)Environment variables (
CR_ENABLE_ROLLBACK)Configuration file (
rollback.enabled)Defaults (rollback enabled)
Configuration Options
CLI Flags
Flag |
Description |
Default |
|---|---|---|
|
Enable automatic rollback on failure |
Enabled |
|
Disable automatic rollback |
- |
Environment Variables
Variable |
Type |
Values |
Default |
|---|---|---|---|
|
boolean |
|
|
Configuration File
YAML:
rollback:
enabled: true
TOML:
[rollback]
enabled = true
Configuration Examples
Example 1: Maximum Safety (Production)
# prod-config.yaml
mode: conflicts-only
rollback:
enabled: true # Always enable in production
validation:
enabled: true # Defense in depth
logging:
level: INFO
file: /var/log/pr-resolver/production.log
Example 2: Performance Optimized
# perf-config.yaml
mode: all
rollback:
enabled: true # Keep safety enabled
validation:
enabled: false # Disable validation for speed, rely on rollback
parallel:
enabled: true
max_workers: 16
Example 3: Testing Environment
# Disable rollback in disposable testing environment
export CR_ENABLE_ROLLBACK="false"
pr-resolve apply --pr 123 --owner myorg --repo myrepo
Architecture
Component Overview
┌─────────────────────────────────────────────────────────┐
│ RollbackManager │
│ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 1. create_checkpoint() │ │
│ │ • git stash push --include-untracked │ │
│ │ • git stash apply │ │
│ │ • Store reference: stash@{0} │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌───────────────────────────────────────────────────┐ │
│ │ 2. Change Application │ │
│ │ • Monitor for exceptions │ │
│ │ • Track success/failure │ │
│ └───────────────────────────────────────────────────┘ │
│ │ │
│ ┌───────────────┴───────────────┐ │
│ ▼ ▼ │
│ ┌──────────────┐ ┌──────────────┐ │
│ │ 3a. commit() │ │ 3b. rollback()│ │
│ │ Success │ │ Failure │ │
│ │ │ │ │ │
│ │ • Drop stash │ │ • reset --hard│ │
│ │ • Keep changes│ │ • clean -fd │ │
│ │ │ │ • apply stash │ │
│ │ │ │ • drop stash │ │
│ └──────────────┘ └──────────────┘ │
└─────────────────────────────────────────────────────────┘
Class Structure
class RollbackManager:
"""Git-based rollback manager for safe change application."""
# Initialization
def __init__(self, repo_path: str | Path) -> None
# Core Operations
def create_checkpoint(self) -> str
def rollback(self) -> bool
def commit(self) -> None
# Status Checking
def has_checkpoint(self) -> bool
# Context Manager
def __enter__(self) -> "RollbackManager"
def __exit__(self, exc_type, exc_val, exc_tb) -> Literal[False]
# Internal Methods
def _is_git_available(self) -> bool
def _is_git_repo(self) -> bool
def _run_git_command(self, args: list[str]) -> CompletedProcess[str]
Git Commands Used
The rollback system uses these git commands:
git status --porcelain- Check for uncommitted changesgit stash push --include-untracked -m "RollbackManager checkpoint"- Create checkpointgit rev-parse stash@{0}- Get stash SHA for logginggit stash apply stash@{0}- Restore checkpoint stategit stash drop stash@{0}- Clean up checkpointgit reset --hard HEAD- Reset tracked filesgit clean -fd- Remove untracked files/directories
State Diagram
┌──────────────┐
│ No Checkpoint│
└───────┬──────┘
│
create_checkpoint()
│
▼
┌──────────────┐
│ Checkpoint │
│ Active │
└───────┬──────┘
│
┌───────────────┴───────────────┐
│ │
Success Exception
│ │
▼ ▼
┌──────────────┐ ┌──────────────┐
│ commit() │ │ rollback() │
│ │ │ │
│ Drop stash │ │ Restore state│
│ Keep changes │ │ Drop stash │
└───────┬──────┘ └───────┬──────┘
│ │
└───────────────┬───────────────┘
│
▼
┌──────────────┐
│ No Checkpoint│
└──────────────┘
Safety Features
1. Checkpoint Protection
Single checkpoint enforcement: Cannot create new checkpoint while one exists
Atomic operations: Checkpoint creation is all-or-nothing
Immediate verification: Verifies stash creation before proceeding
2. Error Handling
Rollback on any exception: Catches all exceptions during application
Cleanup on rollback failure: Attempts to drop stash even if rollback fails
Detailed logging: All operations logged for audit trail
3. State Validation
Git availability check: Verifies git command is available
Repository validation: Confirms path is a valid git repository
Path validation: Checks path exists and is a directory
4. Recovery Mechanisms
Manual rollback support: Can call
rollback()explicitly if auto-rollback failsStash reference tracking: Stores
stash@{0}reference for reliable operationsNon-fatal stash drop: Continues even if stash drop fails (already cleaned up)
Use Cases
Use Case 1: Production Deployment
Scenario: Applying PR changes to production codebase
# Maximum safety configuration
pr-resolve apply --pr 456 --owner myorg --repo production \
--mode conflicts-only \
--rollback \
--validation \
--log-level INFO \
--log-file /var/log/pr-resolver/prod-$(date +%Y%m%d-%H%M%S).log
What happens:
Checkpoint created before any changes
Only conflicting changes applied
All changes validated before application
If any error: automatic rollback to pre-application state
If success: checkpoint committed, changes kept
Full audit trail in log file
Use Case 2: Experimental Changes
Scenario: Testing changes from untrusted source
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager
def test_experimental_changes(changes, repo_path):
"""Apply experimental changes with easy rollback."""
with RollbackManager(repo_path) as manager:
print("Applying experimental changes...")
apply_changes(changes)
# Prompt user for confirmation
response = input("Keep changes? (yes/no): ")
if response.lower() != "yes":
# Explicit rollback
manager.rollback()
print("Changes rolled back")
return False
# Commit if user approves
manager.commit()
print("Changes kept")
return True
Use Case 3: Large PR Processing
Scenario: Applying 100+ changes from large PR
# Apply in stages with rollback protection
# Stage 1: Non-conflicting changes (safer)
pr-resolve apply --pr 789 --owner myorg --repo myrepo \
--mode non-conflicts-only \
--rollback \
--parallel --max-workers 8
# Stage 2: Conflicting changes (riskier, more careful)
pr-resolve apply --pr 789 --owner myorg --repo myrepo \
--mode conflicts-only \
--rollback \
--validation
Benefits:
Each stage independently protected by rollback
If stage 2 fails, stage 1 changes remain
Can recover from partial application
Use Case 4: CI/CD Pipeline
Scenario: Automated PR resolution in CI/CD
# .github/workflows/auto-resolve.yml
- name: Resolve PR Conflicts
run: |
# Enable rollback for safety
export CR_ENABLE_ROLLBACK="true"
export CR_LOG_LEVEL="INFO"
# Apply with rollback protection
pr-resolve apply \
--pr ${{ github.event.pull_request.number }} \
--owner ${{ github.repository_owner }} \
--repo ${{ github.event.repository.name }} \
--mode conflicts-only \
--rollback \
--log-file /tmp/pr-resolve.log
# Upload logs even on failure
continue-on-error: true
- name: Upload Logs
if: always()
uses: actions/upload-artifact@v3
with:
name: resolution-logs
path: /tmp/pr-resolve.log
Use Case 5: Manual Recovery
Scenario: Rollback fails, need manual intervention
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager, RollbackError
def apply_with_fallback(changes, repo_path):
"""Apply changes with multiple recovery levels."""
manager = RollbackManager(repo_path)
try:
# Create checkpoint
checkpoint = manager.create_checkpoint()
print(f"Checkpoint: {checkpoint}")
# Apply changes
apply_changes(changes)
manager.commit()
return True
except Exception as e:
print(f"Application failed: {e}")
# Try automatic rollback
try:
manager.rollback()
print("Automatic rollback successful")
return False
except RollbackError as rollback_error:
print(f"Automatic rollback failed: {rollback_error}")
# Manual recovery
print("Attempting manual recovery...")
manual_recovery(repo_path, checkpoint)
return False
def manual_recovery(repo_path, checkpoint_id):
"""Manual recovery when automatic rollback fails."""
import subprocess
print("Manual recovery steps:")
print(f"1. cd {repo_path}")
print(f"2. git stash list # Find your stash")
print(f"3. git reset --hard HEAD")
print(f"4. git clean -fd")
print(f"5. git stash apply {checkpoint_id}")
# Or attempt programmatic recovery
try:
subprocess.run(["git", "reset", "--hard", "HEAD"], cwd=repo_path, check=True)
subprocess.run(["git", "clean", "-fd"], cwd=repo_path, check=True)
subprocess.run(["git", "stash", "apply", checkpoint_id], cwd=repo_path, check=True)
print("Manual recovery successful")
except subprocess.CalledProcessError as e:
print(f"Manual recovery failed: {e}")
print("Please recover manually using git commands")
Troubleshooting
Common Issues
1. Rollback Not Triggering
Symptoms:
Changes applied but errors occur
No rollback happens
Working directory left in failed state
Causes:
Rollback disabled in configuration
Not using context manager or explicit try/catch
Exception caught and suppressed before rollback
Solutions:
# 1. Verify rollback is enabled
pr-resolve apply --pr 123 --owner org --repo repo --rollback --log-level DEBUG
# 2. Check configuration
echo $CR_ENABLE_ROLLBACK # Should be "true"
cat config.yaml | grep -A 2 "rollback:"
# 3. Use Python API correctly
with RollbackManager(repo_path) as manager:
apply_changes() # Will auto-rollback on exception
manager.commit()
2. Rollback Fails to Restore
Symptoms:
Rollback attempted but fails
Files not restored to previous state
RollbackErrorexception raised
Causes:
Uncommitted changes existed before running
Git stash conflicts with current changes
Repository in detached HEAD state
Stash was manually deleted
Solutions:
# 1. Check git status BEFORE running resolver
git status
# If uncommitted changes exist, commit or stash them first
# 2. Verify git repository state
git branch -v # Should show current branch, not detached HEAD
git log -1 # Should show recent commit
# 3. Check stash list
git stash list
# If stash exists, manually apply it:
git reset --hard HEAD
git clean -fd
git stash apply stash@{0}
# 4. If stash is missing, check git reflog
git reflog stash
git stash apply stash@{N} # Where N is the stash index
3. Repository Left Dirty After Rollback
Symptoms:
Rollback completes
git statusshows uncommitted changesChanges don’t match pre-application state
Causes:
Normal behavior - rollback restores to checkpoint state
Untracked files not captured (in .gitignore)
File permissions changed
Symbolic links modified
Solutions:
# 1. Check what changed
git status
git diff
# 2. If expected (rollback worked correctly)
# These are the changes that existed when checkpoint was created
# 3. If unexpected, manual cleanup
git reset --hard HEAD # Reset tracked files
git clean -fd # Remove untracked files
# 4. Check logs for rollback details
pr-resolve apply --pr 123 --owner org --repo repo --log-level DEBUG 2>&1 | grep -i rollback
4. Git Not Found
Symptoms:
RollbackError: git command not foundCannot initialize RollbackManager
Causes:
Git not installed on system
Git not in PATH
Running in restricted environment
Solutions:
# 1. Verify git is installed
which git
git --version
# 2. Install git if missing
# Ubuntu/Debian:
sudo apt-get install git
# macOS:
brew install git
# 3. Add git to PATH if installed but not found
export PATH="/usr/bin:$PATH"
# 4. Or disable rollback if git unavailable (not recommended)
pr-resolve apply --pr 123 --owner org --repo repo --no-rollback
5. Stash Apply Conflicts
Symptoms:
Rollback fails with “stash apply” conflicts
Git reports merge conflicts during rollback
Causes:
Changes applied by resolver conflict with checkpoint state
File was deleted then recreated differently
Binary file conflicts
Solutions:
# 1. Abort the conflicted stash apply
git reset --hard HEAD
git clean -fd
# 2. Try applying stash with different strategy
git stash apply stash@{0} --index
# 3. Or manually resolve conflicts
git stash apply stash@{0}
# Resolve conflicts manually
git add .
git stash drop stash@{0}
# 4. If unrecoverable, inspect stash contents
git stash show -p stash@{0} # See what's in the stash
Debugging Rollback Issues
Enable Debug Logging
# Maximum logging detail
pr-resolve apply --pr 123 --owner org --repo repo \
--rollback \
--log-level DEBUG \
--log-file /tmp/rollback-debug-$(date +%Y%m%d-%H%M%S).log
# Review rollback-specific logs
grep -i "rollback\|checkpoint\|stash" /tmp/rollback-debug-*.log
Python API Debugging
import logging
from pathlib import Path
from pr_conflict_resolver.core.rollback import RollbackManager
# Enable debug logging
logging.basicConfig(level=logging.DEBUG)
# Create manager with verbose output
manager = RollbackManager(Path("/path/to/repo"))
# Check checkpoint status
print(f"Has checkpoint: {manager.has_checkpoint()}")
print(f"Checkpoint ID: {manager.checkpoint_id}")
# Create checkpoint with debug output
try:
checkpoint = manager.create_checkpoint()
print(f"Created: {checkpoint}")
except Exception as e:
print(f"Failed: {e}")
import traceback
traceback.print_exc()
Manual Inspection
# 1. Check git repository state
git status
git log --oneline -5
git branch -v
# 2. Check stash list
git stash list
git stash show stash@{0}
# 3. Inspect stash contents
git stash show -p stash@{0} # Full diff
# 4. Test stash apply manually
git stash apply stash@{0} --dry-run # Test without applying
Getting Help
When reporting rollback issues, include:
Full command used
pr-resolve apply --pr 123 --owner org --repo repo --rollback --log-level DEBUG
Debug log file
--log-file /tmp/rollback-issue.logGit repository state
git status git stash list git log -5 --oneline git branch -v
Error messages
Full exception stack trace
Git command errors from log
Environment details
OS and version
Git version:
git --versionPython version:
python --version
Best Practices
1. Always Enable Rollback in Production
# production-config.yaml
rollback:
enabled: true # Never disable in production
Rationale: Even with validation, unexpected errors can occur. Rollback provides a last line of defense.
2. Use Context Manager in Python
# Good: Automatic rollback
with RollbackManager(repo_path) as manager:
apply_changes()
manager.commit()
# Avoid: Manual try/catch (easy to forget)
manager = RollbackManager(repo_path)
manager.create_checkpoint()
try:
apply_changes()
manager.commit()
except Exception:
manager.rollback() # Easy to forget
3. Clean Working Directory Before Running
# Check for uncommitted changes first
git status
# If changes exist, commit or stash them
git stash push -m "Before PR resolver"
# Then run resolver
pr-resolve apply --pr 123 --owner org --repo repo --rollback
Rationale: Clean working directory ensures checkpoint captures only resolver changes.
4. Combine with Validation
# Defense in depth: validation + rollback
pr-resolve apply --pr 123 --owner org --repo repo \
--validation \ # Catch errors early
--rollback # Recover if validation misses something
5. Log to File for Audit Trail
# Create audit trail
mkdir -p logs
pr-resolve apply --pr 123 --owner org --repo repo \
--rollback \
--log-level INFO \
--log-file logs/pr-123-$(date +%Y%m%d-%H%M%S).log
# Review logs later
grep -i "rollback\|checkpoint" logs/*.log
6. Test Rollback in Non-Production First
# Test in development environment
pr-resolve apply --pr 123 --owner org --repo test-repo --rollback
# Verify rollback works by intentionally causing failure
# Then apply to production with confidence
7. Monitor Rollback Frequency
# Track how often rollback triggers
grep "Rolling back" logs/*.log | wc -l
# If rollback triggers frequently, investigate root causes
Limitations
1. Requires Git Repository
Limitation: Only works in git repositories
Workaround: Not available for non-git projects
Alternative: Use external backup systems
2. Untracked Files in .gitignore Not Saved
Limitation: Files matching .gitignore patterns not captured
Workaround: Temporarily unignore critical files
Example:
# Temporarily remove from .gitignore echo "!critical-file.log" >> .gitignore
3. Performance Impact
Limitation: Checkpoint creation adds overhead
Impact: ~1-2 seconds for small repos, 5-10 seconds for large repos
Mitigation: Disable for performance-critical operations (not recommended)
4. Requires Git Installed
Limitation:
gitcommand must be availableWorkaround: Install git or disable rollback
Check:
which gitandgit --version
5. Single Checkpoint at a Time
Limitation: Cannot create nested checkpoints
Workaround: Commit or rollback before creating new checkpoint
Example:
manager.create_checkpoint() # ... apply some changes ... manager.commit() # Must commit before new checkpoint manager.create_checkpoint() # Now can create new one
6. No Cross-Branch Rollback
Limitation: Rollback within same branch only
Impact: Cannot rollback across branch switches
Example: If changes cause branch switch, rollback may not restore correctly
7. Large Repository Performance
Limitation: Stash operations slow on large repos (1M+ files)
Impact: Checkpoint creation may take 30+ seconds
Mitigation: Use
--no-rollbackonly if performance critical
See Also
Configuration Reference - Rollback configuration options
Getting Started - Basic rollback usage
API Reference - RollbackManager API documentation
Parallel Processing - Combining rollback with parallel execution
Troubleshooting - General troubleshooting guide