Files
wehub-resource-sync f3d80b4628
Auto Release / auto-release (push) Failing after 1s
CI / lint (push) Failing after 0s
CI / screenshot-quality (push) Failing after 3s
CI / pr-policy (push) Has been skipped
CI / test (3.11) (push) Failing after 0s
CI / test (3.12) (push) Failing after 0s
CI / test (3.13) (push) Failing after 3s
CI / coverage (push) Failing after 1s
Legibility / legibility (push) Failing after 3s
chore: import upstream snapshot with attribution
2026-07-13 12:31:48 +08:00

245 lines
5.5 KiB
Bash
Executable File

#!/bin/sh
set -eu
usage() {
cat <<'EOF'
Usage: scripts/check_pr.sh <pr_number> [--repo owner/repo] [--no-tests]
Options:
--repo OWNER/REPO Override repository (default: current gh repo)
--no-tests Skip local test gates
-h, --help Show this help
EOF
}
require_cmd() {
if ! command -v "$1" >/dev/null 2>&1; then
echo "error: required command not found: $1" >&2
exit 1
fi
}
pr_number=""
repo=""
run_tests=1
while [ "$#" -gt 0 ]; do
case "$1" in
--repo)
if [ "$#" -lt 2 ]; then
echo "error: --repo requires a value" >&2
usage
exit 1
fi
repo="$2"
shift 2
;;
--no-tests)
run_tests=0
shift
;;
-h|--help)
usage
exit 0
;;
-*)
echo "error: unknown option: $1" >&2
usage
exit 1
;;
*)
if [ -n "$pr_number" ]; then
echo "error: unexpected extra argument: $1" >&2
usage
exit 1
fi
pr_number="$1"
shift
;;
esac
done
if [ -z "$pr_number" ]; then
echo "error: missing <pr_number>" >&2
usage
exit 1
fi
require_cmd gh
require_cmd python3
if [ "$run_tests" -eq 1 ]; then
require_cmd uv
fi
if [ -z "$repo" ]; then
repo="$(gh repo view --json nameWithOwner --template '{{.nameWithOwner}}')"
if [ -z "$repo" ]; then
echo "error: failed to detect repository; use --repo owner/repo" >&2
exit 1
fi
fi
metadata="$(gh pr view "$pr_number" --repo "$repo" \
--json title,state,isDraft,mergeStateStatus,headRefName,baseRefName,url \
--template '{{.title}}{{"\n"}}{{.state}}{{"\n"}}{{.isDraft}}{{"\n"}}{{.mergeStateStatus}}{{"\n"}}{{.headRefName}}{{"\n"}}{{.baseRefName}}{{"\n"}}{{.url}}')"
pr_title=$(printf '%s\n' "$metadata" | sed -n '1p')
pr_state=$(printf '%s\n' "$metadata" | sed -n '2p')
pr_draft=$(printf '%s\n' "$metadata" | sed -n '3p')
pr_merge_status=$(printf '%s\n' "$metadata" | sed -n '4p')
pr_head=$(printf '%s\n' "$metadata" | sed -n '5p')
pr_base=$(printf '%s\n' "$metadata" | sed -n '6p')
pr_url=$(printf '%s\n' "$metadata" | sed -n '7p')
pr_body="$(gh pr view "$pr_number" --repo "$repo" --json body --template '{{.body}}')"
pr_files="$(gh pr view "$pr_number" --repo "$repo" --json files --jq '.files[].path')"
if [ -z "$pr_title" ] || [ -z "$pr_state" ] || [ -z "$pr_merge_status" ]; then
echo "error: failed to parse PR metadata from gh" >&2
exit 1
fi
check_lines=""
if check_lines="$(gh pr checks "$pr_number" --repo "$repo" --json bucket,name,state \
--template '{{range .}}{{.bucket}}{{"\t"}}{{.name}}{{"\t"}}{{.state}}{{"\n"}}{{end}}')"; then
:
else
checks_exit=$?
if [ "$checks_exit" -ne 8 ]; then
echo "error: failed to fetch PR checks (gh exit $checks_exit)" >&2
exit 1
fi
fi
pass_count=0
fail_count=0
pending_count=0
check_total=0
if [ -n "$check_lines" ]; then
tab_char=$(printf '\t')
while IFS=$tab_char read -r bucket check_name check_state; do
[ -n "$bucket" ] || continue
check_total=$((check_total + 1))
case "$bucket" in
pass|skipping)
pass_count=$((pass_count + 1))
;;
fail|cancel)
fail_count=$((fail_count + 1))
;;
pending)
pending_count=$((pending_count + 1))
;;
*)
pending_count=$((pending_count + 1))
;;
esac
done <<EOF
$check_lines
EOF
fi
printf 'PR #%s (%s)\n' "$pr_number" "$repo"
printf 'Title: %s\n' "$pr_title"
printf 'URL: %s\n' "$pr_url"
printf 'State: %s | Draft: %s | Merge State: %s\n' "$pr_state" "$pr_draft" "$pr_merge_status"
printf 'Branch: %s -> %s\n' "$pr_head" "$pr_base"
printf 'Checks: pass=%s fail=%s pending=%s total=%s\n' "$pass_count" "$fail_count" "$pending_count" "$check_total"
gate_failed=0
if [ "$run_tests" -eq 1 ]; then
echo 'Local gates:'
if uv run ruff check .; then
echo ' PASS uv run ruff check .'
else
gate_failed=1
echo ' FAIL uv run ruff check .'
fi
if uv run ruff format --check .; then
echo ' PASS uv run ruff format --check .'
else
gate_failed=1
echo ' FAIL uv run ruff format --check .'
fi
if uv run pytest tests/ -x --timeout=60; then
echo ' PASS uv run pytest tests/ -x --timeout=60'
else
gate_failed=1
echo ' FAIL uv run pytest tests/ -x --timeout=60'
fi
else
echo 'Local gates: skipped (--no-tests)'
fi
policy_failed=0
tmp_dir="$(mktemp -d)"
trap 'rm -rf "$tmp_dir"' EXIT HUP INT TERM
printf '%s' "$pr_body" >"$tmp_dir/body.md"
printf '%s\n' "$pr_files" >"$tmp_dir/files.txt"
if python3 scripts/check_pr_policy.py --body-file "$tmp_dir/body.md" --changed-files-file "$tmp_dir/files.txt"; then
:
else
policy_failed=1
fi
ready=1
reasons=""
append_reason() {
if [ -n "$reasons" ]; then
reasons="$reasons; $1"
else
reasons="$1"
fi
}
if [ "$pr_state" != "OPEN" ]; then
ready=0
append_reason "PR state is $pr_state"
fi
if [ "$pr_draft" = "true" ]; then
ready=0
append_reason 'PR is draft'
fi
case "$pr_merge_status" in
CLEAN|HAS_HOOKS)
;;
*)
ready=0
append_reason "mergeStateStatus is $pr_merge_status"
;;
esac
if [ "$fail_count" -gt 0 ]; then
ready=0
append_reason "$fail_count CI check(s) failing"
fi
if [ "$pending_count" -gt 0 ]; then
ready=0
append_reason "$pending_count CI check(s) pending"
fi
if [ "$gate_failed" -eq 1 ]; then
ready=0
append_reason 'local gates failed'
fi
if [ "$policy_failed" -eq 1 ]; then
ready=0
append_reason 'PR policy failed'
fi
if [ "$ready" -eq 1 ]; then
echo 'VERDICT: READY - all merge-readiness checks passed'
exit 0
fi
echo "VERDICT: NOT_READY - $reasons"
exit 2