| """Subcommand to handle PR merge event by processing backports.""" | |
| import argparse | |
| import re | |
| from dev.release.gh import GitHub | |
| from dev.release.git import Git | |
| from dev.release.process_backports import ProcessBackports | |
| from dev.release.release_issue import parse_backports | |
| class OnPrMerged: | |
| """Class to handle PR merge event.""" | |
| def __init__(self, args, git: Git, gh: GitHub): | |
| self.args = args | |
| self.git = git | |
| self.gh = gh | |
| def run(self) -> int: | |
| """Executes the on-pr-merged subcommand.""" | |
| args = self.args | |
| pr_num = args.pr | |
| pr_ref = f"#{pr_num}" | |
| print(f"Verifying PR {pr_ref} has backport comment...") | |
| try: | |
| comments = self.gh.get_pr_comments(pr_num) | |
| has_comment = any( | |
| re.match( | |
| r"^\s*/backport(\s|$)", | |
| comment.get("body") or "", | |
| re.IGNORECASE, | |
| ) | |
| for comment in comments | |
| ) | |
| if not has_comment: | |
| print(f"PR {pr_ref} does not have a /backport comment. Skipping.") | |
| return 1 | |
| except Exception as e: | |
| print(f"Error checking PR comments: {e}") | |
| return 1 | |
| print(f"Searching for active release tracking issue containing PR {pr_ref}...") | |
| open_issues = self.gh.get_open_tracking_issues() | |
| if not open_issues: | |
| print("No open release tracking issues found.") | |
| return 1 | |
| found_issue = None | |
| for issue in open_issues: | |
| issue_num = issue["number"] | |
| body = self.gh.get_issue_body(issue_num) | |
| backports = parse_backports(body) | |
| if any(item.pr_ref == pr_ref for item in backports): | |
| if found_issue: | |
| print( | |
| f"Error: PR {pr_ref} found in multiple open release" | |
| f" tracking issues: #{found_issue} and #{issue_num}" | |
| ) | |
| return 1 | |
| found_issue = issue_num | |
| if not found_issue: | |
| print(f"PR {pr_ref} not found in any active release tracking issue.") | |
| return 1 | |
| print(f"Found PR {pr_ref} in tracking issue #{found_issue}") | |
| # Now run ProcessBackports for this issue | |
| process_args = argparse.Namespace( | |
| issue=found_issue, | |
| remote=args.remote, | |
| add=None, | |
| triggering_comment=None, | |
| dry_run=args.dry_run, | |
| ) | |
| print(f"Processing backports for issue #{found_issue}...") | |
| return ProcessBackports(process_args, self.git, self.gh).run() | |
| @classmethod | |
| def add_parser(cls, subparsers): | |
| """Adds parser for on-pr-merged subcommand.""" | |
| parser = subparsers.add_parser( | |
| "on-pr-merged", | |
| help="Handle PR merge event by processing backports.", | |
| ) | |
| parser.add_argument( | |
| "pr", | |
| type=int, | |
| help="PR number that was merged.", | |
| ) | |
| parser.add_argument( | |
| "--remote", | |
| type=str, | |
| required=True, | |
| help="The git remote to push changes to (required).", | |
| ) | |
| parser.add_argument( | |
| "--dry-run", | |
| action=argparse.BooleanOptionalAction, | |
| default=True, | |
| help="Perform a dry run (default: True). Use --no-dry-run to actually execute.", | |
| ) | |
| parser.set_defaults(command=cls.run_from_args) | |
| @classmethod | |
| def run_from_args(cls, args): | |
| """Instantiates and runs the command from parsed args.""" | |
| git = Git(".") | |
| gh = GitHub() | |
| return cls(args, git, gh).run() |