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
13a2adebf4STom Stellardimport github
14a2adebf4STom Stellardimport os
15daf82a51STom Stellardimport re
16*f673dcc6STom Stellardimport requests
17daf82a51STom Stellardimport sys
1817d4796cSTom Stellardimport time
19daf82a51STom Stellardfrom typing import *
20a2adebf4STom Stellard
21a2adebf4STom Stellardclass IssueSubscriber:
22a2adebf4STom Stellard
23a2adebf4STom Stellard    @property
24a2adebf4STom Stellard    def team_name(self) -> str:
25a2adebf4STom Stellard        return self._team_name
26a2adebf4STom Stellard
27a2adebf4STom Stellard    def __init__(self, token:str, repo:str, issue_number:int, label_name:str):
28a2adebf4STom Stellard        self.repo = github.Github(token).get_repo(repo)
29a2adebf4STom Stellard        self.org = github.Github(token).get_organization(self.repo.organization.login)
30a2adebf4STom Stellard        self.issue = self.repo.get_issue(issue_number)
31a2adebf4STom Stellard        self._team_name = 'issue-subscribers-{}'.format(label_name).lower()
32a2adebf4STom Stellard
33a2adebf4STom Stellard    def run(self) -> bool:
34a2adebf4STom Stellard        for team in self.org.get_teams():
35a2adebf4STom Stellard            if self.team_name != team.name.lower():
36a2adebf4STom Stellard                continue
37a2adebf4STom Stellard            comment = '@llvm/{}'.format(team.slug)
38a2adebf4STom Stellard            self.issue.create_comment(comment)
39a2adebf4STom Stellard            return True
40a2adebf4STom Stellard        return False
41a2adebf4STom Stellard
42daf82a51STom Stellarddef setup_llvmbot_git(git_dir = '.'):
43daf82a51STom Stellard    """
44daf82a51STom Stellard    Configure the git repo in `git_dir` with the llvmbot account so
45daf82a51STom Stellard    commits are attributed to llvmbot.
46daf82a51STom Stellard    """
47daf82a51STom Stellard    repo = Repo(git_dir)
48daf82a51STom Stellard    with repo.config_writer() as config:
49daf82a51STom Stellard        config.set_value('user', 'name', 'llvmbot')
50daf82a51STom Stellard        config.set_value('user', 'email', '[email protected]')
51daf82a51STom Stellard
52*f673dcc6STom Stellarddef phab_api_call(phab_token:str, url:str, args:dict) -> dict:
53*f673dcc6STom Stellard    """
54*f673dcc6STom Stellard    Make an API call to the Phabricator web service and return a dictionary
55*f673dcc6STom Stellard    containing the json response.
56*f673dcc6STom Stellard    """
57*f673dcc6STom Stellard    data = { "api.token" : phab_token }
58*f673dcc6STom Stellard    data.update(args)
59*f673dcc6STom Stellard    response = requests.post(url, data = data)
60*f673dcc6STom Stellard    return response.json()
61*f673dcc6STom Stellard
62*f673dcc6STom Stellard
63*f673dcc6STom Stellarddef phab_login_to_github_login(phab_token:str, repo:github.Repository.Repository, phab_login:str) -> str:
64*f673dcc6STom Stellard    """
65*f673dcc6STom Stellard    Tries to translate a Phabricator login to a github login by
66*f673dcc6STom Stellard    finding a commit made in Phabricator's Differential.
67*f673dcc6STom Stellard    The commit's SHA1 is then looked up in the github repo and
68*f673dcc6STom Stellard    the committer's login associated with that commit is returned.
69*f673dcc6STom Stellard
70*f673dcc6STom Stellard    :param str phab_token: The Conduit API token to use for communication with Pabricator
71*f673dcc6STom Stellard    :param github.Repository.Repository repo: The github repo to use when looking for the SHA1 found in Differential
72*f673dcc6STom Stellard    :param str phab_login: The Phabricator login to be translated.
73*f673dcc6STom Stellard    """
74*f673dcc6STom Stellard
75*f673dcc6STom Stellard    args = {
76*f673dcc6STom Stellard        "constraints[authors][0]" : phab_login,
77*f673dcc6STom Stellard        # PHID for "LLVM Github Monorepo" repository
78*f673dcc6STom Stellard        "constraints[repositories][0]" : "PHID-REPO-f4scjekhnkmh7qilxlcy",
79*f673dcc6STom Stellard        "limit" : 1
80*f673dcc6STom Stellard    }
81*f673dcc6STom Stellard    # API documentation: https://reviews.llvm.org/conduit/method/diffusion.commit.search/
82*f673dcc6STom Stellard    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/diffusion.commit.search", args)
83*f673dcc6STom Stellard    data = r['result']['data']
84*f673dcc6STom Stellard    if len(data) == 0:
85*f673dcc6STom Stellard        # Can't find any commits associated with this user
86*f673dcc6STom Stellard        return None
87*f673dcc6STom Stellard
88*f673dcc6STom Stellard    commit_sha = data[0]['fields']['identifier']
89*f673dcc6STom Stellard    return repo.get_commit(commit_sha).committer.login
90*f673dcc6STom Stellard
91*f673dcc6STom Stellarddef phab_get_commit_approvers(phab_token:str, repo:github.Repository.Repository, commit:github.Commit.Commit) -> list:
92*f673dcc6STom Stellard    args = { "corpus" : commit.commit.message }
93*f673dcc6STom Stellard    # API documentation: https://reviews.llvm.org/conduit/method/differential.parsecommitmessage/
94*f673dcc6STom Stellard    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/differential.parsecommitmessage", args)
95*f673dcc6STom Stellard    review_id = r['result']['revisionIDFieldInfo']['value']
96*f673dcc6STom Stellard
97*f673dcc6STom Stellard    args = {
98*f673dcc6STom Stellard        'constraints[ids][0]' : review_id,
99*f673dcc6STom Stellard        'attachments[reviewers]' : True
100*f673dcc6STom Stellard    }
101*f673dcc6STom Stellard    # API documentation: https://reviews.llvm.org/conduit/method/differential.revision.search/
102*f673dcc6STom Stellard    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/differential.revision.search", args)
103*f673dcc6STom Stellard    reviewers = r['result']['data'][0]['attachments']['reviewers']['reviewers']
104*f673dcc6STom Stellard    accepted = []
105*f673dcc6STom Stellard    for reviewer in reviewers:
106*f673dcc6STom Stellard        if reviewer['status'] != 'accepted':
107*f673dcc6STom Stellard            continue
108*f673dcc6STom Stellard        phid = reviewer['reviewerPHID']
109*f673dcc6STom Stellard        args = { 'constraints[phids][0]' : phid }
110*f673dcc6STom Stellard        # API documentation: https://reviews.llvm.org/conduit/method/user.search/
111*f673dcc6STom Stellard        r = phab_api_call(phab_token, "https://reviews.llvm.org/api/user.search", args)
112*f673dcc6STom Stellard        accepted.append(r['result']['data'][0]['fields']['username'])
113*f673dcc6STom Stellard    return accepted
114*f673dcc6STom Stellard
115daf82a51STom Stellardclass ReleaseWorkflow:
116daf82a51STom Stellard
1173929f913STom Stellard    CHERRY_PICK_FAILED_LABEL = 'release:cherry-pick-failed'
1183929f913STom Stellard
119daf82a51STom Stellard    """
120daf82a51STom Stellard    This class implements the sub-commands for the release-workflow command.
121daf82a51STom Stellard    The current sub-commands are:
122daf82a51STom Stellard        * create-branch
123daf82a51STom Stellard        * create-pull-request
124daf82a51STom Stellard
125daf82a51STom Stellard    The execute_command method will automatically choose the correct sub-command
126daf82a51STom Stellard    based on the text in stdin.
127daf82a51STom Stellard    """
128daf82a51STom Stellard
129daf82a51STom Stellard    def __init__(self, token:str, repo:str, issue_number:int,
130daf82a51STom Stellard                       branch_repo_name:str, branch_repo_token:str,
131*f673dcc6STom Stellard                       llvm_project_dir:str, phab_token:str) -> None:
132daf82a51STom Stellard        self._token = token
133daf82a51STom Stellard        self._repo_name = repo
134daf82a51STom Stellard        self._issue_number = issue_number
135daf82a51STom Stellard        self._branch_repo_name = branch_repo_name
136daf82a51STom Stellard        if branch_repo_token:
137daf82a51STom Stellard            self._branch_repo_token = branch_repo_token
138daf82a51STom Stellard        else:
139daf82a51STom Stellard            self._branch_repo_token = self.token
140daf82a51STom Stellard        self._llvm_project_dir = llvm_project_dir
141*f673dcc6STom Stellard        self._phab_token = phab_token
142daf82a51STom Stellard
143daf82a51STom Stellard    @property
144daf82a51STom Stellard    def token(self) -> str:
145daf82a51STom Stellard        return self._token
146daf82a51STom Stellard
147daf82a51STom Stellard    @property
148daf82a51STom Stellard    def repo_name(self) -> str:
149daf82a51STom Stellard        return self._repo_name
150daf82a51STom Stellard
151daf82a51STom Stellard    @property
152daf82a51STom Stellard    def issue_number(self) -> int:
153daf82a51STom Stellard        return self._issue_number
154daf82a51STom Stellard
155daf82a51STom Stellard    @property
156daf82a51STom Stellard    def branch_repo_name(self) -> str:
157daf82a51STom Stellard        return self._branch_repo_name
158daf82a51STom Stellard
159daf82a51STom Stellard    @property
160daf82a51STom Stellard    def branch_repo_token(self) -> str:
161daf82a51STom Stellard        return self._branch_repo_token
162daf82a51STom Stellard
163daf82a51STom Stellard    @property
164daf82a51STom Stellard    def llvm_project_dir(self) -> str:
165daf82a51STom Stellard        return self._llvm_project_dir
166daf82a51STom Stellard
167daf82a51STom Stellard    @property
168*f673dcc6STom Stellard    def phab_token(self) -> str:
169*f673dcc6STom Stellard        return self._phab_token
170*f673dcc6STom Stellard
171*f673dcc6STom Stellard    @property
172*f673dcc6STom Stellard    def repo(self) -> github.Repository.Repository:
173daf82a51STom Stellard        return github.Github(self.token).get_repo(self.repo_name)
174daf82a51STom Stellard
175daf82a51STom Stellard    @property
176daf82a51STom Stellard    def issue(self) -> github.Issue.Issue:
177*f673dcc6STom Stellard        return self.repo.get_issue(self.issue_number)
178daf82a51STom Stellard
179daf82a51STom Stellard    @property
180daf82a51STom Stellard    def push_url(self) -> str:
181daf82a51STom Stellard        return 'https://{}@github.com/{}'.format(self.branch_repo_token, self.branch_repo_name)
182daf82a51STom Stellard
183daf82a51STom Stellard    @property
184daf82a51STom Stellard    def branch_name(self) -> str:
185daf82a51STom Stellard        return 'issue{}'.format(self.issue_number)
186daf82a51STom Stellard
187daf82a51STom Stellard    @property
188daf82a51STom Stellard    def release_branch_for_issue(self) -> Optional[str]:
189daf82a51STom Stellard        issue = self.issue
190daf82a51STom Stellard        milestone = issue.milestone
191daf82a51STom Stellard        if milestone is None:
192daf82a51STom Stellard            return None
193daf82a51STom Stellard        m = re.search('branch: (.+)',milestone.description)
194daf82a51STom Stellard        if m:
195daf82a51STom Stellard            return m.group(1)
196daf82a51STom Stellard        return None
197daf82a51STom Stellard
198daf82a51STom Stellard    def print_release_branch(self) -> None:
199daf82a51STom Stellard        print(self.release_branch_for_issue)
200daf82a51STom Stellard
201daf82a51STom Stellard    def issue_notify_branch(self) -> None:
202daf82a51STom Stellard        self.issue.create_comment('/branch {}/{}'.format(self.branch_repo_name, self.branch_name))
203daf82a51STom Stellard
204daf82a51STom Stellard    def issue_notify_pull_request(self, pull:github.PullRequest.PullRequest) -> None:
205daf82a51STom Stellard        self.issue.create_comment('/pull-request {}#{}'.format(self.branch_repo_name, pull.number))
206daf82a51STom Stellard
20751eaaa30STom Stellard    def make_ignore_comment(self, comment: str) -> str:
20851eaaa30STom Stellard        """
20951eaaa30STom Stellard        Returns the comment string with a prefix that will cause
21051eaaa30STom Stellard        a Github workflow to skip parsing this comment.
21151eaaa30STom Stellard
21251eaaa30STom Stellard        :param str comment: The comment to ignore
21351eaaa30STom Stellard        """
21451eaaa30STom Stellard        return "<!--IGNORE-->\n"+comment
21551eaaa30STom Stellard
21651eaaa30STom Stellard    def issue_notify_no_milestone(self, comment:List[str]) -> None:
21751eaaa30STom Stellard        message = "{}\n\nError: Command failed due to missing milestone.".format(''.join(['>' + line for line in comment]))
21851eaaa30STom Stellard        self.issue.create_comment(self.make_ignore_comment(message))
21951eaaa30STom Stellard
220daf82a51STom Stellard    @property
221daf82a51STom Stellard    def action_url(self) -> str:
222daf82a51STom Stellard        if os.getenv('CI'):
223daf82a51STom Stellard            return 'https://github.com/{}/actions/runs/{}'.format(os.getenv('GITHUB_REPOSITORY'), os.getenv('GITHUB_RUN_ID'))
224daf82a51STom Stellard        return ""
225daf82a51STom Stellard
226daf82a51STom Stellard    def issue_notify_cherry_pick_failure(self, commit:str) -> github.IssueComment.IssueComment:
22751eaaa30STom Stellard        message = self.make_ignore_comment("Failed to cherry-pick: {}\n\n".format(commit))
228daf82a51STom Stellard        action_url = self.action_url
229daf82a51STom Stellard        if action_url:
230daf82a51STom Stellard            message += action_url + "\n\n"
231daf82a51STom Stellard        message += "Please manually backport the fix and push it to your github fork.  Once this is done, please add a comment like this:\n\n`/branch <user>/<repo>/<branch>`"
232daf82a51STom Stellard        issue = self.issue
233daf82a51STom Stellard        comment = issue.create_comment(message)
2343929f913STom Stellard        issue.add_to_labels(self.CHERRY_PICK_FAILED_LABEL)
235daf82a51STom Stellard        return comment
236daf82a51STom Stellard
237daf82a51STom Stellard    def issue_notify_pull_request_failure(self, branch:str) -> github.IssueComment.IssueComment:
238daf82a51STom Stellard        message = "Failed to create pull request for {} ".format(branch)
239daf82a51STom Stellard        message += self.action_url
240daf82a51STom Stellard        return self.issue.create_comment(message)
241daf82a51STom Stellard
2423929f913STom Stellard    def issue_remove_cherry_pick_failed_label(self):
2433929f913STom Stellard        if self.CHERRY_PICK_FAILED_LABEL in [l.name for l in self.issue.labels]:
2443929f913STom Stellard            self.issue.remove_from_labels(self.CHERRY_PICK_FAILED_LABEL)
245daf82a51STom Stellard
246*f673dcc6STom Stellard    def pr_request_review(self, pr:github.PullRequest.PullRequest):
247*f673dcc6STom Stellard        """
248*f673dcc6STom Stellard        This function will try to find the best reviewers for `commits` and
249*f673dcc6STom Stellard        then add a comment requesting review of the backport and assign the
250*f673dcc6STom Stellard        pull request to the selected reviewers.
251*f673dcc6STom Stellard
252*f673dcc6STom Stellard        The reviewers selected are those users who approved the patch in
253*f673dcc6STom Stellard        Phabricator.
254*f673dcc6STom Stellard        """
255*f673dcc6STom Stellard        reviewers = []
256*f673dcc6STom Stellard        for commit in pr.get_commits():
257*f673dcc6STom Stellard            approvers = phab_get_commit_approvers(self.phab_token, self.repo, commit)
258*f673dcc6STom Stellard            for a in approvers:
259*f673dcc6STom Stellard                login = phab_login_to_github_login(self.phab_token, self.repo, a)
260*f673dcc6STom Stellard                if not login:
261*f673dcc6STom Stellard                    continue
262*f673dcc6STom Stellard                reviewers.append(login)
263*f673dcc6STom Stellard        if len(reviewers):
264*f673dcc6STom Stellard            message = "{} What do you think about merging this PR to the release branch?".format(
265*f673dcc6STom Stellard                    " ".join(["@" + r for r in reviewers]))
266*f673dcc6STom Stellard            pr.create_issue_comment(message)
267*f673dcc6STom Stellard            pr.add_to_assignees(*reviewers)
268*f673dcc6STom Stellard
269daf82a51STom Stellard    def create_branch(self, commits:List[str]) -> bool:
270daf82a51STom Stellard        """
271daf82a51STom Stellard        This function attempts to backport `commits` into the branch associated
272daf82a51STom Stellard        with `self.issue_number`.
273daf82a51STom Stellard
274daf82a51STom Stellard        If this is successful, then the branch is pushed to `self.branch_repo_name`, if not,
275daf82a51STom Stellard        a comment is added to the issue saying that the cherry-pick failed.
276daf82a51STom Stellard
277daf82a51STom Stellard        :param list commits: List of commits to cherry-pick.
278daf82a51STom Stellard
279daf82a51STom Stellard        """
280daf82a51STom Stellard        print('cherry-picking', commits)
281daf82a51STom Stellard        branch_name = self.branch_name
282daf82a51STom Stellard        local_repo = Repo(self.llvm_project_dir)
283daf82a51STom Stellard        local_repo.git.checkout(self.release_branch_for_issue)
284daf82a51STom Stellard
285daf82a51STom Stellard        for c in commits:
286daf82a51STom Stellard            try:
287daf82a51STom Stellard                local_repo.git.cherry_pick('-x', c)
288daf82a51STom Stellard            except Exception as e:
289daf82a51STom Stellard                self.issue_notify_cherry_pick_failure(c)
290daf82a51STom Stellard                raise e
291daf82a51STom Stellard
292daf82a51STom Stellard        push_url = self.push_url
293daf82a51STom Stellard        print('Pushing to {} {}'.format(push_url, branch_name))
2941889170aSTobias Hieta        local_repo.git.push(push_url, 'HEAD:{}'.format(branch_name), force=True)
295daf82a51STom Stellard
296daf82a51STom Stellard        self.issue_notify_branch()
2973929f913STom Stellard        self.issue_remove_cherry_pick_failed_label()
298daf82a51STom Stellard        return True
299daf82a51STom Stellard
30049ad577cSTobias Hieta    def check_if_pull_request_exists(self, repo:github.Repository.Repository, head:str) -> bool:
30149ad577cSTobias Hieta        pulls = repo.get_pulls(head=head)
302fde9ef52STobias Hieta        return pulls.totalCount != 0
303daf82a51STom Stellard
304daf82a51STom Stellard    def create_pull_request(self, owner:str, branch:str) -> bool:
305daf82a51STom Stellard        """
306daf82a51STom Stellard        reate a pull request in `self.branch_repo_name`.  The base branch of the
307daf82a51STom Stellard        pull request will be choosen based on the the milestone attached to
308daf82a51STom Stellard        the issue represented by `self.issue_number`  For example if the milestone
309daf82a51STom Stellard        is Release 13.0.1, then the base branch will be release/13.x. `branch`
310daf82a51STom Stellard        will be used as the compare branch.
311daf82a51STom Stellard        https://docs.github.com/en/get-started/quickstart/github-glossary#base-branch
312daf82a51STom Stellard        https://docs.github.com/en/get-started/quickstart/github-glossary#compare-branch
313daf82a51STom Stellard        """
314daf82a51STom Stellard        repo = github.Github(self.token).get_repo(self.branch_repo_name)
315daf82a51STom Stellard        issue_ref = '{}#{}'.format(self.repo_name, self.issue_number)
316daf82a51STom Stellard        pull = None
317daf82a51STom Stellard        release_branch_for_issue = self.release_branch_for_issue
318daf82a51STom Stellard        if release_branch_for_issue is None:
319daf82a51STom Stellard            return False
32017d4796cSTom Stellard        head_branch = branch
32117d4796cSTom Stellard        if not repo.fork:
32217d4796cSTom Stellard            # If the target repo is not a fork of llvm-project, we need to copy
32317d4796cSTom Stellard            # the branch into the target repo.  GitHub only supports cross-repo pull
32417d4796cSTom Stellard            # requests on forked repos.
32517d4796cSTom Stellard            head_branch = f'{owner}-{branch}'
32617d4796cSTom Stellard            local_repo = Repo(self.llvm_project_dir)
32717d4796cSTom Stellard            push_done = False
32817d4796cSTom Stellard            for i in range(0,5):
32917d4796cSTom Stellard                try:
33017d4796cSTom Stellard                    local_repo.git.fetch(f'https://github.com/{owner}/llvm-project', f'{branch}:{branch}')
33117d4796cSTom Stellard                    local_repo.git.push(self.push_url, f'{branch}:{head_branch}', force=True)
33217d4796cSTom Stellard                    push_done = True
33317d4796cSTom Stellard                    break
33417d4796cSTom Stellard                except Exception as e:
33517d4796cSTom Stellard                    print(e)
33617d4796cSTom Stellard                    time.sleep(30)
33717d4796cSTom Stellard                    continue
33817d4796cSTom Stellard            if not push_done:
33917d4796cSTom Stellard                raise Exception("Failed to mirror branch into {}".format(self.push_url))
34017d4796cSTom Stellard            owner = repo.owner.login
34117d4796cSTom Stellard
34217d4796cSTom Stellard        head = f"{owner}:{head_branch}"
34349ad577cSTobias Hieta        if self.check_if_pull_request_exists(repo, head):
34449ad577cSTobias Hieta            print("PR already exists...")
34549ad577cSTobias Hieta            return True
346daf82a51STom Stellard        try:
34749ad577cSTobias Hieta            pull = repo.create_pull(title=f"PR for {issue_ref}",
348daf82a51STom Stellard                                    body='resolves {}'.format(issue_ref),
349daf82a51STom Stellard                                    base=release_branch_for_issue,
35049ad577cSTobias Hieta                                    head=head,
351daf82a51STom Stellard                                    maintainer_can_modify=False)
352*f673dcc6STom Stellard
353*f673dcc6STom Stellard            try:
354*f673dcc6STom Stellard                if self.phab_token:
355*f673dcc6STom Stellard                    self.pr_request_review(pull)
356*f673dcc6STom Stellard            except Exception as e:
357*f673dcc6STom Stellard                print("error: Failed while searching for reviewers", e)
358*f673dcc6STom Stellard
359daf82a51STom Stellard        except Exception as e:
360daf82a51STom Stellard            self.issue_notify_pull_request_failure(branch)
361daf82a51STom Stellard            raise e
362daf82a51STom Stellard
363daf82a51STom Stellard        if pull is None:
364daf82a51STom Stellard            return False
365daf82a51STom Stellard
366daf82a51STom Stellard        self.issue_notify_pull_request(pull)
3673929f913STom Stellard        self.issue_remove_cherry_pick_failed_label()
368daf82a51STom Stellard
369daf82a51STom Stellard        # TODO(tstellar): Do you really want to always return True?
370daf82a51STom Stellard        return True
371daf82a51STom Stellard
372daf82a51STom Stellard
373daf82a51STom Stellard    def execute_command(self) -> bool:
374daf82a51STom Stellard        """
375daf82a51STom Stellard        This function reads lines from STDIN and executes the first command
376daf82a51STom Stellard        that it finds.  The 2 supported commands are:
377daf82a51STom Stellard        /cherry-pick commit0 <commit1> <commit2> <...>
378daf82a51STom Stellard        /branch <owner>/<repo>/<branch>
379daf82a51STom Stellard        """
380daf82a51STom Stellard        for line in sys.stdin:
381daf82a51STom Stellard            line.rstrip()
382daf82a51STom Stellard            m = re.search("/([a-z-]+)\s(.+)", line)
383daf82a51STom Stellard            if not m:
384daf82a51STom Stellard                continue
385daf82a51STom Stellard            command = m.group(1)
386daf82a51STom Stellard            args = m.group(2)
387daf82a51STom Stellard
388daf82a51STom Stellard            if command == 'cherry-pick':
389daf82a51STom Stellard                return self.create_branch(args.split())
390daf82a51STom Stellard
391daf82a51STom Stellard            if command == 'branch':
392daf82a51STom Stellard                m = re.match('([^/]+)/([^/]+)/(.+)', args)
393daf82a51STom Stellard                if m:
394daf82a51STom Stellard                    owner = m.group(1)
395daf82a51STom Stellard                    branch = m.group(3)
396daf82a51STom Stellard                    return self.create_pull_request(owner, branch)
397daf82a51STom Stellard
398daf82a51STom Stellard        print("Do not understand input:")
399daf82a51STom Stellard        print(sys.stdin.readlines())
400daf82a51STom Stellard        return False
401a2adebf4STom Stellard
402a2adebf4STom Stellardparser = argparse.ArgumentParser()
403daf82a51STom Stellardparser.add_argument('--token', type=str, required=True, help='GitHub authentiation token')
404daf82a51STom Stellardparser.add_argument('--repo', type=str, default=os.getenv('GITHUB_REPOSITORY', 'llvm/llvm-project'),
405daf82a51STom Stellard                    help='The GitHub repository that we are working with in the form of <owner>/<repo> (e.g. llvm/llvm-project)')
406a2adebf4STom Stellardsubparsers = parser.add_subparsers(dest='command')
407a2adebf4STom Stellard
408a2adebf4STom Stellardissue_subscriber_parser = subparsers.add_parser('issue-subscriber')
409a2adebf4STom Stellardissue_subscriber_parser.add_argument('--label-name', type=str, required=True)
410a2adebf4STom Stellardissue_subscriber_parser.add_argument('--issue-number', type=int, required=True)
411a2adebf4STom Stellard
412daf82a51STom Stellardrelease_workflow_parser = subparsers.add_parser('release-workflow')
413daf82a51STom Stellardrelease_workflow_parser.add_argument('--llvm-project-dir', type=str, default='.', help='directory containing the llvm-project checout')
414daf82a51STom Stellardrelease_workflow_parser.add_argument('--issue-number', type=int, required=True, help='The issue number to update')
415*f673dcc6STom Stellardrelease_workflow_parser.add_argument('--phab-token', type=str, help='Phabricator conduit API token. See https://reviews.llvm.org/settings/user/<USER>/page/apitokens/')
416daf82a51STom Stellardrelease_workflow_parser.add_argument('--branch-repo-token', type=str,
417daf82a51STom Stellard                                     help='GitHub authentication token to use for the repository where new branches will be pushed. Defaults to TOKEN.')
41821c060c4STom Stellardrelease_workflow_parser.add_argument('--branch-repo', type=str, default='llvm/llvm-project-release-prs',
419daf82a51STom Stellard                                     help='The name of the repo where new branches will be pushed (e.g. llvm/llvm-project)')
420daf82a51STom Stellardrelease_workflow_parser.add_argument('sub_command', type=str, choices=['print-release-branch', 'auto'],
421daf82a51STom Stellard                                     help='Print to stdout the name of the release branch ISSUE_NUMBER should be backported to')
422daf82a51STom Stellard
423daf82a51STom Stellardllvmbot_git_config_parser = subparsers.add_parser('setup-llvmbot-git', help='Set the default user and email for the git repo in LLVM_PROJECT_DIR to llvmbot')
424daf82a51STom Stellard
425a2adebf4STom Stellardargs = parser.parse_args()
426a2adebf4STom Stellard
427a2adebf4STom Stellardif args.command == 'issue-subscriber':
428a2adebf4STom Stellard    issue_subscriber = IssueSubscriber(args.token, args.repo, args.issue_number, args.label_name)
429a2adebf4STom Stellard    issue_subscriber.run()
430daf82a51STom Stellardelif args.command == 'release-workflow':
431daf82a51STom Stellard    release_workflow = ReleaseWorkflow(args.token, args.repo, args.issue_number,
432daf82a51STom Stellard                                       args.branch_repo, args.branch_repo_token,
433*f673dcc6STom Stellard                                       args.llvm_project_dir, args.phab_token)
43451eaaa30STom Stellard    if not release_workflow.release_branch_for_issue:
43551eaaa30STom Stellard        release_workflow.issue_notify_no_milestone(sys.stdin.readlines())
43651eaaa30STom Stellard        sys.exit(1)
437daf82a51STom Stellard    if args.sub_command == 'print-release-branch':
438daf82a51STom Stellard        release_workflow.print_release_branch()
439daf82a51STom Stellard    else:
440daf82a51STom Stellard        if not release_workflow.execute_command():
441daf82a51STom Stellard            sys.exit(1)
442daf82a51STom Stellardelif args.command == 'setup-llvmbot-git':
443daf82a51STom Stellard    setup_llvmbot_git()
444