canvas-website/scripts/worktree-manager.sh

250 lines
9.0 KiB
Bash
Executable File

#!/bin/bash
#
# Worktree Manager - Helper script for managing Git worktrees
#
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
REPO_NAME=$(basename "$REPO_ROOT")
WORKTREE_BASE=$(dirname "$REPO_ROOT")
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color
show_help() {
cat << EOF
${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
${GREEN}Worktree Manager${NC} - Manage Git worktrees easily
${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
${YELLOW}Usage:${NC}
./worktree-manager.sh <command> [arguments]
${YELLOW}Commands:${NC}
${GREEN}list${NC} List all worktrees
${GREEN}create${NC} <branch> Create a new worktree for a branch
${GREEN}remove${NC} <branch> Remove a worktree
${GREEN}clean${NC} Remove all worktrees except main
${GREEN}goto${NC} <branch> Print command to cd to worktree
${GREEN}status${NC} Show status of all worktrees
${GREEN}help${NC} Show this help message
${YELLOW}Examples:${NC}
./worktree-manager.sh create feature/new-feature
./worktree-manager.sh list
./worktree-manager.sh remove feature/old-feature
./worktree-manager.sh clean
cd \$(./worktree-manager.sh goto feature/new-feature)
${YELLOW}Automatic Worktrees:${NC}
A Git hook is installed that automatically creates worktrees
when you run: ${CYAN}git checkout -b new-branch${NC}
${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}
EOF
}
list_worktrees() {
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Git Worktrees:${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
cd "$REPO_ROOT"
git worktree list --porcelain | awk '
/^worktree/ { path=$2 }
/^HEAD/ { head=$2 }
/^branch/ {
branch=$2
gsub(/^refs\/heads\//, "", branch)
printf "%-40s %s\n", branch, path
}
/^detached/ {
printf "%-40s %s (detached)\n", head, path
}
' | while read line; do
if [[ $line == *"(detached)"* ]]; then
echo -e "${YELLOW} $line${NC}"
else
echo -e "${GREEN} $line${NC}"
fi
done
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
create_worktree() {
local branch=$1
if [ -z "$branch" ]; then
echo -e "${RED}Error: Branch name required${NC}"
echo "Usage: $0 create <branch-name>"
exit 1
fi
local worktree_path="${WORKTREE_BASE}/${REPO_NAME}-${branch}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Creating worktree for branch: ${YELLOW}$branch${NC}"
echo -e "${BLUE}Location: ${YELLOW}$worktree_path${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
cd "$REPO_ROOT"
# Check if branch exists
if git show-ref --verify --quiet "refs/heads/$branch"; then
# Branch exists, just create worktree
git worktree add "$worktree_path" "$branch"
else
# Branch doesn't exist, create it
echo -e "${YELLOW}Branch doesn't exist, creating new branch...${NC}"
git worktree add -b "$branch" "$worktree_path"
fi
echo -e "${GREEN}✅ Worktree created successfully!${NC}"
echo -e ""
echo -e "To switch to the worktree:"
echo -e " ${CYAN}cd $worktree_path${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
remove_worktree() {
local branch=$1
if [ -z "$branch" ]; then
echo -e "${RED}Error: Branch name required${NC}"
echo "Usage: $0 remove <branch-name>"
exit 1
fi
cd "$REPO_ROOT"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Removing worktree for branch: $branch${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
git worktree remove "$branch" --force
echo -e "${GREEN}✅ Worktree removed successfully!${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
clean_worktrees() {
cd "$REPO_ROOT"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${YELLOW}Cleaning up worktrees (keeping main/master)...${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
# Get list of worktrees excluding main/master
git worktree list --porcelain | grep "^branch" | sed 's/^branch refs\/heads\///' | while read branch; do
if [[ "$branch" != "main" ]] && [[ "$branch" != "master" ]]; then
echo -e "${YELLOW}Removing: $branch${NC}"
git worktree remove "$branch" --force 2>/dev/null || echo -e "${RED} Failed to remove $branch${NC}"
fi
done
# Prune deleted worktrees
git worktree prune
echo -e "${GREEN}✅ Cleanup complete!${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
goto_worktree() {
local branch=$1
if [ -z "$branch" ]; then
echo -e "${RED}Error: Branch name required${NC}" >&2
exit 1
fi
cd "$REPO_ROOT"
# Find worktree path for branch
local worktree_path=$(git worktree list --porcelain | awk -v branch="$branch" '
/^worktree/ { path=$2 }
/^branch/ {
b=$2
gsub(/^refs\/heads\//, "", b)
if (b == branch) {
print path
exit
}
}
')
if [ -n "$worktree_path" ]; then
echo "$worktree_path"
else
echo -e "${RED}Error: No worktree found for branch '$branch'${NC}" >&2
exit 1
fi
}
show_status() {
cd "$REPO_ROOT"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
echo -e "${GREEN}Worktree Status:${NC}"
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
git worktree list --porcelain | awk '
/^worktree/ { path=$2 }
/^branch/ {
branch=$2
gsub(/^refs\/heads\//, "", branch)
printf "\n%s%s%s\n", "Branch: ", branch, ""
printf "%s%s%s\n", "Path: ", path, ""
system("cd " path " && git status --short --branch | head -5")
}
' | while IFS= read -r line; do
if [[ $line == Branch:* ]]; then
echo -e "${GREEN}$line${NC}"
elif [[ $line == Path:* ]]; then
echo -e "${BLUE}$line${NC}"
else
echo -e "${YELLOW}$line${NC}"
fi
done
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
}
# Main command dispatcher
case "${1:-help}" in
list|ls)
list_worktrees
;;
create|add)
create_worktree "$2"
;;
remove|rm|delete)
remove_worktree "$2"
;;
clean|cleanup)
clean_worktrees
;;
goto|cd)
goto_worktree "$2"
;;
status|st)
show_status
;;
help|--help|-h)
show_help
;;
*)
echo -e "${RED}Unknown command: $1${NC}"
echo ""
show_help
exit 1
;;
esac