refactor(ce-worktree): trim to creation-only and fix latent bugs (#631)
This commit is contained in:
@@ -1,310 +1,77 @@
|
|||||||
---
|
---
|
||||||
name: ce-worktree
|
name: ce-worktree
|
||||||
description: This skill manages Git worktrees for isolated parallel development. It handles creating, listing, switching, and cleaning up worktrees with a simple interactive interface, following KISS principles.
|
description: Create an isolated git worktree for parallel feature work or PR review. Use when starting work that should not disturb the current checkout, or when `ce-work` or `ce-code-review` offers a worktree option.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Git Worktree Manager
|
# Worktree Creation
|
||||||
|
|
||||||
This skill provides a unified interface for managing Git worktrees across your development workflow. Whether you're reviewing PRs in isolation or working on features in parallel, this skill handles all the complexity.
|
Create a worktree under `.worktrees/<branch>` with branch-specific setup that `git worktree add` alone does not handle:
|
||||||
|
|
||||||
## What This Skill Does
|
- Copies `.env`, `.env.local`, `.env.test`, etc. from the main repo (skips `.env.example`)
|
||||||
|
- Trusts `mise`/`direnv` configs, with branch-aware safety rules so review branches do not auto-grant trust to untrusted `.envrc` content
|
||||||
|
- Adds `.worktrees` to `.gitignore` if not already ignored
|
||||||
|
- Does not modify the main repo checkout — `from-branch` is fetched, not checked out
|
||||||
|
|
||||||
- **Create worktrees** from main branch with clear branch names
|
## Creating a worktree
|
||||||
- **List worktrees** with current status
|
|
||||||
- **Switch between worktrees** for parallel work
|
|
||||||
- **Clean up completed worktrees** automatically
|
|
||||||
- **Interactive confirmations** at each step
|
|
||||||
- **Automatic .gitignore management** for worktree directory
|
|
||||||
- **Automatic .env file copying** from main repo to new worktrees
|
|
||||||
- **Automatic dev tool trusting** for mise and direnv configs with review-safe guardrails
|
|
||||||
|
|
||||||
## CRITICAL: Always Use the Manager Script
|
|
||||||
|
|
||||||
**NEVER call `git worktree add` directly.** Always use the `worktree-manager.sh` script.
|
|
||||||
|
|
||||||
The script handles critical setup that raw git commands don't:
|
|
||||||
1. Copies `.env`, `.env.local`, `.env.test`, etc. from main repo
|
|
||||||
2. Trusts dev tool configs with branch-aware safety rules:
|
|
||||||
- mise: auto-trust only when unchanged from a trusted baseline branch
|
|
||||||
- direnv: auto-allow only for trusted base branches; review worktrees stay manual
|
|
||||||
3. Ensures `.worktrees` is in `.gitignore`
|
|
||||||
4. Creates consistent directory structure
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# ✅ CORRECT - Always use the script
|
bash scripts/worktree-manager.sh create <branch-name> [from-branch]
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create feature-name
|
|
||||||
|
|
||||||
# ❌ WRONG - Never do this directly
|
|
||||||
git worktree add .worktrees/feature-name -b feature-name main
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## When to Use This Skill
|
Defaults:
|
||||||
|
- `from-branch` defaults to origin's default branch (or `main` if that cannot be resolved)
|
||||||
|
- The new branch is created at `origin/<from-branch>` (or the local ref if the remote is unavailable)
|
||||||
|
|
||||||
Use this skill in these scenarios:
|
Examples:
|
||||||
|
```bash
|
||||||
1. **Code Review (`/ce-code-review`)**: If NOT already on the target branch (PR branch or requested branch), offer worktree for isolated review
|
bash scripts/worktree-manager.sh create feat/login
|
||||||
2. **Feature Work (`/ce-work`)**: Always ask if user wants parallel worktree or live branch work
|
bash scripts/worktree-manager.sh create fix/email-validation develop
|
||||||
3. **Parallel Development**: When working on multiple features simultaneously
|
|
||||||
4. **Cleanup**: After completing work in a worktree
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
### In Claude Code Workflows
|
|
||||||
|
|
||||||
The skill is automatically called from `/ce-code-review` and `/ce-work` commands:
|
|
||||||
|
|
||||||
```
|
|
||||||
# For review: offers worktree if not on PR branch
|
|
||||||
# For work: always asks - new branch or worktree?
|
|
||||||
```
|
```
|
||||||
|
|
||||||
### Manual Usage
|
After creation, switch to the worktree with `cd .worktrees/<branch-name>`.
|
||||||
|
|
||||||
You can also invoke the skill directly from bash:
|
## Other worktree operations
|
||||||
|
|
||||||
|
Use `git` directly — no wrapper is needed and none is provided:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
# Create a new worktree (copies .env files automatically)
|
git worktree list # list worktrees
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create feature-login
|
git worktree remove .worktrees/<branch> # remove a worktree
|
||||||
|
cd .worktrees/<branch> # switch to a worktree
|
||||||
# List all worktrees
|
cd "$(git rev-parse --show-toplevel)" # return to main checkout
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh list
|
|
||||||
|
|
||||||
# Switch to a worktree
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh switch feature-login
|
|
||||||
|
|
||||||
# Copy .env files to an existing worktree (if they weren't copied)
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh copy-env feature-login
|
|
||||||
|
|
||||||
# Clean up completed worktrees
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh cleanup
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
To copy `.env*` files into an existing worktree created without them, run this from the main repo (not from inside the worktree, since branch names often contain slashes like `feat/login`):
|
||||||
|
|
||||||
### `create <branch-name> [from-branch]`
|
|
||||||
|
|
||||||
Creates a new worktree with the given branch name.
|
|
||||||
|
|
||||||
**Options:**
|
|
||||||
- `branch-name` (required): The name for the new branch and worktree
|
|
||||||
- `from-branch` (optional): Base branch to create from (defaults to `main`)
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
```bash
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create feature-login
|
cp .env* .worktrees/<branch>/
|
||||||
```
|
```
|
||||||
|
|
||||||
**What happens:**
|
## Dev tool trust behavior
|
||||||
1. Checks if worktree already exists
|
|
||||||
2. Updates the base branch from remote
|
|
||||||
3. Creates new worktree and branch
|
|
||||||
4. **Copies all .env files from main repo** (.env, .env.local, .env.test, etc.)
|
|
||||||
5. **Trusts dev tool configs** with branch-aware safety rules:
|
|
||||||
- trusted bases (`main`, `develop`, `dev`, `trunk`, `staging`, `release/*`) compare against themselves
|
|
||||||
- other branches compare against the default branch
|
|
||||||
- direnv auto-allow is skipped on non-trusted bases because `.envrc` can source unchecked files
|
|
||||||
6. Shows path for cd-ing to the worktree
|
|
||||||
|
|
||||||
### `list` or `ls`
|
When mise or direnv configs are present, the script attempts to trust them so hooks and scripts do not block on interactive prompts. Trust is baseline-checked against a reference branch:
|
||||||
|
|
||||||
Lists all available worktrees with their branches and current status.
|
- **Trusted base branches** (`main`, `develop`, `dev`, `trunk`, `staging`, `release/*`): the new worktree's configs are compared against that branch; unchanged configs are auto-trusted. `direnv allow` is permitted.
|
||||||
|
- **Other branches** (feature branches, PR review branches): configs are compared against the default branch; `direnv allow` is skipped regardless, because `.envrc` can source files that direnv does not validate.
|
||||||
|
|
||||||
**Example:**
|
Modified configs are never auto-trusted. The script prints the manual trust command to run after review.
|
||||||
```bash
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh list
|
|
||||||
```
|
|
||||||
|
|
||||||
**Output shows:**
|
## When to create a worktree
|
||||||
- Worktree name
|
|
||||||
- Branch name
|
|
||||||
- Which is current (marked with ✓)
|
|
||||||
- Main repo status
|
|
||||||
|
|
||||||
### `switch <name>` or `go <name>`
|
Create a worktree when:
|
||||||
|
- Reviewing a PR while keeping the main checkout free for other work
|
||||||
|
- Running multiple features in parallel without branch-switching overhead
|
||||||
|
- Keeping the default branch free of in-progress state
|
||||||
|
|
||||||
Switches to an existing worktree and cd's into it.
|
Do not create a worktree for single-task work that can happen on a branch in the main checkout.
|
||||||
|
|
||||||
**Example:**
|
## Integration
|
||||||
```bash
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh switch feature-login
|
|
||||||
```
|
|
||||||
|
|
||||||
**Optional:**
|
`ce-work` and `ce-code-review` offer this skill as an option. When the user selects "worktree" in those flows, invoke `bash scripts/worktree-manager.sh create <branch>` with a meaningful branch name derived from the work description (e.g., `feat/crowd-sniff`, `fix/email-validation`). Avoid auto-generated names like `worktree-jolly-beaming-raven` that obscure the work.
|
||||||
- If name not provided, lists available worktrees and prompts for selection
|
|
||||||
|
|
||||||
### `cleanup` or `clean`
|
|
||||||
|
|
||||||
Interactively cleans up inactive worktrees with confirmation.
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```bash
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
**What happens:**
|
|
||||||
1. Lists all inactive worktrees
|
|
||||||
2. Asks for confirmation
|
|
||||||
3. Removes selected worktrees
|
|
||||||
4. Cleans up empty directories
|
|
||||||
|
|
||||||
## Workflow Examples
|
|
||||||
|
|
||||||
### Code Review with Worktree
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Claude Code recognizes you're not on the PR branch
|
|
||||||
# Offers: "Use worktree for isolated review? (y/n)"
|
|
||||||
|
|
||||||
# You respond: yes
|
|
||||||
# Script runs (copies .env files automatically):
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create pr-123-feature-name
|
|
||||||
|
|
||||||
# You're now in isolated worktree for review with all env vars
|
|
||||||
cd .worktrees/pr-123-feature-name
|
|
||||||
|
|
||||||
# After review, return to main:
|
|
||||||
cd ../..
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
### Parallel Feature Development
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# For first feature (copies .env files):
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create feature-login
|
|
||||||
|
|
||||||
# Later, start second feature (also copies .env files):
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh create feature-notifications
|
|
||||||
|
|
||||||
# List what you have:
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh list
|
|
||||||
|
|
||||||
# Switch between them as needed:
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh switch feature-login
|
|
||||||
|
|
||||||
# Return to main and cleanup when done:
|
|
||||||
cd .
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Design Principles
|
|
||||||
|
|
||||||
### KISS (Keep It Simple, Stupid)
|
|
||||||
|
|
||||||
- **One manager script** handles all worktree operations
|
|
||||||
- **Simple commands** with sensible defaults
|
|
||||||
- **Interactive prompts** prevent accidental operations
|
|
||||||
- **Clear naming** using branch names directly
|
|
||||||
|
|
||||||
### Opinionated Defaults
|
|
||||||
|
|
||||||
- Worktrees always created from **main** (unless specified)
|
|
||||||
- Worktrees stored in **.worktrees/** directory
|
|
||||||
- Branch name becomes worktree name
|
|
||||||
- **.gitignore** automatically managed
|
|
||||||
|
|
||||||
### Safety First
|
|
||||||
|
|
||||||
- **Confirms before creating** worktrees
|
|
||||||
- **Confirms before cleanup** to prevent accidental removal
|
|
||||||
- **Won't remove current worktree**
|
|
||||||
- **Clear error messages** for issues
|
|
||||||
|
|
||||||
## Integration with Workflows
|
|
||||||
|
|
||||||
### `/ce-code-review`
|
|
||||||
|
|
||||||
Instead of always creating a worktree:
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Check current branch
|
|
||||||
2. If ALREADY on target branch (PR branch or requested branch) → stay there, no worktree needed
|
|
||||||
3. If DIFFERENT branch than the review target → offer worktree:
|
|
||||||
"Use worktree for isolated review? (y/n)"
|
|
||||||
- yes → call ce-worktree skill
|
|
||||||
- no → proceed with PR diff on current branch
|
|
||||||
```
|
|
||||||
|
|
||||||
### `/ce-work`
|
|
||||||
|
|
||||||
Always offer choice:
|
|
||||||
|
|
||||||
```
|
|
||||||
1. Ask: "How do you want to work?
|
|
||||||
1. New branch on current worktree (live work)
|
|
||||||
2. Worktree (parallel work)"
|
|
||||||
|
|
||||||
2. If choice 1 → create new branch normally
|
|
||||||
3. If choice 2 → call ce-worktree skill to create from main
|
|
||||||
```
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
### "Worktree already exists"
|
**"Worktree already exists"**: the path is already in use. Either switch to it (`cd .worktrees/<branch>`) or remove it (`git worktree remove .worktrees/<branch>`) before recreating.
|
||||||
|
|
||||||
If you see this, the script will ask if you want to switch to it instead.
|
**"Cannot remove worktree: it is the current worktree"**: `cd` out of the worktree first, then `git worktree remove`.
|
||||||
|
|
||||||
### "Cannot remove worktree: it is the current worktree"
|
**Dev tool trust was skipped**: the script prints the manual command. Review the config diff (`git diff <base-ref> -- .envrc`), then run the printed command from the worktree directory.
|
||||||
|
|
||||||
Switch out of the worktree first (to main repo), then cleanup:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd $(git rev-parse --show-toplevel)
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh cleanup
|
|
||||||
```
|
|
||||||
|
|
||||||
### Lost in a worktree?
|
|
||||||
|
|
||||||
See where you are:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh list
|
|
||||||
```
|
|
||||||
|
|
||||||
### .env files missing in worktree?
|
|
||||||
|
|
||||||
If a worktree was created without .env files (e.g., via raw `git worktree add`), copy them:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
bash ${CLAUDE_PLUGIN_ROOT}/skills/ce-worktree/scripts/worktree-manager.sh copy-env feature-name
|
|
||||||
```
|
|
||||||
|
|
||||||
Navigate back to main:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd $(git rev-parse --show-toplevel)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Technical Details
|
|
||||||
|
|
||||||
### Directory Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
.worktrees/
|
|
||||||
├── feature-login/ # Worktree 1
|
|
||||||
│ ├── .git
|
|
||||||
│ ├── app/
|
|
||||||
│ └── ...
|
|
||||||
├── feature-notifications/ # Worktree 2
|
|
||||||
│ ├── .git
|
|
||||||
│ ├── app/
|
|
||||||
│ └── ...
|
|
||||||
└── ...
|
|
||||||
|
|
||||||
.gitignore (updated to include .worktrees)
|
|
||||||
```
|
|
||||||
|
|
||||||
### How It Works
|
|
||||||
|
|
||||||
- Uses `git worktree add` for isolated environments
|
|
||||||
- Each worktree has its own branch
|
|
||||||
- Changes in one worktree don't affect others
|
|
||||||
- Share git history with main repo
|
|
||||||
- Can push from any worktree
|
|
||||||
|
|
||||||
### Performance
|
|
||||||
|
|
||||||
- Worktrees are lightweight (just file system links)
|
|
||||||
- No repository duplication
|
|
||||||
- Shared git objects for efficiency
|
|
||||||
- Much faster than cloning or stashing/switching
|
|
||||||
|
|||||||
@@ -1,76 +1,89 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
#
|
||||||
|
# Create a new git worktree with environment files and dev-tool trust.
|
||||||
|
#
|
||||||
|
# The distinctive work this script does (vs. raw `git worktree add`):
|
||||||
|
# 1. Copies .env* files from the main repo (skipping .env.example)
|
||||||
|
# 2. Trusts mise/direnv configs with branch-aware safety rules,
|
||||||
|
# so hooks and scripts don't block on interactive trust prompts
|
||||||
|
# 3. Ensures .worktrees is gitignored (via `git check-ignore`)
|
||||||
|
#
|
||||||
|
# List / remove / switch operations are NOT provided here. Use git directly:
|
||||||
|
# git worktree list
|
||||||
|
# git worktree remove <path>
|
||||||
|
# cd <worktree-path> # switching is just `cd`
|
||||||
|
|
||||||
# Git Worktree Manager
|
set -euo pipefail
|
||||||
# Handles creating, listing, switching, and cleaning up Git worktrees
|
|
||||||
# KISS principle: Simple, interactive, opinionated
|
|
||||||
|
|
||||||
set -e
|
# Resolve the main worktree's working tree, not the current worktree's toplevel.
|
||||||
|
# `git worktree list --porcelain` always emits the main worktree first. This
|
||||||
# Colors for output
|
# handles normal repos, linked worktrees (where --show-toplevel would return
|
||||||
RED='\033[0;31m'
|
# the nested worktree), submodules (where --git-common-dir points under
|
||||||
GREEN='\033[0;32m'
|
# .git/modules), and --separate-git-dir setups (where --git-common-dir points
|
||||||
YELLOW='\033[1;33m'
|
# to an external path). Parse with `sed` to preserve paths containing spaces
|
||||||
BLUE='\033[0;34m'
|
# (awk '{print $2}' would truncate them).
|
||||||
NC='\033[0m' # No Color
|
GIT_ROOT=$(git worktree list --porcelain | sed -n 's/^worktree //p' | head -n 1)
|
||||||
|
|
||||||
# Get repo root
|
|
||||||
GIT_ROOT=$(git rev-parse --show-toplevel)
|
|
||||||
WORKTREE_DIR="$GIT_ROOT/.worktrees"
|
WORKTREE_DIR="$GIT_ROOT/.worktrees"
|
||||||
|
|
||||||
# Ensure .worktrees is in .gitignore
|
usage() {
|
||||||
ensure_gitignore() {
|
cat <<'EOF'
|
||||||
if ! grep -q "^\.worktrees$" "$GIT_ROOT/.gitignore" 2>/dev/null; then
|
Usage: worktree-manager.sh create <branch-name> [from-branch]
|
||||||
echo ".worktrees" >> "$GIT_ROOT/.gitignore"
|
|
||||||
fi
|
Creates .worktrees/<branch-name> with <branch-name> branched from
|
||||||
|
[from-branch] (default: origin's default branch, or main).
|
||||||
|
|
||||||
|
The main repo checkout is not modified; from-branch is fetched but
|
||||||
|
not checked out.
|
||||||
|
EOF
|
||||||
}
|
}
|
||||||
|
|
||||||
# Copy .env files from main repo to worktree
|
# Ensure .worktrees is ignored in the main repo. Runs `git check-ignore` from
|
||||||
copy_env_files() {
|
# the main repo root so it sees the main repo's .gitignore (which is not
|
||||||
local worktree_path="$1"
|
# inherited by linked worktrees). Falls back to a grep guard to avoid
|
||||||
|
# duplicate entries when check-ignore misses an uncommitted gitignore rule.
|
||||||
echo -e "${BLUE}Copying environment files...${NC}"
|
ensure_gitignore() {
|
||||||
|
if (cd "$GIT_ROOT" && git check-ignore -q .worktrees) 2>/dev/null; then
|
||||||
# Find all .env* files in root (excluding .env.example which should be in git)
|
|
||||||
local env_files=()
|
|
||||||
for f in "$GIT_ROOT"/.env*; do
|
|
||||||
if [[ -f "$f" ]]; then
|
|
||||||
local basename=$(basename "$f")
|
|
||||||
# Skip .env.example (that's typically committed to git)
|
|
||||||
if [[ "$basename" != ".env.example" ]]; then
|
|
||||||
env_files+=("$basename")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ ${#env_files[@]} -eq 0 ]]; then
|
|
||||||
echo -e " ${YELLOW}ℹ️ No .env files found in main repository${NC}"
|
|
||||||
return
|
return
|
||||||
fi
|
fi
|
||||||
|
if grep -Fxq ".worktrees" "$GIT_ROOT/.gitignore" 2>/dev/null; then
|
||||||
local copied=0
|
return
|
||||||
for env_file in "${env_files[@]}"; do
|
fi
|
||||||
local source="$GIT_ROOT/$env_file"
|
echo ".worktrees" >> "$GIT_ROOT/.gitignore"
|
||||||
local dest="$worktree_path/$env_file"
|
echo "Added .worktrees to .gitignore"
|
||||||
|
}
|
||||||
if [[ -f "$dest" ]]; then
|
|
||||||
echo -e " ${YELLOW}⚠️ $env_file already exists, backing up to ${env_file}.backup${NC}"
|
# Copy .env* files (except .env.example) from main repo to worktree.
|
||||||
cp "$dest" "${dest}.backup"
|
# Backs up any pre-existing destination file.
|
||||||
fi
|
copy_env_files() {
|
||||||
|
local worktree_path="$1"
|
||||||
cp "$source" "$dest"
|
local copied=0
|
||||||
echo -e " ${GREEN}✓ Copied $env_file${NC}"
|
|
||||||
copied=$((copied + 1))
|
shopt -s nullglob
|
||||||
done
|
for source in "$GIT_ROOT"/.env*; do
|
||||||
|
[[ -f "$source" ]] || continue
|
||||||
echo -e " ${GREEN}✓ Copied $copied environment file(s)${NC}"
|
local name
|
||||||
|
name=$(basename "$source")
|
||||||
|
[[ "$name" == ".env.example" ]] && continue
|
||||||
|
|
||||||
|
local dest="$worktree_path/$name"
|
||||||
|
if [[ -f "$dest" ]]; then
|
||||||
|
cp "$dest" "${dest}.backup"
|
||||||
|
echo " Backed up existing $name to ${name}.backup"
|
||||||
|
fi
|
||||||
|
cp "$source" "$dest"
|
||||||
|
echo " Copied $name"
|
||||||
|
copied=$((copied + 1))
|
||||||
|
done
|
||||||
|
shopt -u nullglob
|
||||||
|
|
||||||
|
if [[ $copied -eq 0 ]]; then
|
||||||
|
echo " No .env files in main repo"
|
||||||
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Resolve the repository default branch, falling back to main when origin/HEAD
|
|
||||||
# is unavailable (for example in single-branch clones).
|
|
||||||
get_default_branch() {
|
get_default_branch() {
|
||||||
local head_ref
|
local head_ref
|
||||||
head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
|
head_ref=$(git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null || true)
|
||||||
|
|
||||||
if [[ -n "$head_ref" ]]; then
|
if [[ -n "$head_ref" ]]; then
|
||||||
echo "${head_ref#refs/remotes/origin/}"
|
echo "${head_ref#refs/remotes/origin/}"
|
||||||
else
|
else
|
||||||
@@ -78,423 +91,149 @@ get_default_branch() {
|
|||||||
fi
|
fi
|
||||||
}
|
}
|
||||||
|
|
||||||
# Auto-trust is only safe when the worktree is created from a long-lived branch
|
# Auto-trust is only safe when the worktree is based on a long-lived branch
|
||||||
# the developer already controls. Review/PR branches should fall back to the
|
# the developer already controls. Review/PR branches fall back to the default
|
||||||
# default branch baseline and require manual direnv approval.
|
# branch baseline and require manual direnv approval.
|
||||||
is_trusted_base_branch() {
|
is_trusted_base_branch() {
|
||||||
local branch="$1"
|
local branch="$1"
|
||||||
local default_branch="$2"
|
local default_branch="$2"
|
||||||
|
|
||||||
[[ "$branch" == "$default_branch" ]] && return 0
|
[[ "$branch" == "$default_branch" ]] && return 0
|
||||||
|
|
||||||
case "$branch" in
|
case "$branch" in
|
||||||
develop|dev|trunk|staging|release/*)
|
develop|dev|trunk|staging|release/*) return 0 ;;
|
||||||
return 0
|
*) return 1 ;;
|
||||||
;;
|
|
||||||
*)
|
|
||||||
return 1
|
|
||||||
;;
|
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
# Trust development tool configs in a new worktree.
|
# Return 0 if worktree's copy of $file has the same blob hash as $base_ref's.
|
||||||
# Worktrees get a new filesystem path that tools like mise and direnv
|
# Symlinks are rejected (can't verify content).
|
||||||
# have never seen. Without trusting, these tools block with interactive
|
config_unchanged() {
|
||||||
# prompts or refuse to load configs, which breaks hooks and scripts.
|
local file="$1" base_ref="$2" worktree_path="$3"
|
||||||
#
|
|
||||||
# Safety: auto-trusts only configs unchanged from a trusted baseline branch.
|
|
||||||
# Review/PR branches fall back to the default-branch baseline, and direnv
|
|
||||||
# auto-allow is limited to trusted base branches because .envrc can source
|
|
||||||
# additional files that direnv does not validate.
|
|
||||||
#
|
|
||||||
# TOCTOU between hash-check and trust is acceptable for local dev use.
|
|
||||||
trust_dev_tools() {
|
|
||||||
local worktree_path="$1"
|
|
||||||
local base_ref="$2"
|
|
||||||
local allow_direnv_auto="$3"
|
|
||||||
local trusted=0
|
|
||||||
local skipped_messages=()
|
|
||||||
local manual_commands=()
|
|
||||||
|
|
||||||
# mise: trust the specific config file if present and unchanged
|
|
||||||
if command -v mise &>/dev/null; then
|
|
||||||
for f in .mise.toml mise.toml .tool-versions; do
|
|
||||||
if [[ -f "$worktree_path/$f" ]]; then
|
|
||||||
if _config_unchanged "$f" "$base_ref" "$worktree_path"; then
|
|
||||||
if (cd "$worktree_path" && mise trust "$f" --quiet); then
|
|
||||||
trusted=$((trusted + 1))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}Warning: 'mise trust $f' failed -- run manually in $worktree_path${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
skipped_messages+=("mise trust $f (config differs from $base_ref)")
|
|
||||||
manual_commands+=("mise trust $f")
|
|
||||||
fi
|
|
||||||
break
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
fi
|
|
||||||
|
|
||||||
# direnv: allow .envrc
|
|
||||||
if command -v direnv &>/dev/null; then
|
|
||||||
if [[ -f "$worktree_path/.envrc" ]]; then
|
|
||||||
if [[ "$allow_direnv_auto" != "true" ]]; then
|
|
||||||
skipped_messages+=("direnv allow (.envrc auto-allow is disabled for non-trusted base branches)")
|
|
||||||
manual_commands+=("direnv allow")
|
|
||||||
elif _config_unchanged ".envrc" "$base_ref" "$worktree_path"; then
|
|
||||||
if (cd "$worktree_path" && direnv allow); then
|
|
||||||
trusted=$((trusted + 1))
|
|
||||||
else
|
|
||||||
echo -e " ${YELLOW}Warning: 'direnv allow' failed -- run manually in $worktree_path${NC}"
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
skipped_messages+=("direnv allow (.envrc differs from $base_ref)")
|
|
||||||
manual_commands+=("direnv allow")
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ $trusted -gt 0 ]]; then
|
|
||||||
echo -e " ${GREEN}✓ Trusted $trusted dev tool config(s)${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ${#skipped_messages[@]} -gt 0 ]]; then
|
|
||||||
echo -e " ${YELLOW}Skipped auto-trust for config(s) requiring manual review:${NC}"
|
|
||||||
for item in "${skipped_messages[@]}"; do
|
|
||||||
echo -e " - $item"
|
|
||||||
done
|
|
||||||
if [[ ${#manual_commands[@]} -gt 0 ]]; then
|
|
||||||
local joined
|
|
||||||
joined=$(printf ' && %s' "${manual_commands[@]}")
|
|
||||||
echo -e " ${BLUE}Review the diff, then run manually: cd $worktree_path${joined}${NC}"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# Check if a config file is unchanged from the base branch.
|
|
||||||
# Returns 0 (true) if the file is identical to the base branch version.
|
|
||||||
# Returns 1 (false) if the file was added or modified by this branch.
|
|
||||||
#
|
|
||||||
# Note: rev-parse returns the stored blob hash; hash-object on a path applies
|
|
||||||
# gitattributes filters. A mismatch causes a false negative (trust skipped),
|
|
||||||
# which is the safe direction.
|
|
||||||
_config_unchanged() {
|
|
||||||
local file="$1"
|
|
||||||
local base_ref="$2"
|
|
||||||
local worktree_path="$3"
|
|
||||||
|
|
||||||
# Reject symlinks -- trust only regular files with verifiable content
|
|
||||||
[[ -L "$worktree_path/$file" ]] && return 1
|
[[ -L "$worktree_path/$file" ]] && return 1
|
||||||
|
local base_hash worktree_hash
|
||||||
# Get the blob hash directly from git's object database via rev-parse
|
|
||||||
local base_hash
|
|
||||||
base_hash=$(git rev-parse "$base_ref:$file" 2>/dev/null) || return 1
|
base_hash=$(git rev-parse "$base_ref:$file" 2>/dev/null) || return 1
|
||||||
|
|
||||||
local worktree_hash
|
|
||||||
worktree_hash=$(git hash-object "$worktree_path/$file") || return 1
|
worktree_hash=$(git hash-object "$worktree_path/$file") || return 1
|
||||||
|
|
||||||
[[ "$base_hash" == "$worktree_hash" ]]
|
[[ "$base_hash" == "$worktree_hash" ]]
|
||||||
}
|
}
|
||||||
|
|
||||||
# Create a new worktree
|
# Trust dev tool configs (mise, direnv) so hooks/scripts don't block on
|
||||||
|
# interactive trust prompts. Auto-trusts only when the config matches the
|
||||||
|
# trusted baseline branch.
|
||||||
|
trust_dev_tools() {
|
||||||
|
local worktree_path="$1" base_ref="$2" allow_direnv_auto="$3"
|
||||||
|
local trusted=0
|
||||||
|
local manual=()
|
||||||
|
|
||||||
|
if command -v mise &>/dev/null; then
|
||||||
|
for f in .mise.toml mise.toml .tool-versions; do
|
||||||
|
[[ -f "$worktree_path/$f" ]] || continue
|
||||||
|
if config_unchanged "$f" "$base_ref" "$worktree_path" \
|
||||||
|
&& (cd "$worktree_path" && mise trust "$f" --quiet); then
|
||||||
|
trusted=$((trusted + 1))
|
||||||
|
else
|
||||||
|
manual+=("mise trust $f")
|
||||||
|
fi
|
||||||
|
break
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if command -v direnv &>/dev/null && [[ -f "$worktree_path/.envrc" ]]; then
|
||||||
|
if [[ "$allow_direnv_auto" == "true" ]] \
|
||||||
|
&& config_unchanged ".envrc" "$base_ref" "$worktree_path" \
|
||||||
|
&& (cd "$worktree_path" && direnv allow); then
|
||||||
|
trusted=$((trusted + 1))
|
||||||
|
else
|
||||||
|
manual+=("direnv allow")
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
[[ $trusted -gt 0 ]] && echo " Trusted $trusted dev tool config(s)"
|
||||||
|
if [[ ${#manual[@]} -gt 0 ]]; then
|
||||||
|
echo " Manual review required for: ${manual[*]}"
|
||||||
|
echo " Review the diff, then run from $worktree_path"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
create_worktree() {
|
create_worktree() {
|
||||||
local branch_name="$1"
|
local branch_name="${1:-}"
|
||||||
local from_branch="${2:-main}"
|
local from_branch="${2:-}"
|
||||||
|
|
||||||
if [[ -z "$branch_name" ]]; then
|
if [[ -z "$branch_name" ]]; then
|
||||||
echo -e "${RED}Error: Branch name required${NC}"
|
echo "Error: branch name required" >&2
|
||||||
|
usage >&2
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
local worktree_path="$WORKTREE_DIR/$branch_name"
|
local default_branch
|
||||||
|
default_branch=$(get_default_branch)
|
||||||
|
from_branch="${from_branch:-$default_branch}"
|
||||||
|
|
||||||
# Check if worktree already exists
|
local worktree_path="$WORKTREE_DIR/$branch_name"
|
||||||
if [[ -d "$worktree_path" ]]; then
|
if [[ -d "$worktree_path" ]]; then
|
||||||
echo -e "${YELLOW}Worktree already exists at: $worktree_path${NC}"
|
echo "Error: worktree already exists at $worktree_path" >&2
|
||||||
echo -e "Switch to it instead? (y/n)"
|
echo "Use 'cd $worktree_path' to switch, or 'git worktree remove' first." >&2
|
||||||
read -r response
|
exit 1
|
||||||
if [[ "$response" == "y" ]]; then
|
|
||||||
switch_worktree "$branch_name"
|
|
||||||
fi
|
|
||||||
return
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${BLUE}Creating worktree: $branch_name${NC}"
|
echo "Creating worktree $branch_name from $from_branch"
|
||||||
echo " From: $from_branch"
|
|
||||||
echo " Path: $worktree_path"
|
|
||||||
|
|
||||||
# Update main branch
|
|
||||||
echo -e "${BLUE}Updating $from_branch...${NC}"
|
|
||||||
git checkout "$from_branch"
|
|
||||||
git pull origin "$from_branch" || true
|
|
||||||
|
|
||||||
# Create worktree
|
|
||||||
mkdir -p "$WORKTREE_DIR"
|
mkdir -p "$WORKTREE_DIR"
|
||||||
ensure_gitignore
|
ensure_gitignore
|
||||||
|
|
||||||
echo -e "${BLUE}Creating worktree...${NC}"
|
# Fetch from-branch without touching the main checkout.
|
||||||
git worktree add -b "$branch_name" "$worktree_path" "$from_branch"
|
if ! git fetch origin "$from_branch" --quiet; then
|
||||||
|
echo "Warning: could not fetch origin/$from_branch; using local ref" >&2
|
||||||
|
fi
|
||||||
|
|
||||||
# Copy environment files
|
# Prefer origin/<from> if available, else fall back to local ref.
|
||||||
|
local base_ref="origin/$from_branch"
|
||||||
|
if ! git rev-parse --verify "$base_ref" &>/dev/null; then
|
||||||
|
base_ref="$from_branch"
|
||||||
|
fi
|
||||||
|
|
||||||
|
git worktree add -b "$branch_name" "$worktree_path" "$base_ref"
|
||||||
|
|
||||||
|
echo "Environment files:"
|
||||||
copy_env_files "$worktree_path"
|
copy_env_files "$worktree_path"
|
||||||
|
|
||||||
# Trust dev tool configs (mise, direnv) so hooks and scripts work immediately.
|
echo "Dev tool trust:"
|
||||||
# Long-lived integration branches can use themselves as the trust baseline,
|
|
||||||
# while review/PR branches fall back to the default branch and require manual
|
|
||||||
# direnv approval.
|
|
||||||
local default_branch
|
|
||||||
default_branch=$(get_default_branch)
|
|
||||||
local trust_branch="$default_branch"
|
local trust_branch="$default_branch"
|
||||||
local allow_direnv_auto="false"
|
local allow_direnv_auto="false"
|
||||||
if is_trusted_base_branch "$from_branch" "$default_branch"; then
|
if is_trusted_base_branch "$from_branch" "$default_branch"; then
|
||||||
trust_branch="$from_branch"
|
trust_branch="$from_branch"
|
||||||
allow_direnv_auto="true"
|
allow_direnv_auto="true"
|
||||||
fi
|
fi
|
||||||
|
# Refresh the trust baseline before the hash-baseline check. Without this,
|
||||||
if ! git fetch origin "$trust_branch" --quiet; then
|
# a stale origin/<default_branch> can cause auto-trust against an outdated
|
||||||
echo -e " ${YELLOW}Warning: could not fetch origin/$trust_branch -- trust check may use stale data${NC}"
|
# baseline when from_branch is untrusted (feature/review branches).
|
||||||
|
if [[ "$trust_branch" != "$from_branch" ]]; then
|
||||||
|
if ! git fetch origin "$trust_branch" --quiet; then
|
||||||
|
echo " Warning: could not fetch origin/$trust_branch; baseline may be stale" >&2
|
||||||
|
fi
|
||||||
fi
|
fi
|
||||||
# Skip trust entirely if the baseline ref doesn't exist locally.
|
local trust_ref="origin/$trust_branch"
|
||||||
if git rev-parse --verify "origin/$trust_branch" &>/dev/null; then
|
if git rev-parse --verify "$trust_ref" &>/dev/null; then
|
||||||
trust_dev_tools "$worktree_path" "origin/$trust_branch" "$allow_direnv_auto"
|
trust_dev_tools "$worktree_path" "$trust_ref" "$allow_direnv_auto"
|
||||||
else
|
else
|
||||||
echo -e " ${YELLOW}Skipping dev tool trust -- origin/$trust_branch not found locally${NC}"
|
echo " Skipped: $trust_ref not available locally"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e "${GREEN}✓ Worktree created successfully!${NC}"
|
|
||||||
echo ""
|
|
||||||
echo "To switch to this worktree:"
|
|
||||||
echo -e "${BLUE}cd $worktree_path${NC}"
|
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "Worktree ready: $worktree_path"
|
||||||
|
echo "Switch with: cd $worktree_path"
|
||||||
}
|
}
|
||||||
|
|
||||||
# List all worktrees
|
|
||||||
list_worktrees() {
|
|
||||||
echo -e "${BLUE}Available worktrees:${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
if [[ ! -d "$WORKTREE_DIR" ]]; then
|
|
||||||
echo -e "${YELLOW}No worktrees found${NC}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
local count=0
|
|
||||||
for worktree_path in "$WORKTREE_DIR"/*; do
|
|
||||||
if [[ -d "$worktree_path" && -e "$worktree_path/.git" ]]; then
|
|
||||||
count=$((count + 1))
|
|
||||||
local worktree_name=$(basename "$worktree_path")
|
|
||||||
local branch=$(git -C "$worktree_path" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
|
|
||||||
if [[ "$PWD" == "$worktree_path" ]]; then
|
|
||||||
echo -e "${GREEN}✓ $worktree_name${NC} (current) → branch: $branch"
|
|
||||||
else
|
|
||||||
echo -e " $worktree_name → branch: $branch"
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $count -eq 0 ]]; then
|
|
||||||
echo -e "${YELLOW}No worktrees found${NC}"
|
|
||||||
else
|
|
||||||
echo ""
|
|
||||||
echo -e "${BLUE}Total: $count worktree(s)${NC}"
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e "${BLUE}Main repository:${NC}"
|
|
||||||
local main_branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || echo "unknown")
|
|
||||||
echo " Branch: $main_branch"
|
|
||||||
echo " Path: $GIT_ROOT"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Switch to a worktree
|
|
||||||
switch_worktree() {
|
|
||||||
local worktree_name="$1"
|
|
||||||
|
|
||||||
if [[ -z "$worktree_name" ]]; then
|
|
||||||
list_worktrees
|
|
||||||
echo -e "${BLUE}Switch to which worktree? (enter name)${NC}"
|
|
||||||
read -r worktree_name
|
|
||||||
fi
|
|
||||||
|
|
||||||
local worktree_path="$WORKTREE_DIR/$worktree_name"
|
|
||||||
|
|
||||||
if [[ ! -d "$worktree_path" ]]; then
|
|
||||||
echo -e "${RED}Error: Worktree not found: $worktree_name${NC}"
|
|
||||||
echo ""
|
|
||||||
list_worktrees
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${GREEN}Switching to worktree: $worktree_name${NC}"
|
|
||||||
cd "$worktree_path"
|
|
||||||
echo -e "${BLUE}Now in: $(pwd)${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Copy env files to an existing worktree (or current directory if in a worktree)
|
|
||||||
copy_env_to_worktree() {
|
|
||||||
local worktree_name="$1"
|
|
||||||
local worktree_path
|
|
||||||
|
|
||||||
if [[ -z "$worktree_name" ]]; then
|
|
||||||
# Check if we're currently in a worktree
|
|
||||||
local current_dir=$(pwd)
|
|
||||||
if [[ "$current_dir" == "$WORKTREE_DIR"/* ]]; then
|
|
||||||
worktree_path="$current_dir"
|
|
||||||
worktree_name=$(basename "$worktree_path")
|
|
||||||
echo -e "${BLUE}Detected current worktree: $worktree_name${NC}"
|
|
||||||
else
|
|
||||||
echo -e "${YELLOW}Usage: worktree-manager.sh copy-env [worktree-name]${NC}"
|
|
||||||
echo "Or run from within a worktree to copy to current directory"
|
|
||||||
list_worktrees
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
else
|
|
||||||
worktree_path="$WORKTREE_DIR/$worktree_name"
|
|
||||||
|
|
||||||
if [[ ! -d "$worktree_path" ]]; then
|
|
||||||
echo -e "${RED}Error: Worktree not found: $worktree_name${NC}"
|
|
||||||
list_worktrees
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
fi
|
|
||||||
|
|
||||||
copy_env_files "$worktree_path"
|
|
||||||
echo ""
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clean up completed worktrees
|
|
||||||
cleanup_worktrees() {
|
|
||||||
if [[ ! -d "$WORKTREE_DIR" ]]; then
|
|
||||||
echo -e "${YELLOW}No worktrees to clean up${NC}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${BLUE}Checking for completed worktrees...${NC}"
|
|
||||||
echo ""
|
|
||||||
|
|
||||||
local found=0
|
|
||||||
local to_remove=()
|
|
||||||
|
|
||||||
for worktree_path in "$WORKTREE_DIR"/*; do
|
|
||||||
if [[ -d "$worktree_path" && -e "$worktree_path/.git" ]]; then
|
|
||||||
local worktree_name=$(basename "$worktree_path")
|
|
||||||
|
|
||||||
# Skip if current worktree
|
|
||||||
if [[ "$PWD" == "$worktree_path" ]]; then
|
|
||||||
echo -e "${YELLOW}(skip) $worktree_name - currently active${NC}"
|
|
||||||
continue
|
|
||||||
fi
|
|
||||||
|
|
||||||
found=$((found + 1))
|
|
||||||
to_remove+=("$worktree_path")
|
|
||||||
echo -e "${YELLOW}• $worktree_name${NC}"
|
|
||||||
fi
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ $found -eq 0 ]]; then
|
|
||||||
echo -e "${GREEN}No inactive worktrees to clean up${NC}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo ""
|
|
||||||
echo -e "Remove $found worktree(s)? (y/n)"
|
|
||||||
read -r response
|
|
||||||
|
|
||||||
if [[ "$response" != "y" ]]; then
|
|
||||||
echo -e "${YELLOW}Cleanup cancelled${NC}"
|
|
||||||
return
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${BLUE}Cleaning up worktrees...${NC}"
|
|
||||||
for worktree_path in "${to_remove[@]}"; do
|
|
||||||
local worktree_name=$(basename "$worktree_path")
|
|
||||||
git worktree remove "$worktree_path" --force 2>/dev/null || true
|
|
||||||
echo -e "${GREEN}✓ Removed: $worktree_name${NC}"
|
|
||||||
done
|
|
||||||
|
|
||||||
# Clean up empty directory if nothing left
|
|
||||||
if [[ -z "$(ls -A "$WORKTREE_DIR" 2>/dev/null)" ]]; then
|
|
||||||
rmdir "$WORKTREE_DIR" 2>/dev/null || true
|
|
||||||
fi
|
|
||||||
|
|
||||||
echo -e "${GREEN}Cleanup complete!${NC}"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Main command handler
|
|
||||||
main() {
|
main() {
|
||||||
local command="${1:-list}"
|
local command="${1:-}"
|
||||||
|
shift || true
|
||||||
case "$command" in
|
case "$command" in
|
||||||
create)
|
create) create_worktree "$@" ;;
|
||||||
create_worktree "$2" "$3"
|
""|help|-h|--help) usage ;;
|
||||||
;;
|
|
||||||
list|ls)
|
|
||||||
list_worktrees
|
|
||||||
;;
|
|
||||||
switch|go)
|
|
||||||
switch_worktree "$2"
|
|
||||||
;;
|
|
||||||
copy-env|env)
|
|
||||||
copy_env_to_worktree "$2"
|
|
||||||
;;
|
|
||||||
cleanup|clean)
|
|
||||||
cleanup_worktrees
|
|
||||||
;;
|
|
||||||
help)
|
|
||||||
show_help
|
|
||||||
;;
|
|
||||||
*)
|
*)
|
||||||
echo -e "${RED}Unknown command: $command${NC}"
|
echo "Error: unknown command '$command'" >&2
|
||||||
echo ""
|
usage >&2
|
||||||
show_help
|
|
||||||
exit 1
|
exit 1
|
||||||
;;
|
;;
|
||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
show_help() {
|
|
||||||
cat << EOF
|
|
||||||
Git Worktree Manager
|
|
||||||
|
|
||||||
Usage: worktree-manager.sh <command> [options]
|
|
||||||
|
|
||||||
Commands:
|
|
||||||
create <branch-name> [from-branch] Create new worktree (copies .env files automatically)
|
|
||||||
(from-branch defaults to main)
|
|
||||||
list | ls List all worktrees
|
|
||||||
switch | go [name] Switch to worktree
|
|
||||||
copy-env | env [name] Copy .env files from main repo to worktree
|
|
||||||
(if name omitted, uses current worktree)
|
|
||||||
cleanup | clean Clean up inactive worktrees
|
|
||||||
help Show this help message
|
|
||||||
|
|
||||||
Environment Files:
|
|
||||||
- Automatically copies .env, .env.local, .env.test, etc. on create
|
|
||||||
- Skips .env.example (should be in git)
|
|
||||||
- Creates .backup files if destination already exists
|
|
||||||
- Use 'copy-env' to refresh env files after main repo changes
|
|
||||||
|
|
||||||
Dev Tool Trust:
|
|
||||||
- Trusts mise config (.mise.toml, mise.toml, .tool-versions) and direnv (.envrc)
|
|
||||||
- Uses trusted base branches directly (main, develop, dev, trunk, staging, release/*)
|
|
||||||
- Other branches fall back to the default branch as the trust baseline
|
|
||||||
- direnv auto-allow is skipped on non-trusted base branches; review manually first
|
|
||||||
- Modified configs are flagged for manual review
|
|
||||||
- Only runs if the tool is installed and config exists
|
|
||||||
- Prevents hooks/scripts from hanging on interactive trust prompts
|
|
||||||
|
|
||||||
Examples:
|
|
||||||
worktree-manager.sh create feature-login
|
|
||||||
worktree-manager.sh create feature-auth develop
|
|
||||||
worktree-manager.sh switch feature-login
|
|
||||||
worktree-manager.sh copy-env feature-login
|
|
||||||
worktree-manager.sh copy-env # copies to current worktree
|
|
||||||
worktree-manager.sh cleanup
|
|
||||||
worktree-manager.sh list
|
|
||||||
|
|
||||||
EOF
|
|
||||||
}
|
|
||||||
|
|
||||||
# Run
|
|
||||||
main "$@"
|
main "$@"
|
||||||
|
|||||||
Reference in New Issue
Block a user