checkout_prep_branch() { local pr="$1" require_artifact .local/prep-context.env # shellcheck disable=SC1091 source .local/prep-context.env local prep_branch prep_branch=$(resolve_prep_branch_name "$pr") git checkout "$prep_branch" } resolve_prep_branch_name() { local pr="$1" require_artifact .local/prep-context.env # shellcheck disable=SC1091 source .local/prep-context.env local prep_branch="${PREP_BRANCH:-pr-$pr-prep}" if ! git show-ref --verify --quiet "refs/heads/$prep_branch"; then echo "Expected prep branch $prep_branch not found. Run prepare-init first." exit 1 fi printf '%s\n' "$prep_branch" } verify_prep_branch_matches_prepared_head() { local pr="$1" local prepared_head_sha="$2" local prep_branch prep_branch=$(resolve_prep_branch_name "$pr") local prep_branch_head_sha prep_branch_head_sha=$(git rev-parse "refs/heads/$prep_branch") if [ "$prep_branch_head_sha" = "$prepared_head_sha" ]; then return 0 fi echo "Local prep branch moved after prepare-push (branch=$prep_branch expected $prepared_head_sha, got $prep_branch_head_sha)." if git merge-base --is-ancestor "$prepared_head_sha" "$prep_branch_head_sha" 2>/dev/null; then echo "Unpushed local commits on prep branch:" git log --oneline "${prepared_head_sha}..${prep_branch_head_sha}" | sed 's/^/ /' || true echo "Run scripts/pr prepare-sync-head $pr to push them before merge." else echo "Prep branch no longer contains the prepared head. Re-run prepare-init." fi exit 1 } prepare_init() { local pr="$1" enter_worktree "$pr" true require_artifact .local/pr-meta.env require_artifact .local/review.md if [ ! -s .local/review.json ]; then echo "WARNING: .local/review.json is missing; structured findings are expected." fi # shellcheck disable=SC1091 source .local/pr-meta.env local json json=$(pr_meta_json "$pr") local head head=$(printf '%s\n' "$json" | jq -r .headRefName) local pr_head_sha_before pr_head_sha_before=$(printf '%s\n' "$json" | jq -r .headRefOid) if [ -n "${PR_HEAD:-}" ] && [ "$head" != "$PR_HEAD" ]; then echo "PR head branch changed from $PR_HEAD to $head. Re-run review-pr." exit 1 fi git fetch origin "pull/$pr/head:pr-$pr" --force git checkout -B "pr-$pr-prep" "pr-$pr" git fetch origin main # Security: shell-escape values to prevent command injection via malicious branch names. printf '%s=%q\n' \ PR_NUMBER "$pr" \ PR_HEAD "$head" \ PR_HEAD_SHA_BEFORE "$pr_head_sha_before" \ PREP_BRANCH "pr-$pr-prep" \ PREP_STARTED_AT "$(date -u +%Y-%m-%dT%H:%M:%SZ)" \ > .local/prep-context.env if [ ! -f .local/prep.md ]; then cat > .local/prep.md <> .local/prep.md < .local/prep.env ls -la .local/prep.md .local/prep.env >/dev/null echo "prepare-push complete" echo "prep_branch=$(git branch --show-current)" echo "prep_head_sha=$prep_head_sha" echo "pr_head_sha=$pr_head_sha_after" echo "artifacts=.local/prep.md .local/prep.env" } prepare_sync_head() { local pr="$1" enter_worktree "$pr" false require_artifact .local/pr-meta.env require_artifact .local/prep-context.env checkout_prep_branch "$pr" # shellcheck disable=SC1091 source .local/pr-meta.env # shellcheck disable=SC1091 source .local/prep-context.env local rebased=false git fetch origin main if ! git merge-base --is-ancestor origin/main HEAD; then git rebase origin/main rebased=true prepare_gates "$pr" checkout_prep_branch "$pr" fi local prep_head_sha prep_head_sha=$(git rev-parse HEAD) local lease_sha lease_sha=$(gh pr view "$pr" --json headRefOid --jq .headRefOid) local push_result_env=".local/prepare-sync-result.env" verify_pr_head_branch_matches_expected "$pr" "$PR_HEAD" push_prep_head_to_pr_branch "$pr" "$PR_HEAD" "$prep_head_sha" "$lease_sha" false false "$push_result_env" # shellcheck disable=SC1090 source "$push_result_env" prep_head_sha="$PUSH_PREP_HEAD_SHA" local pushed_from_sha="$PUSHED_FROM_SHA" local pr_head_sha_after="$PR_HEAD_SHA_AFTER_PUSH" local contrib="${PR_AUTHOR:-}" if [ -z "$contrib" ]; then contrib=$(gh pr view "$pr" --json author --jq .author.login) fi local contrib_id contrib_id=$(gh api "users/$contrib" --jq .id) local coauthor_email="${contrib_id}+${contrib}@users.noreply.github.com" cat >> .local/prep.md < .local/prep.env ls -la .local/prep.md .local/prep.env >/dev/null echo "prepare-sync-head complete" echo "prep_branch=$(git branch --show-current)" echo "prep_head_sha=$prep_head_sha" echo "pr_head_sha=$pr_head_sha_after" echo "artifacts=.local/prep.md .local/prep.env" } prepare_run() { local pr="$1" prepare_init "$pr" prepare_gates "$pr" prepare_push "$pr" echo "prepare-run complete for PR #$pr" }