1
0
Fork 0
mirror of https://gitlab.wikimedia.org/ladsgroup/Phabricator-maintenance-bot synced 2024-11-24 04:52:38 +01:00
Phabricator-maintenance-bot/patchforreview_remover.py

123 lines
4.7 KiB
Python
Raw Normal View History

2019-11-22 22:19:59 +01:00
import re
import time
from collections import defaultdict
from lib import Client
class Checker():
def __init__(self, gerrit_bot_phid, project_patch_for_review_phid, client):
self.gerrit_bot_phid = gerrit_bot_phid
self.project_patch_for_review_phid = project_patch_for_review_phid
self.client = client
def check(self, t_id):
2023-01-19 19:10:16 +01:00
"""
Returns true if the Patch-For-Review project should be removed from the Phabricator
task identified 't_id'.
"""
2019-11-22 22:19:59 +01:00
phid = self.client.lookupPhid(t_id)
return self.phid_check(phid)
def get_change_url(self, raw_comment):
m = re.search(r'https://gerrit(?:-test|)\.wikimedia\.org/r/\d+', raw_comment)
if m:
return m[0]
m = re.search(r'https://gitlab\.wikimedia\.org/repos/.*/-/merge_requests/\d+', raw_comment)
if m:
return m[0]
return None
def get_operation_type(self, raw_comment, url):
"""
If the operation type can be determined from raw_comment, return
it. It will be the string "open" (first patchset created or merge
request created) or "close" (merge or abandon)
"""
# Gitlab style
if re.search(r"opened " + re.escape(url), raw_comment):
return "open"
if re.search(r"(merged|closed) " + re.escape(url), raw_comment):
return "close"
# Gerrit style
if re.search(r"Change \d+ had a related patch set uploaded", raw_comment):
return "open"
if re.search(r'Change \d+ \*\*(?:merged|abandoned)\*\* by ', raw_comment):
return "close"
return None
2023-01-19 19:10:16 +01:00
def phid_check(self, phid) -> bool:
"""
Returns true if the Patch-For-Review project should be removed from the Phabricator
task identified by 'phid'.
"""
2019-11-22 22:19:59 +01:00
gerrit_bot_actions = []
2023-01-19 19:10:16 +01:00
# Note that transactions are returned in reverse chronological order (most recent first).
2019-11-22 22:19:59 +01:00
for transaction in self.client.getTransactions(phid):
if re.findall(re.escape('https://github.com/') + r'.+?/pull', str(transaction)):
2019-11-22 22:19:59 +01:00
return False
if transaction['authorPHID'] == self.gerrit_bot_phid:
gerrit_bot_actions.append(transaction)
else:
2023-01-19 19:10:16 +01:00
# If someone other than GerritBot adds the Patch-For-Review project, don't
# auto-remove it.
2019-11-22 22:19:59 +01:00
if transaction['type'] == 'projects':
check = self.project_patch_for_review_phid in str(
transaction['fields'])
add_check = "'add'" in str(transaction['fields'])
if check and add_check:
return False
gerrit_patch_status = defaultdict(list)
for case in gerrit_bot_actions:
if case['type'] != 'comment':
continue
if len(case['comments']) != 1:
return False
raw_comment = case['comments'][0]['content']['raw']
change_url = self.get_change_url(raw_comment)
if change_url:
op = self.get_operation_type(raw_comment, change_url)
# Append True or False depending on whether the action was to
# open/reopen a change (True) or merge a change (False)
gerrit_patch_status[change_url].append(op in ["open", "reopen"])
2019-11-22 22:19:59 +01:00
for patch in gerrit_patch_status:
2023-01-19 19:10:16 +01:00
# The normal sequence of GerritBot transactions for a Gerrit change is "Change
# \d+ had a related patch set uploaded" (indicated by True in
# gerrit_patch_status) eventually followed by "Change \d+ (merged|abandoned)
# by whoever" (indicated by False in gerrit_patch_status). The transactions
# are returned in reverse order so the opened/merged pattern will appear as
# the reverse of [True, False], which is [False, True].
# FIXME: This logic can't handle a open/close/reopen/merge situation.
2019-11-22 22:19:59 +01:00
if gerrit_patch_status[patch] != [False, True]:
return False
return True
if __name__ == "__main__":
client = Client.newFromCreds()
2019-11-22 22:19:59 +01:00
gerrit_bot_phid = 'PHID-USER-idceizaw6elwiwm5xshb'
project_patch_for_review_phid = 'PHID-PROJ-onnxucoedheq3jevknyr'
checker = Checker(
gerrit_bot_phid,
project_patch_for_review_phid,
client)
gen = client.getTasksWithProject(project_patch_for_review_phid)
for phid in gen:
if checker.phid_check(phid):
print(client.taskDetails(phid)['id'])
try:
client.removeProjectByPhid(project_patch_for_review_phid, phid)
except BaseException:
continue
time.sleep(10)