Changeset 284892 in webkit
- Timestamp:
- Oct 26, 2021, 1:10:33 PM (5 years ago)
- Location:
- trunk/Tools
- Files:
-
- 10 edited
-
ChangeLog (modified) (1 diff)
-
Scripts/libraries/webkitscmpy/setup.py (modified) (1 diff)
-
Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (modified) (1 diff)
-
Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (modified) (5 diffs)
-
Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py (modified) (6 diffs)
-
Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py (modified) (4 diffs)
-
Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py (modified) (3 diffs)
-
Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py (modified) (6 diffs)
-
Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py (modified) (1 diff)
-
Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py (modified) (4 diffs)
Legend:
- Unmodified
- Added
- Removed
-
trunk/Tools/ChangeLog
r284891 r284892 1 2021-10-26 Jonathan Bedard <jbedard@apple.com> 2 3 [webkitscmpy] Comment and close pull-requests 4 https://bugs.webkit.org/show_bug.cgi?id=232095 5 <rdar://problem/84515738> 6 7 Reviewed by Dewei Zhu. 8 9 * Scripts/libraries/webkitscmpy/setup.py: Bump version. 10 * Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py: Ditto. 11 * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py: 12 (BitBucket.request): Add support for activities, opening and closing pull-request. 13 * Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py: 14 (GitHub.__init__): Add issues. 15 (GitHub.request): Access issue underlying pull-request (which includes global comments). 16 * Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py: 17 (PullRequest.Exception): Added. 18 (PullRequest.Comment): Added. 19 (PullRequest.__init__): Add list of pull-request comments, metadata used by generator. 20 (PullRequest.open): Re-open the pull-request. 21 (PullRequest.close): Close the pull-request. 22 (PullRequest.comment): Make a comment on the pull-request. 23 (PullRequest.comments): List all comments on a pull-request. 24 * Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py: 25 (BitBucket.PRGenerator.update): Handle closing and opening of the pull-request. 26 (BitBucket.PRGenerator.comment): Make a comment on a pull-request. 27 (BitBucket.PRGenerator.comments): List all comments on a pull-request. 28 * Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py: 29 (GitHub.PRGenerator.update): Handle closing and opening of the pull-request. 30 (GitHub.PRGenerator.comment): Make a comment on a issue underpinning a pull-request. 31 (GitHub.PRGenerator.comments): List all comments on the pull-request. 32 * Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py: 33 (Scm.PRGenerator.update): Add support for opening and closing a pull-request. 34 (Scm.PRGenerator.comment): Make a comment on a pull-request. 35 (Scm.PRGenerator.comments): List all comments for a pull-request 36 * Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py: 37 1 38 2021-10-26 Simon Fraser <simon.fraser@apple.com> 2 39 -
trunk/Tools/Scripts/libraries/webkitscmpy/setup.py
r284498 r284892 30 30 setup( 31 31 name='webkitscmpy', 32 version='2.2.1 5',32 version='2.2.16', 33 33 description='Library designed to interact with git and svn repositories.', 34 34 long_description=readme(), -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py
r284498 r284892 47 47 ) 48 48 49 version = Version(2, 2, 1 5)49 version = Version(2, 2, 16) 50 50 51 51 AutoInstall.register(Package('fasteners', Version(0, 15, 0))) -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py
r284128 r284892 23 23 import os 24 24 import json 25 import time 25 26 26 27 from webkitcorepy import mocks … … 208 209 if at and candidate.get('fromRef', {}).get('id') != at: 209 210 continue 210 prs.append( candidate)211 prs.append({key: value for key, value in candidate.items() if key != 'activities'}) 211 212 212 213 return mocks.Response.fromJson(dict( … … 224 225 json['toRef']['displayId'] = json['toRef']['id'].split('/')[-2:] 225 226 json['state'] = 'OPEN' 227 json['activities'] = [] 226 228 self.pull_requests.append(json) 227 229 return mocks.Response.fromJson(json) … … 229 231 # Update or access pull-request 230 232 if stripped_url.startswith(pr_base): 231 number = int(stripped_url.split('/')[-1]) 233 split_url = stripped_url.split('/') 234 number = int(split_url[9]) 232 235 existing = None 233 236 for i in range(len(self.pull_requests)): … … 238 241 if method == 'PUT': 239 242 self.pull_requests[existing].update(json) 240 return mocks.Response.fromJson(self.pull_requests[existing]) 243 if len(split_url) < 11: 244 return mocks.Response.fromJson({key: value for key, value in self.pull_requests[existing].items() if key != 'activities'}) 245 246 if method == 'GET' and split_url[-1] == 'activities': 247 return mocks.Response.fromJson(dict( 248 size=len(self.pull_requests[existing].get('activities', [])), 249 isLastPage=True, 250 values=self.pull_requests[existing].get('activities', []), 251 )) 252 if method == 'POST' and split_url[-1] == 'comments': 253 self.pull_requests[existing]['activities'].append(dict(comment=dict( 254 author=dict(displayName='Tim Committer', emailAddress='committer@webkit.org'), 255 createdDate=int(time.time() * 1000), 256 updatedDate=int(time.time() * 1000), 257 text=json.get('text', ''), 258 ))) 259 return mocks.Response.fromJson({}) 260 if method == 'POST' and split_url[-1] == 'decline': 261 self.pull_requests[existing]['open'] = False 262 self.pull_requests[existing]['closed'] = True 263 self.pull_requests[existing]['state'] = 'DECLINED' 264 return mocks.Response.fromJson({}) 265 if method == 'POST' and split_url[-1] == 'reopen': 266 self.pull_requests[existing]['open'] = True 267 self.pull_requests[existing]['closed'] = False 268 self.pull_requests[existing]['state'] = 'OPEN' 269 return mocks.Response.fromJson({}) 270 return mocks.Response.create404(url) 241 271 242 272 return mocks.Response.create404(url) -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py
r284128 r284892 23 23 import os 24 24 import json 25 import time 25 26 26 27 from webkitcorepy import mocks … … 60 61 self.tags = {} 61 62 self.pull_requests = [] 63 self.issues = dict() 62 64 self.users = dict() 63 65 self._environment = None … … 293 295 294 296 def request(self, method, url, data=None, params=None, auth=None, json=None, **kwargs): 297 from datetime import datetime, timedelta 298 295 299 if not url.startswith('http://') and not url.startswith('https://'): 296 300 return mocks.Response.create404(url) … … 399 403 user=dict(login=self.remote.split('/')[-2]), 400 404 ) 405 if json.get('state'): 406 pr['state'] = json.get('state') 401 407 402 408 # Create specifically … … 404 410 pr['number'] = 1 + max([0] + [pr.get('number', 0) for pr in self.pull_requests]) 405 411 pr['user'] = dict(login=auth.username) 412 pr['_links'] = dict(issue=dict(href='https://{}/issues/{}'.format(self.api_remote, pr['number']))) 406 413 self.pull_requests.append(pr) 407 414 return mocks.Response.fromJson(pr, url=url) … … 423 430 return self._users(url, stripped_url.split('/')[-1]) 424 431 432 # Access underlying issue 433 if stripped_url.startswith('{}/issues/'.format(self.api_remote)): 434 number = int(stripped_url.split('/')[5]) 435 issue = self.issues.get(number, dict(comments=[])) 436 if method == 'GET' and stripped_url.split('/')[6] == 'comments': 437 return mocks.Response.fromJson(issue['comments'], url=url) 438 if method == 'POST' and stripped_url.split('/')[6] == 'comments': 439 self.issues[number] = issue 440 now = datetime.utcfromtimestamp(int(time.time()) - timedelta(hours=7).seconds).strftime('%Y-%m-%dT%H:%M:%SZ') 441 self.issues[number]['comments'].append(dict( 442 user=dict(login=auth.username), 443 created_at=now, updated_at=now, 444 body=json.get('body', ''), 445 )) 446 return mocks.Response.fromJson(issue['comments'], url=url) 447 return mocks.Response.create404(url) 448 425 449 return mocks.Response.create404(url) -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/pull_request.py
r284128 r284892 22 22 23 23 import re 24 import six 24 25 25 26 from .commit import Commit 27 from datetime import datetime 28 from webkitscmpy import Contributor 26 29 27 30 28 31 class PullRequest(object): 32 class Exception(RuntimeError): 33 pass 34 35 class Comment(object): 36 def __init__(self, author, timestamp, content): 37 if author and isinstance(author, dict) and author.get('name'): 38 self.author = Contributor(author.get('name'), author.get('emails')) 39 elif author and isinstance(author, six.string_types) and '@' in author: 40 self.author = Contributor(author, [author]) 41 elif author and not isinstance(author, Contributor): 42 raise TypeError("Expected 'author' to be of type {}, got '{}'".format(Contributor, author)) 43 else: 44 self.author = author 45 46 if isinstance(timestamp, six.string_types) and timestamp.isdigit(): 47 timestamp = int(timestamp) 48 if timestamp and not isinstance(timestamp, int): 49 raise TypeError("Expected 'timestamp' to be of type int, got '{}'".format(timestamp)) 50 self.timestamp = timestamp 51 52 if content and not isinstance(content, six.string_types): 53 raise ValueError("Expected 'content' to be a string, got '{}'".format(content)) 54 self.content = content 55 56 def __repr__(self): 57 return '({} @ {}) {}'.format( 58 self.author, 59 datetime.utcfromtimestamp(self.timestamp) if self.timestamp else '-', 60 self.content, 61 ) 62 63 29 64 COMMIT_BODY_RES = [ 30 65 dict( … … 100 135 return body or None, commits 101 136 102 def __init__(self, number, title=None, body=None, author=None, head=None, base=None, opened=None, generator=None): 137 def __init__( 138 self, number, title=None, 139 body=None, author=None, 140 head=None, base=None, 141 opened=None, generator=None, metadata=None, 142 ): 103 143 self.number = number 104 144 self.title = title … … 111 151 self._approvers = None 112 152 self._blockers = None 153 self._metadata = metadata 154 self._comments = None 113 155 self.generator = generator 114 156 … … 137 179 return self._opened 138 180 181 def open(self): 182 if self.opened is True: 183 return self 184 if not self.generator: 185 raise self.Exception('No associated pull-request generator') 186 return self.generator.update(self, opened=True) 187 188 def close(self): 189 if self.opened is False: 190 return self 191 if not self.generator: 192 raise self.Exception('No associated pull-request generator') 193 return self.generator.update(self, opened=False) 194 195 def comment(self, content): 196 if not self.generator: 197 raise self.Exception('No associated pull-request generator') 198 self.generator.comment(self, content) 199 self._comments = None 200 return self 201 202 @property 203 def comments(self): 204 if self._comments is None and self.generator: 205 self._comments = list(self.generator.comments(self)) 206 return self._comments 207 139 208 def __repr__(self): 140 209 return 'PR {}{}'.format(self.number, ' | {}'.format(self.title) if self.title else '') -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/bitbucket.py
r284479 r284892 143 143 return self.PullRequest(response.json()) 144 144 145 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None ):145 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None): 146 146 if not isinstance(pull_request, PullRequest): 147 raise ValueError( 148 "Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request))) 147 raise ValueError("Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request))) 148 149 pr_url = 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}'.format( 150 domain=self.repository.domain, 151 project=self.repository.project, 152 name=self.repository.name, 153 id=pull_request.number, 154 ) 155 156 if opened is not None: 157 response = requests.get(pr_url) 158 if response.status_code // 100 != 2: 159 return None 160 response = requests.post( 161 '{}/{}'.format(pr_url, 'reopen' if opened else 'decline'), 162 json=dict(version=response.json().get('version', 0)), 163 ) 164 if response.status_code // 100 != 2: 165 return None 166 167 pull_request._opened = opened 168 if not any((head, title, body, commits, base)): 169 return pull_request 170 149 171 if not any((head, title, body, commits, base)): 150 172 raise ValueError('No arguments to update pull-request provided') … … 177 199 ) 178 200 179 pr_url = 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}'.format(180 domain=self.repository.domain,181 project=self.repository.project,182 name=self.repository.name,183 id=pull_request.number,184 )185 201 response = requests.get(pr_url) 186 202 if response.status_code // 100 != 2: … … 213 229 pull_request._approvers = got._approvers if got else [] 214 230 return pull_request 231 232 def comment(self, pull_request, content): 233 response = requests.post( 234 'https://{domain}/rest/api/1.0/projects/{project}/repos/{name}/pull-requests/{id}/comments'.format( 235 domain=self.repository.domain, 236 project=self.repository.project, 237 name=self.repository.name, 238 id=pull_request.number, 239 ), json=dict(text=content), 240 ) 241 if response.status_code // 100 != 2: 242 sys.stderr.write("Failed to add comment to '{}'\n".format(pull_request)) 243 244 def comments(self, pull_request): 245 for action in reversed(self.repository.request('pull-requests/{}/activities'.format(pull_request.number)) or []): 246 comment = action.get('comment', {}) 247 user = comment.get('author', {}) 248 if not comment or not user or not comment.get('text'): 249 continue 250 251 yield PullRequest.Comment( 252 author=self.repository.contributors.create(user['displayName'], user['emailAddress']), 253 timestamp=comment.get('updatedDate', comment.get('createdDate')) // 1000, 254 content=comment.get('text'), 255 ) 256 215 257 216 258 @classmethod -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/git_hub.py
r284479 r284892 55 55 ).get(data.get('state'), None), 56 56 generator=self, 57 metadata=dict( 58 issue=data.get('_links', {}).get('issue', {}).get('href'), 59 ), 57 60 ) 58 61 … … 104 107 return self.PullRequest(response.json()) 105 108 106 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None ):109 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None): 107 110 if not isinstance(pull_request, PullRequest): 108 111 raise ValueError("Expected 'pull_request' to be of type '{}' not '{}'".format(PullRequest, type(pull_request))) 109 if not any((head, title, body, commits, base)) :112 if not any((head, title, body, commits, base)) and opened is None: 110 113 raise ValueError('No arguments to update pull-request provided') 111 114 … … 118 121 if body or commits: 119 122 updates['body'] = PullRequest.create_body(body, commits) 123 if opened is not None: 124 updates['state'] = 'open' if opened else 'closed' 120 125 response = requests.post( 121 126 '{api_url}/repos/{owner}/{name}/pulls/{number}'.format( … … 128 133 json=updates, 129 134 ) 135 if response.status_code == 422: 136 pull_request._opened = False 137 return pull_request 130 138 if response.status_code // 100 != 2: 131 139 return None … … 142 150 open=True, 143 151 closed=False, 144 ).get(data.get('state'), None) ,152 ).get(data.get('state'), None) 145 153 pull_request.generator = self 154 pull_request._metadata = dict( 155 issue=data.get('_links', {}).get('issue', {}).get('href'), 156 ) 146 157 147 158 return pull_request … … 184 195 pull_request._reviewers = sorted(pull_request._reviewers) 185 196 return pull_request 197 198 def comment(self, pull_request, content): 199 issue = pull_request._metadata.get('issue') 200 if not issue: 201 old = pull_request 202 pull_request = self.get(old.number) 203 pull_request._reviewers = old._reviewers 204 pull_request._approvers = old._approvers 205 pull_request._blockers = old._blockers 206 issue = pull_request._metadata.get('issue') 207 if not issue: 208 raise self.repository.Exception('Failed to find issue underlying pull-request') 209 response = requests.post( 210 '{}/comments'.format(issue), 211 auth=HTTPBasicAuth(*self.repository.credentials(required=True)), 212 headers=dict(Accept='application/vnd.github.v3+json'), 213 json=dict(body=content), 214 ) 215 if response.status_code // 100 != 2: 216 sys.stderr.write("Failed to add comment to '{}'\n".format(pull_request)) 217 218 def comments(self, pull_request): 219 issue = pull_request._metadata.get('issue') 220 if not issue: 221 old = pull_request 222 pull_request = self.get(old.number) 223 pull_request._reviewers = old._reviewers 224 pull_request._approvers = old._approvers 225 pull_request._blockers = old._blockers 226 issue = pull_request._metadata.get('issue') 227 if not issue: 228 raise self.repository.Exception('Failed to find issue underlying pull-request') 229 response = requests.get( 230 '{}/comments'.format(issue), 231 auth=HTTPBasicAuth(*self.repository.credentials()), 232 headers=dict(Accept='application/vnd.github.v3+json'), 233 ) 234 for node in response.json() if response.status_code // 100 == 2 else []: 235 user = node.get('user', {}).get('login') 236 if not user: 237 continue 238 tm = node.get('updated_at', node.get('created_at')) 239 if tm: 240 tm = int(calendar.timegm(datetime.strptime(tm, '%Y-%m-%dT%H:%M:%SZ').timetuple())) 241 242 yield PullRequest.Comment( 243 author=self._contributor(user), 244 timestamp=tm, 245 content=node.get('body'), 246 ) 186 247 187 248 -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/remote/scm.py
r284128 r284892 40 40 raise NotImplementedError() 41 41 42 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None ):42 def update(self, pull_request, head=None, title=None, body=None, commits=None, base=None, opened=None): 43 43 raise NotImplementedError() 44 44 45 45 def reviewers(self, pull_request): 46 raise NotImplementedError() 47 48 def comment(self, pull_request, content): 49 raise NotImplementedError() 50 51 def comments(self, pull_request): 46 52 raise NotImplementedError() 47 53 -
trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py
r284479 r284892 461 461 dict(user=dict(login='ereviewer'), state='APPROVED'), 462 462 dict(user=dict(login='sreviewer'), state='CHANGES_REQUESTED'), 463 ], 463 ], _links=dict( 464 issue=dict(href='https://{}/issues/1'.format(result.api_remote)), 465 ), 464 466 )] 465 467 return result … … 510 512 self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['sreviewer@webkit.org'])]) 511 513 514 def test_comments(self): 515 with self.webserver(): 516 repo = remote.GitHub(self.remote) 517 pr = repo.pull_requests.get(1) 518 self.assertEqual(pr.comments, []) 519 pr.comment('Commenting!') 520 self.assertEqual([c.content for c in pr.comments], ['Commenting!']) 521 522 def test_open_close(self): 523 with self.webserver(): 524 repo = remote.GitHub(self.remote) 525 pr = repo.pull_requests.get(1) 526 self.assertTrue(pr.opened) 527 pr.close() 528 self.assertFalse(pr.opened) 529 pr.open() 530 self.assertTrue(pr.opened) 531 512 532 513 533 class TestNetworkPullRequestBitBucket(unittest.TestCase): … … 522 542 open=True, 523 543 closed=False, 544 activities=[], 524 545 title='Example Change', 525 546 author=dict( … … 607 628 self.assertEqual(pr.approvers, []) 608 629 self.assertEqual(pr.blockers, [Contributor('Suspicious Reviewer', ['sreviewer@webkit.org'])]) 630 631 def test_comments(self): 632 with self.webserver(): 633 repo = remote.BitBucket(self.remote) 634 pr = repo.pull_requests.get(1) 635 self.assertEqual(pr.comments, []) 636 pr.comment('Commenting!') 637 self.assertEqual([c.content for c in pr.comments], ['Commenting!']) 638 639 def test_open_close(self): 640 with self.webserver(): 641 repo = remote.BitBucket(self.remote) 642 pr = repo.pull_requests.get(1) 643 self.assertTrue(pr.opened) 644 pr.close() 645 self.assertFalse(pr.opened) 646 pr.open() 647 self.assertTrue(pr.opened)
Note:
See TracChangeset
for help on using the changeset viewer.