This repository has been archived by the owner on Oct 15, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 24
/
patchforreview_remover.py
executable file
·125 lines (106 loc) · 4.91 KB
/
patchforreview_remover.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import re
import time
from collections import defaultdict
from lib import Client
class Checker():
def __init__(self, gerrit_bot_phid, code_review_bot_phid, project_patch_for_review_phid, client):
self.gerrit_bot_phid = gerrit_bot_phid
self.code_review_bot_phid = code_review_bot_phid
self.project_patch_for_review_phid = project_patch_for_review_phid
self.client = client
def check(self, t_id):
"""
Returns true if the Patch-For-Review project should be removed from the Phabricator
task identified 't_id'.
"""
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
def phid_check(self, phid) -> bool:
"""
Returns true if the Patch-For-Review project should be removed from the Phabricator
task identified by 'phid'.
"""
gerrit_bot_actions = []
# Note that transactions are returned in reverse chronological order (most recent first).
for transaction in self.client.getTransactions(phid):
if re.findall(re.escape('https://github.com/') + r'.+?/pull', str(transaction)):
return False
if transaction['authorPHID'] in [self.gerrit_bot_phid, self.code_review_bot_phid]:
gerrit_bot_actions.append(transaction)
else:
# If someone other than GerritBot adds the Patch-For-Review project, don't
# auto-remove it.
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"])
for patch in gerrit_patch_status:
# 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.
if gerrit_patch_status[patch] != [False, True]:
return False
return True
if __name__ == "__main__":
client = Client.newFromCreds()
gerrit_bot_phid = 'PHID-USER-idceizaw6elwiwm5xshb'
code_review_bot_phid = 'PHID-USER-ckazlx2gejbyo75y6lid'
project_patch_for_review_phid = 'PHID-PROJ-onnxucoedheq3jevknyr'
checker = Checker(
gerrit_bot_phid,
code_review_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)