13
13
import github
14
14
import os
15
15
import re
16
+ import requests
16
17
import sys
17
18
import time
18
19
from typing import *
@@ -48,6 +49,69 @@ def setup_llvmbot_git(git_dir = '.'):
48
49
config .set_value ('user' , 'name' , 'llvmbot' )
49
50
config .set_value ('user' , 'email' , 'llvmbot@llvm.org' )
50
51
52
+ def phab_api_call (phab_token :str , url :str , args :dict ) -> dict :
53
+ """
54
+ Make an API call to the Phabricator web service and return a dictionary
55
+ containing the json response.
56
+ """
57
+ data = { "api.token" : phab_token }
58
+ data .update (args )
59
+ response = requests .post (url , data = data )
60
+ return response .json ()
61
+
62
+
63
+ def phab_login_to_github_login (phab_token :str , repo :github .Repository .Repository , phab_login :str ) -> str :
64
+ """
65
+ Tries to translate a Phabricator login to a github login by
66
+ finding a commit made in Phabricator's Differential.
67
+ The commit's SHA1 is then looked up in the github repo and
68
+ the committer's login associated with that commit is returned.
69
+
70
+ :param str phab_token: The Conduit API token to use for communication with Pabricator
71
+ :param github.Repository.Repository repo: The github repo to use when looking for the SHA1 found in Differential
72
+ :param str phab_login: The Phabricator login to be translated.
73
+ """
74
+
75
+ args = {
76
+ "constraints[authors][0]" : phab_login ,
77
+ # PHID for "LLVM Github Monorepo" repository
78
+ "constraints[repositories][0]" : "PHID-REPO-f4scjekhnkmh7qilxlcy" ,
79
+ "limit" : 1
80
+ }
81
+ # API documentation: https://reviews.llvm.org/conduit/method/diffusion.commit.search/
82
+ r = phab_api_call (phab_token , "https://reviews.llvm.org/api/diffusion.commit.search" , args )
83
+ data = r ['result' ]['data' ]
84
+ if len (data ) == 0 :
85
+ # Can't find any commits associated with this user
86
+ return None
87
+
88
+ commit_sha = data [0 ]['fields' ]['identifier' ]
89
+ return repo .get_commit (commit_sha ).committer .login
90
+
91
+ def phab_get_commit_approvers (phab_token :str , repo :github .Repository .Repository , commit :github .Commit .Commit ) -> list :
92
+ args = { "corpus" : commit .commit .message }
93
+ # API documentation: https://reviews.llvm.org/conduit/method/differential.parsecommitmessage/
94
+ r = phab_api_call (phab_token , "https://reviews.llvm.org/api/differential.parsecommitmessage" , args )
95
+ review_id = r ['result' ]['revisionIDFieldInfo' ]['value' ]
96
+
97
+ args = {
98
+ 'constraints[ids][0]' : review_id ,
99
+ 'attachments[reviewers]' : True
100
+ }
101
+ # API documentation: https://reviews.llvm.org/conduit/method/differential.revision.search/
102
+ r = phab_api_call (phab_token , "https://reviews.llvm.org/api/differential.revision.search" , args )
103
+ reviewers = r ['result' ]['data' ][0 ]['attachments' ]['reviewers' ]['reviewers' ]
104
+ accepted = []
105
+ for reviewer in reviewers :
106
+ if reviewer ['status' ] != 'accepted' :
107
+ continue
108
+ phid = reviewer ['reviewerPHID' ]
109
+ args = { 'constraints[phids][0]' : phid }
110
+ # API documentation: https://reviews.llvm.org/conduit/method/user.search/
111
+ r = phab_api_call (phab_token , "https://reviews.llvm.org/api/user.search" , args )
112
+ accepted .append (r ['result' ]['data' ][0 ]['fields' ]['username' ])
113
+ return accepted
114
+
51
115
class ReleaseWorkflow :
52
116
53
117
CHERRY_PICK_FAILED_LABEL = 'release:cherry-pick-failed'
@@ -64,7 +128,7 @@ class ReleaseWorkflow:
64
128
65
129
def __init__ (self , token :str , repo :str , issue_number :int ,
66
130
branch_repo_name :str , branch_repo_token :str ,
67
- llvm_project_dir :str ) -> None :
131
+ llvm_project_dir :str , phab_token : str ) -> None :
68
132
self ._token = token
69
133
self ._repo_name = repo
70
134
self ._issue_number = issue_number
@@ -74,6 +138,7 @@ def __init__(self, token:str, repo:str, issue_number:int,
74
138
else :
75
139
self ._branch_repo_token = self .token
76
140
self ._llvm_project_dir = llvm_project_dir
141
+ self ._phab_token = phab_token
77
142
78
143
@property
79
144
def token (self ) -> str :
@@ -100,12 +165,16 @@ def llvm_project_dir(self) -> str:
100
165
return self ._llvm_project_dir
101
166
102
167
@property
103
- def __repo (self ) -> github .Repository .Repository :
168
+ def phab_token (self ) -> str :
169
+ return self ._phab_token
170
+
171
+ @property
172
+ def repo (self ) -> github .Repository .Repository :
104
173
return github .Github (self .token ).get_repo (self .repo_name )
105
174
106
175
@property
107
176
def issue (self ) -> github .Issue .Issue :
108
- return self .__repo .get_issue (self .issue_number )
177
+ return self .repo .get_issue (self .issue_number )
109
178
110
179
@property
111
180
def push_url (self ) -> str :
@@ -174,6 +243,29 @@ def issue_remove_cherry_pick_failed_label(self):
174
243
if self .CHERRY_PICK_FAILED_LABEL in [l .name for l in self .issue .labels ]:
175
244
self .issue .remove_from_labels (self .CHERRY_PICK_FAILED_LABEL )
176
245
246
+ def pr_request_review (self , pr :github .PullRequest .PullRequest ):
247
+ """
248
+ This function will try to find the best reviewers for `commits` and
249
+ then add a comment requesting review of the backport and assign the
250
+ pull request to the selected reviewers.
251
+
252
+ The reviewers selected are those users who approved the patch in
253
+ Phabricator.
254
+ """
255
+ reviewers = []
256
+ for commit in pr .get_commits ():
257
+ approvers = phab_get_commit_approvers (self .phab_token , self .repo , commit )
258
+ for a in approvers :
259
+ login = phab_login_to_github_login (self .phab_token , self .repo , a )
260
+ if not login :
261
+ continue
262
+ reviewers .append (login )
263
+ if len (reviewers ):
264
+ message = "{} What do you think about merging this PR to the release branch?" .format (
265
+ " " .join (["@" + r for r in reviewers ]))
266
+ pr .create_issue_comment (message )
267
+ pr .add_to_assignees (* reviewers )
268
+
177
269
def create_branch (self , commits :List [str ]) -> bool :
178
270
"""
179
271
This function attempts to backport `commits` into the branch associated
@@ -257,6 +349,13 @@ def create_pull_request(self, owner:str, branch:str) -> bool:
257
349
base = release_branch_for_issue ,
258
350
head = head ,
259
351
maintainer_can_modify = False )
352
+
353
+ try :
354
+ if self .phab_token :
355
+ self .pr_request_review (pull )
356
+ except Exception as e :
357
+ print ("error: Failed while searching for reviewers" , e )
358
+
260
359
except Exception as e :
261
360
self .issue_notify_pull_request_failure (branch )
262
361
raise e
@@ -313,6 +412,7 @@ def execute_command(self) -> bool:
313
412
release_workflow_parser = subparsers .add_parser ('release-workflow' )
314
413
release_workflow_parser .add_argument ('--llvm-project-dir' , type = str , default = '.' , help = 'directory containing the llvm-project checout' )
315
414
release_workflow_parser .add_argument ('--issue-number' , type = int , required = True , help = 'The issue number to update' )
415
+ release_workflow_parser .add_argument ('--phab-token' , type = str , help = 'Phabricator conduit API token. See https://reviews.llvm.org/settings/user/<USER>/page/apitokens/' )
316
416
release_workflow_parser .add_argument ('--branch-repo-token' , type = str ,
317
417
help = 'GitHub authentication token to use for the repository where new branches will be pushed. Defaults to TOKEN.' )
318
418
release_workflow_parser .add_argument ('--branch-repo' , type = str , default = 'llvm/llvm-project-release-prs' ,
@@ -330,7 +430,7 @@ def execute_command(self) -> bool:
330
430
elif args .command == 'release-workflow' :
331
431
release_workflow = ReleaseWorkflow (args .token , args .repo , args .issue_number ,
332
432
args .branch_repo , args .branch_repo_token ,
333
- args .llvm_project_dir )
433
+ args .llvm_project_dir , args . phab_token )
334
434
if not release_workflow .release_branch_for_issue :
335
435
release_workflow .issue_notify_no_milestone (sys .stdin .readlines ())
336
436
sys .exit (1 )
0 commit comments