xref: /llvm-project/llvm/utils/git/github-automation.py (revision 5adbce072c1d9036eb1b31daafc085e318126f3d)
1a2adebf4STom Stellard#!/usr/bin/env python3
2a2adebf4STom Stellard#
3a2adebf4STom Stellard# ======- github-automation - LLVM GitHub Automation Routines--*- python -*--==#
4a2adebf4STom Stellard#
5a2adebf4STom Stellard# Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
6a2adebf4STom Stellard# See https://llvm.org/LICENSE.txt for license information.
7a2adebf4STom Stellard# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
8a2adebf4STom Stellard#
9a2adebf4STom Stellard# ==-------------------------------------------------------------------------==#
10a2adebf4STom Stellard
11a2adebf4STom Stellardimport argparse
12daf82a51STom Stellardfrom git import Repo  # type: ignore
1364751ea2STom Stellardimport html
142879a036STom Stellardimport json
15a2adebf4STom Stellardimport github
16a2adebf4STom Stellardimport os
17daf82a51STom Stellardimport re
18f673dcc6STom Stellardimport requests
19daf82a51STom Stellardimport sys
2017d4796cSTom Stellardimport time
21c116bd9fSKeith Smileyfrom typing import List, Optional
22a2adebf4STom Stellard
23b71edfaaSTobias Hietabeginner_comment = """
2409effa70STimm BäderHi!
2509effa70STimm Bäder
2609effa70STimm BäderThis issue may be a good introductory issue for people new to working on LLVM. If you would like to work on this issue, your first steps are:
2709effa70STimm Bäder
28cd8286a6SDanny Mösch1. Check that no other contributor has already been assigned to this issue. If you believe that no one is actually working on it despite an assignment, ping the person. After one week without a response, the assignee may be changed.
29cd8286a6SDanny Mösch1. In the comments of this issue, request for it to be assigned to you, or just create a [pull request](https://github.com/llvm/llvm-project/pulls) after following the steps below. [Mention](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this issue in the description of the pull request.
30cd8286a6SDanny Mösch1. Fix the issue locally.
31cd8286a6SDanny Mösch1. [Run the test suite](https://llvm.org/docs/TestingGuide.html#unit-and-regression-tests) locally. Remember that the subdirectories under `test/` create fine-grained testing targets, so you can e.g. use `make check-clang-ast` to only run Clang's AST tests.
32cd8286a6SDanny Mösch1. Create a Git commit.
33cd8286a6SDanny Mösch1. Run [`git clang-format HEAD~1`](https://clang.llvm.org/docs/ClangFormat.html#git-integration) to format your changes.
34cd8286a6SDanny Mösch1. Open a [pull request](https://github.com/llvm/llvm-project/pulls) to the [upstream repository](https://github.com/llvm/llvm-project) on GitHub. Detailed instructions can be found [in GitHub's documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request). [Mention](https://docs.github.com/en/issues/tracking-your-work-with-issues/linking-a-pull-request-to-an-issue) this issue in the description of the pull request.
3509effa70STimm Bäder
366a4489a7SDanny MöschIf you have any further questions about this issue, don't hesitate to ask via a comment in the thread below.
3709effa70STimm Bäder"""
3809effa70STimm Bäder
39a2adebf4STom Stellard
408a7f021fSJ. Ryan Stinnettdef _get_current_team(team_name, teams) -> Optional[github.Team.Team]:
4138e90068SMehdi Amini    for team in teams:
4238e90068SMehdi Amini        if team_name == team.name.lower():
4338e90068SMehdi Amini            return team
4438e90068SMehdi Amini    return None
4538e90068SMehdi Amini
4638e90068SMehdi Amini
471b18e986Scor3ntindef escape_description(str):
485d39f3f6SAiden Grossman    # If the description of an issue/pull request is empty, the Github API
495d39f3f6SAiden Grossman    # library returns None instead of an empty string. Handle this here to
505d39f3f6SAiden Grossman    # avoid failures from trying to manipulate None.
515d39f3f6SAiden Grossman    if str is None:
525d39f3f6SAiden Grossman        return ""
531b18e986Scor3ntin    # https://github.com/github/markup/issues/1168#issuecomment-494946168
541b18e986Scor3ntin    str = html.escape(str, False)
551b18e986Scor3ntin    # '@' followed by alphanum is a user name
563058d290SCorentin Jabot    str = re.sub("@(?=\w)", "@<!-- -->", str)
571b18e986Scor3ntin    # '#' followed by digits is considered an issue number
583058d290SCorentin Jabot    str = re.sub("#(?=\d)", "#<!-- -->", str)
591b18e986Scor3ntin    return str
601b18e986Scor3ntin
611b18e986Scor3ntin
62b71edfaaSTobias Hietaclass IssueSubscriber:
63a2adebf4STom Stellard    @property
64a2adebf4STom Stellard    def team_name(self) -> str:
65a2adebf4STom Stellard        return self._team_name
66a2adebf4STom Stellard
67a2adebf4STom Stellard    def __init__(self, token: str, repo: str, issue_number: int, label_name: str):
68a2adebf4STom Stellard        self.repo = github.Github(token).get_repo(repo)
69a2adebf4STom Stellard        self.org = github.Github(token).get_organization(self.repo.organization.login)
70a2adebf4STom Stellard        self.issue = self.repo.get_issue(issue_number)
71b71edfaaSTobias Hieta        self._team_name = "issue-subscribers-{}".format(label_name).lower()
72a2adebf4STom Stellard
73a2adebf4STom Stellard    def run(self) -> bool:
748a7f021fSJ. Ryan Stinnett        team = _get_current_team(self.team_name, self.org.get_teams())
7538e90068SMehdi Amini        if not team:
7638e90068SMehdi Amini            print(f"couldn't find team named {self.team_name}")
7738e90068SMehdi Amini            return False
7881761bd0STimm Baeder
79b71edfaaSTobias Hieta        comment = ""
80b71edfaaSTobias Hieta        if team.slug == "issue-subscribers-good-first-issue":
81b71edfaaSTobias Hieta            comment = "{}\n".format(beginner_comment)
8281761bd0STimm Baeder            self.issue.create_comment(comment)
8309effa70STimm Bäder
841b18e986Scor3ntin        body = escape_description(self.issue.body)
851b18e986Scor3ntin        comment = f"""
861b18e986Scor3ntin@llvm/{team.slug}
871b18e986Scor3ntin
88f8148e48SAiden GrossmanAuthor: {self.issue.user.name} ({self.issue.user.login})
89f8148e48SAiden Grossman
901b18e986Scor3ntin<details>
911b18e986Scor3ntin{body}
921b18e986Scor3ntin</details>
931b18e986Scor3ntin"""
9438e90068SMehdi Amini
95a2adebf4STom Stellard        self.issue.create_comment(comment)
96a2adebf4STom Stellard        return True
97a2adebf4STom Stellard
98b71edfaaSTobias Hieta
994085cb38SMehdi Aminidef human_readable_size(size, decimal_places=2):
1004085cb38SMehdi Amini    for unit in ["B", "KiB", "MiB", "GiB", "TiB", "PiB"]:
1014085cb38SMehdi Amini        if size < 1024.0 or unit == "PiB":
1024085cb38SMehdi Amini            break
1034085cb38SMehdi Amini        size /= 1024.0
1044085cb38SMehdi Amini    return f"{size:.{decimal_places}f} {unit}"
1054085cb38SMehdi Amini
1064085cb38SMehdi Amini
1075f16a3a4STom Stellardclass PRSubscriber:
1085f16a3a4STom Stellard    @property
1095f16a3a4STom Stellard    def team_name(self) -> str:
1105f16a3a4STom Stellard        return self._team_name
1115f16a3a4STom Stellard
1125f16a3a4STom Stellard    def __init__(self, token: str, repo: str, pr_number: int, label_name: str):
1135f16a3a4STom Stellard        self.repo = github.Github(token).get_repo(repo)
1145f16a3a4STom Stellard        self.org = github.Github(token).get_organization(self.repo.organization.login)
1155f16a3a4STom Stellard        self.pr = self.repo.get_issue(pr_number).as_pull_request()
116cff72d70STom Stellard        self._team_name = "pr-subscribers-{}".format(
117cff72d70STom Stellard            label_name.replace("+", "x")
118cff72d70STom Stellard        ).lower()
11964751ea2STom Stellard        self.COMMENT_TAG = "<!--LLVM PR SUMMARY COMMENT-->\n"
12064751ea2STom Stellard
12164751ea2STom Stellard    def get_summary_comment(self) -> github.IssueComment.IssueComment:
12264751ea2STom Stellard        for comment in self.pr.as_issue().get_comments():
12364751ea2STom Stellard            if self.COMMENT_TAG in comment.body:
12464751ea2STom Stellard                return comment
12564751ea2STom Stellard        return None
1265f16a3a4STom Stellard
1275f16a3a4STom Stellard    def run(self) -> bool:
1284085cb38SMehdi Amini        patch = None
1298a7f021fSJ. Ryan Stinnett        team = _get_current_team(self.team_name, self.org.get_teams())
1304085cb38SMehdi Amini        if not team:
1314085cb38SMehdi Amini            print(f"couldn't find team named {self.team_name}")
1324085cb38SMehdi Amini            return False
1334085cb38SMehdi Amini
1341b18e986Scor3ntin        # GitHub limits comments to 65,536 characters, let's limit the diff
1351b18e986Scor3ntin        # and the file list to 20kB each.
1361b18e986Scor3ntin        STAT_LIMIT = 20 * 1024
1371b18e986Scor3ntin        DIFF_LIMIT = 20 * 1024
1381b18e986Scor3ntin
1394085cb38SMehdi Amini        # Get statistics for each file
1404085cb38SMehdi Amini        diff_stats = f"{self.pr.changed_files} Files Affected:\n\n"
1414085cb38SMehdi Amini        for file in self.pr.get_files():
1424085cb38SMehdi Amini            diff_stats += f"- ({file.status}) {file.filename} ("
1434085cb38SMehdi Amini            if file.additions:
1444085cb38SMehdi Amini                diff_stats += f"+{file.additions}"
1454085cb38SMehdi Amini            if file.deletions:
1464085cb38SMehdi Amini                diff_stats += f"-{file.deletions}"
1474085cb38SMehdi Amini            diff_stats += ") "
1484085cb38SMehdi Amini            if file.status == "renamed":
1494085cb38SMehdi Amini                print(f"(from {file.previous_filename})")
1504085cb38SMehdi Amini            diff_stats += "\n"
1511b18e986Scor3ntin            if len(diff_stats) > STAT_LIMIT:
1521b18e986Scor3ntin                break
1534085cb38SMehdi Amini
1544085cb38SMehdi Amini        # Get the diff
1555f16a3a4STom Stellard        try:
1561b18e986Scor3ntin            patch = requests.get(self.pr.diff_url).text
1575f16a3a4STom Stellard        except:
1585f16a3a4STom Stellard            patch = ""
1594085cb38SMehdi Amini
1604085cb38SMehdi Amini        patch_link = f"Full diff: {self.pr.diff_url}\n"
1614085cb38SMehdi Amini        if len(patch) > DIFF_LIMIT:
1624085cb38SMehdi Amini            patch_link = f"\nPatch is {human_readable_size(len(patch))}, truncated to {human_readable_size(DIFF_LIMIT)} below, full version: {self.pr.diff_url}\n"
1631b18e986Scor3ntin            patch = patch[0:DIFF_LIMIT] + "...\n[truncated]\n"
16464751ea2STom Stellard        team_mention = "@llvm/{}".format(team.slug)
1654085cb38SMehdi Amini
1661b18e986Scor3ntin        body = escape_description(self.pr.body)
1671b18e986Scor3ntin        # Note: the comment is in markdown and the code below
1681b18e986Scor3ntin        # is sensible to line break
16964751ea2STom Stellard        comment = f"""
17064751ea2STom Stellard{self.COMMENT_TAG}
17164751ea2STom Stellard{team_mention}
1724085cb38SMehdi Amini
173f8148e48SAiden GrossmanAuthor: {self.pr.user.name} ({self.pr.user.login})
174f8148e48SAiden Grossman
17564751ea2STom Stellard<details>
17664751ea2STom Stellard<summary>Changes</summary>
1771b18e986Scor3ntin
17864751ea2STom Stellard{body}
1793ce8eda5SCorentin Jabot
1801b18e986Scor3ntin---
18164751ea2STom Stellard{patch_link}
1821b18e986Scor3ntin
18364751ea2STom Stellard{diff_stats}
1841b18e986Scor3ntin
1851b18e986Scor3ntin``````````diff
1861b18e986Scor3ntin{patch}
1871b18e986Scor3ntin``````````
1881b18e986Scor3ntin
18964751ea2STom Stellard</details>
19064751ea2STom Stellard"""
19164751ea2STom Stellard
19264751ea2STom Stellard        summary_comment = self.get_summary_comment()
19364751ea2STom Stellard        if not summary_comment:
1945f16a3a4STom Stellard            self.pr.as_issue().create_comment(comment)
19564751ea2STom Stellard        elif team_mention + "\n" in summary_comment.body:
19664751ea2STom Stellard            print("Team {} already mentioned.".format(team.slug))
19764751ea2STom Stellard        else:
19864751ea2STom Stellard            summary_comment.edit(
19964751ea2STom Stellard                summary_comment.body.replace(
20064751ea2STom Stellard                    self.COMMENT_TAG, self.COMMENT_TAG + team_mention + "\n"
20164751ea2STom Stellard                )
20264751ea2STom Stellard            )
2035f16a3a4STom Stellard        return True
2045f16a3a4STom Stellard
2058a7f021fSJ. Ryan Stinnett    def _get_current_team(self) -> Optional[github.Team.Team]:
2064085cb38SMehdi Amini        for team in self.org.get_teams():
2074085cb38SMehdi Amini            if self.team_name == team.name.lower():
2084085cb38SMehdi Amini                return team
2094085cb38SMehdi Amini        return None
2104085cb38SMehdi Amini
2115f16a3a4STom Stellard
21277249546SDavid Spickettclass PRGreeter:
21344ba4c73SDavid Spickett    COMMENT_TAG = "<!--LLVM NEW CONTRIBUTOR COMMENT-->\n"
21444ba4c73SDavid Spickett
21577249546SDavid Spickett    def __init__(self, token: str, repo: str, pr_number: int):
21677249546SDavid Spickett        repo = github.Github(token).get_repo(repo)
21777249546SDavid Spickett        self.pr = repo.get_issue(pr_number).as_pull_request()
21877249546SDavid Spickett
21977249546SDavid Spickett    def run(self) -> bool:
22077249546SDavid Spickett        # We assume that this is only called for a PR that has just been opened
22177249546SDavid Spickett        # by a user new to LLVM and/or GitHub itself.
22277249546SDavid Spickett
22377249546SDavid Spickett        # This text is using Markdown formatting.
22444ba4c73SDavid Spickett
22577249546SDavid Spickett        comment = f"""\
22644ba4c73SDavid Spickett{PRGreeter.COMMENT_TAG}
22777249546SDavid SpickettThank you for submitting a Pull Request (PR) to the LLVM Project!
22877249546SDavid Spickett
229*5adbce07SDavid SpickettThis PR will be automatically labeled and the relevant teams will be notified.
23077249546SDavid Spickett
23177249546SDavid SpickettIf you wish to, you can add reviewers by using the "Reviewers" section on this page.
23277249546SDavid Spickett
233*5adbce07SDavid SpickettIf this is not working for you, it is probably because you do not have write permissions for the repository. In which case you can instead tag reviewers by name in a comment by using `@` followed by their GitHub username.
23477249546SDavid Spickett
235*5adbce07SDavid SpickettIf you have received no comments on your PR for a week, you can request a review by "ping"ing the PR by adding a comment “Ping”. The common courtesy "ping" rate is once a week. Please remember that you are asking for valuable time from other developers.
23677249546SDavid Spickett
23777249546SDavid SpickettIf you have further questions, they may be answered by the [LLVM GitHub User Guide](https://llvm.org/docs/GitHub.html).
23877249546SDavid Spickett
23977249546SDavid SpickettYou can also ask questions in a comment on this PR, on the [LLVM Discord](https://discord.com/invite/xS7Z362) or on the [forums](https://discourse.llvm.org/)."""
24077249546SDavid Spickett        self.pr.as_issue().create_comment(comment)
24177249546SDavid Spickett        return True
24277249546SDavid Spickett
24377249546SDavid Spickett
24444ba4c73SDavid Spickettclass PRBuildbotInformation:
24544ba4c73SDavid Spickett    COMMENT_TAG = "<!--LLVM BUILDBOT INFORMATION COMMENT-->\n"
24644ba4c73SDavid Spickett
24744ba4c73SDavid Spickett    def __init__(self, token: str, repo: str, pr_number: int, author: str):
24844ba4c73SDavid Spickett        repo = github.Github(token).get_repo(repo)
24944ba4c73SDavid Spickett        self.pr = repo.get_issue(pr_number).as_pull_request()
25044ba4c73SDavid Spickett        self.author = author
25144ba4c73SDavid Spickett
25244ba4c73SDavid Spickett    def should_comment(self) -> bool:
25344ba4c73SDavid Spickett        # As soon as a new contributor has a PR merged, they are no longer a new contributor.
25444ba4c73SDavid Spickett        # We can tell that they were a new contributor previously because we would have
25544ba4c73SDavid Spickett        # added a new contributor greeting comment when they opened the PR.
25644ba4c73SDavid Spickett        found_greeting = False
25744ba4c73SDavid Spickett        for comment in self.pr.as_issue().get_comments():
25844ba4c73SDavid Spickett            if PRGreeter.COMMENT_TAG in comment.body:
25944ba4c73SDavid Spickett                found_greeting = True
26044ba4c73SDavid Spickett            elif PRBuildbotInformation.COMMENT_TAG in comment.body:
26144ba4c73SDavid Spickett                # When an issue is reopened, then closed as merged again, we should not
26244ba4c73SDavid Spickett                # add a second comment. This event will be rare in practice as it seems
26344ba4c73SDavid Spickett                # like it's only possible when the main branch is still at the exact
26444ba4c73SDavid Spickett                # revision that the PR was merged on to, beyond that it's closed forever.
26544ba4c73SDavid Spickett                return False
26644ba4c73SDavid Spickett        return found_greeting
26744ba4c73SDavid Spickett
26844ba4c73SDavid Spickett    def run(self) -> bool:
26944ba4c73SDavid Spickett        if not self.should_comment():
27044ba4c73SDavid Spickett            return
27144ba4c73SDavid Spickett
27244ba4c73SDavid Spickett        # This text is using Markdown formatting. Some of the lines are longer
27344ba4c73SDavid Spickett        # than others so that the final text is some reasonable looking paragraphs
27444ba4c73SDavid Spickett        # after the long URLs are rendered.
27544ba4c73SDavid Spickett        comment = f"""\
27644ba4c73SDavid Spickett{PRBuildbotInformation.COMMENT_TAG}
27744ba4c73SDavid Spickett@{self.author} Congratulations on having your first Pull Request (PR) merged into the LLVM Project!
27844ba4c73SDavid Spickett
279*5adbce07SDavid SpickettYour changes will be combined with recent changes from other authors, then tested by our [build bots](https://lab.llvm.org/buildbot/). If there is a problem with a build, you may receive a report in an email or a comment on this PR.
28044ba4c73SDavid Spickett
281*5adbce07SDavid SpickettPlease check whether problems have been caused by your change specifically, as the builds can include changes from many authors. It is not uncommon for your change to be included in a build that fails due to someone else's changes, or infrastructure issues.
28244ba4c73SDavid Spickett
28344ba4c73SDavid SpickettHow to do this, and the rest of the post-merge process, is covered in detail [here](https://llvm.org/docs/MyFirstTypoFix.html#myfirsttypofix-issues-after-landing-your-pr).
28444ba4c73SDavid Spickett
285*5adbce07SDavid SpickettIf your change does cause a problem, it may be reverted, or you can revert it yourself. This is a normal part of [LLVM development](https://llvm.org/docs/DeveloperPolicy.html#patch-reversion-policy). You can fix your changes and open a new PR to merge them again.
28644ba4c73SDavid Spickett
28744ba4c73SDavid SpickettIf you don't get any reports, no action is required from you. Your changes are working as expected, well done!
28844ba4c73SDavid Spickett"""
28944ba4c73SDavid Spickett        self.pr.as_issue().create_comment(comment)
29044ba4c73SDavid Spickett        return True
29144ba4c73SDavid Spickett
29244ba4c73SDavid Spickett
293b71edfaaSTobias Hietadef setup_llvmbot_git(git_dir="."):
294daf82a51STom Stellard    """
295daf82a51STom Stellard    Configure the git repo in `git_dir` with the llvmbot account so
296daf82a51STom Stellard    commits are attributed to llvmbot.
297daf82a51STom Stellard    """
298daf82a51STom Stellard    repo = Repo(git_dir)
299daf82a51STom Stellard    with repo.config_writer() as config:
300b71edfaaSTobias Hieta        config.set_value("user", "name", "llvmbot")
301b71edfaaSTobias Hieta        config.set_value("user", "email", "llvmbot@llvm.org")
302b71edfaaSTobias Hieta
303daf82a51STom Stellard
304405932afSTimm Bäderdef extract_commit_hash(arg: str):
305405932afSTimm Bäder    """
306405932afSTimm Bäder    Extract the commit hash from the argument passed to /action github
307405932afSTimm Bäder    comment actions. We currently only support passing the commit hash
308405932afSTimm Bäder    directly or use the github URL, such as
309405932afSTimm Bäder    https://github.com/llvm/llvm-project/commit/2832d7941f4207f1fcf813b27cf08cecc3086959
310405932afSTimm Bäder    """
311405932afSTimm Bäder    github_prefix = "https://github.com/llvm/llvm-project/commit/"
312405932afSTimm Bäder    if arg.startswith(github_prefix):
313405932afSTimm Bäder        return arg[len(github_prefix) :]
314405932afSTimm Bäder    return arg
315405932afSTimm Bäder
316405932afSTimm Bäder
317daf82a51STom Stellardclass ReleaseWorkflow:
318b71edfaaSTobias Hieta    CHERRY_PICK_FAILED_LABEL = "release:cherry-pick-failed"
3193929f913STom Stellard
320daf82a51STom Stellard    """
321daf82a51STom Stellard    This class implements the sub-commands for the release-workflow command.
322daf82a51STom Stellard    The current sub-commands are:
323daf82a51STom Stellard        * create-branch
324daf82a51STom Stellard        * create-pull-request
325daf82a51STom Stellard
326daf82a51STom Stellard    The execute_command method will automatically choose the correct sub-command
327daf82a51STom Stellard    based on the text in stdin.
328daf82a51STom Stellard    """
329daf82a51STom Stellard
330b71edfaaSTobias Hieta    def __init__(
331b71edfaaSTobias Hieta        self,
332b71edfaaSTobias Hieta        token: str,
333b71edfaaSTobias Hieta        repo: str,
334b71edfaaSTobias Hieta        issue_number: int,
335b71edfaaSTobias Hieta        branch_repo_name: str,
336b71edfaaSTobias Hieta        branch_repo_token: str,
337b71edfaaSTobias Hieta        llvm_project_dir: str,
338bd655478STom Stellard        requested_by: str,
339b71edfaaSTobias Hieta    ) -> None:
340daf82a51STom Stellard        self._token = token
341daf82a51STom Stellard        self._repo_name = repo
342daf82a51STom Stellard        self._issue_number = issue_number
343daf82a51STom Stellard        self._branch_repo_name = branch_repo_name
344daf82a51STom Stellard        if branch_repo_token:
345daf82a51STom Stellard            self._branch_repo_token = branch_repo_token
346daf82a51STom Stellard        else:
347daf82a51STom Stellard            self._branch_repo_token = self.token
348daf82a51STom Stellard        self._llvm_project_dir = llvm_project_dir
349bd655478STom Stellard        self._requested_by = requested_by
350daf82a51STom Stellard
351daf82a51STom Stellard    @property
352daf82a51STom Stellard    def token(self) -> str:
353daf82a51STom Stellard        return self._token
354daf82a51STom Stellard
355daf82a51STom Stellard    @property
356daf82a51STom Stellard    def repo_name(self) -> str:
357daf82a51STom Stellard        return self._repo_name
358daf82a51STom Stellard
359daf82a51STom Stellard    @property
360daf82a51STom Stellard    def issue_number(self) -> int:
361daf82a51STom Stellard        return self._issue_number
362daf82a51STom Stellard
363daf82a51STom Stellard    @property
364e99edf6bSTom Stellard    def branch_repo_owner(self) -> str:
365e99edf6bSTom Stellard        return self.branch_repo_name.split("/")[0]
366e99edf6bSTom Stellard
367e99edf6bSTom Stellard    @property
368daf82a51STom Stellard    def branch_repo_name(self) -> str:
369daf82a51STom Stellard        return self._branch_repo_name
370daf82a51STom Stellard
371daf82a51STom Stellard    @property
372daf82a51STom Stellard    def branch_repo_token(self) -> str:
373daf82a51STom Stellard        return self._branch_repo_token
374daf82a51STom Stellard
375daf82a51STom Stellard    @property
376daf82a51STom Stellard    def llvm_project_dir(self) -> str:
377daf82a51STom Stellard        return self._llvm_project_dir
378daf82a51STom Stellard
379daf82a51STom Stellard    @property
380bd655478STom Stellard    def requested_by(self) -> str:
381bd655478STom Stellard        return self._requested_by
382bd655478STom Stellard
383bd655478STom Stellard    @property
384f673dcc6STom Stellard    def repo(self) -> github.Repository.Repository:
385daf82a51STom Stellard        return github.Github(self.token).get_repo(self.repo_name)
386daf82a51STom Stellard
387daf82a51STom Stellard    @property
388daf82a51STom Stellard    def issue(self) -> github.Issue.Issue:
389f673dcc6STom Stellard        return self.repo.get_issue(self.issue_number)
390daf82a51STom Stellard
391daf82a51STom Stellard    @property
392daf82a51STom Stellard    def push_url(self) -> str:
393b71edfaaSTobias Hieta        return "https://{}@github.com/{}".format(
394b71edfaaSTobias Hieta            self.branch_repo_token, self.branch_repo_name
395b71edfaaSTobias Hieta        )
396daf82a51STom Stellard
397daf82a51STom Stellard    @property
398daf82a51STom Stellard    def branch_name(self) -> str:
399b71edfaaSTobias Hieta        return "issue{}".format(self.issue_number)
400daf82a51STom Stellard
401daf82a51STom Stellard    @property
402daf82a51STom Stellard    def release_branch_for_issue(self) -> Optional[str]:
403daf82a51STom Stellard        issue = self.issue
404daf82a51STom Stellard        milestone = issue.milestone
405daf82a51STom Stellard        if milestone is None:
406daf82a51STom Stellard            return None
407b71edfaaSTobias Hieta        m = re.search("branch: (.+)", milestone.description)
408daf82a51STom Stellard        if m:
409daf82a51STom Stellard            return m.group(1)
410daf82a51STom Stellard        return None
411daf82a51STom Stellard
412daf82a51STom Stellard    def print_release_branch(self) -> None:
413daf82a51STom Stellard        print(self.release_branch_for_issue)
414daf82a51STom Stellard
415daf82a51STom Stellard    def issue_notify_branch(self) -> None:
416b71edfaaSTobias Hieta        self.issue.create_comment(
417b71edfaaSTobias Hieta            "/branch {}/{}".format(self.branch_repo_name, self.branch_name)
418b71edfaaSTobias Hieta        )
419daf82a51STom Stellard
420daf82a51STom Stellard    def issue_notify_pull_request(self, pull: github.PullRequest.PullRequest) -> None:
421b71edfaaSTobias Hieta        self.issue.create_comment(
422f33e9276STom Stellard            "/pull-request {}#{}".format(self.repo_name, pull.number)
423b71edfaaSTobias Hieta        )
424daf82a51STom Stellard
42551eaaa30STom Stellard    def make_ignore_comment(self, comment: str) -> str:
42651eaaa30STom Stellard        """
42751eaaa30STom Stellard        Returns the comment string with a prefix that will cause
42851eaaa30STom Stellard        a Github workflow to skip parsing this comment.
42951eaaa30STom Stellard
43051eaaa30STom Stellard        :param str comment: The comment to ignore
43151eaaa30STom Stellard        """
43251eaaa30STom Stellard        return "<!--IGNORE-->\n" + comment
43351eaaa30STom Stellard
43451eaaa30STom Stellard    def issue_notify_no_milestone(self, comment: List[str]) -> None:
435b71edfaaSTobias Hieta        message = "{}\n\nError: Command failed due to missing milestone.".format(
436b71edfaaSTobias Hieta            "".join([">" + line for line in comment])
437b71edfaaSTobias Hieta        )
43851eaaa30STom Stellard        self.issue.create_comment(self.make_ignore_comment(message))
43951eaaa30STom Stellard
440daf82a51STom Stellard    @property
441daf82a51STom Stellard    def action_url(self) -> str:
442b71edfaaSTobias Hieta        if os.getenv("CI"):
443b71edfaaSTobias Hieta            return "https://github.com/{}/actions/runs/{}".format(
444b71edfaaSTobias Hieta                os.getenv("GITHUB_REPOSITORY"), os.getenv("GITHUB_RUN_ID")
445b71edfaaSTobias Hieta            )
446daf82a51STom Stellard        return ""
447daf82a51STom Stellard
448b71edfaaSTobias Hieta    def issue_notify_cherry_pick_failure(
449b71edfaaSTobias Hieta        self, commit: str
450b71edfaaSTobias Hieta    ) -> github.IssueComment.IssueComment:
451b71edfaaSTobias Hieta        message = self.make_ignore_comment(
452b71edfaaSTobias Hieta            "Failed to cherry-pick: {}\n\n".format(commit)
453b71edfaaSTobias Hieta        )
454daf82a51STom Stellard        action_url = self.action_url
455daf82a51STom Stellard        if action_url:
456daf82a51STom Stellard            message += action_url + "\n\n"
457e99edf6bSTom Stellard        message += "Please manually backport the fix and push it to your github fork.  Once this is done, please create a [pull request](https://github.com/llvm/llvm-project/compare)"
458daf82a51STom Stellard        issue = self.issue
459daf82a51STom Stellard        comment = issue.create_comment(message)
4603929f913STom Stellard        issue.add_to_labels(self.CHERRY_PICK_FAILED_LABEL)
461daf82a51STom Stellard        return comment
462daf82a51STom Stellard
463b71edfaaSTobias Hieta    def issue_notify_pull_request_failure(
464b71edfaaSTobias Hieta        self, branch: str
465b71edfaaSTobias Hieta    ) -> github.IssueComment.IssueComment:
466daf82a51STom Stellard        message = "Failed to create pull request for {} ".format(branch)
467daf82a51STom Stellard        message += self.action_url
468daf82a51STom Stellard        return self.issue.create_comment(message)
469daf82a51STom Stellard
4703929f913STom Stellard    def issue_remove_cherry_pick_failed_label(self):
4713929f913STom Stellard        if self.CHERRY_PICK_FAILED_LABEL in [l.name for l in self.issue.labels]:
4723929f913STom Stellard            self.issue.remove_from_labels(self.CHERRY_PICK_FAILED_LABEL)
473daf82a51STom Stellard
474f33e9276STom Stellard    def get_main_commit(self, cherry_pick_sha: str) -> github.Commit.Commit:
475f33e9276STom Stellard        commit = self.repo.get_commit(cherry_pick_sha)
476f33e9276STom Stellard        message = commit.commit.message
477f33e9276STom Stellard        m = re.search("\(cherry picked from commit ([0-9a-f]+)\)", message)
478f33e9276STom Stellard        if not m:
479f33e9276STom Stellard            return None
480f33e9276STom Stellard        return self.repo.get_commit(m.group(1))
481f33e9276STom Stellard
482f673dcc6STom Stellard    def pr_request_review(self, pr: github.PullRequest.PullRequest):
483f673dcc6STom Stellard        """
484f673dcc6STom Stellard        This function will try to find the best reviewers for `commits` and
485f33e9276STom Stellard        then add a comment requesting review of the backport and add them as
486f33e9276STom Stellard        reviewers.
487f673dcc6STom Stellard
488f33e9276STom Stellard        The reviewers selected are those users who approved the pull request
489f33e9276STom Stellard        for the main branch.
490f673dcc6STom Stellard        """
491f673dcc6STom Stellard        reviewers = []
492f673dcc6STom Stellard        for commit in pr.get_commits():
493f33e9276STom Stellard            main_commit = self.get_main_commit(commit.sha)
494f33e9276STom Stellard            if not main_commit:
495f673dcc6STom Stellard                continue
496f33e9276STom Stellard            for pull in main_commit.get_pulls():
497f33e9276STom Stellard                for review in pull.get_reviews():
498f33e9276STom Stellard                    if review.state != "APPROVED":
499f33e9276STom Stellard                        continue
500f33e9276STom Stellard                reviewers.append(review.user.login)
501f673dcc6STom Stellard        if len(reviewers):
502f673dcc6STom Stellard            message = "{} What do you think about merging this PR to the release branch?".format(
503b71edfaaSTobias Hieta                " ".join(["@" + r for r in reviewers])
504b71edfaaSTobias Hieta            )
505f673dcc6STom Stellard            pr.create_issue_comment(message)
506f33e9276STom Stellard            pr.create_review_request(reviewers)
507f673dcc6STom Stellard
508daf82a51STom Stellard    def create_branch(self, commits: List[str]) -> bool:
509daf82a51STom Stellard        """
510daf82a51STom Stellard        This function attempts to backport `commits` into the branch associated
511daf82a51STom Stellard        with `self.issue_number`.
512daf82a51STom Stellard
513daf82a51STom Stellard        If this is successful, then the branch is pushed to `self.branch_repo_name`, if not,
514daf82a51STom Stellard        a comment is added to the issue saying that the cherry-pick failed.
515daf82a51STom Stellard
516daf82a51STom Stellard        :param list commits: List of commits to cherry-pick.
517daf82a51STom Stellard
518daf82a51STom Stellard        """
519b71edfaaSTobias Hieta        print("cherry-picking", commits)
520daf82a51STom Stellard        branch_name = self.branch_name
521daf82a51STom Stellard        local_repo = Repo(self.llvm_project_dir)
522daf82a51STom Stellard        local_repo.git.checkout(self.release_branch_for_issue)
523daf82a51STom Stellard
524daf82a51STom Stellard        for c in commits:
525daf82a51STom Stellard            try:
526b71edfaaSTobias Hieta                local_repo.git.cherry_pick("-x", c)
527daf82a51STom Stellard            except Exception as e:
528daf82a51STom Stellard                self.issue_notify_cherry_pick_failure(c)
529daf82a51STom Stellard                raise e
530daf82a51STom Stellard
531daf82a51STom Stellard        push_url = self.push_url
532b71edfaaSTobias Hieta        print("Pushing to {} {}".format(push_url, branch_name))
533b71edfaaSTobias Hieta        local_repo.git.push(push_url, "HEAD:{}".format(branch_name), force=True)
534daf82a51STom Stellard
5353929f913STom Stellard        self.issue_remove_cherry_pick_failed_label()
536e99edf6bSTom Stellard        return self.create_pull_request(
537bd655478STom Stellard            self.branch_repo_owner, self.repo_name, branch_name, commits
538e99edf6bSTom Stellard        )
539daf82a51STom Stellard
540b71edfaaSTobias Hieta    def check_if_pull_request_exists(
541b71edfaaSTobias Hieta        self, repo: github.Repository.Repository, head: str
542b71edfaaSTobias Hieta    ) -> bool:
54349ad577cSTobias Hieta        pulls = repo.get_pulls(head=head)
544fde9ef52STobias Hieta        return pulls.totalCount != 0
545daf82a51STom Stellard
546bd655478STom Stellard    def create_pull_request(
547bd655478STom Stellard        self, owner: str, repo_name: str, branch: str, commits: List[str]
548bd655478STom Stellard    ) -> bool:
549daf82a51STom Stellard        """
550f33e9276STom Stellard        Create a pull request in `self.repo_name`.  The base branch of the
551a2d45017SKazu Hirata        pull request will be chosen based on the the milestone attached to
552daf82a51STom Stellard        the issue represented by `self.issue_number`  For example if the milestone
553daf82a51STom Stellard        is Release 13.0.1, then the base branch will be release/13.x. `branch`
554daf82a51STom Stellard        will be used as the compare branch.
555daf82a51STom Stellard        https://docs.github.com/en/get-started/quickstart/github-glossary#base-branch
556daf82a51STom Stellard        https://docs.github.com/en/get-started/quickstart/github-glossary#compare-branch
557daf82a51STom Stellard        """
558f33e9276STom Stellard        repo = github.Github(self.token).get_repo(self.repo_name)
559b71edfaaSTobias Hieta        issue_ref = "{}#{}".format(self.repo_name, self.issue_number)
560daf82a51STom Stellard        pull = None
561daf82a51STom Stellard        release_branch_for_issue = self.release_branch_for_issue
562daf82a51STom Stellard        if release_branch_for_issue is None:
563daf82a51STom Stellard            return False
56417d4796cSTom Stellard
56556444d56SNikita Popov        head = f"{owner}:{branch}"
56649ad577cSTobias Hieta        if self.check_if_pull_request_exists(repo, head):
56749ad577cSTobias Hieta            print("PR already exists...")
56849ad577cSTobias Hieta            return True
569daf82a51STom Stellard        try:
570bd655478STom Stellard            commit_message = repo.get_commit(commits[-1]).commit.message
571bd655478STom Stellard            message_lines = commit_message.splitlines()
572bd655478STom Stellard            title = "{}: {}".format(release_branch_for_issue, message_lines[0])
573bd655478STom Stellard            body = "Backport {}\n\nRequested by: @{}".format(
574bd655478STom Stellard                " ".join(commits), self.requested_by
575bd655478STom Stellard            )
576b71edfaaSTobias Hieta            pull = repo.create_pull(
577bd655478STom Stellard                title=title,
578bd655478STom Stellard                body=body,
579daf82a51STom Stellard                base=release_branch_for_issue,
58049ad577cSTobias Hieta                head=head,
581d125d557STom Stellard                maintainer_can_modify=True,
582b71edfaaSTobias Hieta            )
583f673dcc6STom Stellard
584f33e9276STom Stellard            pull.as_issue().edit(milestone=self.issue.milestone)
585f33e9276STom Stellard
5869805c051STom Stellard            # Once the pull request has been created, we can close the
5879805c051STom Stellard            # issue that was used to request the cherry-pick
5889805c051STom Stellard            self.issue.edit(state="closed", state_reason="completed")
5899805c051STom Stellard
590f673dcc6STom Stellard            try:
591f673dcc6STom Stellard                self.pr_request_review(pull)
592f673dcc6STom Stellard            except Exception as e:
593f673dcc6STom Stellard                print("error: Failed while searching for reviewers", e)
594f673dcc6STom Stellard
595daf82a51STom Stellard        except Exception as e:
596daf82a51STom Stellard            self.issue_notify_pull_request_failure(branch)
597daf82a51STom Stellard            raise e
598daf82a51STom Stellard
599daf82a51STom Stellard        if pull is None:
600daf82a51STom Stellard            return False
601daf82a51STom Stellard
602daf82a51STom Stellard        self.issue_notify_pull_request(pull)
6033929f913STom Stellard        self.issue_remove_cherry_pick_failed_label()
604daf82a51STom Stellard
605daf82a51STom Stellard        # TODO(tstellar): Do you really want to always return True?
606daf82a51STom Stellard        return True
607daf82a51STom Stellard
608daf82a51STom Stellard    def execute_command(self) -> bool:
609daf82a51STom Stellard        """
610daf82a51STom Stellard        This function reads lines from STDIN and executes the first command
6119a894e7dSShourya Goel        that it finds.  The supported command is:
6129a894e7dSShourya Goel        /cherry-pick< ><:> commit0 <commit1> <commit2> <...>
613daf82a51STom Stellard        """
614daf82a51STom Stellard        for line in sys.stdin:
615daf82a51STom Stellard            line.rstrip()
6169a894e7dSShourya Goel            m = re.search(r"/cherry-pick\s*:? *(.*)", line)
617daf82a51STom Stellard            if not m:
618daf82a51STom Stellard                continue
619daf82a51STom Stellard
6209a894e7dSShourya Goel            args = m.group(1)
6219a894e7dSShourya Goel
622405932afSTimm Bäder            arg_list = args.split()
623405932afSTimm Bäder            commits = list(map(lambda a: extract_commit_hash(a), arg_list))
624405932afSTimm Bäder            return self.create_branch(commits)
625daf82a51STom Stellard
626daf82a51STom Stellard        print("Do not understand input:")
627daf82a51STom Stellard        print(sys.stdin.readlines())
628daf82a51STom Stellard        return False
629a2adebf4STom Stellard
630b71edfaaSTobias Hieta
631c99d1156STom Stellarddef request_release_note(token: str, repo_name: str, pr_number: int):
632c99d1156STom Stellard    repo = github.Github(token).get_repo(repo_name)
633c99d1156STom Stellard    pr = repo.get_issue(pr_number).as_pull_request()
634c99d1156STom Stellard    submitter = pr.user.login
635c99d1156STom Stellard    if submitter == "llvmbot":
636c99d1156STom Stellard        m = re.search("Requested by: @(.+)$", pr.body)
637c99d1156STom Stellard        if not m:
638c99d1156STom Stellard            submitter = None
639c99d1156STom Stellard            print("Warning could not determine user who requested backport.")
640c99d1156STom Stellard        submitter = m.group(1)
641c99d1156STom Stellard
642c99d1156STom Stellard    mention = ""
643c99d1156STom Stellard    if submitter:
644c99d1156STom Stellard        mention = f"@{submitter}"
645c99d1156STom Stellard
646c99d1156STom Stellard    comment = f"{mention} (or anyone else). If you would like to add a note about this fix in the release notes (completely optional). Please reply to this comment with a one or two sentence description of the fix.  When you are done, please add the release:note label to this PR. "
6472879a036STom Stellard    try:
648c99d1156STom Stellard        pr.as_issue().create_comment(comment)
6492879a036STom Stellard    except:
6502879a036STom Stellard        # Failed to create comment so emit file instead
6512879a036STom Stellard        with open("comments", "w") as file:
6522879a036STom Stellard            data = [{"body": comment}]
6532879a036STom Stellard            json.dump(data, file)
654c99d1156STom Stellard
655c99d1156STom Stellard
656a2adebf4STom Stellardparser = argparse.ArgumentParser()
657b71edfaaSTobias Hietaparser.add_argument(
6588a7f021fSJ. Ryan Stinnett    "--token", type=str, required=True, help="GitHub authentication token"
659b71edfaaSTobias Hieta)
660b71edfaaSTobias Hietaparser.add_argument(
661b71edfaaSTobias Hieta    "--repo",
662b71edfaaSTobias Hieta    type=str,
663b71edfaaSTobias Hieta    default=os.getenv("GITHUB_REPOSITORY", "llvm/llvm-project"),
664b71edfaaSTobias Hieta    help="The GitHub repository that we are working with in the form of <owner>/<repo> (e.g. llvm/llvm-project)",
665b71edfaaSTobias Hieta)
666b71edfaaSTobias Hietasubparsers = parser.add_subparsers(dest="command")
667a2adebf4STom Stellard
668b71edfaaSTobias Hietaissue_subscriber_parser = subparsers.add_parser("issue-subscriber")
669b71edfaaSTobias Hietaissue_subscriber_parser.add_argument("--label-name", type=str, required=True)
670b71edfaaSTobias Hietaissue_subscriber_parser.add_argument("--issue-number", type=int, required=True)
671a2adebf4STom Stellard
6725f16a3a4STom Stellardpr_subscriber_parser = subparsers.add_parser("pr-subscriber")
6735f16a3a4STom Stellardpr_subscriber_parser.add_argument("--label-name", type=str, required=True)
6745f16a3a4STom Stellardpr_subscriber_parser.add_argument("--issue-number", type=int, required=True)
6755f16a3a4STom Stellard
67677249546SDavid Spickettpr_greeter_parser = subparsers.add_parser("pr-greeter")
67777249546SDavid Spickettpr_greeter_parser.add_argument("--issue-number", type=int, required=True)
67877249546SDavid Spickett
67944ba4c73SDavid Spickettpr_buildbot_information_parser = subparsers.add_parser("pr-buildbot-information")
68044ba4c73SDavid Spickettpr_buildbot_information_parser.add_argument("--issue-number", type=int, required=True)
68144ba4c73SDavid Spickettpr_buildbot_information_parser.add_argument("--author", type=str, required=True)
68244ba4c73SDavid Spickett
683b71edfaaSTobias Hietarelease_workflow_parser = subparsers.add_parser("release-workflow")
684b71edfaaSTobias Hietarelease_workflow_parser.add_argument(
685b71edfaaSTobias Hieta    "--llvm-project-dir",
686b71edfaaSTobias Hieta    type=str,
687b71edfaaSTobias Hieta    default=".",
6888a7f021fSJ. Ryan Stinnett    help="directory containing the llvm-project checkout",
689b71edfaaSTobias Hieta)
690b71edfaaSTobias Hietarelease_workflow_parser.add_argument(
691b71edfaaSTobias Hieta    "--issue-number", type=int, required=True, help="The issue number to update"
692b71edfaaSTobias Hieta)
693b71edfaaSTobias Hietarelease_workflow_parser.add_argument(
694b71edfaaSTobias Hieta    "--branch-repo-token",
695b71edfaaSTobias Hieta    type=str,
696b71edfaaSTobias Hieta    help="GitHub authentication token to use for the repository where new branches will be pushed. Defaults to TOKEN.",
697b71edfaaSTobias Hieta)
698b71edfaaSTobias Hietarelease_workflow_parser.add_argument(
699b71edfaaSTobias Hieta    "--branch-repo",
700b71edfaaSTobias Hieta    type=str,
701f33e9276STom Stellard    default="llvmbot/llvm-project",
702b71edfaaSTobias Hieta    help="The name of the repo where new branches will be pushed (e.g. llvm/llvm-project)",
703b71edfaaSTobias Hieta)
704b71edfaaSTobias Hietarelease_workflow_parser.add_argument(
705b71edfaaSTobias Hieta    "sub_command",
706b71edfaaSTobias Hieta    type=str,
707b71edfaaSTobias Hieta    choices=["print-release-branch", "auto"],
708b71edfaaSTobias Hieta    help="Print to stdout the name of the release branch ISSUE_NUMBER should be backported to",
709b71edfaaSTobias Hieta)
710daf82a51STom Stellard
711b71edfaaSTobias Hietallvmbot_git_config_parser = subparsers.add_parser(
712b71edfaaSTobias Hieta    "setup-llvmbot-git",
713b71edfaaSTobias Hieta    help="Set the default user and email for the git repo in LLVM_PROJECT_DIR to llvmbot",
714b71edfaaSTobias Hieta)
715bd655478STom Stellardrelease_workflow_parser.add_argument(
716bd655478STom Stellard    "--requested-by",
717bd655478STom Stellard    type=str,
718bd655478STom Stellard    required=True,
719bd655478STom Stellard    help="The user that requested this backport",
720bd655478STom Stellard)
721daf82a51STom Stellard
722c99d1156STom Stellardrequest_release_note_parser = subparsers.add_parser(
723c99d1156STom Stellard    "request-release-note",
724c99d1156STom Stellard    help="Request a release note for a pull request",
725c99d1156STom Stellard)
726c99d1156STom Stellardrequest_release_note_parser.add_argument(
727c99d1156STom Stellard    "--pr-number",
728c99d1156STom Stellard    type=int,
729c99d1156STom Stellard    required=True,
730c99d1156STom Stellard    help="The pull request to request the release note",
731c99d1156STom Stellard)
732c99d1156STom Stellard
733c99d1156STom Stellard
734a2adebf4STom Stellardargs = parser.parse_args()
735a2adebf4STom Stellard
736b71edfaaSTobias Hietaif args.command == "issue-subscriber":
737b71edfaaSTobias Hieta    issue_subscriber = IssueSubscriber(
738b71edfaaSTobias Hieta        args.token, args.repo, args.issue_number, args.label_name
739b71edfaaSTobias Hieta    )
740a2adebf4STom Stellard    issue_subscriber.run()
7415f16a3a4STom Stellardelif args.command == "pr-subscriber":
7425f16a3a4STom Stellard    pr_subscriber = PRSubscriber(
7435f16a3a4STom Stellard        args.token, args.repo, args.issue_number, args.label_name
7445f16a3a4STom Stellard    )
7455f16a3a4STom Stellard    pr_subscriber.run()
74677249546SDavid Spickettelif args.command == "pr-greeter":
74777249546SDavid Spickett    pr_greeter = PRGreeter(args.token, args.repo, args.issue_number)
74877249546SDavid Spickett    pr_greeter.run()
74944ba4c73SDavid Spickettelif args.command == "pr-buildbot-information":
75044ba4c73SDavid Spickett    pr_buildbot_information = PRBuildbotInformation(
75144ba4c73SDavid Spickett        args.token, args.repo, args.issue_number, args.author
75244ba4c73SDavid Spickett    )
75344ba4c73SDavid Spickett    pr_buildbot_information.run()
754b71edfaaSTobias Hietaelif args.command == "release-workflow":
755b71edfaaSTobias Hieta    release_workflow = ReleaseWorkflow(
756b71edfaaSTobias Hieta        args.token,
757b71edfaaSTobias Hieta        args.repo,
758b71edfaaSTobias Hieta        args.issue_number,
759b71edfaaSTobias Hieta        args.branch_repo,
760b71edfaaSTobias Hieta        args.branch_repo_token,
761b71edfaaSTobias Hieta        args.llvm_project_dir,
762bd655478STom Stellard        args.requested_by,
763b71edfaaSTobias Hieta    )
76451eaaa30STom Stellard    if not release_workflow.release_branch_for_issue:
76551eaaa30STom Stellard        release_workflow.issue_notify_no_milestone(sys.stdin.readlines())
76651eaaa30STom Stellard        sys.exit(1)
767b71edfaaSTobias Hieta    if args.sub_command == "print-release-branch":
768daf82a51STom Stellard        release_workflow.print_release_branch()
769daf82a51STom Stellard    else:
770daf82a51STom Stellard        if not release_workflow.execute_command():
771daf82a51STom Stellard            sys.exit(1)
772b71edfaaSTobias Hietaelif args.command == "setup-llvmbot-git":
773daf82a51STom Stellard    setup_llvmbot_git()
774c99d1156STom Stellardelif args.command == "request-release-note":
775c99d1156STom Stellard    request_release_note(args.token, args.repo, args.pr_number)
776