From 306711f7276dcf0169fdf9c6c775f874fb11227a Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 15:52:25 -0700 Subject: [PATCH 01/14] First pass at auto-assigning issues --- sympy_bot/webapp.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index f767ee8..dcdc342 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -3,6 +3,7 @@ import os import base64 from subprocess import CalledProcessError +import re from aiohttp import web, ClientSession @@ -66,6 +67,8 @@ async def pull_request_edited(event, gh, *args, **kwargs): await pull_request_comment(event, gh) + await pull_request_assign_issue(event, gh) + async def pull_request_comment(event, gh): comments_url = event.data["pull_request"]["comments_url"] number = event.data["pull_request"]["number"] @@ -274,3 +277,28 @@ async def error_comment(event, gh, message): description='There was an error updating the release notes on the wiki.', context='sympy-bot/release-notes', )) + +FIXES_ISSUE = re.compile(r'(?:fixes|closes) +#(\d+)') + +async def pull_request_assign_issue(event, gh): + commits_url = event.data["pull_request"]["commits_url"] + commits = gh.getiter(commits_url) + user = event.data['pull_request']['user']['login'] + body = event.data['pull_request']['body'] + number = event.data["pull_request"]["number"] + fixed_issues = set() + + for m in FIXES_ISSUE.finditer(body): + fixed_issues.add(m.group(1)) + + async for commit in commits: + message = commit['commit']['message'] + for m in FIXES_ISSUE.finditer(message): + fixed_issues.add(m.group(1)) + + issues_url = event.data['pull_request']['base']['repo']['issues_url'] + + for issue_number in sorted(fixed_issues): + print(f"#{number}: Assigning {user} to issue {issue_number}") + gh.post(issues_url.replace('{/number}', issue_number) + 'assignees', + data=dict(assignees=[user])) From eacbaa1435c378df24dd86019085a801bdb01604 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 15:57:37 -0700 Subject: [PATCH 02/14] Fix async/await issue --- sympy_bot/webapp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index dcdc342..f285d44 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -300,5 +300,5 @@ async def pull_request_assign_issue(event, gh): for issue_number in sorted(fixed_issues): print(f"#{number}: Assigning {user} to issue {issue_number}") - gh.post(issues_url.replace('{/number}', issue_number) + 'assignees', - data=dict(assignees=[user])) + await gh.post(issues_url.replace('{/number}', issue_number) + 'assignees', + data=dict(assignees=[user])) From 878c19b5bc8bd21dc3933cfc527aece634d56349 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:00:40 -0700 Subject: [PATCH 03/14] Debug --- sympy_bot/webapp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index f285d44..1506741 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -291,6 +291,7 @@ async def pull_request_assign_issue(event, gh): for m in FIXES_ISSUE.finditer(body): fixed_issues.add(m.group(1)) + print(f"DEBUG: fixed issues={fixed_issues}") async for commit in commits: message = commit['commit']['message'] for m in FIXES_ISSUE.finditer(message): From 62fd3b24e7e79b445623b1de703a56789405f32e Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:02:24 -0700 Subject: [PATCH 04/14] Fix the regular expression to ignore case --- sympy_bot/webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 1506741..5bfe33e 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -278,7 +278,7 @@ async def error_comment(event, gh, message): context='sympy-bot/release-notes', )) -FIXES_ISSUE = re.compile(r'(?:fixes|closes) +#(\d+)') +FIXES_ISSUE = re.compile(r'(?:fixes|closes) +#(\d+)', re.I) async def pull_request_assign_issue(event, gh): commits_url = event.data["pull_request"]["commits_url"] From d7111bc5b4c288e30c7c49c16d62536413424203 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:02:38 -0700 Subject: [PATCH 05/14] Remove debug print --- sympy_bot/webapp.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 5bfe33e..fd8177a 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -291,7 +291,6 @@ async def pull_request_assign_issue(event, gh): for m in FIXES_ISSUE.finditer(body): fixed_issues.add(m.group(1)) - print(f"DEBUG: fixed issues={fixed_issues}") async for commit in commits: message = commit['commit']['message'] for m in FIXES_ISSUE.finditer(message): From a96fa4681556d223061b63c297c5be5262679596 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:04:54 -0700 Subject: [PATCH 06/14] Debug --- sympy_bot/webapp.py | 1 + 1 file changed, 1 insertion(+) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index fd8177a..0419d35 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -300,5 +300,6 @@ async def pull_request_assign_issue(event, gh): for issue_number in sorted(fixed_issues): print(f"#{number}: Assigning {user} to issue {issue_number}") + print(f"URL: {issues_url.replace('{/number}', issue_number) + 'assignees'}") await gh.post(issues_url.replace('{/number}', issue_number) + 'assignees', data=dict(assignees=[user])) From 94bde2f092f453be4f360d008e602c5b714d26a8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:06:17 -0700 Subject: [PATCH 07/14] Fix assignees URL --- sympy_bot/webapp.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 0419d35..d10a965 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -300,6 +300,5 @@ async def pull_request_assign_issue(event, gh): for issue_number in sorted(fixed_issues): print(f"#{number}: Assigning {user} to issue {issue_number}") - print(f"URL: {issues_url.replace('{/number}', issue_number) + 'assignees'}") - await gh.post(issues_url.replace('{/number}', issue_number) + 'assignees', + await gh.post(issues_url.replace('{/number}', f'/{issue_number}') + '/assignees', data=dict(assignees=[user])) From 5083e52d946d9cadb89d63b60556afb9c433c1f5 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:16:20 -0700 Subject: [PATCH 08/14] Unassign people when a PR is closed without merging Close with merging will close the issues (by definition), so in that case there is no need to unassign. --- sympy_bot/webapp.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index d10a965..50cd5ab 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -67,7 +67,7 @@ async def pull_request_edited(event, gh, *args, **kwargs): await pull_request_comment(event, gh) - await pull_request_assign_issue(event, gh) + await pull_request_assign_issues(event, gh) async def pull_request_comment(event, gh): comments_url = event.data["pull_request"]["comments_url"] @@ -211,7 +211,8 @@ async def pull_request_closed(event, gh, *args, **kwargs): pr_number = event.data['pull_request']['number'] print(f"PR #{pr_number} was {event.data['action']}.") if not event.data['pull_request']['merged']: - print(f"PR #{pr_number} was closed without merging, skipping") + await pull_request_unassign_issues(event, gh) + print(f"PR #{pr_number} was closed without merging, skipping release notes processing") return status, release_notes_file, changelogs, comment, users = await pull_request_comment(event, gh, *args, **kwargs) @@ -280,7 +281,13 @@ async def error_comment(event, gh, message): FIXES_ISSUE = re.compile(r'(?:fixes|closes) +#(\d+)', re.I) -async def pull_request_assign_issue(event, gh): +async def pull_request_assign_issues(event, gh): + await _pull_request_assign(event, gh, 'assign') + +async def pull_request_unassign_issues(event, gh): + await _pull_request_assign(event, gh, 'unassign') + +async def _pull_request_assign(event, gh, assign): commits_url = event.data["pull_request"]["commits_url"] commits = gh.getiter(commits_url) user = event.data['pull_request']['user']['login'] @@ -299,6 +306,10 @@ async def pull_request_assign_issue(event, gh): issues_url = event.data['pull_request']['base']['repo']['issues_url'] for issue_number in sorted(fixed_issues): - print(f"#{number}: Assigning {user} to issue {issue_number}") - await gh.post(issues_url.replace('{/number}', f'/{issue_number}') + '/assignees', - data=dict(assignees=[user])) + assignees_url = issues_url.replace('{/number}', f'/{issue_number}') + '/assignees' + if assign == 'assign': + print(f"PR #{number}: Assigning @{user} to issue #{issue_number}") + gh.post(assignees_url, data=dict(assignees=[user])) + else: + print(f"PR #{number}: Unassigning @{user} to issue #{issue_number}") + gh.delete(assignees_url, data=dict(assignees=[user])) From 625416b0e01749dbed68668f73b0f36e08ab6535 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 16:18:15 -0700 Subject: [PATCH 09/14] Forgot to use await again --- sympy_bot/webapp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 50cd5ab..0af195e 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -309,7 +309,7 @@ async def _pull_request_assign(event, gh, assign): assignees_url = issues_url.replace('{/number}', f'/{issue_number}') + '/assignees' if assign == 'assign': print(f"PR #{number}: Assigning @{user} to issue #{issue_number}") - gh.post(assignees_url, data=dict(assignees=[user])) + await gh.post(assignees_url, data=dict(assignees=[user])) else: print(f"PR #{number}: Unassigning @{user} to issue #{issue_number}") - gh.delete(assignees_url, data=dict(assignees=[user])) + await gh.delete(assignees_url, data=dict(assignees=[user])) From 3e8babce61875eed5c27aabb2a605e2427d02a02 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 17:17:23 -0700 Subject: [PATCH 10/14] Don't assign/unassign people if they have been manually assigned/unassigned before --- sympy_bot/webapp.py | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 0af195e..f2b232d 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -306,10 +306,27 @@ async def _pull_request_assign(event, gh, assign): issues_url = event.data['pull_request']['base']['repo']['issues_url'] for issue_number in sorted(fixed_issues): - assignees_url = issues_url.replace('{/number}', f'/{issue_number}') + '/assignees' + issue_url = issues_url.replace('{/number}', f'/{issue_number}') + assignees_url = issue_url + '/assignees' + if not await should_assign(event, gh, issue_url): + print(f"PR #{number}: Skipping {assign} of @{user} on issue #{issue_number} " + f"as they have previously been manually assigned/unassigned") + continue if assign == 'assign': print(f"PR #{number}: Assigning @{user} to issue #{issue_number}") await gh.post(assignees_url, data=dict(assignees=[user])) else: print(f"PR #{number}: Unassigning @{user} to issue #{issue_number}") await gh.delete(assignees_url, data=dict(assignees=[user])) + +async def should_assign(event, gh, issue_url): + # Required to make the timelines API work. + # https://developer.github.com/v3/issues/timeline/ + # headers = {"Accept": "application/vnd.github.mockingbird-preview"} + + timeline_url = issue_url + '/timeline' + async for event in gh.getitem(timeline_url): + if (event['event'] in ['assigned', 'unassigned'] and + event['assignee']['login'] != 'sympy-bot'): + return False + return True From f2f17a5e8fb00468e679d4aefce124f90ffde866 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 17:18:52 -0700 Subject: [PATCH 11/14] Should be getiter, not getitem --- sympy_bot/webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index f2b232d..52295f7 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -325,7 +325,7 @@ async def should_assign(event, gh, issue_url): # headers = {"Accept": "application/vnd.github.mockingbird-preview"} timeline_url = issue_url + '/timeline' - async for event in gh.getitem(timeline_url): + async for event in gh.getiter(timeline_url): if (event['event'] in ['assigned', 'unassigned'] and event['assignee']['login'] != 'sympy-bot'): return False From 092d94600a87c61de43919d935b2427e7234bf8d Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 17:23:07 -0700 Subject: [PATCH 12/14] Grammar --- sympy_bot/webapp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 52295f7..992c172 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -309,7 +309,7 @@ async def _pull_request_assign(event, gh, assign): issue_url = issues_url.replace('{/number}', f'/{issue_number}') assignees_url = issue_url + '/assignees' if not await should_assign(event, gh, issue_url): - print(f"PR #{number}: Skipping {assign} of @{user} on issue #{issue_number} " + print(f"PR #{number}: Skipping {assign}ing of @{user} on issue #{issue_number} " f"as they have previously been manually assigned/unassigned") continue if assign == 'assign': From 221db454b8e7aaa5b64be71845bcf6042f4b92b8 Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 17:23:11 -0700 Subject: [PATCH 13/14] Fix the accept header for the timelines API --- sympy_bot/webapp.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index 992c172..a61f409 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -322,10 +322,10 @@ async def _pull_request_assign(event, gh, assign): async def should_assign(event, gh, issue_url): # Required to make the timelines API work. # https://developer.github.com/v3/issues/timeline/ - # headers = {"Accept": "application/vnd.github.mockingbird-preview"} + accept = sansio.accept_format(version='mockingbird-preview') timeline_url = issue_url + '/timeline' - async for event in gh.getiter(timeline_url): + async for event in gh.getiter(timeline_url, accept=accept): if (event['event'] in ['assigned', 'unassigned'] and event['assignee']['login'] != 'sympy-bot'): return False From aa45c36f8909531e916effe8df339e9aef8b96be Mon Sep 17 00:00:00 2001 From: Aaron Meurer Date: Wed, 6 Nov 2019 17:25:37 -0700 Subject: [PATCH 14/14] Fix should_assign logic --- sympy_bot/webapp.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/sympy_bot/webapp.py b/sympy_bot/webapp.py index a61f409..e0c4419 100644 --- a/sympy_bot/webapp.py +++ b/sympy_bot/webapp.py @@ -308,7 +308,7 @@ async def _pull_request_assign(event, gh, assign): for issue_number in sorted(fixed_issues): issue_url = issues_url.replace('{/number}', f'/{issue_number}') assignees_url = issue_url + '/assignees' - if not await should_assign(event, gh, issue_url): + if not await should_assign(event, gh, user, issue_url): print(f"PR #{number}: Skipping {assign}ing of @{user} on issue #{issue_number} " f"as they have previously been manually assigned/unassigned") continue @@ -319,7 +319,7 @@ async def _pull_request_assign(event, gh, assign): print(f"PR #{number}: Unassigning @{user} to issue #{issue_number}") await gh.delete(assignees_url, data=dict(assignees=[user])) -async def should_assign(event, gh, issue_url): +async def should_assign(event, gh, user, issue_url): # Required to make the timelines API work. # https://developer.github.com/v3/issues/timeline/ accept = sansio.accept_format(version='mockingbird-preview') @@ -327,6 +327,7 @@ async def should_assign(event, gh, issue_url): timeline_url = issue_url + '/timeline' async for event in gh.getiter(timeline_url, accept=accept): if (event['event'] in ['assigned', 'unassigned'] and - event['assignee']['login'] != 'sympy-bot'): + event['assignee']['login'] == user and + event['actor']['login'] != 'sympy-bot'): return False return True