Diff
Modified: trunk/Tools/ChangeLog (284127 => 284128)
--- trunk/Tools/ChangeLog 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/ChangeLog 2021-10-13 22:09:42 UTC (rev 284128)
@@ -1,3 +1,45 @@
+2021-10-13 Jonathan Bedard <[email protected]>
+
+ [webkitscmpy] List reviewers for pull-request
+ https://bugs.webkit.org/show_bug.cgi?id=231577
+ <rdar://problem/84146807>
+
+ Reviewed by Dewei Zhu.
+
+ * Scripts/libraries/webkitscmpy/setup.py: Bump version.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py:
+ (BitBucket.request): Add state to created pull-reqest.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py:
+ (GitHub):
+ (GitHub._users): Vend json representation of user for username.
+ (GitHub.request): Filter review details, add users endpoint.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py:
+ (PullRequest.main): Match both closed and opened pull-requests.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py:
+ (PullRequest): Remove state.
+ (PullRequest.__init__): Add opened flag, reviewers and generator.
+ (PullRequest.reviewers): List the users who are reviewing this change.
+ (PullRequest.approvers): List the users who have aproved this change.
+ (PullRequest.blockers): List the users that are blocking this change from landing.
+ (PullRequest.opened): Check the pull-request is opened.
+ (PullRequest.State): Deleted.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py:
+ (BitBucket.PRGenerator.PullRequest): Set opened flag, specify reviewers.
+ (BitBucket.PRGenerator.find): Filter by opened flag instead of state.
+ (BitBucket.PRGenerator.update): Set generator.
+ (BitBucket.PRGenerator.reviewers): Set reviewer lists.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py:
+ (GitHub.PRGenerator.PullRequest): Set opened flag, pass generator.
+ (GitHub.PRGenerator.find): Filter by opened flag instead of state.
+ (GitHub.PRGenerator.update): Set generator and opened flag.
+ (GitHub.PRGenerator._contributor): Find contributor matching username.
+ (GitHub.PRGenerator.reviewers): Populate lists of reviewers for a pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py:
+ (Scm.PRGenerator.find): Filter via opened flag instead of state.
+ (Scm.PRGenerator.reviewers): Function which populates reviewer lists for a pull-request.
+ * Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py:
+
2021-10-13 Alex Christensen <[email protected]>
Remove WTF::Variant and WTF::get
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/setup.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -29,7 +29,7 @@
setup(
name='webkitscmpy',
- version='2.2.8',
+ version='2.2.9',
description='Library designed to interact with git and svn repositories.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(2, 2, 8)
+version = Version(2, 2, 9)
AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
AutoInstall.register(Package('monotonic', Version(1, 5)))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -222,6 +222,7 @@
json['id'] = 1 + max([0] + [pr.get('id', 0) for pr in self.pull_requests])
json['fromRef']['displayId'] = json['fromRef']['id'].split('/')[-2:]
json['toRef']['displayId'] = json['toRef']['id'].split('/')[-2:]
+ json['state'] = 'OPEN'
self.pull_requests.append(json)
return mocks.Response.fromJson(json)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -59,6 +59,7 @@
self.head = self.commits[self.default_branch][-1]
self.tags = {}
self.pull_requests = []
+ self.users = dict()
self._environment = None
def __enter__(self):
@@ -281,6 +282,15 @@
), url=""
)
+ def _users(self, url, username):
+ user = self.users.get(username)
+ if not user:
+ return mocks.Response.create404(url)
+ return mocks.Response.fromJson(dict(
+ name=user.name,
+ email=user.email,
+ ), url=""
+
def request(self, method, url, data="" params=None, auth=None, json=None, **kwargs):
if not url.startswith('http://') and not url.startswith('https://'):
return mocks.Response.create404(url)
@@ -357,8 +367,16 @@
# Pull-request by number
if method == 'GET' and stripped_url.startswith(pr_base):
for candidate in self.pull_requests:
- if stripped_url.split('/')[-1] == str(candidate['number']):
- return mocks.Response.fromJson(candidate, url=""
+ if stripped_url.split('/')[5] == str(candidate['number']):
+ if len(stripped_url.split('/')) == 7:
+ if stripped_url.split('/')[6] == 'requested_reviewers':
+ return mocks.Response.fromJson(dict(users=candidate.get('requested_reviews', [])))
+ if stripped_url.split('/')[6] == 'reviews':
+ return mocks.Response.fromJson(candidate.get('reviews', []))
+ return mocks.Response.create404(url)
+ return mocks.Response.fromJson({
+ key: value for key, value in candidate.items() if key not in ('requested_reviews', 'reviews')
+ }, url=""
return mocks.Response.create404(url)
# Create/update pull-request
@@ -398,6 +416,10 @@
if existing is None:
return mocks.Response.create404(url)
self.pull_requests[existing].update(pr)
- return mocks.Response.fromJson(self.pull_requests[i], url=""
+ return mocks.Response.fromJson(self.pull_requests[existing], url=""
+ # Access user
+ if method == 'GET' and stripped_url.startswith('{}/users'.format(self.api_remote.split('/')[0])):
+ return self._users(url, stripped_url.split('/')[-1])
+
return mocks.Response.create404(url)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -121,7 +121,7 @@
sys.stderr.write("'{}' cannot generate pull-requests\n".format(rmt.url))
return 1
user, _ = rmt.credentials(required=True) if isinstance(rmt, remote.GitHub) else (repository.config()['user.email'], None)
- candidates = list(rmt.pull_requests.find(head=repository.branch))
+ candidates = list(rmt.pull_requests.find(opened=None, head=repository.branch))
commits = list(repository.commits(begin=dict(hash=branch_point.hash), end=dict(branch=repository.branch)))
title = commits[0].message.splitlines()[0]
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -44,10 +44,6 @@
'&': '&',
}
- class State(object):
- OPENED = 'opened'
- CLOSED = 'closed'
-
@classmethod
def escape_html(cls, message):
message = ''.join(cls.ESCAPE_TABLE.get(c, c) for c in message)
@@ -103,7 +99,7 @@
body = part.rstrip().lstrip()
return body or None, commits
- def __init__(self, number, title=None, body=None, author=None, head=None, base=None):
+ def __init__(self, number, title=None, body=None, author=None, head=None, base=None, opened=None, generator=None):
self.number = number
self.title = title
self.body, self.commits = self.parse_body(body)
@@ -110,6 +106,35 @@
self.author = author
self.head = head
self.base = base
+ self._opened = opened
+ self._reviewers = None
+ self._approvers = None
+ self._blockers = None
+ self.generator = generator
+ @property
+ def reviewers(self):
+ if self._reviewers is None and self.generator:
+ self.generator.reviewers(self)
+ return self._reviewers
+
+ @property
+ def approvers(self):
+ if self._approvers is None and self.generator:
+ self.generator.reviewers(self)
+ return self._approvers
+
+ @property
+ def blockers(self):
+ if self._blockers is None and self.generator:
+ self.generator.reviewers(self)
+ return self._blockers
+
+ @property
+ def opened(self):
+ if self._opened is None:
+ return '?'
+ return self._opened
+
def __repr__(self):
return 'PR {}{}'.format(self.number, ' | {}'.format(self.title) if self.title else '')
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -42,30 +42,52 @@
def PullRequest(self, data):
if not data:
return None
- return PullRequest(
+ result = PullRequest(
number=data['id'],
title=data.get('title'),
body=data.get('description'),
author=self.repository.contributors.create(
data['author']['user']['displayName'],
- data['author']['user']['emailAddress'],
+ data['author']['user'].get('emailAddress', None),
), head=data['fromRef']['displayId'],
base=data['toRef']['displayId'],
+ opened=True if data.get('open') else (False if data.get('closed') else None),
+ generator=self,
)
+ result._reviewers = []
+ result._approvers = []
+ result._blockers = []
+ for rdata in data.get('reviewers', []):
+ reviewer = self.repository.contributors.create(
+ rdata['user']['displayName'],
+ rdata['user'].get('emailAddress', None),
+ )
+ result._reviewers.append(reviewer)
+ if rdata.get('approved', False):
+ result._approvers.append(reviewer)
+ if rdata.get('status') == 'NEEDS_WORK':
+ result._blockers.append(reviewer)
+
+ result._reviewers = sorted(result._reviewers)
+ return result
+
def get(self, number):
return self.PullRequest(self.repository.request('pull-requests/{}'.format(int(number))))
- def find(self, state=None, head=None, base=None):
+ def find(self, opened=True, head=None, base=None):
+ assert opened in (True, False, None)
+
params = dict(
limit=100,
withProperties='false',
withAttributes='false',
)
- if state == PullRequest.State.OPENED:
+ if opened is True:
params['state'] = 'OPEN'
- if state == PullRequest.State.CLOSED:
+ elif not opened:
params['state'] = ['DECLINED', 'MERGED', 'SUPERSEDED']
+
if head:
params['direction'] = 'OUTGOING'
params['at'] = 'refs/heads/{}'.format(head)
@@ -75,6 +97,15 @@
continue
yield self.PullRequest(datum)
+ # Stash is bad at filter for open and closed PRs at the same time
+ if opened is None:
+ params['state'] = 'OPEN'
+ data = "" params=params)
+ for datum in data or []:
+ if base and not datum['toRef']['id'].endswith(base):
+ continue
+ yield self.PullRequest(datum)
+
def create(self, head, title, body=None, commits=None, base=None):
for key, value in dict(head=head, title=title).items():
if not value:
@@ -173,9 +204,15 @@
pull_request.author = self.repository.contributors.create(user['displayName'], user['emailAddress'])
pull_request.head = data.get('fromRef', {}).get('displayId', pull_request.base)
pull_request.base = data.get('toRef', {}).get('displayId', pull_request.base)
+ pull_request.generator = self
return pull_request
+ def reviewers(self, pull_request):
+ got = self.get(pull_request.number)
+ pull_request._reviewers = got._reviewers if got else []
+ pull_request._approvers = got._approvers if got else []
+ return pull_request
@classmethod
def is_webserver(cls, url):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -29,7 +29,7 @@
from datetime import datetime
from requests.auth import HTTPBasicAuth
from webkitcorepy import credentials, decorators
-from webkitscmpy import Commit, PullRequest
+from webkitscmpy import Commit, Contributor, PullRequest
from webkitscmpy.remote.scm import Scm
from xml.dom import minidom
@@ -49,17 +49,26 @@
author=self.repository.contributors.create(data['user']['login']),
head=data['head']['ref'],
base=data['base']['ref'],
+ opened=dict(
+ open=True,
+ closed=False,
+ ).get(data.get('state'), None),
+ generator=self,
)
def get(self, number):
return self.PullRequest(self.repository.request('pulls/{}'.format(int(number))))
- def find(self, state=None, head=None, base=None):
- if not state:
- state = 'all'
+ def find(self, opened=True, head=None, base=None):
+ assert opened in (True, False, None)
+
user, _ = self.repository.credentials()
data = "" params=dict(
- state=state,
+ state={
+ None: 'all',
+ True: 'open',
+ False: 'closed',
+ }.get(opened),
base=base,
head='{}:{}'.format(user, head) if user and head else head,
))
@@ -129,10 +138,52 @@
pull_request.author = self.repository.contributors.create(data['user']['login'])
pull_request.head = data.get('head', {}).get('displayId', pull_request.base)
pull_request.base = data.get('base', {}).get('displayId', pull_request.base)
+ pull_request._opened = dict(
+ open=True,
+ closed=False,
+ ).get(data.get('state'), None),
+ pull_request.generator = self
return pull_request
+ def _contributor(self, username):
+ result = self.repository.contributors.get(username, None)
+ if result:
+ return result
+ response = requests.get(
+ '{api_url}/users/{username}'.format(
+ api_url=self.repository.api_url,
+ username=username,
+ ), auth=HTTPBasicAuth(*self.repository.credentials(required=True)),
+ headers=dict(Accept='application/vnd.github.v3+json'),
+ )
+ if response.status_code // 100 != 2:
+ return Contributor(username)
+
+ data = ""
+ result = self.repository.contributors.create(data.get('name', username), data.get('email'))
+ result.github = username
+ self.repository.contributors[username] = result
+ return result
+
+ def reviewers(self, pull_request):
+ response = self.repository.request('pulls/{}/requested_reviewers'.format(pull_request.number))
+ pull_request._reviewers = [self._contributor(user['login']) for user in response.get('users', [])]
+ pull_request._approvers = []
+ pull_request._blockers = []
+ for review in self.repository.request('pulls/{}/reviews'.format(pull_request.number)):
+ contributor = self._contributor(review['user']['login'])
+ pull_request._reviewers.append(contributor)
+ if review.get('state') == 'APPROVED':
+ pull_request._approvers.append(contributor)
+ elif review.get('state') == 'CHANGES_REQUESTED':
+ pull_request._blockers.append(contributor)
+
+ pull_request._reviewers = sorted(pull_request._reviewers)
+ return pull_request
+
+
@classmethod
def is_webserver(cls, url):
return True if cls.URL_RE.match(url) else False
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -1,4 +1,4 @@
-# Copyright (C) 2020 Apple Inc. All rights reserved.
+# Copyright (C) 2020, 2021 Apple Inc. All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
@@ -33,7 +33,7 @@
def get(self, number):
raise NotImplementedError()
- def find(self, state=None, head=None, base=None):
+ def find(self, opened=True, head=None, base=None):
raise NotImplementedError()
def create(self, head, title, body=None, commits=None, base=None):
@@ -42,7 +42,10 @@
def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None):
raise NotImplementedError()
+ def reviewers(self, pull_request):
+ raise NotImplementedError()
+
@classmethod
def from_url(cls, url, contributors=None):
from webkitscmpy import remote
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py (284127 => 284128)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2021-10-13 21:40:32 UTC (rev 284127)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2021-10-13 22:09:42 UTC (rev 284128)
@@ -25,7 +25,7 @@
import unittest
from webkitcorepy import OutputCapture, testing
-from webkitscmpy import Commit, PullRequest, program, mocks, remote
+from webkitscmpy import Contributor, Commit, PullRequest, program, mocks, remote
class TestPullRequest(unittest.TestCase):
@@ -433,6 +433,12 @@
@classmethod
def webserver(cls):
result = mocks.remote.GitHub()
+ result.users = dict(
+ ereviewer=Contributor('Eager Reviewer', ['[email protected]'], github='ereviewer'),
+ rreviewer=Contributor('Reluctant Reviewer', ['[email protected]'], github='rreviewer'),
+ sreviewer=Contributor('Suspicious Reviewer', ['[email protected]'], github='sreviewer'),
+ tcontributor=Contributor('Tim Contributor', ['[email protected]']),
+ )
result.pull_requests = [dict(
number=1,
state='open',
@@ -450,6 +456,11 @@
''',
head=dict(ref='eng/pull-request'),
base=dict(ref='main'),
+ requested_reviews=[dict(login='rreviewer')],
+ reviews=[
+ dict(user=dict(login='ereviewer'), state='APPROVED'),
+ dict(user=dict(login='sreviewer'), state='CHANGES_REQUESTED'),
+ ],
)]
return result
@@ -466,11 +477,23 @@
with self.webserver():
pr = remote.GitHub(self.remote).pull_requests.get(1)
self.assertEqual(pr.number, 1)
+ self.assertTrue(pr.opened)
self.assertEqual(pr.title, 'Example Change')
self.assertEqual(pr.head, 'eng/pull-request')
self.assertEqual(pr.base, 'main')
+ def test_reviewers(self):
+ with self.webserver():
+ pr = remote.GitHub(self.remote).pull_requests.get(1)
+ self.assertEqual(pr.reviewers, [
+ Contributor('Eager Reviewer', ['[email protected]']),
+ Contributor('Reluctant Reviewer', ['[email protected]']),
+ Contributor('Suspicious Reviewer', ['[email protected]']),
+ ])
+ self.assertEqual(pr.approvers, [Contributor('Eager Reviewer', ['[email protected]'])])
+ self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['[email protected]'])])
+
class TestNetworkPullRequestBitBucket(unittest.TestCase):
remote = 'https://bitbucket.example.com/projects/WEBKIT/repos/webkit'
@@ -480,6 +503,8 @@
result.pull_requests = [dict(
id=1,
state='OPEN',
+ open=True,
+ closed=False,
title='Example Change',
author=dict(
user=dict(
@@ -499,6 +524,24 @@
''',
fromRef=dict(displayId='eng/pull-request'),
toRef=dict(displayId='main'),
+ reviewers=[
+ dict(
+ user=dict(
+ displayName='Reluctant Reviewer',
+ emailAddress='[email protected]',
+ ), approved=False,
+ ), dict(
+ user=dict(
+ displayName='Eager Reviewer',
+ emailAddress='[email protected]',
+ ), approved=True,
+ ), dict(
+ user=dict(
+ displayName='Suspicious Reviewer',
+ emailAddress='[email protected]',
+ ), status='NEEDS_WORK',
+ ),
+ ],
)]
return result
@@ -516,6 +559,18 @@
with self.webserver():
pr = remote.BitBucket(self.remote).pull_requests.get(1)
self.assertEqual(pr.number, 1)
+ self.assertTrue(pr.opened)
self.assertEqual(pr.title, 'Example Change')
self.assertEqual(pr.head, 'eng/pull-request')
self.assertEqual(pr.base, 'main')
+
+ def test_reviewers(self):
+ with self.webserver():
+ pr = remote.BitBucket(self.remote).pull_requests.get(1)
+ self.assertEqual(pr.reviewers, [
+ Contributor('Eager Reviewer', ['[email protected]']),
+ Contributor('Reluctant Reviewer', ['[email protected]']),
+ Contributor('Suspicious Reviewer', ['[email protected]']),
+ ])
+ self.assertEqual(pr.approvers, [Contributor('Eager Reviewer', ['[email protected]'])])
+ self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['[email protected]'])])