Files
gh-cskiro-claudex/skills/devops/git-worktree-setup/modes/mode3-cleanup.md
2025-11-29 18:16:40 +08:00

15 KiB
Raw Blame History

Mode 3: Worktree Cleanup

Overview

This mode safely removes git worktrees after work is complete. It includes safety checks for uncommitted changes, optional branch deletion, and cleanup verification.

When to Use

  • User says "remove worktree [name]"
  • User wants to "clean up worktrees"
  • User mentions "delete" or "cleanup" with "worktree"
  • After feature is merged and branch is no longer needed

Workflow

Phase 0: Prerequisites & Context

Step 0.1: Verify Git Repository

git rev-parse --is-inside-work-tree

Expected Output:

true

If fails:

  • Stop immediately
  • Error: "Not in a git repository"

Step 0.2: List Current Worktrees

git worktree list

Purpose:

  • Show user all available worktrees
  • Help identify what to remove
  • Detect if worktrees exist

Expected Output:

/Users/connor/myapp               abc123 [main]
/Users/connor/myapp-feature-a     def456 [feature-a]
/Users/connor/myapp-bugfix-123    ghi789 [bugfix-123]

If only one worktree (main):

  • Message: "No additional worktrees to remove"
  • Stop (nothing to cleanup)

Phase 1: Identify Target Worktrees

Step 1.1: Determine Cleanup Scope

Parse user request:

  • "remove worktree feature-a" → Single worktree
  • "remove all worktrees" → All non-main worktrees
  • "clean up worktrees" → All or let user select?
  • "remove worktree at /path" → By path

Extract:

  • CLEANUP_MODE: "single" | "all" | "selective"
  • TARGET: branch name or path

Step 1.2: Map to Worktree Paths

For single worktree:

# If user provided branch name
TARGET_BRANCH="feature-a"
TARGET_PATH=$(git worktree list | grep "\[$TARGET_BRANCH\]" | awk '{print $1}')

# If user provided path
TARGET_PATH="/Users/connor/myapp-feature-a"
TARGET_BRANCH=$(git worktree list | grep "$TARGET_PATH" | grep -oP '\[\K[^\]]+')

Verify target exists:

if [ -z "$TARGET_PATH" ]; then
  echo "Error: Worktree not found"
  exit 1
fi

For all worktrees:

# Get all non-main worktrees
MAIN_PATH=$(git rev-parse --show-toplevel)
mapfile -t ALL_WORKTREES < <(git worktree list | grep -v "$MAIN_PATH" | awk '{print $1}')

Step 1.3: Show Cleanup Plan

For single:

Planning to remove:
  Branch: feature-a
  Path: /Users/connor/myapp-feature-a
  Status: [to be determined in next step]

For all:

Planning to remove all worktrees:
  feature-a → /Users/connor/myapp-feature-a
  bugfix-123 → /Users/connor/myapp-bugfix-123

Main worktree will be preserved:
  main → /Users/connor/myapp

Confirm: "Proceed with cleanup? (yes/no)"


Phase 2: Safety Checks

Step 2.1: Check for Uncommitted Changes

For each target worktree:

cd "$WORKTREE_PATH"
CHANGES=$(git status --porcelain)

if [ -n "$CHANGES" ]; then
  echo "⚠️  Uncommitted changes detected"
  git status --short
fi

If changes found:

⚠️  Warning: feature-a has uncommitted changes:

M  src/auth.ts
?? src/new-file.ts

Options:
1. Abort cleanup (save your work first)
2. Show diff and decide
3. Continue anyway (DANGER: changes will be lost)
4. Stash changes automatically

Recommended: Abort and let user save work

If user chooses stash:

cd "$WORKTREE_PATH"
git stash push -m "Auto-stash before worktree removal $(date)"
STASH_CREATED=true

Record stash location:

✓ Changes stashed in main repository
  To recover: git stash list
  Stash name: stash@{0}: Auto-stash before worktree removal...

Step 2.2: Check Branch Merge Status

Determine if branch is merged:

