From daa8631e5061fbdd6ebcb5c890a7df9baf7bf8bf Mon Sep 17 00:00:00 2001 From: Erica Pisani Date: Fri, 20 Feb 2026 15:08:28 -0500 Subject: [PATCH] feat(scripts): Add git worktree management tools Add helper scripts and make targets to simplify creating and managing git worktrees. The worktree-create script automates branch creation and virtual environment setup (preferring uv if available). The worktree-delete script removes worktrees and offers to clean up associated branches. Worktrees are particularly useful when working on multiple features independently of each other without needing to switch branches. This also enables parallelizing Claude Code sessions if desired, reducing friction when managing multiple development contexts. Co-Authored-By: Claude --- .gitignore | 2 ++ Makefile | 19 +++++++++++++++ scripts/worktree-create.sh | 47 ++++++++++++++++++++++++++++++++++++++ scripts/worktree-delete.sh | 41 +++++++++++++++++++++++++++++++++ 4 files changed, 109 insertions(+) create mode 100755 scripts/worktree-create.sh create mode 100755 scripts/worktree-delete.sh diff --git a/.gitignore b/.gitignore index 4e35d43fb5..e89206b677 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,5 @@ pip-wheel-metadata # for running AWS Lambda tests using AWS SAM sam.template.yaml + +.worktrees/ diff --git a/Makefile b/Makefile index fb5900e5ea..c8a8170947 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ help: @echo @echo "make apidocs: Build the API documentation" @echo "make aws-lambda-layer: Build AWS Lambda layer directory for serverless integration" + @echo "make worktree-create NAME=: Create a worktree with a new feature branch and virtual environment" + @echo "make worktree-delete NAME=: Remove a worktree (prompts to delete branch)" + @echo "make worktree-list: List all active worktrees" @echo @echo "Also make sure to read ./CONTRIBUTING.md" @echo @@ -33,3 +36,19 @@ aws-lambda-layer: dist $(VENV_PATH)/bin/pip install -r requirements-aws-lambda-layer.txt $(VENV_PATH)/bin/python -m scripts.build_aws_lambda_layer .PHONY: aws-lambda-layer + +worktree-create: + @test -n "$(NAME)" || (echo "Error: NAME is required. Usage: make worktree-create NAME=" && false) + @echo "$(NAME)" | grep -qE '^[a-zA-Z0-9_/-]+$$' || (echo "Error: NAME contains invalid characters" && false) + ./scripts/worktree-create.sh "$(NAME)" +.PHONY: worktree-create + +worktree-delete: + @test -n "$(NAME)" || (echo "Error: NAME is required. Usage: make worktree-delete NAME=" && false) + @echo "$(NAME)" | grep -qE '^[a-zA-Z0-9_/-]+$$' || (echo "Error: NAME contains invalid characters" && false) + ./scripts/worktree-delete.sh "$(NAME)" +.PHONY: worktree-delete + +worktree-list: + @git worktree list +.PHONY: worktree-list diff --git a/scripts/worktree-create.sh b/scripts/worktree-create.sh new file mode 100755 index 0000000000..8ce9c5bee0 --- /dev/null +++ b/scripts/worktree-create.sh @@ -0,0 +1,47 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +POSITIONAL_ARGS=("$@") + +# For simplicity, we use the same value for both the worktree name and branch name. +WORKTREE_NAME="${POSITIONAL_ARGS[0]}" +BRANCH_NAME="${POSITIONAL_ARGS[0]}" + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" +WORKTREE_DIR="$REPO_ROOT/.worktrees/$WORKTREE_NAME" + +if [[ -d "$WORKTREE_DIR" ]]; then + echo "Error: worktree directory already exists: $WORKTREE_DIR" >&2 + exit 1 +fi + +if git -C "$REPO_ROOT" branch --list "$BRANCH_NAME" | grep -q .; then + echo "Error: branch '$BRANCH_NAME' already exists. Delete it first or choose a different name." >&2 + exit 1 +fi + +echo "Creating worktree '$WORKTREE_NAME' on branch '$BRANCH_NAME'..." +git -C "$REPO_ROOT" worktree add "$WORKTREE_DIR" -b "$BRANCH_NAME" + +if command -v uv &>/dev/null; then + echo "Setting up virtual environment with uv..." + uv venv "$WORKTREE_DIR/.venv" +else + echo "uv not found — falling back to python -m venv..." + python -m venv "$WORKTREE_DIR/.venv" +fi + +echo "" +echo "Worktree ready!" +echo " Path: $WORKTREE_DIR" +echo " Branch: $BRANCH_NAME" +echo "" +echo "To start working:" +echo " cd $WORKTREE_DIR" +echo " source .venv/bin/activate" diff --git a/scripts/worktree-delete.sh b/scripts/worktree-delete.sh new file mode 100755 index 0000000000..7c86724698 --- /dev/null +++ b/scripts/worktree-delete.sh @@ -0,0 +1,41 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $# -lt 1 ]]; then + echo "Usage: $0 " >&2 + exit 1 +fi + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +for WORKTREE_NAME in "$@"; do + WORKTREE_DIR="$REPO_ROOT/.worktrees/$WORKTREE_NAME" + + if [[ ! -d "$WORKTREE_DIR" ]]; then + echo "Warning: worktree directory not found: $WORKTREE_DIR — skipping" >&2 + continue + fi + + # Capture branch name before removal + BRANCH_NAME="$(git -C "$WORKTREE_DIR" branch --show-current 2>/dev/null || true)" + + echo "Removing worktree '$WORKTREE_NAME'..." + git -C "$REPO_ROOT" worktree remove "$WORKTREE_DIR" + echo " Removed: $WORKTREE_DIR" + + if [[ -n "$BRANCH_NAME" ]]; then + if [[ -t 0 ]]; then + read -r -p " Delete branch '$BRANCH_NAME'? [y/N] " REPLY + echo + else + REPLY="n" + fi + if [[ "$REPLY" == "y" || "$REPLY" == "Y" ]]; then + git -C "$REPO_ROOT" branch -d "$BRANCH_NAME" + echo " Deleted branch: $BRANCH_NAME" + fi + fi + + echo " Done." +done