Add branch_changer.py script to maintainer-scripts
authorMartin Liska <mliska@suse.cz>
Wed, 3 Aug 2016 12:43:11 +0000 (14:43 +0200)
committerMartin Liska <marxin@gcc.gnu.org>
Wed, 3 Aug 2016 12:43:11 +0000 (12:43 +0000)
* branch_changer.py: New file.

From-SVN: r239066

maintainer-scripts/ChangeLog
maintainer-scripts/branch_changer.py [new file with mode: 0644]

index abe32058cc72083da4034d928896d9ffa3d7cd87..84894d0415e781537de403a01ce62ef1bf755929 100644 (file)
@@ -1,3 +1,7 @@
+2016-08-03  Martin Liska  <mliska@suse.cz>
+
+       * branch_changer.py: New file.
+
 2016-07-26  Richard Biener  <rguenther@suse.de>
 
        * update_version_svn: Ignore the GCC 4.9 branch.
diff --git a/maintainer-scripts/branch_changer.py b/maintainer-scripts/branch_changer.py
new file mode 100644 (file)
index 0000000..5e1681b
--- /dev/null
@@ -0,0 +1,195 @@
+#!/usr/bin/env python3
+
+# The script requires simplejson, requests, semantic_version packages, in case
+# of openSUSE:
+# zypper in python3-simplejson python3-requests
+# pip3 install semantic_version
+
+import requests
+import json
+import argparse
+import re
+
+from semantic_version import Version
+
+base_url = 'https://gcc.gnu.org/bugzilla/rest.cgi/'
+statuses = ['UNCONFIRMED', 'ASSIGNED', 'SUSPENDED', 'NEW', 'WAITING', 'REOPENED']
+search_summary = ' Regression]'
+regex = '(.*\[)([0-9\./]*)( [rR]egression])(.*)'
+
+class Bug:
+    def __init__(self, data):
+        self.data = data
+        self.versions = None
+        self.fail_versions = []
+        self.is_regression = False
+
+        self.parse_summary()
+        self.parse_known_to_fail()
+
+    def parse_summary(self):
+        m = re.match(regex, self.data['summary'])
+        if m != None:
+            self.versions = m.group(2).split('/')
+            self.is_regression = True
+            self.regex_match = m
+
+    def parse_known_to_fail(self):
+        v = self.data['cf_known_to_fail'].strip()
+        if v != '':
+            self.fail_versions = [x for x in re.split(' |,', v) if x != '']
+
+    def name(self):
+        return 'PR%d (%s)' % (self.data['id'], self.data['summary'])
+
+    def remove_release(self, release):
+        # Do not remove last value of [x Regression]
+        if len(self.versions) == 1:
+            return
+        self.versions = list(filter(lambda x: x != release, self.versions))
+
+    def add_release(self, releases):
+        parts = releases.split(':')
+        assert len(parts) == 2
+        for i, v in enumerate(self.versions):
+            if v == parts[0]:
+                self.versions.insert(i + 1, parts[1])
+                break
+
+    def add_known_to_fail(self, release):
+        if release in self.fail_versions:
+            return False
+        else:
+            self.fail_versions.append(release)
+            return True
+
+    def update_summary(self, api_key, doit):
+        summary = self.data['summary']
+        new_summary = self.serialize_summary()
+        if new_summary != summary:
+            print(self.name())
+            print('  changing summary: "%s" to "%s"' % (summary, new_summary))
+            self.modify_bug(api_key, {'summary': new_summary}, doit)
+
+            return True
+
+        return False
+
+    def change_milestone(self, api_key, old_milestone, new_milestone, comment, new_fail_version, doit):
+        old_major = Bug.get_major_version(old_milestone)
+        new_major = Bug.get_major_version(new_milestone)
+
+        print(self.name())
+        args = {}
+        if old_major == new_major:
+            args['target_milestone'] = new_milestone
+            print('  changing target milestone: "%s" to "%s" (same branch)' % (old_milestone, new_milestone))
+        elif self.is_regression and new_major in self.versions:
+            args['target_milestone'] = new_milestone
+            print('  changing target milestone: "%s" to "%s" (regresses with the new milestone)' % (old_milestone, new_milestone))
+        else:
+            print('  not changing target milestone: not a regression or does not regress with the new milestone')
+
+        if 'target_milestone' in args and comment != None:
+            print('  adding comment: "%s"' % comment)
+            args['comment'] = {'comment': comment }
+
+        if new_fail_version != None:
+            if self.add_known_to_fail(new_fail_version):
+                s = self.serialize_known_to_fail()
+                print('  changing known_to_fail: "%s" to "%s"' % (self.data['cf_known_to_fail'], s))
+                args['cf_known_to_fail'] = s
+
+        if len(args.keys()) != 0:
+            self.modify_bug(api_key, args, doit)
+            return True
+        else:
+            return False
+
+    def serialize_summary(self):
+        assert self.versions != None
+        assert self.is_regression == True
+
+        new_version = '/'.join(self.versions)
+        new_summary = self.regex_match.group(1) + new_version + self.regex_match.group(3) + self.regex_match.group(4)
+        return new_summary
+
+    def serialize_known_to_fail(self):
+        assert type(self.fail_versions) is list
+        return ', '.join(sorted(self.fail_versions, key = lambda x: Version(x, partial = True)))
+
+    def modify_bug(self, api_key, params, doit):
+        u = base_url + 'bug/' + str(self.data['id'])
+
+        data = {
+            'ids': [self.data['id']],
+            'api_key': api_key }
+
+        data.update(params)
+
+        if doit:
+            r = requests.put(u, data = json.dumps(data), headers = {"content-type": "text/javascript"})
+            print(r)
+
+    @staticmethod
+    def get_major_version(release):
+        parts = release.split('.')
+        assert len(parts) == 2 or len(parts) == 3
+        return '.'.join(parts[:-1])
+
+    @staticmethod
+    def get_bugs(api_key, query):
+        u = base_url + 'bug'
+        r = requests.get(u, params = query)
+        return [Bug(x) for x in r.json()['bugs']]
+
+def search(api_key, remove, add, limit, doit):
+    bugs = Bug.get_bugs(api_key, {'api_key': api_key, 'summary': search_summary, 'bug_status': statuses})
+    bugs = list(filter(lambda x: x.is_regression, bugs))
+
+    modified = 0
+    for bug in bugs:
+        if remove != None:
+            bug.remove_release(remove)
+        if add != None:
+            bug.add_release(add)
+
+        if bug.update_summary(api_key, doit):
+            modified += 1
+            if modified == limit:
+                break
+
+    print('\nModified PRs: %d' % modified)
+
+def replace_milestone(api_key, limit, old_milestone, new_milestone, comment, add_known_to_fail, doit):
+    bugs = Bug.get_bugs(api_key, {'api_key': api_key, 'bug_status': statuses, 'target_milestone': old_milestone})
+
+    modified = 0
+    for bug in bugs:
+        if bug.change_milestone(api_key, old_milestone, new_milestone, comment, add_known_to_fail, doit):
+            modified += 1
+            if modified == limit:
+                break
+
+    print('\nModified PRs: %d' % modified)
+
+parser = argparse.ArgumentParser(description='')
+parser.add_argument('api_key', help = 'API key')
+parser.add_argument('--remove', nargs = '?', help = 'Remove a release from summary')
+parser.add_argument('--add', nargs = '?', help = 'Add a new release to summary, e.g. 6:7 will add 7 where 6 is included')
+parser.add_argument('--limit', nargs = '?', help = 'Limit number of bugs affected by the script')
+parser.add_argument('--doit', action = 'store_true', help = 'Really modify BUGs in the bugzilla')
+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')
+parser.add_argument('--add-known-to-fail', help = 'Set a new known to fail for all PRs affected by --new-target-milestone')
+parser.add_argument('--comment', help = 'Comment a PR for which we set a new target milestore')
+
+args = parser.parse_args()
+# Python3 does not have sys.maxint
+args.limit = int(args.limit) if args.limit != None else 10**10
+
+if args.remove != None or args.add != None:
+    search(args.api_key, args.remove, args.add, args.limit, args.doit)
+if args.new_target_milestone != None:
+    t = args.new_target_milestone.split(':')
+    assert len(t) == 2
+    replace_milestone(args.api_key, args.limit, t[0], t[1], args.comment, args.add_known_to_fail, args.doit)