import github
import os
import re
+import requests
import sys
import time
from typing import *
config.set_value('user', 'name', 'llvmbot')
config.set_value('user', 'email', 'llvmbot@llvm.org')
+def phab_api_call(phab_token:str, url:str, args:dict) -> dict:
+ """
+ Make an API call to the Phabricator web service and return a dictionary
+ containing the json response.
+ """
+ data = { "api.token" : phab_token }
+ data.update(args)
+ response = requests.post(url, data = data)
+ return response.json()
+
+
+def phab_login_to_github_login(phab_token:str, repo:github.Repository.Repository, phab_login:str) -> str:
+ """
+ Tries to translate a Phabricator login to a github login by
+ finding a commit made in Phabricator's Differential.
+ The commit's SHA1 is then looked up in the github repo and
+ the committer's login associated with that commit is returned.
+
+ :param str phab_token: The Conduit API token to use for communication with Pabricator
+ :param github.Repository.Repository repo: The github repo to use when looking for the SHA1 found in Differential
+ :param str phab_login: The Phabricator login to be translated.
+ """
+
+ args = {
+ "constraints[authors][0]" : phab_login,
+ # PHID for "LLVM Github Monorepo" repository
+ "constraints[repositories][0]" : "PHID-REPO-f4scjekhnkmh7qilxlcy",
+ "limit" : 1
+ }
+ # API documentation: https://reviews.llvm.org/conduit/method/diffusion.commit.search/
+ r = phab_api_call(phab_token, "https://reviews.llvm.org/api/diffusion.commit.search", args)
+ data = r['result']['data']
+ if len(data) == 0:
+ # Can't find any commits associated with this user
+ return None
+
+ commit_sha = data[0]['fields']['identifier']
+ return repo.get_commit(commit_sha).committer.login
+
+def phab_get_commit_approvers(phab_token:str, repo:github.Repository.Repository, commit:github.Commit.Commit) -> list:
+ args = { "corpus" : commit.commit.message }
+ # API documentation: https://reviews.llvm.org/conduit/method/differential.parsecommitmessage/
+ r = phab_api_call(phab_token, "https://reviews.llvm.org/api/differential.parsecommitmessage", args)
+ review_id = r['result']['revisionIDFieldInfo']['value']
+
+ args = {
+ 'constraints[ids][0]' : review_id,
+ 'attachments[reviewers]' : True
+ }
+ # API documentation: https://reviews.llvm.org/conduit/method/differential.revision.search/
+ r = phab_api_call(phab_token, "https://reviews.llvm.org/api/differential.revision.search", args)
+ reviewers = r['result']['data'][0]['attachments']['reviewers']['reviewers']
+ accepted = []
+ for reviewer in reviewers:
+ if reviewer['status'] != 'accepted':
+ continue
+ phid = reviewer['reviewerPHID']
+ args = { 'constraints[phids][0]' : phid }
+ # API documentation: https://reviews.llvm.org/conduit/method/user.search/
+ r = phab_api_call(phab_token, "https://reviews.llvm.org/api/user.search", args)
+ accepted.append(r['result']['data'][0]['fields']['username'])
+ return accepted
+
class ReleaseWorkflow:
CHERRY_PICK_FAILED_LABEL = 'release:cherry-pick-failed'
def __init__(self, token:str, repo:str, issue_number:int,
branch_repo_name:str, branch_repo_token:str,
- llvm_project_dir:str) -> None:
+ llvm_project_dir:str, phab_token:str) -> None:
self._token = token
self._repo_name = repo
self._issue_number = issue_number
else:
self._branch_repo_token = self.token
self._llvm_project_dir = llvm_project_dir
+ self._phab_token = phab_token
@property
def token(self) -> str:
return self._llvm_project_dir
@property
- def __repo(self) -> github.Repository.Repository:
+ def phab_token(self) -> str:
+ return self._phab_token
+
+ @property
+ def repo(self) -> github.Repository.Repository:
return github.Github(self.token).get_repo(self.repo_name)
@property
def issue(self) -> github.Issue.Issue:
- return self.__repo.get_issue(self.issue_number)
+ return self.repo.get_issue(self.issue_number)
@property
def push_url(self) -> str:
if self.CHERRY_PICK_FAILED_LABEL in [l.name for l in self.issue.labels]:
self.issue.remove_from_labels(self.CHERRY_PICK_FAILED_LABEL)
+ def pr_request_review(self, pr:github.PullRequest.PullRequest):
+ """
+ This function will try to find the best reviewers for `commits` and
+ then add a comment requesting review of the backport and assign the
+ pull request to the selected reviewers.
+
+ The reviewers selected are those users who approved the patch in
+ Phabricator.
+ """
+ reviewers = []
+ for commit in pr.get_commits():
+ approvers = phab_get_commit_approvers(self.phab_token, self.repo, commit)
+ for a in approvers:
+ login = phab_login_to_github_login(self.phab_token, self.repo, a)
+ if not login:
+ continue
+ reviewers.append(login)
+ if len(reviewers):
+ message = "{} What do you think about merging this PR to the release branch?".format(
+ " ".join(["@" + r for r in reviewers]))
+ pr.create_issue_comment(message)
+ pr.add_to_assignees(*reviewers)
+
def create_branch(self, commits:List[str]) -> bool:
"""
This function attempts to backport `commits` into the branch associated
base=release_branch_for_issue,
head=head,
maintainer_can_modify=False)
+
+ try:
+ if self.phab_token:
+ self.pr_request_review(pull)
+ except Exception as e:
+ print("error: Failed while searching for reviewers", e)
+
except Exception as e:
self.issue_notify_pull_request_failure(branch)
raise e
release_workflow_parser = subparsers.add_parser('release-workflow')
release_workflow_parser.add_argument('--llvm-project-dir', type=str, default='.', help='directory containing the llvm-project checout')
release_workflow_parser.add_argument('--issue-number', type=int, required=True, help='The issue number to update')
+release_workflow_parser.add_argument('--phab-token', type=str, help='Phabricator conduit API token. See https://reviews.llvm.org/settings/user/<USER>/page/apitokens/')
release_workflow_parser.add_argument('--branch-repo-token', type=str,
help='GitHub authentication token to use for the repository where new branches will be pushed. Defaults to TOKEN.')
release_workflow_parser.add_argument('--branch-repo', type=str, default='llvm/llvm-project-release-prs',
elif args.command == 'release-workflow':
release_workflow = ReleaseWorkflow(args.token, args.repo, args.issue_number,
args.branch_repo, args.branch_repo_token,
- args.llvm_project_dir)
+ args.llvm_project_dir, args.phab_token)
if not release_workflow.release_branch_for_issue:
release_workflow.issue_notify_no_milestone(sys.stdin.readlines())
sys.exit(1)