annotate maintainer-scripts/branch_changer.py @ 158:494b0b89df80 default tip

...
author Shinji KONO <kono@ie.u-ryukyu.ac.jp>
date Mon, 25 May 2020 18:13:55 +0900
parents 04ced10e8804
children
Ignore whitespace changes - Everywhere: Within whitespace: At end of lines:
rev   line source
111
kono
parents:
diff changeset
1 #!/usr/bin/env python3
kono
parents:
diff changeset
2
kono
parents:
diff changeset
3 # This script is used by maintainers to modify Bugzilla entries in batch
kono
parents:
diff changeset
4 # mode.
kono
parents:
diff changeset
5 # Currently it can remove and add a release from/to PRs that are prefixed
kono
parents:
diff changeset
6 # with '[x Regression]'. Apart from that, it can also change target
kono
parents:
diff changeset
7 # milestones and optionally enhance the list of known-to-fail versions.
kono
parents:
diff changeset
8 #
kono
parents:
diff changeset
9 # The script utilizes the Bugzilla API, as documented here:
kono
parents:
diff changeset
10 # http://bugzilla.readthedocs.io/en/latest/api/index.html
kono
parents:
diff changeset
11 #
kono
parents:
diff changeset
12 # It requires the simplejson, requests, semantic_version packages.
kono
parents:
diff changeset
13 # In case of openSUSE:
kono
parents:
diff changeset
14 # zypper in python3-simplejson python3-requests
kono
parents:
diff changeset
15 # pip3 install semantic_version
kono
parents:
diff changeset
16 #
kono
parents:
diff changeset
17 # Sample usages of the script:
kono
parents:
diff changeset
18 #
kono
parents:
diff changeset
19 # $ ./maintainer-scripts/branch_changer.py api_key --new-target-milestone=6.2:6.3 --comment '6.2 has been released....' --add-known-to-fail=6.2 --limit 3
kono
parents:
diff changeset
20 #
kono
parents:
diff changeset
21 # The invocation will set target milestone to 6.3 for all issues that
kono
parents:
diff changeset
22 # have mistone equal to 6.2. Apart from that, a comment is added to these
kono
parents:
diff changeset
23 # issues and 6.2 version is added to known-to-fail versions.
kono
parents:
diff changeset
24 # At maximum 3 issues will be modified and the script will run
kono
parents:
diff changeset
25 # in dry mode (no issues are modified), unless you append --doit option.
kono
parents:
diff changeset
26 #
kono
parents:
diff changeset
27 # $ ./maintainer-scripts/branch_changer.py api_key --new-target-milestone=5.5:6.3 --comment 'GCC 5 branch is being closed' --remove 5 --limit 3
kono
parents:
diff changeset
28 #
kono
parents:
diff changeset
29 # Very similar to previous invocation, but instead of adding to known-to-fail,
kono
parents:
diff changeset
30 # '5' release is removed from all issues that have the regression prefix.
kono
parents:
diff changeset
31 #
kono
parents:
diff changeset
32 # $ ./maintainer-scripts/branch_changer.py api_key --add=7:8
kono
parents:
diff changeset
33 #
kono
parents:
diff changeset
34 # Aforementioned invocation adds '8' release to the regression prefix of all
kono
parents:
diff changeset
35 # issues that contain '7' in its regression prefix.
kono
parents:
diff changeset
36 #
kono
parents:
diff changeset
37
kono
parents:
diff changeset
38 import requests
kono
parents:
diff changeset
39 import json
kono
parents:
diff changeset
40 import argparse
kono
parents:
diff changeset
41 import re
kono
parents:
diff changeset
42
kono
parents:
diff changeset
43 from semantic_version import Version
kono
parents:
diff changeset
44
kono
parents:
diff changeset
45 base_url = 'https://gcc.gnu.org/bugzilla/rest.cgi/'
kono
parents:
diff changeset
46 statuses = ['UNCONFIRMED', 'ASSIGNED', 'SUSPENDED', 'NEW', 'WAITING', 'REOPENED']
kono
parents:
diff changeset
47 search_summary = ' Regression]'
kono
parents:
diff changeset
48 regex = '(.*\[)([0-9\./]*)( [rR]egression])(.*)'
kono
parents:
diff changeset
49
kono
parents:
diff changeset
50 class Bug:
kono
parents:
diff changeset
51 def __init__(self, data):
kono
parents:
diff changeset
52 self.data = data
kono
parents:
diff changeset
53 self.versions = None
kono
parents:
diff changeset
54 self.fail_versions = []
kono
parents:
diff changeset
55 self.is_regression = False
kono
parents:
diff changeset
56
kono
parents:
diff changeset
57 self.parse_summary()
kono
parents:
diff changeset
58 self.parse_known_to_fail()
kono
parents:
diff changeset
59
kono
parents:
diff changeset
60 def parse_summary(self):
kono
parents:
diff changeset
61 m = re.match(regex, self.data['summary'])
kono
parents:
diff changeset
62 if m != None:
kono
parents:
diff changeset
63 self.versions = m.group(2).split('/')
kono
parents:
diff changeset
64 self.is_regression = True
kono
parents:
diff changeset
65 self.regex_match = m
kono
parents:
diff changeset
66
kono
parents:
diff changeset
67 def parse_known_to_fail(self):
kono
parents:
diff changeset
68 v = self.data['cf_known_to_fail'].strip()
kono
parents:
diff changeset
69 if v != '':
kono
parents:
diff changeset
70 self.fail_versions = [x for x in re.split(' |,', v) if x != '']
kono
parents:
diff changeset
71
kono
parents:
diff changeset
72 def name(self):
kono
parents:
diff changeset
73 return 'PR%d (%s)' % (self.data['id'], self.data['summary'])
kono
parents:
diff changeset
74
kono
parents:
diff changeset
75 def remove_release(self, release):
kono
parents:
diff changeset
76 # Do not remove last value of [x Regression]
kono
parents:
diff changeset
77 if len(self.versions) == 1:
kono
parents:
diff changeset
78 return
kono
parents:
diff changeset
79 self.versions = list(filter(lambda x: x != release, self.versions))
kono
parents:
diff changeset
80
kono
parents:
diff changeset
81 def add_release(self, releases):
kono
parents:
diff changeset
82 parts = releases.split(':')
kono
parents:
diff changeset
83 assert len(parts) == 2
kono
parents:
diff changeset
84 for i, v in enumerate(self.versions):
kono
parents:
diff changeset
85 if v == parts[0]:
kono
parents:
diff changeset
86 self.versions.insert(i + 1, parts[1])
kono
parents:
diff changeset
87 break
kono
parents:
diff changeset
88
kono
parents:
diff changeset
89 def add_known_to_fail(self, release):
kono
parents:
diff changeset
90 if release in self.fail_versions:
kono
parents:
diff changeset
91 return False
kono
parents:
diff changeset
92 else:
kono
parents:
diff changeset
93 self.fail_versions.append(release)
kono
parents:
diff changeset
94 return True
kono
parents:
diff changeset
95
kono
parents:
diff changeset
96 def update_summary(self, api_key, doit):
kono
parents:
diff changeset
97 summary = self.data['summary']
kono
parents:
diff changeset
98 new_summary = self.serialize_summary()
kono
parents:
diff changeset
99 if new_summary != summary:
kono
parents:
diff changeset
100 print(self.name())
kono
parents:
diff changeset
101 print(' changing summary: "%s" to "%s"' % (summary, new_summary))
kono
parents:
diff changeset
102 self.modify_bug(api_key, {'summary': new_summary}, doit)
kono
parents:
diff changeset
103
kono
parents:
diff changeset
104 return True
kono
parents:
diff changeset
105
kono
parents:
diff changeset
106 return False
kono
parents:
diff changeset
107
kono
parents:
diff changeset
108 def change_milestone(self, api_key, old_milestone, new_milestone, comment, new_fail_version, doit):
kono
parents:
diff changeset
109 old_major = Bug.get_major_version(old_milestone)
kono
parents:
diff changeset
110 new_major = Bug.get_major_version(new_milestone)
kono
parents:
diff changeset
111
kono
parents:
diff changeset
112 print(self.name())
kono
parents:
diff changeset
113 args = {}
kono
parents:
diff changeset
114 if old_major == new_major:
kono
parents:
diff changeset
115 args['target_milestone'] = new_milestone
kono
parents:
diff changeset
116 print(' changing target milestone: "%s" to "%s" (same branch)' % (old_milestone, new_milestone))
kono
parents:
diff changeset
117 elif self.is_regression and new_major in self.versions:
kono
parents:
diff changeset
118 args['target_milestone'] = new_milestone
kono
parents:
diff changeset
119 print(' changing target milestone: "%s" to "%s" (regresses with the new milestone)' % (old_milestone, new_milestone))
kono
parents:
diff changeset
120 else:
kono
parents:
diff changeset
121 print(' not changing target milestone: not a regression or does not regress with the new milestone')
kono
parents:
diff changeset
122
kono
parents:
diff changeset
123 if 'target_milestone' in args and comment != None:
kono
parents:
diff changeset
124 print(' adding comment: "%s"' % comment)
kono
parents:
diff changeset
125 args['comment'] = {'comment': comment }
kono
parents:
diff changeset
126
kono
parents:
diff changeset
127 if new_fail_version != None:
kono
parents:
diff changeset
128 if self.add_known_to_fail(new_fail_version):
kono
parents:
diff changeset
129 s = self.serialize_known_to_fail()
kono
parents:
diff changeset
130 print(' changing known_to_fail: "%s" to "%s"' % (self.data['cf_known_to_fail'], s))
kono
parents:
diff changeset
131 args['cf_known_to_fail'] = s
kono
parents:
diff changeset
132
kono
parents:
diff changeset
133 if len(args.keys()) != 0:
kono
parents:
diff changeset
134 self.modify_bug(api_key, args, doit)
kono
parents:
diff changeset
135 return True
kono
parents:
diff changeset
136 else:
kono
parents:
diff changeset
137 return False
kono
parents:
diff changeset
138
kono
parents:
diff changeset
139 def serialize_summary(self):
kono
parents:
diff changeset
140 assert self.versions != None
kono
parents:
diff changeset
141 assert self.is_regression == True
kono
parents:
diff changeset
142
kono
parents:
diff changeset
143 new_version = '/'.join(self.versions)
kono
parents:
diff changeset
144 new_summary = self.regex_match.group(1) + new_version + self.regex_match.group(3) + self.regex_match.group(4)
kono
parents:
diff changeset
145 return new_summary
kono
parents:
diff changeset
146
kono
parents:
diff changeset
147 def serialize_known_to_fail(self):
kono
parents:
diff changeset
148 assert type(self.fail_versions) is list
kono
parents:
diff changeset
149 return ', '.join(sorted(self.fail_versions, key = lambda x: Version(x, partial = True)))
kono
parents:
diff changeset
150
kono
parents:
diff changeset
151 def modify_bug(self, api_key, params, doit):
kono
parents:
diff changeset
152 u = base_url + 'bug/' + str(self.data['id'])
kono
parents:
diff changeset
153
kono
parents:
diff changeset
154 data = {
kono
parents:
diff changeset
155 'ids': [self.data['id']],
kono
parents:
diff changeset
156 'api_key': api_key }
kono
parents:
diff changeset
157
kono
parents:
diff changeset
158 data.update(params)
kono
parents:
diff changeset
159
kono
parents:
diff changeset
160 if doit:
kono
parents:
diff changeset
161 r = requests.put(u, data = json.dumps(data), headers = {"content-type": "text/javascript"})
kono
parents:
diff changeset
162 print(r)
kono
parents:
diff changeset
163
kono
parents:
diff changeset
164 @staticmethod
kono
parents:
diff changeset
165 def get_major_version(release):
kono
parents:
diff changeset
166 parts = release.split('.')
kono
parents:
diff changeset
167 assert len(parts) == 2 or len(parts) == 3
kono
parents:
diff changeset
168 return '.'.join(parts[:-1])
kono
parents:
diff changeset
169
kono
parents:
diff changeset
170 @staticmethod
kono
parents:
diff changeset
171 def get_bugs(api_key, query):
kono
parents:
diff changeset
172 u = base_url + 'bug'
kono
parents:
diff changeset
173 r = requests.get(u, params = query)
kono
parents:
diff changeset
174 return [Bug(x) for x in r.json()['bugs']]
kono
parents:
diff changeset
175
kono
parents:
diff changeset
176 def search(api_key, remove, add, limit, doit):
kono
parents:
diff changeset
177 bugs = Bug.get_bugs(api_key, {'api_key': api_key, 'summary': search_summary, 'bug_status': statuses})
kono
parents:
diff changeset
178 bugs = list(filter(lambda x: x.is_regression, bugs))
kono
parents:
diff changeset
179
kono
parents:
diff changeset
180 modified = 0
kono
parents:
diff changeset
181 for bug in bugs:
kono
parents:
diff changeset
182 if remove != None:
kono
parents:
diff changeset
183 bug.remove_release(remove)
kono
parents:
diff changeset
184 if add != None:
kono
parents:
diff changeset
185 bug.add_release(add)
kono
parents:
diff changeset
186
kono
parents:
diff changeset
187 if bug.update_summary(api_key, doit):
kono
parents:
diff changeset
188 modified += 1
kono
parents:
diff changeset
189 if modified == limit:
kono
parents:
diff changeset
190 break
kono
parents:
diff changeset
191
kono
parents:
diff changeset
192 print('\nModified PRs: %d' % modified)
kono
parents:
diff changeset
193
kono
parents:
diff changeset
194 def replace_milestone(api_key, limit, old_milestone, new_milestone, comment, add_known_to_fail, doit):
kono
parents:
diff changeset
195 bugs = Bug.get_bugs(api_key, {'api_key': api_key, 'bug_status': statuses, 'target_milestone': old_milestone})
kono
parents:
diff changeset
196
kono
parents:
diff changeset
197 modified = 0
kono
parents:
diff changeset
198 for bug in bugs:
kono
parents:
diff changeset
199 if bug.change_milestone(api_key, old_milestone, new_milestone, comment, add_known_to_fail, doit):
kono
parents:
diff changeset
200 modified += 1
kono
parents:
diff changeset
201 if modified == limit:
kono
parents:
diff changeset
202 break
kono
parents:
diff changeset
203
kono
parents:
diff changeset
204 print('\nModified PRs: %d' % modified)
kono
parents:
diff changeset
205
kono
parents:
diff changeset
206 parser = argparse.ArgumentParser(description='')
kono
parents:
diff changeset
207 parser.add_argument('api_key', help = 'API key')
kono
parents:
diff changeset
208 parser.add_argument('--remove', nargs = '?', help = 'Remove a release from summary')
kono
parents:
diff changeset
209 parser.add_argument('--add', nargs = '?', help = 'Add a new release to summary, e.g. 6:7 will add 7 where 6 is included')
kono
parents:
diff changeset
210 parser.add_argument('--limit', nargs = '?', help = 'Limit number of bugs affected by the script')
kono
parents:
diff changeset
211 parser.add_argument('--doit', action = 'store_true', help = 'Really modify BUGs in the bugzilla')
kono
parents:
diff changeset
212 parser.add_argument('--new-target-milestone', help = 'Set a new target milestone, e.g. 4.9.3:4.9.4 will set milestone to 4.9.4 for all PRs having milestone set to 4.9.3')
kono
parents:
diff changeset
213 parser.add_argument('--add-known-to-fail', help = 'Set a new known to fail for all PRs affected by --new-target-milestone')
kono
parents:
diff changeset
214 parser.add_argument('--comment', help = 'Comment a PR for which we set a new target milestore')
kono
parents:
diff changeset
215
kono
parents:
diff changeset
216 args = parser.parse_args()
kono
parents:
diff changeset
217 # Python3 does not have sys.maxint
kono
parents:
diff changeset
218 args.limit = int(args.limit) if args.limit != None else 10**10
kono
parents:
diff changeset
219
kono
parents:
diff changeset
220 if args.remove != None or args.add != None:
kono
parents:
diff changeset
221 search(args.api_key, args.remove, args.add, args.limit, args.doit)
kono
parents:
diff changeset
222 if args.new_target_milestone != None:
kono
parents:
diff changeset
223 t = args.new_target_milestone.split(':')
kono
parents:
diff changeset
224 assert len(t) == 2
kono
parents:
diff changeset
225 replace_milestone(args.api_key, args.limit, t[0], t[1], args.comment, args.add_known_to_fail, args.doit)