16 KiB
16 KiB
description, args
| description | args | |||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Safely rollback a failed or unwanted feature with automatic artifact cleanup |
|
User Input
$ARGUMENTS
Goal
Safely rollback a feature that failed or is no longer wanted, including:
- Reverting all code changes since branch creation
- Cleaning up feature artifacts (spec.md, plan.md, tasks.md)
- Optionally deleting the feature branch
- Switching back to the parent branch
Provides two rollback strategies with comprehensive safety checks.
Execution Steps
Step 1: Safety Checks
echo "🔍 Performing safety checks..."
echo ""
# Check 1: Get current branch
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
if [ -z "$CURRENT_BRANCH" ]; then
echo "❌ Error: Not in a git repository"
exit 1
fi
# Check 2: Prevent rollback of main/master/develop
PROTECTED_BRANCHES=("main" "master" "develop" "production")
for protected in "${PROTECTED_BRANCHES[@]}"; do
if [ "$CURRENT_BRANCH" = "$protected" ]; then
echo "❌ Error: Cannot rollback protected branch '$CURRENT_BRANCH'"
echo ""
echo "💡 Rollback is designed for feature branches only."
echo " If you need to revert changes on $CURRENT_BRANCH, use:"
echo " git revert <commit-hash>"
exit 1
fi
done
# Check 3: Check for uncommitted changes
DIRTY=$(git status --porcelain 2>/dev/null)
if [ -n "$DIRTY" ]; then
echo "❌ Error: Uncommitted changes detected"
echo ""
echo "You have uncommitted changes. Please commit or stash them first:"
git status --short
echo ""
echo "To commit:"
echo " git add ."
echo " git commit -m \"WIP: save before rollback\""
echo ""
echo "To stash:"
echo " git stash push -m \"Before rollback\""
exit 1
fi
# Check 4: Detect parent branch
# First try git config
PARENT_BRANCH=$(git config "branch.$CURRENT_BRANCH.parent" 2>/dev/null)
# Fallback to common parent branches
if [ -z "$PARENT_BRANCH" ]; then
for candidate in "develop" "development" "dev" "main" "master"; do
if git show-ref --verify --quiet "refs/heads/$candidate"; then
PARENT_BRANCH="$candidate"
break
fi
done
fi
# Last resort: ask user
if [ -z "$PARENT_BRANCH" ]; then
echo "⚠️ Could not auto-detect parent branch"
PARENT_BRANCH="main" # default
fi
echo "✅ Safety checks passed"
echo ""
Step 2: Gather Rollback Information
echo "📊 Analyzing feature branch: $CURRENT_BRANCH"
echo ""
# Find divergence point from parent
DIVERGE_POINT=$(git merge-base "$CURRENT_BRANCH" "$PARENT_BRANCH" 2>/dev/null)
if [ -z "$DIVERGE_POINT" ]; then
echo "❌ Error: Could not find divergence point with $PARENT_BRANCH"
echo " Please specify the correct parent branch."
exit 1
fi
# Count commits to rollback
COMMIT_COUNT=$(git rev-list --count "$DIVERGE_POINT..$CURRENT_BRANCH" 2>/dev/null)
COMMITS=$(git log --oneline "$DIVERGE_POINT..$CURRENT_BRANCH" 2>/dev/null)
# Check if already merged to parent
MERGED=$(git branch --merged "$PARENT_BRANCH" | grep -w "$CURRENT_BRANCH" || true)
# Check for feature artifacts
FEATURE_DIR=".feature"
ARTIFACTS_FOUND=false
ARTIFACT_LIST=()
if [ -d "$FEATURE_DIR" ]; then
if [ -f "$FEATURE_DIR/spec.md" ]; then
ARTIFACTS_FOUND=true
ARTIFACT_LIST+=("spec.md ($(wc -l < "$FEATURE_DIR/spec.md" 2>/dev/null || echo "0") lines)")
fi
if [ -f "$FEATURE_DIR/plan.md" ]; then
ARTIFACTS_FOUND=true
ARTIFACT_LIST+=("plan.md ($(wc -l < "$FEATURE_DIR/plan.md" 2>/dev/null || echo "0") lines)")
fi
if [ -f "$FEATURE_DIR/tasks.md" ]; then
ARTIFACTS_FOUND=true
TASK_COUNT=$(grep -c "^-" "$FEATURE_DIR/tasks.md" 2>/dev/null || echo "0")
TASK_COMPLETE=$(grep -c "^- \[x\]" "$FEATURE_DIR/tasks.md" 2>/dev/null || echo "0")
ARTIFACT_LIST+=("tasks.md ($TASK_COUNT tasks, $TASK_COMPLETE completed)")
fi
fi
# Check remote tracking
REMOTE_TRACKING=$(git rev-parse --abbrev-ref --symbolic-full-name "@{u}" 2>/dev/null || echo "")
# Display information
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ROLLBACK ANALYSIS"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Branch Information:"
echo " Current Branch: $CURRENT_BRANCH"
echo " Parent Branch: $PARENT_BRANCH"
echo " Commits: $COMMIT_COUNT"
echo " Merged Status: $([[ -n "$MERGED" ]] && echo "Already merged to $PARENT_BRANCH" || echo "Not merged")"
echo " Remote Tracking: ${REMOTE_TRACKING:-"None (local only)"}"
echo ""
if [ "$COMMIT_COUNT" -gt 0 ]; then
echo "Commits to Rollback:"
echo "$COMMITS" | head -5
if [ "$COMMIT_COUNT" -gt 5 ]; then
echo " ... and $((COMMIT_COUNT - 5)) more"
fi
echo ""
fi
if [ "$ARTIFACTS_FOUND" = true ]; then
echo "Feature Artifacts Found:"
for artifact in "${ARTIFACT_LIST[@]}"; do
echo " ✓ $artifact"
done
echo ""
fi
if [ -n "$MERGED" ]; then
echo "⚠️ WARNING: This branch has already been merged to $PARENT_BRANCH"
echo " Rollback will create revert commits on $PARENT_BRANCH"
echo ""
fi
if [ -n "$REMOTE_TRACKING" ]; then
echo "⚠️ WARNING: This branch is pushed to remote ($REMOTE_TRACKING)"
echo " Other collaborators may have checked out this branch"
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
Step 3: Dry Run Mode
If --dry-run flag is present, show what would happen and exit:
if echo "$ARGUMENTS" | grep -q "\-\-dry-run"; then
echo "🔍 DRY RUN MODE - No changes will be made"
echo ""
echo "The following actions WOULD be performed:"
echo ""
echo "1. Rollback Strategy: (User would choose)"
echo " Option A: Soft rollback (create $COMMIT_COUNT revert commits)"
echo " Option B: Hard rollback (reset to $PARENT_BRANCH)"
echo ""
echo "2. Artifact Cleanup:"
if [ "$ARTIFACTS_FOUND" = true ]; then
echo " - Delete or backup: ${ARTIFACT_LIST[*]}"
else
echo " - No artifacts to clean up"
fi
echo ""
echo "3. Branch Cleanup:"
echo " - Optionally delete branch: $CURRENT_BRANCH"
echo ""
echo "4. Final State:"
echo " - Switch to: $PARENT_BRANCH"
echo ""
echo "Run without --dry-run to execute rollback."
exit 0
fi
Step 4: Interactive Rollback Configuration (if not --force)
Skip if --force flag is present (use soft rollback defaults).
Use AskUserQuestion tool:
Question 1: "Choose rollback strategy"
Header: "Strategy"
Options:
1. "Soft rollback (revert commits)"
Description: "Creates revert commits, preserves history (RECOMMENDED)"
2. "Hard rollback (reset to parent)"
Description: "Deletes all commits, rewrites history (DANGEROUS)"
3. "Cancel rollback"
Description: "Abort and keep current state"
Store in $ROLLBACK_STRATEGY.
If $ROLLBACK_STRATEGY == "Cancel rollback", exit with message.
Question 2: "Delete feature branch after rollback?"
Header: "Branch Cleanup"
Options:
1. "Yes, delete branch"
Description: "Remove $CURRENT_BRANCH completely"
2. "No, keep branch"
Description: "Keep branch for reference (can delete later)"
Store in $DELETE_BRANCH.
Question 3 (if ARTIFACTS_FOUND=true): "What should we do with feature artifacts?"
Header: "Artifacts"
Options:
1. "Delete artifacts"
Description: "Remove spec.md, plan.md, tasks.md permanently"
2. "Backup artifacts"
Description: "Move to .feature.backup/$(date +%Y%m%d-%H%M%S)/"
3. "Keep artifacts"
Description: "Leave artifacts in place (not recommended)"
Store in $ARTIFACT_ACTION.
Question 4: "Final confirmation - Type 'rollback' to proceed"
Header: "Confirm"
Options:
- Text input required
- Must type exactly "rollback" (case-sensitive)
Store in $CONFIRMATION.
if [ "$CONFIRMATION" != "rollback" ]; then
echo "❌ Rollback cancelled - confirmation text did not match"
echo " You must type exactly: rollback"
exit 1
fi
Step 5: Execute Rollback
echo ""
echo "🔄 Executing rollback..."
echo ""
# Step 5a: Handle artifacts first (before git operations)
if [ "$ARTIFACTS_FOUND" = true ]; then
case "$ARTIFACT_ACTION" in
"Delete artifacts")
echo "🗑️ Deleting feature artifacts..."
rm -rf "$FEATURE_DIR"
echo "✅ Deleted artifacts"
;;
"Backup artifacts")
BACKUP_DIR=".feature.backup/$(date +%Y%m%d-%H%M%S)"
echo "💾 Backing up artifacts to $BACKUP_DIR..."
mkdir -p "$BACKUP_DIR"
cp -r "$FEATURE_DIR"/* "$BACKUP_DIR/" 2>/dev/null
rm -rf "$FEATURE_DIR"
echo "✅ Artifacts backed up to $BACKUP_DIR"
;;
"Keep artifacts")
echo "ℹ️ Keeping artifacts in place"
;;
esac
echo ""
fi
# Step 5b: Execute rollback strategy
case "$ROLLBACK_STRATEGY" in
"Soft rollback (revert commits)")
echo "🔄 Creating revert commits..."
if [ "$COMMIT_COUNT" -gt 0 ]; then
# Create revert commits in reverse order
git revert --no-edit "$DIVERGE_POINT..$CURRENT_BRANCH" 2>&1 || {
echo ""
echo "⚠️ Revert encountered conflicts"
echo " Conflicts must be resolved manually:"
echo " 1. Fix conflicts in the listed files"
echo " 2. git add <resolved-files>"
echo " 3. git revert --continue"
echo ""
echo " Or to abort:"
echo " git revert --abort"
exit 1
}
echo "✅ Created $COMMIT_COUNT revert commits"
else
echo "ℹ️ No commits to revert"
fi
# Stay on current branch for soft rollback
SWITCH_BRANCH=false
;;
"Hard rollback (reset to parent)")
echo "⚠️ WARNING: Performing hard rollback (history will be lost)"
echo ""
# Switch to parent branch
echo "🔀 Switching to $PARENT_BRANCH..."
git checkout "$PARENT_BRANCH" 2>&1 || {
echo "❌ Failed to switch to $PARENT_BRANCH"
exit 1
}
echo "✅ Switched to $PARENT_BRANCH"
SWITCH_BRANCH=true
;;
esac
echo ""
# Step 5c: Branch cleanup
if [ "$DELETE_BRANCH" = "Yes, delete branch" ] && [ "$SWITCH_BRANCH" = false ]; then
# For soft rollback, need to switch before deleting
echo "🔀 Switching to $PARENT_BRANCH before branch deletion..."
git checkout "$PARENT_BRANCH" 2>&1 || {
echo "❌ Failed to switch to $PARENT_BRANCH"
exit 1
}
SWITCH_BRANCH=true
fi
if [ "$DELETE_BRANCH" = "Yes, delete branch" ]; then
echo "🗑️ Deleting branch $CURRENT_BRANCH..."
# Check if branch has unmerged changes
UNMERGED=$(git branch --no-merged "$PARENT_BRANCH" | grep -w "$CURRENT_BRANCH" || true)
if [ -n "$UNMERGED" ] && [ "$ROLLBACK_STRATEGY" != "Hard rollback (reset to parent)" ]; then
# Force delete for unmerged branches
git branch -D "$CURRENT_BRANCH" 2>&1 || {
echo "❌ Failed to delete branch $CURRENT_BRANCH"
exit 1
}
else
git branch -d "$CURRENT_BRANCH" 2>&1 || {
# Try force delete if normal delete fails
git branch -D "$CURRENT_BRANCH" 2>&1 || {
echo "❌ Failed to delete branch $CURRENT_BRANCH"
exit 1
}
}
fi
echo "✅ Deleted branch $CURRENT_BRANCH"
# Delete remote branch if exists
if [ -n "$REMOTE_TRACKING" ]; then
REMOTE_NAME=$(echo "$REMOTE_TRACKING" | cut -d'/' -f1)
echo ""
echo "⚠️ Remote branch exists: $REMOTE_TRACKING"
echo " To delete remote branch, run:"
echo " git push $REMOTE_NAME --delete $CURRENT_BRANCH"
fi
fi
echo ""
Step 6: Summary and Final State
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo " ✅ ROLLBACK COMPLETE"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo ""
echo "Summary:"
echo " Strategy: $ROLLBACK_STRATEGY"
if [ "$COMMIT_COUNT" -gt 0 ]; then
if [ "$ROLLBACK_STRATEGY" = "Soft rollback (revert commits)" ]; then
echo " Commits: $COMMIT_COUNT reverted"
else
echo " Commits: $COMMIT_COUNT removed"
fi
fi
if [ "$ARTIFACTS_FOUND" = true ]; then
echo " Artifacts: $ARTIFACT_ACTION"
fi
if [ "$DELETE_BRANCH" = "Yes, delete branch" ]; then
echo " Branch: $CURRENT_BRANCH deleted"
else
echo " Branch: $CURRENT_BRANCH kept"
fi
echo " Current: $(git rev-parse --abbrev-ref HEAD)"
echo ""
if [ "$ROLLBACK_STRATEGY" = "Soft rollback (revert commits)" ] && [ "$SWITCH_BRANCH" = false ]; then
echo "💡 Next Steps:"
echo " 1. Review the revert commits: git log"
echo " 2. Push to remote if needed: git push"
echo " 3. Merge to parent: /specswarm:complete"
echo ""
elif [ "$SWITCH_BRANCH" = true ]; then
echo "💡 Next Steps:"
echo " 1. Review current state: git status"
echo " 2. Start a new feature: /specswarm:build \"feature\""
echo ""
fi
if [ -d ".feature.backup" ]; then
echo "📦 Backups:"
echo " Feature artifacts backed up to:"
ls -1d .feature.backup/*/ 2>/dev/null | tail -1
echo ""
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
Important Notes
Safety Features
- Protected Branch Prevention: Cannot rollback main/master/develop/production
- Uncommitted Changes Check: Requires clean working tree
- Confirmation Required: Must type "rollback" to proceed (unless --force)
- Artifact Backup: Option to backup before deletion
- Dry Run: Preview changes before executing
Rollback Strategies
Soft Rollback (Recommended):
- Creates revert commits for each commit since divergence
- Preserves complete history
- Safe for shared/pushed branches
- Can be undone by reverting the reverts
Hard Rollback (Dangerous):
- Resets to parent branch state
- Deletes all commits permanently
- Only safe for local-only branches
- Cannot be undone easily
When to Use Each Strategy
Use Soft Rollback when:
- Branch has been pushed to remote
- Other developers may have based work on this branch
- You want to preserve history
- You're unsure if you might need the code later
Use Hard Rollback when:
- Branch is local-only (never pushed)
- You're absolutely certain code should be deleted
- You want a clean slate
Remote Branch Handling
If branch is tracked remotely:
- Local branch can be deleted safely
- Remote branch deletion must be done manually:
git push origin --delete feature-branch-name - Provides command at end of rollback
Merged Branch Rollback
If branch is already merged to parent:
- Soft rollback creates revert commits on current branch
- These reverts must then be merged to parent
- Hard rollback switches to parent (no changes to parent)
- Consider using
/specswarm:deprecatefor features in production
Example Usage
Basic Rollback (Interactive)
/specswarm:rollback
# Guided through strategy selection
# Confirms with "rollback" text
Preview Rollback (Dry Run)
/specswarm:rollback --dry-run
# Shows what would happen without executing
Quick Rollback with Defaults
/specswarm:rollback --force
# Uses soft rollback, no confirmation
# NOT RECOMMENDED unless you know what you're doing
Rollback with Artifact Backup
/specswarm:rollback --keep-artifacts
# Backs up artifacts automatically
Undo a Rollback (if soft rollback was used)
# Find the revert commits
git log --oneline | grep "Revert"
# Revert the reverts to restore original state
git revert <revert-commit-hash>