workflows(release): support /backport comment on release tracking issue and pr (#4050)
Release tracking issues previously expected `/add-backports` while PRs
used `/backport`, leading to inconsistent command conventions across
release workflows and documentation.
Update the comment workflow handler to support `/backport` and
`/backports` consistently across both release tracking issues and
PRs. Documentation and issue templates are updated to reflect the
unified syntax, and missing arguments now emit GitHub Actions workflow
error annotations.
diff --git a/.github/ISSUE_TEMPLATE/release_tracking_template.md b/.github/ISSUE_TEMPLATE/release_tracking_template.md
index ab634e7..efa78a9 100644
--- a/.github/ISSUE_TEMPLATE/release_tracking_template.md
+++ b/.github/ISSUE_TEMPLATE/release_tracking_template.md
@@ -11,24 +11,29 @@
- [ ] Tag Final
## Backports
-
-To request a backport, add it to the checklist below and process it. See [RELEASING.md: How to add backports](https://github.com/bazel-contrib/rules_python/blob/main/RELEASING.md#how-to-add-backports) for details.
-
+
+To request a backport, comment `/backport` on the PR, comment `/backport <PR>`
+on this issue, or add it to the checklist below. See
+[RELEASING.md: How to add backports](https://github.com/bazel-contrib/rules_python/blob/main/RELEASING.md#how-to-add-backports)
+for details.
+
---
-
-To manually control the release flow, see the [RELEASING.md: Manual Editing](https://github.com/bazel-contrib/rules_python/blob/main/RELEASING.md#manual-editing-of-tracking-issue) section.
-
+
+To manually control the release flow, see the
+[RELEASING.md: Manual Editing](https://github.com/bazel-contrib/rules_python/blob/main/RELEASING.md#manual-editing-of-tracking-issue)
+section.
+
<details>
<summary><b>Available Commands</b></summary>
-
+
Comment commands:
- `/prepare`: Determines version, creates tracking issue and preparation PR.
- `/prepare-complete [PR]`: Marks preparation task as complete.
- `/create-release-branch`: Cuts and pushes the release branch.
- `/create-rc`: Tags and publishes a new release candidate (RC).
- `/process-backports`: Cherry-picks pending backports.
-- `/add-backports <PRs>`: Adds PRs to the backports and processes backports.
+- `/backport <PRs>`: Adds PRs to the backports and processes backports.
- `/promote`: Promotes the latest RC to final release.
-
+
See [RELEASING.md](https://github.com/bazel-contrib/rules_python/blob/main/RELEASING.md) for details on how to use them.
</details>
diff --git a/.github/workflows/on_comment.py b/.github/workflows/on_comment.py
index 4d4b4eb..1aa0f1b 100755
--- a/.github/workflows/on_comment.py
+++ b/.github/workflows/on_comment.py
@@ -15,11 +15,17 @@
return val.lower() == "true"
-def _match_command(command: str, comment_body: str) -> re.Match[str] | None:
+def _match_command(
+ command: str | tuple[str, ...], comment_body: str
+) -> re.Match[str] | None:
"""Matches a slash command at the start of any line, capturing optional trailing args."""
- cmd = command.lstrip("/")
+ if isinstance(command, str):
+ commands = (command,)
+ else:
+ commands = command
+ pattern = "|".join(re.escape(cmd.lstrip("/")) for cmd in commands)
return re.search(
- rf"^\s*/{re.escape(cmd)}(?:\s+(\S.*?))?\s*$",
+ rf"^\s*/(?:{pattern})(?:\s+(\S.*?))?\s*$",
comment_body,
re.MULTILINE,
)
@@ -61,7 +67,7 @@
def _react_negative(repo: str, comment_id: str) -> None:
"""Logs error and adds a negative reaction to the comment."""
- print("Error: No PRs specified for add-backports.", file=sys.stderr)
+ print("::error::No PRs specified for backport.")
if comment_id and repo:
_add_comment_reaction(repo=repo, comment_id=comment_id, content="-1")
@@ -95,7 +101,7 @@
_write_github_output("command", "process-backports")
return
- if m := _match_command("add-backports", comment_body):
+ if m := _match_command(("backport", "backports"), comment_body):
raw_args = m.group(1) if m.group(1) else ""
items = [item for item in re.split(r"[\s,]+", raw_args) if item]
if csv := ",".join(items):
@@ -128,7 +134,7 @@
def _process_pr_comment(comment_body: str, pr_number: str) -> None:
"""Processes comments on a pull request."""
- if _match_command("backport", comment_body):
+ if _match_command(("backport", "backports"), comment_body):
_write_github_output("command", "pr-backport")
_write_github_output("pr_number", pr_number)
return
diff --git a/RELEASING.md b/RELEASING.md
index 6749e2f..f740cb3 100644
--- a/RELEASING.md
+++ b/RELEASING.md
@@ -108,7 +108,7 @@
### Method B: Comment on the Tracking Issue
-Comment `/add-backports <PR_REF> [<PR_REF> ...]` (space or comma separated) on
+Comment `/backport <PR_REF> [<PR_REF> ...]` (space or comma separated) on
the tracking issue. The `<PR_REF>` can be a PR number (optionally prefixed with
`#`) or a PR URL (strictly for the configured repository). This will
automatically add the PRs to the checklist and trigger processing.
diff --git a/tests/workflows/on_comment_test.py b/tests/workflows/on_comment_test.py
index 9ec9413..d7df7da 100644
--- a/tests/workflows/on_comment_test.py
+++ b/tests/workflows/on_comment_test.py
@@ -152,10 +152,10 @@
assert gha_env.read_env() == {"issue_number": "100"}
-def test_release_issue_add_backports(monkeypatch, gha_env):
+def test_release_issue_backport(monkeypatch, gha_env):
_run_comment(
monkeypatch,
- "/add-backports 1, 2, 3",
+ "/backport 1, 2, 3",
has_release_label="true",
)
assert gha_env.read_outputs() == {
@@ -166,10 +166,10 @@
assert gha_env.read_env() == {"issue_number": "100"}
-def test_release_issue_add_backports_hashes(monkeypatch, gha_env):
+def test_release_issue_backport_hashes(monkeypatch, gha_env):
_run_comment(
monkeypatch,
- "/add-backports #123 #567",
+ "/backport #123 #567",
has_release_label="true",
)
assert gha_env.read_outputs() == {
@@ -180,12 +180,10 @@
assert gha_env.read_env() == {"issue_number": "100"}
-def test_release_issue_add_backports_empty(
- monkeypatch, gha_env, mock_add_reaction, capsys
-):
+def test_release_issue_backport_empty(monkeypatch, gha_env, mock_add_reaction, capsys):
_run_comment(
monkeypatch,
- "/add-backports",
+ "/backport",
has_release_label="true",
repo="bazel-contrib/rules_python",
comment_id="789",
@@ -196,7 +194,7 @@
}
assert gha_env.read_env() == {"issue_number": "100"}
captured = capsys.readouterr()
- assert "Error: No PRs specified for add-backports." in captured.err
+ assert "::error::No PRs specified for backport." in captured.out
mock_add_reaction.assert_called_once_with(
repo="bazel-contrib/rules_python",
comment_id="789",
@@ -311,6 +309,20 @@
assert gha_env.read_env() == {}
+def test_pr_backports_plural_alias(monkeypatch, gha_env):
+ _run_comment(
+ monkeypatch,
+ "/backports",
+ is_pr="true",
+ event_number="300",
+ )
+ assert gha_env.read_outputs() == {
+ "command": "pr-backport",
+ "pr_number": "300",
+ }
+ assert gha_env.read_env() == {}
+
+
def test_pr_prepare_complete(monkeypatch, gha_env):
_run_comment(
monkeypatch,