Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion .github/PULL_REQUEST_TEMPLATE.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
## Description
<!-- Please include a summary of the change, including which issue it fixes or what feature it adds. Include relevant motivation, context and documentation as appropriate. List dependencies that are required for this change, if any. -->
<!--
Squash-merge commit body = this section only (until the next ## heading).
Keep it short: what changed and why. Reviewer-only notes go in the sections below.
When merging via UI, paste this section into the squash commit message (or use
`scripts/pr-squash-merge.sh <pr>`).
-->

Issue Number: closes #xxx

Expand Down
66 changes: 66 additions & 0 deletions scripts/pr-description.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#!/usr/bin/env bash
# Extract the ## Description section from a GitHub PR body.
#
# Usage:
# scripts/pr-description.sh <pr-number-or-url> [--repo OWNER/REPO]
#
# Prints the Description section (until the next ## heading), with HTML
# comments stripped. Intended as the squash-merge commit message body.
set -euo pipefail

usage() {
echo "Usage: $0 <pr-number-or-url> [--repo OWNER/REPO]" >&2
exit 2
}

if [[ $# -lt 1 ]]; then
usage
fi

pr="$1"
shift
repo_args=()
while [[ $# -gt 0 ]]; do
case "$1" in
--repo)
[[ $# -ge 2 ]] || usage
repo_args=(--repo "$2")
shift 2
;;
*)
usage
;;
esac
done

# Accept bare numbers or full PR URLs.
if [[ "$pr" =~ ^https?://github.com/([^/]+/[^/]+)/pull/([0-9]+) ]]; then
repo_args=(--repo "${BASH_REMATCH[1]}")
pr="${BASH_REMATCH[2]}"
fi

body="$(gh pr view "$pr" "${repo_args[@]}" --json body -q .body)"

python3 - "$body" <<'PY'
import re, sys

body = sys.argv[1]
# Strip HTML comments.
body = re.sub(r"<!--.*?-->", "", body, flags=re.S)

# Find ## Description (allow optional trailing whitespace / BOM noise).
m = re.search(r"(?im)^##\s+Description\s*\n", body)
if not m:
sys.stderr.write("error: no '## Description' section found in PR body\n")
sys.exit(1)

rest = body[m.end():]
# Next markdown H2 ends the section.
nxt = re.search(r"(?m)^##\s+\S", rest)
section = rest[: nxt.start()] if nxt else rest
Comment on lines +59 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Ignore fenced-code contents when locating the next heading

For a Description containing a fenced Markdown example with a line such as ## Example, this regex treats that line as the next section heading even though Markdown renders it as code. The extractor consequently truncates the commit body—potentially in the middle of an open fence—and drops all subsequent Description text. A minimal body of ## Description, before, an opening fence, ## Example, a closing fence, after, and then the real next H2 reproduces output containing only before and the opening fence; heading detection needs to account for fenced blocks.

AGENTS.md reference: AGENTS.md:L6-L12

Useful? React with 👍 / 👎.

section = section.strip() + "\n"
if not section.strip():
sys.stderr.write("error: '## Description' section is empty\n")
sys.exit(1)
sys.stdout.write(section)
PY
59 changes: 59 additions & 0 deletions scripts/pr-squash-merge.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env bash
# Squash-merge a PR using PR title as subject and ## Description as body.
#
# Usage:
# scripts/pr-squash-merge.sh <pr-number-or-url> [--repo OWNER/REPO] [extra gh pr merge args...]
#
# Examples:
# scripts/pr-squash-merge.sh 824
# scripts/pr-squash-merge.sh 824 --repo Galxe/gravity-sdk --admin
set -euo pipefail

usage() {
echo "Usage: $0 <pr-number-or-url> [--repo OWNER/REPO] [extra gh pr merge args...]" >&2
exit 2
}

if [[ $# -lt 1 ]]; then
usage
fi

pr="$1"
shift

repo_args=()
extra=()
while [[ $# -gt 0 ]]; do
case "$1" in
--repo)
[[ $# -ge 2 ]] || usage
repo_args=(--repo "$2")
shift 2
;;
-h|--help)
usage
;;
*)
extra+=("$1")
shift
;;
esac
done

if [[ "$pr" =~ ^https?://github.com/([^/]+/[^/]+)/pull/([0-9]+) ]]; then
repo_args=(--repo "${BASH_REMATCH[1]}")
pr="${BASH_REMATCH[2]}"
fi

root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
desc="$("$root/scripts/pr-description.sh" "$pr" "${repo_args[@]}")"
title="$(gh pr view "$pr" "${repo_args[@]}" --json title -q .title)"

echo "Squash-merging #$pr"
echo "---- subject ----"
echo "$title"
echo "---- body (## Description) ----"
printf '%s' "$desc"
echo "-----------------"

gh pr merge "$pr" "${repo_args[@]}" --squash --subject "$title" --body "$desc" "${extra[@]}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Pin the PR head before invoking the merge

When this helper is run with the advertised --admin option—or with rules that do not invalidate approval after a push—a contributor can update the PR after the body/title reads on lines 49–50 but before this command executes. gh pr merge then merges the PR's new head while the operator sees metadata fetched from the earlier state, potentially admitting unreviewed code. gh pr merge --help documents --match-head-commit SHA as requiring the PR head to match that SHA; fetch headRefOid with the metadata and pass that option. This can be validated by pausing a mocked second gh pr view, changing the head, and observing that the current invocation contains no expected SHA.

AGENTS.md reference: AGENTS.md:L5-L12

Useful? React with 👍 / 👎.

Loading