191 lines
3.4 KiB
Bash
Executable File
191 lines
3.4 KiB
Bash
Executable File
#!/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 <name> [base]"
|
|
cmd_new "$2" "${3:-}"
|
|
;;
|
|
list)
|
|
cmd_list
|
|
;;
|
|
path)
|
|
[ $# -lt 2 ] && fail "Usage: wt path <name>"
|
|
cmd_path "$2"
|
|
;;
|
|
finish)
|
|
[ $# -lt 2 ] && fail "Usage: wt finish <name>"
|
|
cmd_finish "$2" "${@:3}"
|
|
;;
|
|
rm)
|
|
[ $# -lt 2 ] && fail "Usage: wt rm <name>"
|
|
cmd_rm "$2"
|
|
;;
|
|
prune)
|
|
cmd_prune
|
|
;;
|
|
*)
|
|
cat <<EOF
|
|
Usage:
|
|
wt new <name> [base]
|
|
wt list
|
|
wt path <name>
|
|
wt finish <name> [--keep-worktree]
|
|
wt rm <name>
|
|
wt prune
|
|
EOF
|
|
exit 1
|
|
;;
|
|
esac
|