[llvm] f673dcc - github: Automatically assign reviewers for backport requests

Tom Stellard via llvm-commits llvm-commits at lists.llvm.org
Tue Jul 26 15:30:45 PDT 2022


Author: Tom Stellard
Date: 2022-07-26T15:30:27-07:00
New Revision: f673dcc693da741e1662b516bbd8053e39a4fbc2

URL: https://github.com/llvm/llvm-project/commit/f673dcc693da741e1662b516bbd8053e39a4fbc2
DIFF: https://github.com/llvm/llvm-project/commit/f673dcc693da741e1662b516bbd8053e39a4fbc2.diff

LOG: github: Automatically assign reviewers for backport requests

When there is a backport request, the GitHub Action that handles the
backport will now automatically assign the issue to the user(s) who
approved the commit in Phabricator and create an issue comment asking
them to review the request.

Reviewed By: thieta, kwk

Differential Revision: https://reviews.llvm.org/D126423

Added: 
    

Modified: 
    .github/workflows/issue-release-workflow.yml
    llvm/utils/git/github-automation.py

Removed: 
    


################################################################################
diff  --git a/.github/workflows/issue-release-workflow.yml b/.github/workflows/issue-release-workflow.yml
index 6a62001f9ecfe..927ffb8ae550e 100644
--- a/.github/workflows/issue-release-workflow.yml
+++ b/.github/workflows/issue-release-workflow.yml
@@ -58,6 +58,7 @@ jobs:
           --token ${{ secrets.RELEASE_WORKFLOW_PUSH_SECRET }} \
           release-workflow \
           --issue-number ${{ github.event.issue.number }} \
+          --phab-token ${{ secrets.RELEASE_WORKFLOW_PHAB_TOKEN }} \
           auto
 
   create-pull-request:

diff  --git a/llvm/utils/git/github-automation.py b/llvm/utils/git/github-automation.py
index 3862ade65221e..8699bda7b18fc 100755
--- a/llvm/utils/git/github-automation.py
+++ b/llvm/utils/git/github-automation.py
@@ -13,6 +13,7 @@
 import github
 import os
 import re
+import requests
 import sys
 import time
 from typing import *
@@ -48,6 +49,69 @@ def setup_llvmbot_git(git_dir = '.'):
         config.set_value('user', 'name', 'llvmbot')
         config.set_value('user', 'email', 'llvmbot at 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/
diff usion.commit.search/
+    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/
diff usion.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/
diff erential.parsecommitmessage/
+    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/
diff erential.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/
diff erential.revision.search/
+    r = phab_api_call(phab_token, "https://reviews.llvm.org/api/
diff erential.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'
@@ -64,7 +128,7 @@ class ReleaseWorkflow:
 
     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
@@ -74,6 +138,7 @@ def __init__(self, token:str, repo:str, issue_number:int,
         else:
             self._branch_repo_token = self.token
         self._llvm_project_dir = llvm_project_dir
+        self._phab_token = phab_token
 
     @property
     def token(self) -> str:
@@ -100,12 +165,16 @@ def llvm_project_dir(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:
@@ -174,6 +243,29 @@ def issue_remove_cherry_pick_failed_label(self):
         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
@@ -257,6 +349,13 @@ def create_pull_request(self, owner:str, branch:str) -> bool:
                                     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
@@ -313,6 +412,7 @@ def execute_command(self) -> bool:
 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',
@@ -330,7 +430,7 @@ def execute_command(self) -> bool:
 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)


        


More information about the llvm-commits mailing list