|
#!/usr/bin/env bash |
|
set -euo pipefail |
|
|
|
# create_worktree.sh - Create a new git worktree for parallel development |
|
# |
|
# Worktrees allow you to work on multiple branches simultaneously without |
|
# stashing or committing. Each worktree is a separate working directory |
|
# with its own branch, sharing the same git history. |
|
|
|
readonly WORKTREES_BASE="$HOME/wt/lizy-backend" |
|
|
|
show_help() { |
|
cat << EOF |
|
Usage: $(basename "$0") [worktree_name] [base_branch] |
|
|
|
Create a new git worktree for parallel development work. |
|
|
|
Arguments: |
|
worktree_name Name for the worktree/branch. If not provided, generates |
|
a unique human-readable name (e.g., calm-river). |
|
Slashes in the name are converted to dashes for the directory. |
|
base_branch Branch to create from (default: trunk) |
|
|
|
Examples: |
|
$(basename "$0") # Auto-generated name from trunk |
|
# → branch: calm-river, dir: calm-river |
|
$(basename "$0") feat/GEN-1234-add-auth # New feature from trunk |
|
# → branch: feat/GEN-1234-add-auth, dir: feat-GEN-1234-add-auth |
|
$(basename "$0") feat/v2 feat/v1 # Branch from specific base |
|
|
|
The worktree will be created at: $WORKTREES_BASE/<worktree_name> |
|
|
|
What this script does: |
|
1. Creates a new worktree with a new branch |
|
2. Symlinks shared config files (.env, .mcp.json, ssl/, etc.) |
|
3. Runs 'make setup' to install dependencies |
|
4. Cleans up automatically if setup fails |
|
EOF |
|
exit 0 |
|
} |
|
|
|
# Handle --help/-h flag |
|
if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then |
|
show_help |
|
fi |
|
|
|
# Function to generate a unique worktree name |
|
generate_unique_name() { |
|
local -r adjectives=( |
|
"calm" "bright" "gentle" "steady" "quiet" "swift" "clear" "warm" |
|
"cool" "soft" "solid" "smooth" "brave" "kind" "wise" "fresh" |
|
"light" "open" "simple" "stable" "focused" "balanced" "grounded" "clean" |
|
"radiant" "soothing" "glimmering" "mellow" "tranquil" "shining" |
|
"noble" "peaceful" "serene" "vivid" "luminous" |
|
) |
|
local -r nouns=( |
|
"river" "path" "spark" "atlas" "orbit" "horizon" "forest" "breeze" |
|
"signal" "echo" "stone" "cloud" "wave" "beacon" "compass" "anchor" |
|
"harbor" "delta" "comet" "summit" "valley" "drift" "ember" "flame" |
|
"trail" "bridge" "island" "source" "core" "pulse" "thread" "link" |
|
"sparkle" "glow" "flare" "current" |
|
) |
|
|
|
local -r adj=${adjectives[$RANDOM % ${#adjectives[@]}]} |
|
local -r noun=${nouns[$RANDOM % ${#nouns[@]}]} |
|
|
|
echo "${adj}-${noun}" |
|
} |
|
|
|
# Validate we're in a git repository |
|
if ! git rev-parse --git-dir > /dev/null 2>&1; then |
|
echo "❌ Error: Not in a git repository" >&2 |
|
exit 1 |
|
fi |
|
|
|
# Auto-detect main repo via git worktree list (first entry is always the main repo) |
|
MAIN_REPO=$(git worktree list | head -1 | awk '{print $1}') |
|
readonly MAIN_REPO |
|
|
|
# Get worktree name from parameter or generate one |
|
readonly WORKTREE_NAME=${1:-$(generate_unique_name)} |
|
|
|
# Sanitize name for directory (replace / with -) |
|
readonly WORKTREE_DIR="${WORKTREE_NAME//\//-}" |
|
readonly WORKTREE_PATH="${WORKTREES_BASE}/${WORKTREE_DIR}" |
|
|
|
# Get base branch from second parameter, default to trunk |
|
readonly BASE_BRANCH=${2:-trunk} |
|
|
|
echo "🌳 Creating worktree: ${WORKTREE_NAME}" |
|
echo "📁 Location: ${WORKTREE_PATH}" |
|
echo "🏠 Main repo: ${MAIN_REPO}" |
|
echo "🔀 Base branch: ${BASE_BRANCH}" |
|
|
|
# Create worktrees base directory if it doesn't exist |
|
if [[ ! -d "$WORKTREES_BASE" ]]; then |
|
echo "📁 Creating worktrees base directory: $WORKTREES_BASE" |
|
mkdir -p "$WORKTREES_BASE" |
|
fi |
|
|
|
# Check if worktree already exists |
|
if [[ -d "$WORKTREE_PATH" ]]; then |
|
echo "❌ Error: Worktree directory already exists: $WORKTREE_PATH" >&2 |
|
exit 1 |
|
fi |
|
|
|
# Change to main repo to create worktree |
|
cd "$MAIN_REPO" |
|
|
|
# Create worktree (creates branch if it doesn't exist) |
|
BRANCH_CREATED=false # Track to preserve pre-existing branches on failure |
|
if git show-ref --verify --quiet "refs/heads/${WORKTREE_NAME}"; then |
|
echo "📋 Using existing branch: ${WORKTREE_NAME}" |
|
git worktree add "$WORKTREE_PATH" "$WORKTREE_NAME" |
|
else |
|
echo "🆕 Creating new branch: ${WORKTREE_NAME}" |
|
git worktree add -b "$WORKTREE_NAME" "$WORKTREE_PATH" "$BASE_BRANCH" |
|
BRANCH_CREATED=true |
|
fi |
|
|
|
# Symlink config files from main repo instead of copying. |
|
# This ensures all worktrees share the same configuration (single source of truth) |
|
# and changes to .env or secrets propagate automatically. |
|
echo "🔗 Creating symlinks..." |
|
|
|
if [[ -f "$MAIN_REPO/.env" ]]; then |
|
ln -sf "$MAIN_REPO/.env" "$WORKTREE_PATH/.env" |
|
echo " ✓ .env" |
|
fi |
|
|
|
if [[ -f "$MAIN_REPO/.env.secrets" ]]; then |
|
ln -sf "$MAIN_REPO/.env.secrets" "$WORKTREE_PATH/.env.secrets" |
|
echo " ✓ .env.secrets" |
|
fi |
|
|
|
if [[ -f "$MAIN_REPO/.env.test" ]]; then |
|
ln -sf "$MAIN_REPO/.env.test" "$WORKTREE_PATH/.env.test" |
|
echo " ✓ .env.test" |
|
fi |
|
|
|
if [[ -f "$MAIN_REPO/.mcp.json" ]]; then |
|
ln -sf "$MAIN_REPO/.mcp.json" "$WORKTREE_PATH/.mcp.json" |
|
echo " ✓ .mcp.json" |
|
fi |
|
|
|
if [[ -d "$MAIN_REPO/ssl" ]]; then |
|
# Use -snf to avoid creating nested ssl/ssl if directory exists |
|
ln -snf "$MAIN_REPO/ssl" "$WORKTREE_PATH/ssl" |
|
echo " ✓ ssl/" |
|
fi |
|
|
|
# Symlink settings.local.json (gitignored user settings) so Claude Code works in the worktree. |
|
# The rest of .claude/ is tracked in git and already present in the worktree. |
|
if [[ -f "$MAIN_REPO/.claude/settings.local.json" ]]; then |
|
ln -sf "$MAIN_REPO/.claude/settings.local.json" "$WORKTREE_PATH/.claude/settings.local.json" |
|
echo " ✓ .claude/settings.local.json" |
|
fi |
|
|
|
# Change to worktree directory and run setup |
|
cd "$WORKTREE_PATH" |
|
|
|
echo "🔧 Setting up worktree dependencies..." |
|
if ! make setup; then |
|
# Clean up on failure to avoid leaving broken worktrees that confuse users. |
|
# --force is needed because the worktree may have generated files. |
|
echo "❌ Setup failed. Cleaning up worktree..." >&2 |
|
cd "$MAIN_REPO" |
|
git worktree remove --force "$WORKTREE_PATH" |
|
if $BRANCH_CREATED; then |
|
git branch -D "$WORKTREE_NAME" 2>/dev/null || true |
|
fi |
|
echo "❌ Not allowed to create worktree from a branch that isn't passing setup." >&2 |
|
exit 1 |
|
fi |
|
|
|
echo "" |
|
echo "✅ Worktree created successfully!" |
|
echo "📁 Path: ${WORKTREE_PATH}" |
|
echo "🔀 Branch: ${WORKTREE_NAME}" |
|
echo "" |
|
echo "To work in this worktree:" |
|
echo " cd ${WORKTREE_PATH}" |
|
echo "" |
|
echo "To remove this worktree later:" |
|
echo " ${MAIN_REPO}/local_scripts/worktree/cleanup_worktree.sh ${WORKTREE_NAME}" |