Files
sysadmin 46db3f73e8 feat: complete isolated-worktree helpers — worktree-review, worktree-clean, tests (#39)
Finishes the isolated-worktree standard begun in #38 (which merged the
branches/ gitignore, runbook, and scripts/worktree-start). Adds the two
remaining helpers and their tests.

- scripts/worktree-review: isolated DETACHED review worktree under
  branches/review-<branch> (fetch/prune first, refuse to overwrite, print path,
  --dry-run). Detached so a reviewer cannot accidentally commit and review work
  never blocks the author's implementation folder.
- scripts/worktree-clean: the only deleting helper — removes a branches/
  worktree after merge/close, refuses a dirty worktree (no --force), optionally
  safe-deletes a merged branch (git branch -d), fetch/prune first, --dry-run.
  Deletes nothing unless explicitly invoked.
- tests/test_worktrees.py: path generation + refuse-to-overwrite for all three
  helpers via --dry-run (no real worktrees/branches/network/deletions).
- runbook: reference worktree-review / worktree-clean and the --dry-run flag.

Checks: bash -n clean on all three scripts; git diff --check clean; full suite
286 passed, 0 failures.

Closes #39. Follow-up to #38.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-02 02:54:50 -04:00

75 lines
2.2 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
usage: scripts/worktree-clean [--dry-run] [--delete-branch] <branch-name|folder-name>
Remove a branch worktree under branches/ AFTER its PR is merged or closed. This
is the ONLY helper that deletes anything, and it deletes nothing unless you
invoke it explicitly. It refuses to remove a worktree with uncommitted changes
(no --force is offered). With --delete-branch it also deletes the local branch,
but only with a safe `git branch -d` (fails unless the branch is merged).
Pass the branch name (with slashes) so --delete-branch can resolve it; the
folder is branches/<branch-with-slashes-replaced-by-dashes>.
Examples:
scripts/worktree-clean --dry-run fix/issue-123-example
scripts/worktree-clean --delete-branch fix/issue-123-example
EOF
}
dry_run=0
del_branch=0
while [[ "${1:-}" == --* ]]; do
case "$1" in
--dry-run) dry_run=1 ;;
--delete-branch) del_branch=1 ;;
--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
shift
done
if [[ $# -ne 1 ]]; then
usage >&2
exit 2
fi
branch="$1"
worktree_name="${branch//\//-}"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
worktree_path="$repo_root/branches/$worktree_name"
if [[ ! -d "$worktree_path" ]]; then
printf 'No such worktree: %s\n' "$worktree_path" >&2
exit 1
fi
if [[ "$dry_run" -eq 1 ]]; then
printf 'repo: %s\n' "$repo_root"
printf 'worktree: %s\n' "$worktree_path"
printf 'delete-branch: %s\n' "$del_branch"
printf 'commands:\n'
printf ' git -C %q fetch prgs --prune\n' "$repo_root"
printf ' git -C %q worktree remove %q\n' "$repo_root" "$worktree_path"
if [[ "$del_branch" -eq 1 ]]; then
printf ' git -C %q branch -d %q\n' "$repo_root" "$branch"
fi
exit 0
fi
git -C "$repo_root" fetch prgs --prune
# No --force: `git worktree remove` fails on uncommitted changes, on purpose.
git -C "$repo_root" worktree remove "$worktree_path"
printf 'removed worktree: %s\n' "$worktree_path"
if [[ "$del_branch" -eq 1 ]]; then
# Safe delete only: refuses to drop an unmerged branch.
git -C "$repo_root" branch -d "$branch"
printf 'deleted branch: %s\n' "$branch"
fi