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