Files
computer-playbook/scripts/github/update/open_pr.sh
T
kevinveenbirkenbachandClaude Opus 4.7 da77dd063b feat(update/open-pr): dedupe across open PRs and supersede stale class siblings
Two new steps wrapped around the existing push/create flow:

1. Dedupe before push: hash the local diff vs origin/<base-branch> and
   compare against gh pr diff of every open PR (any author, any branch)
   targeting the same base. On a match, exit 0 without pushing and
   without creating a PR. This is what stops the same image-version
   bump from being opened repeatedly.

2. Supersede after create: close every other open PR whose branch
   starts with UPDATE_BRANCH_PREFIX with a "Superseded by #N." comment.
   The prefix filter keeps the bot from ever closing repository-refs,
   skills, or human-authored PRs, even though dedupe itself sees them.

CRLF normalization on both sides of the hash compare so a runner that
checks out the patch with native-CRLF translation still produces a
stable hash.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-20 00:50:26 +02:00

130 lines
3.9 KiB
Bash
Executable File

#!/usr/bin/env bash
set -euo pipefail
: "${GH_TOKEN:?Missing GH_TOKEN}"
: "${UPDATE_BRANCH_PREFIX:?Missing UPDATE_BRANCH_PREFIX}"
: "${UPDATE_COMMIT_MESSAGE:?Missing UPDATE_COMMIT_MESSAGE}"
: "${UPDATE_PR_TITLE:?Missing UPDATE_PR_TITLE}"
: "${UPDATE_PR_BODY:?Missing UPDATE_PR_BODY}"
UPDATE_BASE_BRANCH="${UPDATE_BASE_BRANCH:-master}"
UPDATE_BRANCH_SUFFIX="${UPDATE_BRANCH_SUFFIX:-$(date +%Y%m%d)}"
BRANCH="${UPDATE_BRANCH_PREFIX}-${UPDATE_BRANCH_SUFFIX}"
if ! command -v gh >/dev/null 2>&1; then
echo "ERROR: gh CLI not found." >&2
exit 1
fi
REPO="$(git remote get-url origin | sed 's|.*github\.com[:/]||' | sed 's|\.git$||')"
OWNER="${REPO%/*}"
if [[ -n "${APP_SLUG:-}" ]]; then
BOT_LOGIN="${APP_SLUG}[bot]"
BOT_USER_ID="$(gh api "/users/${BOT_LOGIN}" --jq .id)"
git config user.name "${BOT_LOGIN}"
git config user.email "${BOT_USER_ID}+${BOT_LOGIN}@users.noreply.github.com"
else
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
fi
git checkout -B "${BRANCH}"
if (($# > 0)); then
git add -- "$@"
else
git add -A
fi
if git diff --cached --quiet; then
echo "No staged update changes found."
exit 0
fi
git commit -m "${UPDATE_COMMIT_MESSAGE}"
# Normalize CRLF endings so a git-produced patch hashes identically to a
# gh-produced one regardless of which side the runner picked up.
normalize_diff() { sed -e 's/\r$//'; }
CURRENT_HASH="$(git diff "origin/${UPDATE_BASE_BRANCH}..HEAD" | normalize_diff | sha256sum | awk '{print $1}')"
echo "Local diff hash: ${CURRENT_HASH}"
# Dedupe across *every* open PR against this base branch (the bot must
# skip not just sibling bot-PRs but also any human-authored PR that
# happens to carry the same change). The current branch is excluded so
# the check never compares us to ourselves.
mapfile -t OPEN_PRS < <(
gh pr list \
--repo "${REPO}" \
--state open \
--base "${UPDATE_BASE_BRANCH}" \
--limit 100 \
--json number,headRefName \
--jq ".[] | select(.headRefName != \"${BRANCH}\") | \"\(.number)\t\(.headRefName)\""
)
DUPLICATE_PR=""
for entry in "${OPEN_PRS[@]}"; do
[[ -z "${entry}" ]] && continue
pr_num="${entry%%$'\t'*}"
pr_branch="${entry##*$'\t'}"
pr_hash="$(gh pr diff "${pr_num}" --repo "${REPO}" | normalize_diff | sha256sum | awk '{print $1}')"
echo " open PR #${pr_num} (${pr_branch}): ${pr_hash}"
if [[ "${pr_hash}" == "${CURRENT_HASH}" ]]; then
DUPLICATE_PR="${pr_num}"
break
fi
done
if [[ -n "${DUPLICATE_PR}" ]]; then
echo "Open PR #${DUPLICATE_PR} already carries this exact diff. Skipping push and PR creation."
exit 0
fi
git push --force origin "${BRANCH}"
PR_NUMBER="$(
gh pr list \
--repo "${REPO}" \
--head "${BRANCH}" \
--base "${UPDATE_BASE_BRANCH}" \
--json number \
--jq '.[0].number // empty'
)"
if [[ -n "${PR_NUMBER}" ]]; then
echo "Updating existing PR #${PR_NUMBER} for ${BRANCH}"
gh pr edit "${PR_NUMBER}" \
--repo "${REPO}" \
--title "${UPDATE_PR_TITLE}" \
--body "${UPDATE_PR_BODY}"
else
echo "Creating PR for ${BRANCH}"
PR_URL="$(
gh pr create \
--repo "${REPO}" \
--title "${UPDATE_PR_TITLE}" \
--body "${UPDATE_PR_BODY}" \
--base "${UPDATE_BASE_BRANCH}" \
--head "${OWNER}:${BRANCH}"
)"
PR_NUMBER="${PR_URL##*/}"
echo "Created PR #${PR_NUMBER}: ${PR_URL}"
fi
# Close every other open PR in the same update class as superseded. A
# PR is in the same class when its branch starts with the configured
# UPDATE_BRANCH_PREFIX, so e.g. an image-versions run only retires
# stale image-versions branches and never touches repository-refs,
# skills, or human-authored PRs.
for entry in "${OPEN_PRS[@]}"; do
[[ -z "${entry}" ]] && continue
pr_branch="${entry##*$'\t'}"
[[ "${pr_branch}" == "${UPDATE_BRANCH_PREFIX}-"* ]] || continue
pr_num="${entry%%$'\t'*}"
echo "Closing superseded PR #${pr_num} (${pr_branch})"
gh pr close "${pr_num}" \
--repo "${REPO}" \
--comment "Superseded by #${PR_NUMBER}."
done