#!/usr/bin/env bash # # wt-clean.sh — tidy up worktrees whose branch has merged. # # WHAT it does (safe by default): # 1. `git worktree prune` — drop admin entries for worktrees whose # directory is already gone. # 2. For every *other* worktree (never the primary checkout, never the one # you're standing in) whose branch is fully merged into origin/main: # remove the worktree and delete the local branch. # # SAFETY: it refuses to remove a worktree with uncommitted changes (no # --force) and only ever touches branches git confirms are merged into # origin/main, so unmerged work is never lost. Run it whenever; it's not # tied to any moment. # # USAGE # scripts/wt-clean.sh # do it # scripts/wt-clean.sh --dry-run # show what it would remove set -euo pipefail log() { printf ' wt-clean: %s\n' "$*" >&2; } dry=0 [ "${1:-}" = "--dry-run" ] && dry=1 git rev-parse --git-dir >/dev/null 2>&1 || { echo "wt-clean: not in a git repo" >&2; exit 1; } log "fetching origin/main" git fetch --quiet --prune origin main || true log "pruning stale worktree entries" if (( dry )); then git worktree prune --dry-run -v else git worktree prune -v fi # The primary checkout and the current worktree are off-limits. main_wt="$(git worktree list --porcelain | awk '/^worktree /{print $2; exit}')" here="$(git rev-parse --show-toplevel)" # Branches fully merged into origin/main (whitespace-trimmed, sans markers). merged="$(git branch --format='%(refname:short)' --merged origin/main)" is_merged() { grep -qxF "$1" <<<"$merged"; } removed=0 # Iterate worktrees: paired "worktree " / "branch refs/heads/". path=""; while read -r key val; do case "$key" in worktree) path="$val" ;; branch) br="${val#refs/heads/}" if [ "$path" != "$main_wt" ] && [ "$path" != "$here" ] && is_merged "$br"; then if (( dry )); then log "would remove worktree $path (branch $br, merged)" else if git worktree remove "$path" 2>/dev/null; then git branch -d "$br" >/dev/null 2>&1 || true log "removed worktree $path + branch $br" removed=$((removed+1)) else log "skipped $path — has uncommitted changes (remove it by hand)" fi fi fi ;; esac done < <(git worktree list --porcelain) (( dry )) || log "done — removed $removed merged worktree(s)"