Files
2025-11-29 18:48:58 +08:00

253 lines
7.4 KiB
Bash
Executable File

#!/bin/bash
# Git Worktree Cleanup
# Interactive removal of old/merged worktrees
set -e
# Colors
GREEN='\033[0;32m'
BLUE='\033[0;34m'
YELLOW='\033[1;33m'
RED='\033[0;31m'
CYAN='\033[0;36m'
GRAY='\033[0;90m'
NC='\033[0m' # No Color
# Check if we're in a git repository
if ! git rev-parse --git-dir > /dev/null 2>&1; then
echo -e "${RED}✗ Error: Not in a git repository${NC}"
exit 1
fi
REPO_ROOT=$(git rev-parse --show-toplevel)
REPO_NAME=$(basename "$REPO_ROOT")
echo -e "${BLUE}╔════════════════════════════════════════════════╗${NC}"
echo -e "${BLUE}║ Git Worktree Cleanup Tool ║${NC}"
echo -e "${BLUE}╚════════════════════════════════════════════════╝${NC}"
echo ""
# Get list of worktrees (excluding main)
WORKTREES=$(git worktree list --porcelain | grep -v "^$" | grep -v "^HEAD" | grep -v "^bare")
if [ -z "$WORKTREES" ]; then
echo -e "${YELLOW}No feature worktrees found${NC}"
echo "Only the main worktree exists."
exit 0
fi
# Arrays to store worktree info
declare -a PATHS
declare -a BRANCHES
declare -a STATUSES
declare -a MERGED_FLAGS
COUNT=0
# Parse worktrees
CURRENT_PATH=""
CURRENT_BRANCH=""
while IFS= read -r line; do
if [[ $line == worktree* ]]; then
CURRENT_PATH=$(echo "$line" | awk '{print $2}')
elif [[ $line == branch* ]]; then
CURRENT_BRANCH=$(echo "$line" | awk '{print $2}' | sed 's|refs/heads/||')
# Skip main worktree
if [ "$CURRENT_PATH" != "$REPO_ROOT" ] && [ -n "$CURRENT_BRANCH" ]; then
PATHS[$COUNT]="$CURRENT_PATH"
BRANCHES[$COUNT]="$CURRENT_BRANCH"
# Check if branch is merged into main
cd "$REPO_ROOT"
if git branch --merged main | grep -q "^[* ]*${CURRENT_BRANCH}$"; then
MERGED_FLAGS[$COUNT]="merged"
STATUSES[$COUNT]="${GREEN}✓ Merged to main${NC}"
else
MERGED_FLAGS[$COUNT]="not-merged"
# Check if worktree is clean
cd "$CURRENT_PATH"
if git diff-index --quiet HEAD -- 2>/dev/null; then
STATUSES[$COUNT]="${YELLOW}⚠ Not merged, clean${NC}"
else
CHANGE_COUNT=$(git status --short | wc -l | tr -d ' ')
STATUSES[$COUNT]="${RED}⚠ Not merged, $CHANGE_COUNT changes${NC}"
fi
fi
((COUNT++))
fi
CURRENT_PATH=""
CURRENT_BRANCH=""
fi
done <<< "$WORKTREES"
if [ $COUNT -eq 0 ]; then
echo -e "${YELLOW}No feature worktrees to clean up${NC}"
exit 0
fi
# Display worktrees
echo -e "${CYAN}Found $COUNT feature worktree(s):${NC}"
echo ""
for i in "${!PATHS[@]}"; do
echo -e "${CYAN}[$((i+1))]${NC} ${BRANCHES[$i]}"
echo -e " Path: ${GRAY}${PATHS[$i]}${NC}"
echo -e " Status: ${STATUSES[$i]}"
echo ""
done
# Prompt for selection
echo -e "${YELLOW}Which worktrees would you like to remove?${NC}"
echo -e " Enter numbers separated by spaces (e.g., '1 3 4')"
echo -e " Enter 'merged' to remove all merged worktrees"
echo -e " Enter 'all' to remove all worktrees"
echo -e " Press Enter to cancel"
echo ""
read -p "Selection: " SELECTION
# Parse selection
if [ -z "$SELECTION" ]; then
echo -e "${GRAY}Cancelled${NC}"
exit 0
fi
# Build list of indices to remove
declare -a TO_REMOVE
if [ "$SELECTION" = "all" ]; then
for i in "${!PATHS[@]}"; do
TO_REMOVE+=($i)
done
elif [ "$SELECTION" = "merged" ]; then
for i in "${!PATHS[@]}"; do
if [ "${MERGED_FLAGS[$i]}" = "merged" ]; then
TO_REMOVE+=($i)
fi
done
if [ ${#TO_REMOVE[@]} -eq 0 ]; then
echo -e "${YELLOW}No merged worktrees found${NC}"
exit 0
fi
else
# Parse space-separated numbers
for num in $SELECTION; do
# Validate number
if [[ "$num" =~ ^[0-9]+$ ]] && [ "$num" -ge 1 ] && [ "$num" -le "$COUNT" ]; then
TO_REMOVE+=($((num - 1)))
else
echo -e "${RED}Invalid selection: $num${NC}"
exit 1
fi
done
fi
# Confirmation
echo ""
echo -e "${YELLOW}About to remove ${#TO_REMOVE[@]} worktree(s):${NC}"
echo ""
for idx in "${TO_REMOVE[@]}"; do
echo -e " ${RED}${NC} ${BRANCHES[$idx]}"
echo -e " ${GRAY}${PATHS[$idx]}${NC}"
done
echo ""
# Extra warning for non-merged branches
HAS_UNMERGED=false
for idx in "${TO_REMOVE[@]}"; do
if [ "${MERGED_FLAGS[$idx]}" != "merged" ]; then
HAS_UNMERGED=true
break
fi
done
if [ "$HAS_UNMERGED" = true ]; then
echo -e "${RED}⚠ WARNING: Some branches are NOT merged!${NC}"
echo -e "${RED} You may lose uncommitted work.${NC}"
echo ""
fi
read -p "Are you sure? (yes/no): " CONFIRM
if [ "$CONFIRM" != "yes" ]; then
echo -e "${GRAY}Cancelled${NC}"
exit 0
fi
# Remove worktrees
echo ""
echo -e "${BLUE}Removing worktrees...${NC}"
echo ""
cd "$REPO_ROOT"
for idx in "${TO_REMOVE[@]}"; do
WORKTREE_PATH="${PATHS[$idx]}"
BRANCH_NAME="${BRANCHES[$idx]}"
echo -e "${CYAN}${NC} Removing worktree: ${BRANCH_NAME}"
# Remove worktree
if git worktree remove "$WORKTREE_PATH" --force 2>/dev/null; then
echo -e " ${GREEN}${NC} Worktree removed"
else
# Try manual removal if git worktree remove fails
if [ -d "$WORKTREE_PATH" ]; then
rm -rf "$WORKTREE_PATH"
git worktree prune
echo -e " ${GREEN}${NC} Worktree removed (manual cleanup)"
else
echo -e " ${YELLOW}${NC} Worktree already removed"
fi
fi
# Ask about deleting the branch
if [ "${MERGED_FLAGS[$idx]}" = "merged" ]; then
# Auto-delete merged branches
git branch -d "$BRANCH_NAME" 2>/dev/null && \
echo -e " ${GREEN}${NC} Branch deleted" || \
echo -e " ${GRAY}Branch already deleted or doesn't exist${NC}"
else
# Prompt for unmerged branches
echo -e " ${YELLOW}Branch '$BRANCH_NAME' is not merged.${NC}"
read -p " Delete branch anyway? (y/n): " DELETE_BRANCH
if [[ "$DELETE_BRANCH" =~ ^[Yy]$ ]]; then
git branch -D "$BRANCH_NAME" 2>/dev/null && \
echo -e " ${GREEN}${NC} Branch force-deleted" || \
echo -e " ${GRAY}Could not delete branch${NC}"
else
echo -e " ${GRAY}Branch kept${NC}"
fi
fi
echo ""
done
# Prune any stale references
echo -e "${CYAN}${NC} Cleaning up stale references..."
git worktree prune
echo -e "${GREEN}${NC} Done"
echo ""
# Summary
echo -e "${GREEN}✓ Cleanup complete!${NC}"
echo ""
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo -e "Removed: ${CYAN}${#TO_REMOVE[@]}${NC} worktree(s)"
echo -e "${BLUE}═══════════════════════════════════════════════${NC}"
echo ""
# Show remaining worktrees
REMAINING=$(git worktree list | wc -l)
if [ $REMAINING -gt 1 ]; then
echo -e "${CYAN}Remaining worktrees: $((REMAINING - 1))${NC}"
echo -e "${GRAY}Run 'scripts/list_worktrees.sh' to see them${NC}"
else
echo -e "${CYAN}No feature worktrees remaining${NC}"
fi
echo ""