The first wt-clean.sh removed every worktree whose branch was merged into origin/main — but long-lived branches (website, the ci line) are merged too, so it would have deleted their worktrees. Caught on review before any real use. Replace the auto-sweep with an explicit `wt-rm <branch>` that removes only the named worktree; safety comes from git's own refusals — it won't remove a dirty worktree (no --force) and deletes the local branch only when it's merged into origin/main, otherwise keeps it (and its unmerged commits). Update test-wt.sh to 15 checks (incl. "unnamed worktree untouched", primary refused, dirty refused, unmerged branch preserved) and the wt-clean references in ADR-0059 / CLAUDE.md / the README index.
69 lines
2.7 KiB
Bash
Executable File
69 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# wt-rm.sh — remove the worktree for a named branch.
|
|
#
|
|
# EXPLICIT BY DESIGN: it removes ONLY the branch you name. There is no
|
|
# scanning and no "is it merged into main?" heuristic to decide *what* to
|
|
# remove — long-lived branches (e.g. `website`, the `ci` line) are merged
|
|
# into main too, so auto-detection would happily delete them. Naming the
|
|
# target is the safety.
|
|
#
|
|
# Safety still comes from git's own refusals, not from cleverness here:
|
|
# - the worktree won't be removed if it has uncommitted/untracked changes
|
|
# (no `--force`);
|
|
# - the local branch is deleted ONLY if it is fully merged into
|
|
# `origin/main` — otherwise the worktree goes but the branch (and its
|
|
# unmerged commits) is kept.
|
|
#
|
|
# USAGE
|
|
# scripts/wt-rm.sh <branch>
|
|
# scripts/wt-rm.sh feat/clause-concept-hints
|
|
#
|
|
# For a worktree whose directory you already deleted by hand, use the
|
|
# built-in `git worktree prune` (it only clears bookkeeping — never a
|
|
# directory or a branch).
|
|
|
|
set -euo pipefail
|
|
|
|
log() { printf ' wt-rm: %s\n' "$*" >&2; }
|
|
die() { printf 'wt-rm: ERROR: %s\n' "$*" >&2; exit 1; }
|
|
|
|
[ $# -eq 1 ] || die "usage: wt-rm.sh <branch>"
|
|
branch="$1"
|
|
|
|
git rev-parse --git-dir >/dev/null 2>&1 || die "not inside a git repo"
|
|
|
|
# Resolve the named branch to its worktree path.
|
|
path=""; cur=""
|
|
while read -r key val; do
|
|
case "$key" in
|
|
worktree) cur="$val" ;;
|
|
branch) [ "${val#refs/heads/}" = "$branch" ] && path="$cur" ;;
|
|
esac
|
|
done < <(git worktree list --porcelain)
|
|
|
|
[ -n "$path" ] || die "no worktree is checked out on branch '$branch' (see: git worktree list)"
|
|
|
|
main_wt="$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}')"
|
|
here="$(git rev-parse --show-toplevel)"
|
|
[ "$path" != "$main_wt" ] || die "refusing to remove the primary checkout ($path)"
|
|
[ "$path" != "$here" ] || die "refusing to remove the worktree you're standing in — run this from elsewhere"
|
|
|
|
log "removing worktree $path (branch $branch)"
|
|
git worktree remove "$path" \
|
|
|| die "could not remove $path — it likely has uncommitted or untracked changes; clean it (or remove by hand) first"
|
|
|
|
# Delete the local branch only if it is safely merged into origin/main;
|
|
# otherwise keep it so unmerged commits are never lost. (No auto-fetch — it
|
|
# judges against your current origin/main; run `git fetch` first if you want
|
|
# a freshly-merged branch to be eligible.)
|
|
if git rev-parse --verify --quiet origin/main >/dev/null \
|
|
&& git merge-base --is-ancestor "$branch" origin/main; then
|
|
git branch -D "$branch" >/dev/null
|
|
log "deleted local branch $branch (merged into origin/main)"
|
|
else
|
|
log "kept local branch $branch — not merged into origin/main; delete with 'git branch -D $branch' if you're sure"
|
|
fi
|
|
|
|
log "done"
|