#!/usr/bin/env bash set -euo pipefail ######################################## # Utilities ######################################## fail() { echo "Error: $1" >&2 exit 1 } ensure_repo() { git rev-parse --is-inside-work-tree >/dev/null 2>&1 || fail "Not inside a git repository" } repo_root() { git rev-parse --show-toplevel } detect_base() { if git show-ref --verify --quiet refs/heads/main; then echo "main" elif git show-ref --verify --quiet refs/heads/master; then echo "master" else git branch --show-current fi } ensure_clean() { git diff --quiet || fail "Working tree is dirty" git diff --cached --quiet || fail "Index is dirty" } ######################################## # Core Paths ######################################## ROOT=$(repo_root) WT_DIR="$ROOT/.worktrees" ######################################## # Commands ######################################## cmd_new() { local name="$1" local base="${2:-$(detect_base)}" local path="$WT_DIR/$name" [ -d "$path" ] && fail "Worktree directory already exists" git show-ref --verify --quiet "refs/heads/$name" && fail "Branch '$name' already exists" mkdir -p "$WT_DIR" git worktree add -b "$name" "$path" "$base" echo "Created worktree:" echo " branch: $name" echo " path: $path" } cmd_list() { git worktree list } cmd_path() { echo "$WT_DIR/$1" } cmd_rm() { local name="$1" local path="$WT_DIR/$name" [ -d "$path" ] || fail "Worktree not found" git worktree remove "$path" git branch -D "$name" >/dev/null 2>&1 || true echo "Removed worktree '$name'" } cmd_finish() { local name="$1" shift local keep_worktree=0 local path="$WT_DIR/$name" local base while [[ $# -gt 0 ]]; do case "$1" in --keep-worktree) keep_worktree=1 shift ;; *) fail "Unknown option for finish: $1" ;; esac done [ -d "$path" ] || fail "Worktree not found" git show-ref --verify --quiet "refs/heads/$name" || fail "Branch '$name' not found" base=$(detect_base) echo "Base branch: $base" echo "Feature branch: $name" git checkout "$base" >/dev/null ensure_clean if ! git merge --squash "$name"; then echo echo "Resolve conflicts, then run:" echo " git commit" echo " wt rm $name" exit 1 fi git commit -m "$name" if [ -d "$path" ] && [ "$keep_worktree" -eq 0 ]; then if ! git worktree remove "$path"; then git worktree remove --force "$path" || fail "Failed to remove worktree '$name'" fi fi if [ "$keep_worktree" -eq 0 ]; then git branch -D "$name" fi if [ "$keep_worktree" -eq 1 ]; then echo "Merged (squash) into '$base' and kept worktree '$path'" else echo "Merged (squash) and removed '$name'" fi } cmd_prune() { git worktree prune echo "Pruned stale worktree metadata" } ######################################## # Entry ######################################## ensure_repo case "${1:-}" in new) [ $# -lt 2 ] && fail "Usage: wt new [base]" cmd_new "$2" "${3:-}" ;; list) cmd_list ;; path) [ $# -lt 2 ] && fail "Usage: wt path " cmd_path "$2" ;; finish) [ $# -lt 2 ] && fail "Usage: wt finish " cmd_finish "$2" "${@:3}" ;; rm) [ $# -lt 2 ] && fail "Usage: wt rm " cmd_rm "$2" ;; prune) cmd_prune ;; *) cat < [base] wt list wt path wt finish [--keep-worktree] wt rm wt prune EOF exit 1 ;; esac