499 lines
12 KiB
Bash
Executable File
499 lines
12 KiB
Bash
Executable File
#!/bin/bash
|
||
# Git Worktree Batch Setup Script
|
||
# Generated by git-worktree-setup skill
|
||
#
|
||
# This script automates the creation of multiple git worktrees with parallel dependency installation.
|
||
#
|
||
# Usage:
|
||
# ./batch-setup.sh branch1 branch2 branch3 [--no-install] [--pattern=/path/{branch}]
|
||
|
||
set -euo pipefail
|
||
|
||
# ============================================================================
|
||
# Configuration
|
||
# ============================================================================
|
||
|
||
BRANCHES=()
|
||
SKIP_INSTALL=false
|
||
PATH_PATTERN=""
|
||
|
||
# Parse arguments
|
||
for arg in "$@"; do
|
||
case $arg in
|
||
--no-install)
|
||
SKIP_INSTALL=true
|
||
;;
|
||
--pattern=*)
|
||
PATH_PATTERN="${arg#*=}"
|
||
;;
|
||
*)
|
||
BRANCHES+=("$arg")
|
||
;;
|
||
esac
|
||
done
|
||
|
||
# Colors for output
|
||
RED='\033[0;31m'
|
||
GREEN='\033[0;32m'
|
||
YELLOW='\033[1;33m'
|
||
BLUE='\033[0;34m'
|
||
NC='\033[0m' # No Color
|
||
|
||
# ============================================================================
|
||
# Helper Functions
|
||
# ============================================================================
|
||
|
||
print_success() {
|
||
echo -e "${GREEN}✓${NC} $1"
|
||
}
|
||
|
||
print_error() {
|
||
echo -e "${RED}✗${NC} $1" >&2
|
||
}
|
||
|
||
print_warning() {
|
||
echo -e "${YELLOW}⚠${NC} $1"
|
||
}
|
||
|
||
print_info() {
|
||
echo -e "${BLUE}ℹ${NC} $1"
|
||
}
|
||
|
||
print_header() {
|
||
echo ""
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo "$1"
|
||
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
||
echo ""
|
||
}
|
||
|
||
# ============================================================================
|
||
# Prerequisite Checks
|
||
# ============================================================================
|
||
|
||
check_prerequisites() {
|
||
print_header "Checking Prerequisites"
|
||
|
||
# Check if in git repository
|
||
if ! git rev-parse --is-inside-work-tree &>/dev/null; then
|
||
print_error "Not in a git repository"
|
||
exit 1
|
||
fi
|
||
print_success "Git repository detected"
|
||
|
||
# Get repository info
|
||
REPO_NAME=$(basename "$(git rev-parse --show-toplevel)")
|
||
REPO_ROOT=$(git rev-parse --show-toplevel)
|
||
CURRENT_BRANCH=$(git branch --show-current)
|
||
|
||
print_info "Repository: $REPO_NAME"
|
||
print_info "Current branch: $CURRENT_BRANCH"
|
||
|
||
# Check for branch names
|
||
if [ ${#BRANCHES[@]} -eq 0 ]; then
|
||
print_error "No branch names provided"
|
||
echo "Usage: $0 branch1 branch2 branch3 [--no-install] [--pattern=/path/{branch}]"
|
||
exit 1
|
||
fi
|
||
|
||
print_info "Branches to create: ${#BRANCHES[@]}"
|
||
for branch in "${BRANCHES[@]}"; do
|
||
echo " - $branch"
|
||
done
|
||
}
|
||
|
||
validate_branches() {
|
||
print_header "Validating Branches"
|
||
|
||
declare -gA BRANCH_TYPES
|
||
declare -gA WORKTREE_PATHS
|
||
|
||
for branch in "${BRANCHES[@]}"; do
|
||
# Check if branch exists
|
||
if git show-ref --verify "refs/heads/$branch" &>/dev/null; then
|
||
BRANCH_TYPES[$branch]="existing"
|
||
print_info "$branch: existing branch"
|
||
elif git show-ref --verify "refs/remotes/origin/$branch" &>/dev/null; then
|
||
BRANCH_TYPES[$branch]="remote"
|
||
print_info "$branch: exists on remote"
|
||
else
|
||
BRANCH_TYPES[$branch]="new"
|
||
print_info "$branch: will create new branch"
|
||
fi
|
||
|
||
# Determine worktree path
|
||
if [ -n "$PATH_PATTERN" ]; then
|
||
# Use custom pattern
|
||
WORKTREE_PATHS[$branch]="${PATH_PATTERN//\{branch\}/$branch}"
|
||
else
|
||
# Use default pattern
|
||
WORKTREE_PATHS[$branch]="$(dirname "$REPO_ROOT")/$REPO_NAME-$branch"
|
||
fi
|
||
|
||
echo " → ${WORKTREE_PATHS[$branch]}"
|
||
done
|
||
}
|
||
|
||
check_directory_conflicts() {
|
||
print_header "Checking for Directory Conflicts"
|
||
|
||
CONFLICTS=()
|
||
for branch in "${BRANCHES[@]}"; do
|
||
path="${WORKTREE_PATHS[$branch]}"
|
||
if [ -d "$path" ]; then
|
||
CONFLICTS+=("$branch → $path")
|
||
fi
|
||
done
|
||
|
||
if [ ${#CONFLICTS[@]} -gt 0 ]; then
|
||
print_warning "Found ${#CONFLICTS[@]} directory conflicts:"
|
||
for conflict in "${CONFLICTS[@]}"; do
|
||
echo " $conflict"
|
||
done
|
||
echo ""
|
||
echo "Options:"
|
||
echo " 1. Skip conflicting worktrees"
|
||
echo " 2. Remove and recreate"
|
||
echo " 3. Abort"
|
||
read -p "Choose (1/2/3): " -r choice
|
||
|
||
case $choice in
|
||
1)
|
||
print_info "Will skip conflicting directories"
|
||
for conflict in "${CONFLICTS[@]}"; do
|
||
branch="${conflict%% →*}"
|
||
# Remove from BRANCHES array
|
||
BRANCHES=("${BRANCHES[@]/$branch}")
|
||
done
|
||
# Remove empty elements
|
||
BRANCHES=("${BRANCHES[@]}")
|
||
;;
|
||
2)
|
||
print_warning "Removing conflicting directories..."
|
||
for conflict in "${CONFLICTS[@]}"; do
|
||
path="${conflict##*→ }"
|
||
rm -rf "$path"
|
||
print_success "Removed: $path"
|
||
done
|
||
;;
|
||
3)
|
||
print_info "Aborted by user"
|
||
exit 0
|
||
;;
|
||
esac
|
||
else
|
||
print_success "No directory conflicts"
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Worktree Creation
|
||
# ============================================================================
|
||
|
||
create_worktrees() {
|
||
print_header "Creating Worktrees"
|
||
|
||
declare -gA CREATED_WORKTREES
|
||
declare -ga FAILED_WORKTREES
|
||
|
||
local total=${#BRANCHES[@]}
|
||
local current=0
|
||
|
||
for branch in "${BRANCHES[@]}"; do
|
||
((current++))
|
||
echo ""
|
||
print_info "[$current/$total] Creating worktree for $branch..."
|
||
|
||
path="${WORKTREE_PATHS[$branch]}"
|
||
branch_type="${BRANCH_TYPES[$branch]}"
|
||
|
||
case $branch_type in
|
||
new)
|
||
if git worktree add "$path" -b "$branch" 2>&1; then
|
||
CREATED_WORKTREES[$branch]="$path"
|
||
print_success "$branch: Created with new branch"
|
||
else
|
||
FAILED_WORKTREES+=("$branch")
|
||
print_error "$branch: Failed to create"
|
||
fi
|
||
;;
|
||
|
||
existing)
|
||
if git worktree add "$path" "$branch" 2>&1; then
|
||
CREATED_WORKTREES[$branch]="$path"
|
||
print_success "$branch: Created from existing branch"
|
||
else
|
||
FAILED_WORKTREES+=("$branch")
|
||
print_error "$branch: Failed to create"
|
||
fi
|
||
;;
|
||
|
||
remote)
|
||
if git worktree add "$path" -b "$branch" --track "origin/$branch" 2>&1; then
|
||
CREATED_WORKTREES[$branch]="$path"
|
||
print_success "$branch: Created tracking remote"
|
||
else
|
||
FAILED_WORKTREES+=("$branch")
|
||
print_error "$branch: Failed to create"
|
||
fi
|
||
;;
|
||
esac
|
||
done
|
||
|
||
echo ""
|
||
print_header "Worktree Creation Summary"
|
||
print_success "Created: ${#CREATED_WORKTREES[@]}/$total worktrees"
|
||
|
||
if [ ${#FAILED_WORKTREES[@]} -gt 0 ]; then
|
||
print_error "Failed: ${#FAILED_WORKTREES[@]} worktrees"
|
||
for failed in "${FAILED_WORKTREES[@]}"; do
|
||
echo " - $failed"
|
||
done
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Development Environment Setup
|
||
# ============================================================================
|
||
|
||
detect_package_manager() {
|
||
cd "$REPO_ROOT"
|
||
|
||
if [ -f "pnpm-lock.yaml" ]; then
|
||
PKG_MANAGER="pnpm"
|
||
INSTALL_CMD="pnpm install"
|
||
elif [ -f "yarn.lock" ]; then
|
||
PKG_MANAGER="yarn"
|
||
INSTALL_CMD="yarn install"
|
||
elif [ -f "bun.lockb" ]; then
|
||
PKG_MANAGER="bun"
|
||
INSTALL_CMD="bun install"
|
||
elif [ -f "package-lock.json" ]; then
|
||
PKG_MANAGER="npm"
|
||
INSTALL_CMD="npm install"
|
||
elif [ -f "package.json" ]; then
|
||
PKG_MANAGER="npm"
|
||
INSTALL_CMD="npm install"
|
||
else
|
||
PKG_MANAGER="none"
|
||
INSTALL_CMD=""
|
||
fi
|
||
|
||
if [ "$PKG_MANAGER" != "none" ]; then
|
||
print_info "Using $PKG_MANAGER for all worktrees"
|
||
fi
|
||
}
|
||
|
||
install_dependencies() {
|
||
if [ "$SKIP_INSTALL" = true ]; then
|
||
print_info "Skipping dependency installation (--no-install flag)"
|
||
return 0
|
||
fi
|
||
|
||
if [ "$PKG_MANAGER" = "none" ]; then
|
||
print_info "No package.json found, skipping dependency installation"
|
||
return 0
|
||
fi
|
||
|
||
if [ ${#CREATED_WORKTREES[@]} -eq 0 ]; then
|
||
print_warning "No worktrees created, skipping dependency installation"
|
||
return 0
|
||
fi
|
||
|
||
print_header "Installing Dependencies (Parallel)"
|
||
|
||
declare -gA INSTALL_SUCCESS
|
||
declare -gA INSTALL_FAILED
|
||
declare -A INSTALL_PIDS
|
||
|
||
# Start installations in parallel
|
||
for branch in "${!CREATED_WORKTREES[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
print_info "Starting installation in $branch..."
|
||
|
||
# Run in background
|
||
(
|
||
cd "$path"
|
||
$INSTALL_CMD > "/tmp/install-$branch.log" 2>&1
|
||
) &
|
||
|
||
INSTALL_PIDS[$branch]=$!
|
||
done
|
||
|
||
# Wait for all installations
|
||
for branch in "${!INSTALL_PIDS[@]}"; do
|
||
pid=${INSTALL_PIDS[$branch]}
|
||
print_info "Waiting for $branch (PID: $pid)..."
|
||
|
||
if wait "$pid"; then
|
||
INSTALL_SUCCESS[$branch]=true
|
||
print_success "$branch: Dependencies installed"
|
||
else
|
||
INSTALL_FAILED[$branch]=true
|
||
print_error "$branch: Installation failed (see /tmp/install-$branch.log)"
|
||
fi
|
||
done
|
||
|
||
echo ""
|
||
print_header "Installation Summary"
|
||
print_success "Successful: ${#INSTALL_SUCCESS[@]}/${#CREATED_WORKTREES[@]}"
|
||
|
||
if [ ${#INSTALL_FAILED[@]} -gt 0 ]; then
|
||
print_warning "Failed: ${#INSTALL_FAILED[@]}"
|
||
for branch in "${!INSTALL_FAILED[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
echo " - $branch (manually run: cd $path && $INSTALL_CMD)"
|
||
done
|
||
fi
|
||
}
|
||
|
||
# ============================================================================
|
||
# Summary and Next Steps
|
||
# ============================================================================
|
||
|
||
print_final_summary() {
|
||
print_header "Batch Worktree Creation Complete"
|
||
|
||
echo "Created: ${#CREATED_WORKTREES[@]}/${#BRANCHES[@]} worktrees"
|
||
if [ ${#FAILED_WORKTREES[@]} -gt 0 ]; then
|
||
echo "Failed: ${#FAILED_WORKTREES[@]}/${#BRANCHES[@]} worktrees"
|
||
fi
|
||
echo ""
|
||
|
||
if [ ${#CREATED_WORKTREES[@]} -gt 0 ]; then
|
||
echo "Successful worktrees:"
|
||
for branch in "${!CREATED_WORKTREES[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
branch_type="${BRANCH_TYPES[$branch]}"
|
||
print_success "$branch → $path ($branch_type)"
|
||
done
|
||
fi
|
||
echo ""
|
||
|
||
if [ ${#FAILED_WORKTREES[@]} -gt 0 ]; then
|
||
echo "Failed worktrees:"
|
||
for branch in "${FAILED_WORKTREES[@]}"; do
|
||
print_error "$branch"
|
||
done
|
||
echo ""
|
||
fi
|
||
|
||
print_header "Next Steps"
|
||
|
||
echo "Start working on each worktree:"
|
||
echo ""
|
||
for branch in "${!CREATED_WORKTREES[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
echo "${BLUE}# $branch${NC}"
|
||
echo "cd $path"
|
||
echo "claude"
|
||
echo ""
|
||
done
|
||
|
||
print_header "All Worktrees"
|
||
git worktree list
|
||
echo ""
|
||
|
||
print_header "Batch Management"
|
||
echo "List all: git worktree list"
|
||
echo "Remove all:"
|
||
for branch in "${!CREATED_WORKTREES[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
echo " git worktree remove $path"
|
||
done
|
||
echo ""
|
||
|
||
# Offer to generate cleanup script
|
||
read -p "Generate cleanup script? (yes/no): " -r
|
||
if [[ $REPLY =~ ^[Yy]es$ ]]; then
|
||
generate_cleanup_script
|
||
fi
|
||
}
|
||
|
||
generate_cleanup_script() {
|
||
CLEANUP_SCRIPT="cleanup-worktrees.sh"
|
||
|
||
cat > "$CLEANUP_SCRIPT" << 'EOF'
|
||
#!/bin/bash
|
||
# Cleanup script for batch-created worktrees
|
||
# Generated by git-worktree-setup skill
|
||
|
||
set -euo pipefail
|
||
|
||
GREEN='\033[0;32m'
|
||
RED='\033[0;31m'
|
||
NC='\033[0m'
|
||
|
||
echo "Cleaning up worktrees..."
|
||
|
||
EOF
|
||
|
||
for branch in "${!CREATED_WORKTREES[@]}"; do
|
||
path="${CREATED_WORKTREES[$branch]}"
|
||
cat >> "$CLEANUP_SCRIPT" << EOF
|
||
|
||
echo "Removing $branch..."
|
||
if git worktree remove "$path" 2>/dev/null; then
|
||
echo -e "\${GREEN}✓\${NC} Removed: $branch"
|
||
|
||
read -p "Delete branch '$branch'? (yes/no): " -r
|
||
if [[ \$REPLY =~ ^[Yy]es$ ]]; then
|
||
git branch -d "$branch" 2>/dev/null && echo -e "\${GREEN}✓\${NC} Deleted branch: $branch" || echo -e "\${RED}✗\${NC} Failed to delete branch: $branch"
|
||
fi
|
||
else
|
||
echo -e "\${RED}✗\${NC} Failed to remove: $branch"
|
||
fi
|
||
|
||
EOF
|
||
done
|
||
|
||
cat >> "$CLEANUP_SCRIPT" << 'EOF'
|
||
|
||
echo ""
|
||
echo "Cleanup complete!"
|
||
echo ""
|
||
echo "Remaining worktrees:"
|
||
git worktree list
|
||
EOF
|
||
|
||
chmod +x "$CLEANUP_SCRIPT"
|
||
print_success "Generated cleanup script: $CLEANUP_SCRIPT"
|
||
print_info "Run with: ./$CLEANUP_SCRIPT"
|
||
}
|
||
|
||
# ============================================================================
|
||
# Main Execution
|
||
# ============================================================================
|
||
|
||
main() {
|
||
print_header "Git Worktree Batch Setup"
|
||
|
||
# Phase 0: Prerequisites
|
||
check_prerequisites
|
||
validate_branches
|
||
check_directory_conflicts
|
||
|
||
# Confirm before proceeding
|
||
echo ""
|
||
read -p "Create ${#BRANCHES[@]} worktrees? (yes/no): " -r
|
||
if [[ ! $REPLY =~ ^[Yy]es$ ]]; then
|
||
print_info "Aborted by user"
|
||
exit 0
|
||
fi
|
||
|
||
# Phase 1: Create worktrees
|
||
create_worktrees
|
||
|
||
# Phase 2: Setup development environments
|
||
detect_package_manager
|
||
install_dependencies
|
||
|
||
# Phase 3: Summary
|
||
print_final_summary
|
||
|
||
print_success "Batch setup complete!"
|
||
}
|
||
|
||
# Run main function
|
||
main "$@"
|