Skip to content

Commit 06b7b03

Browse files
committed
Modify create-rc to process backports and support triggering comment
- Call ProcessBackports in CreateRc before creating RC. - Add --triggering-comment option to create-rc to react with thumbs-down on failure. - Pass triggering comment from GHA workflows. - Update tests and documentation.
1 parent 1d99f70 commit 06b7b03

5 files changed

Lines changed: 188 additions & 4 deletions

File tree

.github/workflows/on_issue_comment.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,7 @@ jobs:
7373
uses: ./.github/workflows/release_create_rc.yaml
7474
with:
7575
issue: ${{ needs.parse_comment.outputs.issue_number }}
76+
comment_id: "${{ github.event.comment.id }}"
7677
secrets: inherit
7778

7879
call_prepare:

.github/workflows/release_create_rc.yaml

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ on:
77
description: 'The Release Tracking Issue Number (e.g., 142)'
88
required: true
99
type: string
10+
comment_id:
11+
description: 'The ID of the comment that triggered this run (optional)'
12+
required: false
13+
type: string
1014
workflow_call:
1115
inputs:
1216
issue:
1317
description: 'The Release Tracking Issue Number (e.g., 142)'
1418
required: true
1519
type: string
20+
comment_id:
21+
description: 'The ID of the comment that triggered this run (optional)'
22+
required: false
23+
type: string
1624

1725
permissions:
1826
contents: write
@@ -44,9 +52,14 @@ jobs:
4452
env:
4553
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4654
ISSUE: ${{ inputs.issue }}
55+
COMMENT_ID: ${{ inputs.comment_id }}
4756
run: |
57+
ARGS=()
58+
if [ -n "$COMMENT_ID" ]; then
59+
ARGS+=("--triggering-comment=$COMMENT_ID")
60+
fi
4861
bazel run //tools/private/release -- \
49-
create-rc --issue "$ISSUE" --remote origin
62+
create-rc --issue "$ISSUE" --remote origin "${ARGS[@]}"
5063
5164
call_release:
5265
needs: tag_rc

RELEASING.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,8 +33,9 @@ Release Tracking Issue and automated workflows triggered by comments or issue ed
3333
3. **Add Backports (if needed)**: If there are backports, add them following
3434
the [How to add backports](#how-to-add-backports) steps.
3535

36-
4. **Create an RC**: Comment `/create-rc` on the tracking issue. All pending
37-
backports must be successfully processed before creating the RC.
36+
4. **Create an RC**: Comment `/create-rc` on the tracking issue. This will
37+
automatically process pending backports before creating the RC. If any
38+
backport fails, the RC creation will abort.
3839

3940
5. **Iterate**: Repeat steps 3 and 4 until backports and RCs are no longer
4041
needed.

tests/tools/private/release/create_rc_test.py

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,130 @@ def test_create_rc_auto_add_task(self):
253253
call2_args[1],
254254
)
255255

