mirror of
https://github.com/block/buzz.git
synced 2026-08-18 06:50:31 +02:00
Merge main into push iOS blockers
Co-authored-by: Tom Brow <tomb@block.xyz> Signed-off-by: Tom Brow <tomb@block.xyz>
This commit is contained in:
co-authored by
Tom Brow
commit
db73dc44af
@@ -71,7 +71,7 @@ cat >"$body" <<EOF
|
||||
- **Previous desktop release:** \`$previous_tag\`
|
||||
- **Proposed immutable tag:** \`desktop-v$version\`
|
||||
|
||||
This PR must be merged with **Create a merge commit**. Squash/rebase, stale-head approval, incomplete notes, or a candidate mismatch produce no tag.
|
||||
This PR must be **squash merged** only after the Desktop Release Candidate check passes. The branch must remain based directly on current \`main\`; stale base, payload drift, incomplete notes, or an unauthorized merge produce no tag.
|
||||
|
||||
The checked-in changelog accounts for every non-merge commit in the release range. Publication remains bound to the immutable candidate tag.
|
||||
EOF
|
||||
|
||||
+69
@@ -0,0 +1,69 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
repo_root=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
|
||||
tmp=$(mktemp -d)
|
||||
trap 'rm -rf "$tmp"' EXIT
|
||||
mkdir -p "$tmp/bin"
|
||||
cat >"$tmp/bin/gh" <<'GH'
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
printf '%q ' "$@" >>"$GH_CALLS"
|
||||
printf '\n' >>"$GH_CALLS"
|
||||
|
||||
[[ "${1:-}" == api ]] || { echo "expected gh api" >&2; exit 91; }
|
||||
if [[ "${2:-}" == graphql ]]; then
|
||||
expected_query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}'
|
||||
[[ "$#" -eq 12 && "$3" == -f && "$4" == "query=$expected_query" &&
|
||||
"$5" == -F && "$6" == owner=block &&
|
||||
"$7" == -F && "$8" == repo=buzz &&
|
||||
"$9" == -F && "${10}" == number=123 &&
|
||||
"${11}" == --jq && "${12}" == '.data.repository.pullRequest' ]] || {
|
||||
echo "GraphQL call does not match the deployed query contract" >&2; exit 92;
|
||||
}
|
||||
if [[ -n "${REVIEW_DECISION:-}" ]]; then printf '%s\n' "$REVIEW_DECISION"; else printf '%s\n' '{"reviewDecision":"APPROVED"}'; fi
|
||||
elif [[ "$#" -eq 4 && "$2" == --paginate && "$3" == --slurp && "$4" == "repos/block/buzz/pulls/123/reviews?per_page=100&page=1" ]]; then
|
||||
[[ "${GH_FAIL_REVIEWS:-false}" != true ]] || { echo "simulated reviews API failure" >&2; exit 94; }
|
||||
if [[ -n "${REVIEWS:-}" ]]; then printf '%s\n' "$REVIEWS"; else printf '%s\n' '[[],[{"state":"APPROVED","commit_id":"head","author_association":"MEMBER"}]]'; fi
|
||||
else
|
||||
echo "unexpected or malformed gh call: $*" >&2
|
||||
exit 95
|
||||
fi
|
||||
GH
|
||||
chmod +x "$tmp/bin/gh"
|
||||
|
||||
run_authorization() {
|
||||
(cd "$repo_root" && PATH="$tmp/bin:$PATH" GH_CALLS="$tmp/calls" GH_TOKEN=test \
|
||||
GITHUB_REPOSITORY=block/buzz PR_NUMBER=123 PR_HEAD_SHA=head \
|
||||
REVIEW_DECISION="${REVIEW_DECISION-}" REVIEWS="${REVIEWS-}" GH_FAIL_REVIEWS="${GH_FAIL_REVIEWS-false}" \
|
||||
scripts/verify-desktop-release-authorization.sh)
|
||||
}
|
||||
|
||||
: >"$tmp/calls"
|
||||
run_authorization
|
||||
! grep -Fq 'rule-suites' "$tmp/calls"
|
||||
|
||||
for invalid in \
|
||||
'[[{"state":"APPROVED","commit_id":"stale","author_association":"MEMBER"}]]' \
|
||||
'[[{"state":"APPROVED","commit_id":"head","author_association":"NONE"}]]' \
|
||||
'[[{"state":"CHANGES_REQUESTED","commit_id":"head","author_association":"MEMBER"}]]'; do
|
||||
: >"$tmp/calls"
|
||||
if REVIEWS="$invalid" run_authorization >/dev/null 2>&1; then
|
||||
echo "invalid approval was accepted: $invalid" >&2
|
||||
exit 1
|
||||
fi
|
||||
done
|
||||
|
||||
: >"$tmp/calls"
|
||||
if REVIEW_DECISION='{"reviewDecision":"CHANGES_REQUESTED"}' run_authorization >/dev/null 2>&1; then
|
||||
echo "changes-requested review decision was accepted" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
: >"$tmp/calls"
|
||||
if GH_FAIL_REVIEWS=true run_authorization >/dev/null 2>&1; then
|
||||
echo "reviews API failure was ignored" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "desktop release authorization passed"
|
||||
@@ -62,8 +62,22 @@ grep -q 'permission-contents: write' "$auto_tag"
|
||||
grep -q 'GH_TOKEN:.*steps\.release-tagger\.outputs\.token' "$auto_tag"
|
||||
grep -Fq 'git/refs' "$auto_tag"
|
||||
grep -Fq 'TAG_PREFIX="desktop-v"' "$auto_tag"
|
||||
grep -Fq 'target_sha=${{ github.event.pull_request.head.sha }}' "$auto_tag"
|
||||
grep -Fq 'target_sha=${{ github.event.pull_request.merge_commit_sha }}' "$auto_tag"
|
||||
grep -Fq 'scripts/verify-desktop-release-merge.sh' "$auto_tag"
|
||||
grep -Fq 'current \`main\`' "$repo_root/scripts/prepare-desktop-release.sh"
|
||||
if grep -Fq 'current `main`' "$repo_root/scripts/prepare-desktop-release.sh"; then
|
||||
echo "desktop release PR body contains executable command substitution" >&2
|
||||
exit 1
|
||||
fi
|
||||
"$repo_root/scripts/test-desktop-release-authorization.sh"
|
||||
if rg -q 'rule-suites|desktop-release-bypass-authorized|MERGED_BY' \
|
||||
"$repo_root/scripts/verify-desktop-release-merge.sh" \
|
||||
"$repo_root/scripts/verify-desktop-release-authorization.sh" \
|
||||
"$auto_tag"; then
|
||||
echo "desktop auto-tag still depends on unavailable rule-suite authorization" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
review_filter="$repo_root/scripts/review-decision-approved.jq"
|
||||
for fixture in \
|
||||
'{"reviewDecision":"CHANGES_REQUESTED"}' \
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
|
||||
: "${PR_HEAD_SHA:?}"
|
||||
: "${PR_NUMBER:?}"
|
||||
: "${GITHUB_REPOSITORY:?}"
|
||||
: "${GH_TOKEN:?}"
|
||||
|
||||
review="$(gh api graphql -f query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}' -F owner="${GITHUB_REPOSITORY%/*}" -F repo="${GITHUB_REPOSITORY#*/}" -F number="$PR_NUMBER" --jq '.data.repository.pullRequest')"
|
||||
reviews="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews?per_page=100&page=1")"
|
||||
valid_approvals="$(jq --arg sha "$PR_HEAD_SHA" '[.[][] | select(.state == "APPROVED" and .commit_id == $sha and (.author_association == "MEMBER" or .author_association == "OWNER" or .author_association == "COLLABORATOR"))] | length' <<<"$reviews")"
|
||||
if ! jq -e -f scripts/review-decision-approved.jq <<<"$review" >/dev/null || [[ "$valid_approvals" -eq 0 ]]; then
|
||||
echo "release lacks an exact-head approval" >&2
|
||||
exit 1
|
||||
fi
|
||||
@@ -21,6 +21,7 @@ required_checks=(
|
||||
"Relay E2E"
|
||||
"Desktop Build (macOS)"
|
||||
"DCO Check"
|
||||
"Desktop Release Candidate"
|
||||
)
|
||||
|
||||
expected_branch="version-bump/$VERSION"
|
||||
@@ -30,21 +31,19 @@ expected_branch="version-bump/$VERSION"
|
||||
|
||||
git fetch origin "$MERGE_SHA" "$PR_HEAD_SHA" refs/heads/main:refs/remotes/origin/main --no-tags
|
||||
mapfile -t parents < <(git show -s --format='%P' "$MERGE_SHA" | tr ' ' '\n')
|
||||
[[ "${#parents[@]}" -eq 2 ]] || { echo "desktop release was not merged with a true merge commit" >&2; exit 1; }
|
||||
[[ "${parents[1]}" == "$PR_HEAD_SHA" ]] || { echo "merge parent 2 is not the reviewed candidate" >&2; exit 1; }
|
||||
git merge-base --is-ancestor "$PR_HEAD_SHA" origin/main || { echo "candidate is not reachable from current main" >&2; exit 1; }
|
||||
[[ "${#parents[@]}" -eq 1 ]] || { echo "desktop release was not squash merged" >&2; exit 1; }
|
||||
base_sha="$(git show "$PR_HEAD_SHA:.release/desktop-candidate.json" | jq -r .base_sha)"
|
||||
[[ "${parents[0]}" == "$base_sha" ]] || { echo "squash parent is not the frozen candidate base" >&2; exit 1; }
|
||||
[[ "$(git show -s --format=%T "$MERGE_SHA")" == "$(git show -s --format=%T "$PR_HEAD_SHA")" ]] || {
|
||||
echo "squash tree differs from the validated candidate" >&2
|
||||
exit 1
|
||||
}
|
||||
git merge-base --is-ancestor "$MERGE_SHA" origin/main || { echo "squash commit is not reachable from current main" >&2; exit 1; }
|
||||
|
||||
git checkout --detach "$PR_HEAD_SHA"
|
||||
scripts/desktop_release.py validate --candidate "$PR_HEAD_SHA" --version "$VERSION" --repo "$GITHUB_REPOSITORY"
|
||||
|
||||
review=$(gh api graphql -f query='query($owner:String!,$repo:String!,$number:Int!){repository(owner:$owner,name:$repo){pullRequest(number:$number){reviewDecision}}}' -F owner="${GITHUB_REPOSITORY%/*}" -F repo="${GITHUB_REPOSITORY#*/}" -F number="$PR_NUMBER" --jq '.data.repository.pullRequest')
|
||||
jq -e -f scripts/review-decision-approved.jq <<<"$review" >/dev/null || {
|
||||
echo "pull request effective review decision is not APPROVED" >&2
|
||||
exit 1
|
||||
}
|
||||
reviews="$(gh api --paginate "repos/$GITHUB_REPOSITORY/pulls/$PR_NUMBER/reviews?per_page=100")"
|
||||
valid_approvals="$(jq --arg sha "$PR_HEAD_SHA" '[.[] | select(.state == "APPROVED" and .commit_id == $sha and (.author_association == "MEMBER" or .author_association == "OWNER" or .author_association == "COLLABORATOR"))] | length' <<<"$reviews")"
|
||||
[[ "$valid_approvals" -gt 0 ]] || { echo "candidate lacks an exact-head approval from a repository member or collaborator" >&2; exit 1; }
|
||||
scripts/verify-desktop-release-authorization.sh
|
||||
|
||||
checks="$(gh api --paginate --slurp "repos/$GITHUB_REPOSITORY/commits/$PR_HEAD_SHA/check-runs?per_page=100")"
|
||||
for required in "${required_checks[@]}"; do
|
||||
@@ -59,4 +58,4 @@ jq -e '(.total_count == 0) or (.state == "success")' <<<"$status" >/dev/null ||
|
||||
exit 1
|
||||
}
|
||||
|
||||
echo "verified reviewed desktop candidate $PR_HEAD_SHA at merge $MERGE_SHA"
|
||||
echo "verified desktop candidate $PR_HEAD_SHA at squash $MERGE_SHA"
|
||||
|
||||
Reference in New Issue
Block a user