BRANCH_NAME=$(cd "$WORKTREE_PATH" && git branch --show-current)
BASE_BRANCH="main"  # or detect from git config

# Check if merged to base
if git branch --merged "$BASE_BRANCH" | grep -q "$BRANCH_NAME"; then
  MERGE_STATUS="merged"
else
  MERGE_STATUS="unmerged"
fi

Report status:

Branch status:
  ✓ feature-a: Merged to main
  ⚠️  bugfix-123: NOT merged to main

If unmerged:

⚠️  Warning: bugfix-123 is not merged to main

Unmerged commits:
  [Show last few commits unique to this branch]

Options:
1. Abort cleanup (merge first)
2. Create backup branch (bugfix-123-backup)
3. Continue and delete branch
4. Remove worktree but keep branch

Step 2.3: Check Active Processes

Check if worktree is in use:

# Check for running processes in worktree
lsof +D "$WORKTREE_PATH" 2>/dev/null

# Check for open editors
if lsof +D "$WORKTREE_PATH" | grep -q "vim\|nvim\|code\|claude"; then
  echo "⚠️  Active processes detected in worktree"
fi

If processes found:

⚠️  Warning: Processes are using this worktree:
  - VS Code (PID: 12345)
  - Claude Code (PID: 67890)

Please close these applications first, or force cleanup (not recommended).

Phase 3: Execute Cleanup

Step 3.1: Remove Worktree

Standard removal:

git worktree remove "$WORKTREE_PATH"

If removal fails (locked):

# Try with force
git worktree remove --force "$WORKTREE_PATH"

Expected Output:

✓ Removed worktree: /Users/connor/myapp-feature-a

Verify removal:

if ! git worktree list | grep -q "$WORKTREE_PATH"; then
  echo "✓ Worktree removed from git"
fi

if [ ! -d "$WORKTREE_PATH" ]; then
  echo "✓ Directory removed"
fi

Step 3.2: Handle Branch Deletion

Ask user:

Worktree removed successfully.

Delete branch 'feature-a' too? (yes/no/backup)

Options:
  yes    - Delete branch permanently
  no     - Keep branch (can checkout later)
  backup - Create backup branch before deleting

If yes:

git branch -d "$BRANCH_NAME"

If force needed (unmerged):

# Warn user first
echo "⚠️  Branch is unmerged. Use -D to force delete."
read -p "Force delete? (yes/no): " force

if [ "$force" == "yes" ]; then
  git branch -D "$BRANCH_NAME"
fi

If backup:

# Create backup branch
BACKUP_NAME="${BRANCH_NAME}-backup-$(date +%Y%m%d)"
git branch "$BACKUP_NAME" "$BRANCH_NAME"

echo "✓ Created backup: $BACKUP_NAME"

# Then delete original
git branch -d "$BRANCH_NAME"

Step 3.3: Cleanup Verification

Verify complete removal:

# Check worktree list
if git worktree list | grep -q "$WORKTREE_PATH"; then
  echo "✗ Worktree still in git worktree list"
  ERROR=true
fi

# Check directory
if [ -d "$WORKTREE_PATH" ]; then
  echo "✗ Directory still exists"
  ERROR=true
fi

# Check branch (if deleted)
if [ "$DELETE_BRANCH" == "yes" ]; then
  if git branch | grep -q "$BRANCH_NAME"; then
    echo "✗ Branch still exists"
    ERROR=true
  fi
fi

Success confirmation:

✓ Cleanup verified:
  ✓ Worktree removed from git
  ✓ Directory deleted
  ✓ Branch deleted (if requested)

Phase 4: Batch Cleanup (If Multiple Worktrees)

For cleanup mode "all":

Step 4.1: Process Each Worktree

REMOVED=()
FAILED=()
KEPT_BRANCHES=()
DELETED_BRANCHES=()

