blob: 78bcf7879b1a0b601b62f4c25691ad2c6acc78a6 [file]
import os
from dev.release.gh import GitHub
from dev.release.git import Git
from dev.release.release_issue import (
RELEASE_TITLE_RE,
add_backports_to_body,
add_rc_task_to_body,
add_sync_changelog_task_to_body,
load_release_tracking_template,
parse_checklist_state,
)
from dev.release.utils import determine_next_version
class AddBackports:
"""Class to add PRs to the release tracking issue."""
def __init__(self, args, gh: GitHub, git: Git | None = None):
self.args = args
self.gh = gh
self.git = git or Git(os.getcwd())
def run(self) -> int:
"""Executes the add-backports subcommand."""
args = self.args
issue_num = args.issue
if not issue_num:
print(
"No issue specified. Trying to auto-discover active release"
" tracking issue..."
)
try:
open_issues = self.gh.get_open_tracking_issues()
if len(open_issues) > 1:
print(
"::error::Multiple open release tracking issues found."
" Cannot determine active one:"
)
for issue in open_issues:
print(f"- #{issue['number']}: {issue['title']}")
return 1
elif len(open_issues) == 1:
issue_num = open_issues[0]["number"]
print(
f"Auto-discovered active release tracking issue: #{issue_num}"
)
else:
print(
"No open release tracking issue found. Creating a new"
" patch release tracking issue..."
)
patch_version = determine_next_version(git=self.git, is_patch=True)
template_content = load_release_tracking_template(
version=patch_version
)
issue_num = self.gh.create_release_tracking_issue(
patch_version, template_content
)
print(
f"::notice::Created patch release tracking issue #{issue_num} for"
f" v{patch_version}"
)
except Exception as e:
print(
f"::error::Error auto-discovering or creating tracking issue: {e}"
)
return 1
resolved_prs = []
for pr_ref in args.prs:
try:
pr_num = self.gh.resolve_pr_number(pr_ref)
resolved_prs.append(pr_num)
except Exception as e:
print(f"::error::Error resolving PR ref '{pr_ref}': {e}")
return 1
print(
f"Adding backports {resolved_prs} (resolved from {args.prs}) to tracking issue #{issue_num}..."
)
try:
body = self.gh.get_issue_body(issue_num)
items_to_add = [{"ref": f"#{pr}"} for pr in resolved_prs]
body = add_backports_to_body(body, items_to_add)
for pr in resolved_prs:
body = add_sync_changelog_task_to_body(body, pr)
state = parse_checklist_state(body)
rc_tags = state.get("rc_tags", {})
has_pending_rc = any(
not task.checked and task.status != "done" for task in rc_tags.values()
)
next_rc_num = max(rc_tags.keys()) + 1 if rc_tags else 0
issue_title = self.gh.get_issue_title(issue_num)
version_match = RELEASE_TITLE_RE.search(issue_title)
is_patch = False
if version_match:
version = version_match.group(1)
is_patch = not version.endswith(".0")
if not has_pending_rc and (rc_tags or not is_patch):
print(
f"No pending RC task found. Adding 'Tag"
f" RC{next_rc_num}' to checklist..."
)
body = add_rc_task_to_body(body, next_rc_num)
except ValueError as e:
print(f"::error::{e}")
return 1
except Exception as e:
print(f"::error::Failed to update tracking issue: {e}")
return 1
try:
self.gh.update_issue_body(issue_num, body)
print(
f"::notice::Successfully updated tracking issue #{issue_num} checklist."
)
except Exception as e:
print(f"::error::Failed to update tracking issue body: {e}")
return 1
return 0
@classmethod
def add_parser(cls, subparsers):
"""Adds parser for add-backports subcommand."""
parser = subparsers.add_parser(
"add-backports",
help="Add PRs to the release tracking issue backports checklist.",
)
parser.add_argument(
"prs",
type=str,
nargs="+",
help="PR references (numbers, #numbers, or URLs) to add (positional, space-separated).",
)
parser.add_argument(
"--issue",
type=int,
help="The tracking issue number. If omitted, will try to auto-discover the active release tracking issue.",
)
parser.set_defaults(command=cls.run_from_args)
@classmethod
def run_from_args(cls, args):
"""Instantiates and runs the command from parsed args."""
gh = GitHub()
git = Git(os.getcwd())
return cls(args, gh, git).run()