78 lines
1.9 KiB
Bash
78 lines
1.9 KiB
Bash
|
|
#!/usr/bin/env bash
|
||
|
|
|
||
|
|
set -euo pipefail
|
||
|
|
|
||
|
|
usage() {
|
||
|
|
cat <<'USAGE'
|
||
|
|
usage: tool/worktrees.sh list | prune [--force]
|
||
|
|
|
||
|
|
list Show every worktree with its owner tool and whether it has uncommitted changes.
|
||
|
|
prune Remove worktrees that are clean, detached or locked-by-nobody, and not the main checkout.
|
||
|
|
Dirty worktrees are listed and kept; pass --force to remove them as well.
|
||
|
|
USAGE
|
||
|
|
}
|
||
|
|
|
||
|
|
owner_of() {
|
||
|
|
case "$1" in
|
||
|
|
*/.claude/worktrees/*) echo claude ;;
|
||
|
|
*/.codex/worktrees/*) echo codex ;;
|
||
|
|
*/.gemini/*) echo gemini ;;
|
||
|
|
*) echo other ;;
|
||
|
|
esac
|
||
|
|
}
|
||
|
|
|
||
|
|
root="$(git rev-parse --show-toplevel)"
|
||
|
|
|
||
|
|
each_worktree() {
|
||
|
|
git -C "$root" worktree list --porcelain | awk '/^worktree /{print substr($0, 10)}'
|
||
|
|
}
|
||
|
|
|
||
|
|
is_locked() {
|
||
|
|
git -C "$root" worktree list --porcelain | awk -v p="$1" '$0=="worktree " p{f=1; next} /^worktree /{f=0} f && /^locked/{found=1} END{exit !found}'
|
||
|
|
}
|
||
|
|
|
||
|
|
state_of() {
|
||
|
|
local path="$1"
|
||
|
|
if [ ! -d "$path" ]; then
|
||
|
|
echo missing
|
||
|
|
elif [ -n "$(git -C "$path" status --porcelain 2>/dev/null)" ]; then
|
||
|
|
echo dirty
|
||
|
|
else
|
||
|
|
echo clean
|
||
|
|
fi
|
||
|
|
}
|
||
|
|
|
||
|
|
cmd="${1:-list}"
|
||
|
|
force=0
|
||
|
|
[ "${2:-}" = "--force" ] && force=1
|
||
|
|
|
||
|
|
case "$cmd" in
|
||
|
|
list)
|
||
|
|
printf '%-8s %-8s %s\n' OWNER STATE PATH
|
||
|
|
while IFS= read -r path; do
|
||
|
|
printf '%-8s %-8s %s\n' "$(owner_of "$path")" "$(state_of "$path")" "$path"
|
||
|
|
done < <(each_worktree)
|
||
|
|
;;
|
||
|
|
prune)
|
||
|
|
git -C "$root" worktree prune
|
||
|
|
while IFS= read -r path; do
|
||
|
|
[ "$path" = "$root" ] && continue
|
||
|
|
if is_locked "$path"; then
|
||
|
|
echo "keep (locked): $path"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
state="$(state_of "$path")"
|
||
|
|
if [ "$state" = dirty ] && [ "$force" -eq 0 ]; then
|
||
|
|
echo "keep (dirty): $path"
|
||
|
|
continue
|
||
|
|
fi
|
||
|
|
git -C "$root" worktree remove --force "$path" && echo "removed: $path"
|
||
|
|
done < <(each_worktree)
|
||
|
|
git -C "$root" worktree prune
|
||
|
|
;;
|
||
|
|
*)
|
||
|
|
usage
|
||
|
|
exit 1
|
||
|
|
;;
|
||
|
|
esac
|