for worktree in "${ALL_WORKTREES[@]}"; do
  echo "Processing: $worktree"

  # Get branch name
  BRANCH=$(git worktree list | grep "$worktree" | grep -oP '\[\K[^\]]+')

  # Safety checks
  cd "$worktree"
  if [ -n "$(git status --porcelain)" ]; then
    echo "  ⚠️  Uncommitted changes - skipping"
    FAILED+=("$worktree (uncommitted changes)")
    continue
  fi

  # Remove worktree
  if git worktree remove "$worktree"; then
    REMOVED+=("$worktree")
    echo "  ✓ Removed"

    # Ask about branch (or use default policy)
    if git branch --merged main | grep -q "$BRANCH"; then
      git branch -d "$BRANCH"
      DELETED_BRANCHES+=("$BRANCH")
      echo "  ✓ Deleted merged branch: $BRANCH"
    else
      KEPT_BRANCHES+=("$BRANCH")
      echo "    Kept unmerged branch: $BRANCH"
    fi
  else
    FAILED+=("$worktree")
    echo "  ✗ Failed"
  fi
done

Step 4.2: Batch Cleanup Summary

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Batch Cleanup Complete
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Removed: ${#REMOVED[@]} worktrees
Failed:  ${#FAILED[@]} worktrees

Successfully removed:
  ✓ /Users/connor/myapp-feature-a
  ✓ /Users/connor/myapp-feature-b

Failed to remove:
  ✗ /Users/connor/myapp-bugfix-123 (uncommitted changes)

Branches deleted:
  ✓ feature-a (merged)
  ✓ feature-b (merged)

Branches kept:
    bugfix-123 (unmerged)

━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Phase 5: Post-Cleanup Actions

Step 5.1: Show Remaining Worktrees

git worktree list

Output:

Remaining worktrees:
  /Users/connor/myapp (main) ← current
  /Users/connor/myapp-bugfix-123 (bugfix-123) ← has uncommitted changes

Step 5.2: Cleanup Orphaned References

Prune removed worktrees:

git worktree prune

Purpose:

  • Remove administrative files for deleted worktrees
  • Clean up .git/worktrees directory

Output:

✓ Pruned orphaned worktree references

Step 5.3: Show Recovery Information

If changes were stashed:

📦 Stashed Changes:

Your uncommitted changes are saved in git stash:
  Stash: stash@{0}
  Message: Auto-stash before worktree removal 2025-09-04

To recover:
  git stash list          # List all stashes
  git stash show stash@{0}  # Preview changes
  git stash pop stash@{0}   # Apply and remove stash
  git stash apply stash@{0} # Apply but keep stash

If branches were backed up:

💾 Backed Up Branches:

Created backup branches before deletion:
  feature-a-backup-20250904
  feature-b-backup-20250904

To restore:
  git checkout feature-a-backup-20250904
  git branch feature-a  # Recreate original branch

To remove backups:
  git branch -D feature-a-backup-20250904

Step 5.4: Disk Space Reclaimed

Calculate space saved:

# Estimate space freed (approximate)
echo "Disk space reclaimed: ~$(du -sh $WORKTREE_PATH 2>/dev/null || echo 'N/A')"

Show before/after:

Disk Space Summary:
  Before: 2.4 GB (3 worktrees)
  After:  800 MB (1 worktree)
  Reclaimed: ~1.6 GB

Safety Protocols

Before Removal

Mandatory checks:

  • Worktree exists in git worktree list
  • Not removing main/current worktree
  • Checked for uncommitted changes
  • Checked for unmerged commits
  • Confirmed with user

During Removal

Safe removal process:

  • Use git worktree remove (not rm -rf)
  • Verify removal succeeded
  • Handle locked worktrees appropriately
  • Preserve stashes if needed

After Removal

Verification:

  • Worktree not in git worktree list
  • Directory removed
  • Branch handled per user request
  • Stashes accessible (if created)
  • Backups created (if requested)

Error Handling

Error: Uncommitted Changes

Error: Cannot remove worktree with uncommitted changes

Current changes in feature-a:
  M  src/auth.ts
  ?? src/new-file.ts

Solutions:
1. Commit changes:
   cd /path/to/worktree
   git add .
   git commit -m "Save work"

2. Stash changes:
   Let me stash them automatically

3. Force remove (DANGER):
   Changes will be lost forever

Error: Worktree Locked

Error: Worktree is locked

Reason: Usually means processes are using it

Solutions:
1. Close all applications using this worktree
2. Check: lsof +D /path/to/worktree
3. Force remove: git worktree remove --force

Error: Branch Checkout Elsewhere

Error: Cannot delete branch - checked out elsewhere

Branch 'feature-a' is checked out at:
  /other/path/to/worktree

Solution:
1. Remove that worktree first
2. Or skip branch deletion (keep branch)

Error: Unmerged Commits

Error: Branch has unmerged commits

Commits not in main:
  abc123 feat: Add new feature
  def456 fix: Bug fix

Options:
1. Merge first: git merge feature-a
2. Create backup: feature-a-backup
3. Force delete: git branch -D feature-a

Advanced Features

Feature 1: Smart Cleanup

Auto-detect removable worktrees:

# Find merged branches
git for-each-ref --format='%(refname:short)' refs/heads/ |
  while read branch; do
    if git branch --merged main | grep -q "$branch" &&
       [ "$branch" != "main" ]; then
      echo "Removable: $branch (merged)"
    fi
  done

Offer cleanup:

Found 3 worktrees with merged branches:
  feature-a (merged 3 days ago)
  feature-b (merged 1 week ago)
  bugfix-old (merged 2 weeks ago)

Clean up all merged worktrees? (yes/select/no)

Feature 2: Archive Instead of Delete

Create archive before removal:

ARCHIVE_DIR="$HOME/.git-worktree-archives"
ARCHIVE_NAME="${BRANCH_NAME}-$(date +%Y%m%d-%H%M%S).tar.gz"

# Archive worktree
tar -czf "$ARCHIVE_DIR/$ARCHIVE_NAME" -C "$(dirname $WORKTREE_PATH)" "$(basename $WORKTREE_PATH)"

echo "✓ Archived to: $ARCHIVE_DIR/$ARCHIVE_NAME"
echo "  To restore: tar -xzf $ARCHIVE_NAME"

Feature 3: Cleanup Report

Generate detailed report:

# Worktree Cleanup Report
Date: 2025-09-04 15:30:00

## Removed Worktrees
- feature-a (merged, 15 commits, 3 days old)
- feature-b (merged, 8 commits, 1 week old)

## Branches Deleted
- feature-a (merged to main)
- feature-b (merged to main)

## Disk Space Reclaimed
- Total: 1.6 GB
- Per worktree: ~800 MB

## Stashes Created
- stash@{0}: Auto-stash feature-a
- stash@{1}: Auto-stash feature-b

## Recommendations
- Review stashes and apply if needed
- Consider cleanup policy for future
- Monitor disk usage regularly

Stop Conditions

Stop immediately if:

  • Not in a git repository
  • Target worktree doesn't exist
  • User cancels during confirmation
  • Trying to remove main worktree
  • Uncommitted changes and user aborts

Continue with warnings if:

  • Some worktrees fail in batch mode
  • Branch deletion fails
  • Prune operation fails

Success Criteria

  • Worktree removed from git worktree list
  • Directory deleted from filesystem
  • Branch handled per user request (deleted/kept/backed up)
  • No uncommitted changes lost
  • Cleanup verified
  • Recovery information provided (if applicable)

Example: Complete Flow

User: Remove the feature-authentication worktree

Claude:
Checking worktree: feature-authentication
Location: /Users/connor/myapp-feature-authentication

Safety checks:
  ✓ No uncommitted changes
  ✓ Branch merged to main
  ✓ No active processes

Proceed with removal? (yes/no)

User: yes

Claude:
Removing worktree...
  ✓ Worktree removed from git
  ✓ Directory deleted

Delete branch 'feature-authentication' too? (yes/no/backup)

User: yes

Claude:
  ✓ Branch deleted (was merged to main)

Cleanup complete!

Remaining worktrees:
  /Users/connor/myapp (main) ← current

Disk space reclaimed: ~800 MB