256+
@patch("tools.private.release.create_rc.ProcessBackports")
257+
def test_create_rc_calls_process_backports(self, mock_pb_class):
258+
# Arrange
259+
mock_pb = mock_pb_class.return_value
260+
mock_pb.run.return_value = 0
261+
262+
args = MagicMock(issue=123, remote="my-remote")
263+
self.mock_gh.get_issue_title.return_value = "Release 2.0.0"
264+
self.mock_gh.get_issue_body.return_value = """
265+
## Checklist
266+
- [x] Prepare Release | status=done pr=#122 commit=abcdef12
267+
- [x] Create Release branch | status=done branch=release/2.0 commit=abcdef12
268+
- [ ] Tag RC0 | status=pending
269+
"""
270+
self.mock_git.get_remote_tags.return_value = []
271+
self.mock_git.get_commit_sha.return_value = "1234567890"
272+
273+
# Act
274+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
275+
276+
# Assert
277+
self.assertEqual(result, 0)
278+
mock_pb_class.assert_called_once()
279+
called_args = mock_pb_class.call_args[0][0]
280+
self.assertEqual(called_args.issue, 123)
281+
self.assertEqual(called_args.remote, "my-remote")
282+
self.assertFalse(called_args.dry_run)
283+
self.assertIsNone(called_args.add)
284+
self.assertIsNone(called_args.triggering_comment)
285+
mock_pb.run.assert_called_once()
286+
287+
@patch("tools.private.release.create_rc.ProcessBackports")
288+
def test_create_rc_aborts_on_process_backports_failure(self, mock_pb_class):
289+
# Arrange
290+
mock_pb = mock_pb_class.return_value
291+
mock_pb.run.return_value = 1
292+
293+
args = MagicMock(issue=123, remote="my-remote")
294+
295+
# Act
296+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
297+
298+
# Assert
299+
self.assertEqual(result, 1)
300+
mock_pb_class.assert_called_once()
301+
mock_pb.run.assert_called_once()
302+
self.mock_gh.get_issue_body.assert_not_called()
303+
self.mock_git.tag.assert_not_called()
304+
305+
@patch("tools.private.release.create_rc.ProcessBackports")
306+
def test_create_rc_failure_reacts_to_comment(self, mock_pb_class):
307+
# Arrange
308+
mock_pb = mock_pb_class.return_value
309+
mock_pb.run.return_value = 1 # Simulate failure
310+
311+
args = MagicMock(issue=123, remote="my-remote", triggering_comment=456)
312+
313+
# Act
314+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
315+
316+
# Assert
317+
self.assertEqual(result, 1)
318+
self.mock_gh.add_comment_reaction.assert_called_once_with(456, "-1")
319+
320+
@patch("tools.private.release.create_rc.ProcessBackports")
321+
def test_create_rc_failure_no_comment_no_reaction(self, mock_pb_class):
322+
# Arrange
323+
mock_pb = mock_pb_class.return_value
324+
mock_pb.run.return_value = 1 # Simulate failure
325+
326+
args = MagicMock(issue=123, remote="my-remote", triggering_comment=None)
327+
328+
# Act
329+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
330+
331+
# Assert
332+
self.assertEqual(result, 1)
333+
self.mock_gh.add_comment_reaction.assert_not_called()
334+
335+
@patch("tools.private.release.create_rc.ProcessBackports")
336+
def test_create_rc_success_with_comment_no_reaction(self, mock_pb_class):
337+
# Arrange
338+
mock_pb = mock_pb_class.return_value
339+
mock_pb.run.return_value = 0
340+
341+
args = MagicMock(issue=123, remote="my-remote", triggering_comment=456)
342+
self.mock_gh.get_issue_title.return_value = "Release 2.0.0"
343+
self.mock_gh.get_issue_body.return_value = """
344+
## Checklist
345+
- [x] Prepare Release | status=done pr=#122 commit=abcdef12
346+
- [x] Create Release branch | status=done branch=release/2.0 commit=abcdef12
347+
- [ ] Tag RC0 | status=pending
348+
"""
349+
self.mock_git.get_remote_tags.return_value = []
350+
self.mock_git.get_commit_sha.return_value = "1234567890"
351+
352+
# Act
353+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
354+
355+
# Assert
356+
self.assertEqual(result, 0)
357+
self.mock_gh.add_comment_reaction.assert_not_called()
358+
359+
@patch("tools.private.release.create_rc.ProcessBackports")
360+
def test_create_rc_precondition_failure_reacts_to_comment(self, mock_pb_class):
361+
# Arrange
362+
mock_pb = mock_pb_class.return_value
363+
mock_pb.run.return_value = 0 # Backports succeed
364+
365+
args = MagicMock(issue=123, remote="my-remote", triggering_comment=456)
366+
self.mock_gh.get_issue_body.return_value = """
367+
## Checklist
368+
- [ ] Prepare Release | status=pending
369+
- [ ] Create Release branch | status=pending
370+
- [ ] Tag RC0 | status=pending
371+
"""
372+
373+
# Act
374+
result = CreateRc(args, self.mock_git, self.mock_gh).run()
375+
376+
# Assert
377+
self.assertEqual(result, 1)
378+
self.mock_gh.add_comment_reaction.assert_called_once_with(456, "-1")
379+
256380

257381
if __name__ == "__main__":
258382
unittest.main()

tools/private/release/create_rc.py

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
"""Subcommand to tag and push the next release candidate."""
22

3-
from tools.private.release.gh import GitHub
3+
from argparse import Namespace
4+
5+
from tools.private.release.gh import GH_REACTION_THUMBS_DOWN, GitHub
46
from tools.private.release.git import Git
7+
from tools.private.release.process_backports import ProcessBackports
58
from tools.private.release.release_issue import (
69
RELEASE_TITLE_RE,
710
add_rc_task_to_body,
@@ -26,6 +29,43 @@ def __init__(self, args, git: Git, gh: GitHub):
2629
def run(self) -> int:
2730
"""Executes the create-rc subcommand."""
2831
args = self.args
32+
exit_code = 0
33+
try:
34+
exit_code = self._run_internal()
35+
except Exception as e:
36+
print(f"Unexpected error: {e}")
37+
exit_code = 1
38+
39+
if exit_code != 0 and args.triggering_comment:
40+
print(f"Reacting with thumbs-down to comment {args.triggering_comment}...")
41+
try:
42+
self.gh.add_comment_reaction(
43+
args.triggering_comment, GH_REACTION_THUMBS_DOWN
44+
)
45+
except Exception as e:
46+
print(f"Failed to add reaction to comment: {e}")
47+
48+
return exit_code
49+
50+
def _run_internal(self) -> int:
51+
"""Internal implementation of create-rc."""
52+
args = self.args
53+
54+
# Try to process pending backports first
55+
print("Processing pending backports before creating RC...")
56+
backport_args = Namespace(
57+
issue=args.issue,
58+
remote=args.remote,
59+
add=None,
60+
triggering_comment=None,
61+
dry_run=False,
62+
)
63+
pb = ProcessBackports(backport_args, self.git, self.gh)
64+
backports_exit_code = pb.run()
65+
if backports_exit_code != 0:
66+
print("Error: Processing backports failed. Aborting RC creation.")
67+
return backports_exit_code
68+
2969
body = self.gh.get_issue_body(args.issue)
3070
state = parse_checklist_state(body)
3171

@@ -153,6 +193,11 @@ def add_parser(cls, subparsers):
153193
required=True,
154194
help="The git remote to push the RC tag to (required).",
155195
)
196+
parser.add_argument(
197+
"--triggering-comment",
198+
type=int,
199+
help="The ID of the comment that triggered this run (optional).",
200+
)
156201
parser.set_defaults(command=cls.run_from_args)
157202

158203
@classmethod

0 commit comments

Comments
 (0)