Files
Gitea-Tools/scripts/worktree-start
T
sysadminandClaude Opus 4.8 3ad7547a3f feat: harden issue lock store against concurrent session clobbering (Closes #438)
Replace read-modify-write on the global /tmp/gitea_issue_lock.json pointer with
atomic per-issue locks under GITEA_ISSUE_LOCK_DIR using flock serialization,
PID/session metadata, fail-closed stale recovery, and pre-mutation freshness
re-checks in gitea_create_pr. Queue inventory now surfaces live_issue_locks.

Co-Authored-By: Claude Opus 4.8 (1M context) <[email protected]>
2026-07-07 17:21:53 -04:00

113 lines
3.1 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
usage() {
cat <<'EOF'
usage: scripts/worktree-start [--dry-run] [--allow-unlinked] <branch-name> [start-ref]
Create an issue-linked git worktree under branches/<branch-name-with-slashes-replaced>.
Branch names must be traceable to an issue (or a PR, for review branches):
implementation: (fix|feat|docs|chore)/issue-<number>-<short-description>
review: review/pr-<number>-<short-description>
Use --allow-unlinked to bypass the check (discouraged).
Examples:
scripts/worktree-start fix/issue-123-example
scripts/worktree-start --dry-run review/pr-456-scope-check prgs/master
EOF
}
dry_run=0
allow_unlinked=0
while [[ "${1:-}" == --* ]]; do
case "$1" in
--dry-run) dry_run=1 ;;
--allow-unlinked) allow_unlinked=1 ;;
--help) usage; exit 0 ;;
*) usage >&2; exit 2 ;;
esac
shift
done
if [[ $# -lt 1 || $# -gt 2 ]]; then
usage >&2
exit 2
fi
branch="$1"
start_ref="${2:-prgs/master}"
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
repo_root="$(cd "$script_dir/.." && pwd)"
# Enforce issue-linked, traceable branch names (issue → branch → worktree → PR).
if [[ "$allow_unlinked" -eq 0 ]]; then
locked_branch=$(PYTHONPATH="$repo_root" python3 - <<'PY' "$branch")
import sys
import issue_lock_store
branch = sys.argv[1]
lock = issue_lock_store.resolve_lock_for_branch(branch)
if not lock:
print("", end="")
sys.exit(1)
print(lock.get("branch_name", ""), end="")
PY
)
if [[ -z "$locked_branch" ]]; then
echo "Error: No issue lock found for branch '$branch'. Lock the issue before branch creation (fail closed)." >&2
exit 2
fi
if [[ "$branch" != "$locked_branch" ]]; then
echo "Error: Requested branch '$branch' does not match locked branch '$locked_branch' (fail closed)." >&2
exit 2
fi
if [[ "$branch" =~ ^(fix|feat|docs|chore)/issue-[0-9]+-.+ ]] \
|| [[ "$branch" =~ ^review/pr-[0-9]+-.+ ]]; then
:
else
cat >&2 <<EOF
Untraceable branch name: $branch
Implementation branches must be issue-linked:
(fix|feat|docs|chore)/issue-<number>-<short-description>
Review branches:
review/pr-<number>-<short-description>
Fix the branch name, or pass --allow-unlinked to override (discouraged).
EOF
exit 2
fi
fi
worktree_name="${branch//\//-}"
worktree_path="$repo_root/branches/$worktree_name"
if [[ -e "$worktree_path" ]]; then
cat >&2 <<EOF
Refusing to reuse existing branch worktree:
$worktree_path
Inspect it manually. Do not overwrite or clean another issue's branch folder
unless that issue is explicitly assigned and cleanup is part of the task.
EOF
exit 1
fi
if [[ "$dry_run" -eq 1 ]]; then
printf 'repo: %s\n' "$repo_root"
printf 'branch: %s\n' "$branch"
printf 'start-ref: %s\n' "$start_ref"
printf 'worktree: %s\n' "$worktree_path"
printf 'commands:\n'
printf ' git -C %q fetch prgs --prune\n' "$repo_root"
printf ' git -C %q worktree add -b %q %q %q\n' \
"$repo_root" "$branch" "$worktree_path" "$start_ref"
exit 0
fi
git -C "$repo_root" fetch prgs --prune
git -C "$repo_root" worktree add -b "$branch" "$worktree_path" "$start_ref"
printf '%s\n' "$worktree_path"