From c6b5a33ef4c2ce99f2ef81454aa8e154026861ec Mon Sep 17 00:00:00 2001 From: Thomas Haller Date: Mon, 14 Oct 2013 16:31:45 +0200 Subject: [PATCH] contrib: add fetching data from bugzilla to bzutil.py This is heavily borrowed from http://git.app.eng.bos.redhat.com/rcm/dist-git-utils.git/tree/src/gitbz Signed-off-by: Thomas Haller --- contrib/bzutil.py | 175 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 7 deletions(-) diff --git a/contrib/bzutil.py b/contrib/bzutil.py index 0a792918ac..d604c5e8ac 100755 --- a/contrib/bzutil.py +++ b/contrib/bzutil.py @@ -5,6 +5,9 @@ import argparse import subprocess import os import re +import kobo.xmlrpc +import xmlrpclib +import termcolor devnull = open(os.devnull, 'w') @@ -17,6 +20,20 @@ def _call(args): sys.exit(1) return output +def _read_config_file(filename): + values = {} + with open(filename) as f: + for line in f: + line = line.strip() + if not line or line[0] == '#': + continue + name, var = line.partition("=")[::2] + var = var.strip() + if var and ((var[0]=='"' and var[-1]=='"') or (var[0]=="'" and var[-1]=="'")): + var = var[1:-1] + values[name.strip()] = var + return values + def git_ref_list(commit): return _call(['git', 'rev-list', '--no-walk', commit]).splitlines() @@ -44,6 +61,9 @@ def git_get_commit_date(shaid): return _git_get_commit_date[shaid] +class PasswordError(ValueError): + pass + class CmdBase: def __init__(self, name): @@ -54,6 +74,52 @@ class CmdBase: print_usage() +class BzClient: + COMMON_FIELDS = ['id', 'depends_on', 'blocks', 'flags', 'keywords', 'status', 'component'] + + def __init__(self, url, config): + transport = None + use_https = False + if url.startswith('https://'): + transport = kobo.xmlrpc.SafeCookieTransport() + use_https = True + else: + transport = kobo.xmlrpc.CookieTransport() + self._key_part = (url, use_https) + self._client = xmlrpclib.ServerProxy(url, transport=transport) + self._config = config + + def _login(self, user, password): + self._client.User.login({'login': user, + 'password': password}) + + _getBZDataCache = {} + def getBZData(self, bzid, include_fields = COMMON_FIELDS): + if not self._config.get('rhbz_passwd', None) or not self._config.get('rhbz_user', None): + raise PasswordError('The Bugzilla password has not been set') + user = self._config['rhbz_user']; + passwd = self._config['rhbz_passwd'] + + key = sorted(include_fields) + key.append(self._key_part) + key.append(user) + key.append(passwd) + key.append(bzid) + key = tuple(key) + + if BzClient._getBZDataCache.has_key(key): + return BzClient._getBZDataCache[key] + + self._login(user, passwd) + + bugs_data = self._client.Bug.get({'ids': bzid, + 'include_fields': include_fields}) + bug_data = bugs_data['bugs'][0] + BzClient._getBZDataCache[key] = bug_data + return bug_data + + + # class to hold information about a bugzilla entry class BzInfo: def __init__(self, bzid): @@ -71,7 +137,20 @@ class BzInfo: def __str__(self): return "%s #%s" % (self.bztype, self.bzid) def __repr__(self): - return "BzInfo(\"%s\", \"%s\")" % (self.bztype, self.bzid) + return "(\"%s\", \"%s\")" % (self.bztype, self.bzid) + + def getBZData(self, field=None): + if not hasattr(self, '_bzdata'): + self._bzdata = self._fetchBZData() + if self._bzdata is None: + self._bzdata = {} + if field is None: + return self._bzdata + return self._bzdata.get(field, None) + def _fetchBZData(self): + return None + + class BzInfoBgo(BzInfo): def __init__(self, bzid): @@ -83,6 +162,7 @@ class BzInfoBgo(BzInfo): def url(self): return "https://bugzilla.gnome.org/show_bug.cgi?id=%s" % self.bzid + class BzInfoRhbz(BzInfo): def __init__(self, bzid): BzInfo.__init__(self, bzid) @@ -93,6 +173,10 @@ class BzInfoRhbz(BzInfo): def url(self): return "https://bugzilla.redhat.com/show_bug.cgi?id=%s" % self.bzid + def _fetchBZData(self): + return BzClient('https://bugzilla.redhat.com/xmlrpc.cgi', config).getBZData(self.bzid) + + class UtilParseCommitMessage(CmdBase): @@ -106,13 +190,15 @@ class UtilParseCommitMessage(CmdBase): ] _patterns = [(re.compile(p[0]), p[1]) for p in _patterns] - def __init__(self, commit): + def __init__(self, commit, result=None, git_backend=True, commit_date=0): self.commit = commit - self._result = None + self._result = result + self._git_backend = git_backend + self._commit_date = commit_date @property def result(self): - if not self._result: + if not self._result and self._git_backend: message = git_commit_message(self.commit) data = [] @@ -125,11 +211,27 @@ class UtilParseCommitMessage(CmdBase): self._result = list(set(data)) return self._result + def __cmp__(self, other): + if self._git_backend != other._git_backend: + return cmp(self._git_backend, other._git_backend) + return cmp(self.commit, other.commit) + def __hash__(self): + return hash(self.commit) def __str__(self): return str( (self.commit, self.result) ) def __repr__(self): return str(self) + def commit_summary(self, color): + if self._git_backend: + return git_summary(self.commit, color) + return self.commit + def get_commit_date(self): + if self._git_backend: + return git_get_commit_date(self.commit) + return self._commit_date + + @@ -140,29 +242,88 @@ class CmdParseCommitMessage(CmdBase): self.parser = argparse.ArgumentParser(prog=sys.argv[0] + " " + name, description='Parse commit messages.') self.parser.add_argument('--color', '-c', dest='color', action='store_true') + self.parser.add_argument('--conf', metavar='conf', default=('%s/.bzutil.conf' % os.path.expanduser("~"))) + self.parser.add_argument('--bz', action='append') self.parser.add_argument('commit', metavar='commit', type=str, nargs='+', help='commit ids to parse') + @staticmethod + def _order_keys(keys, ordered): + return [o for o in ordered if o in keys] + + _colormap_flag = { + '+': 'green', + '?': 'yellow', + } + _colormap_status = { + 'POST': 'green', + 'MODIFIED': 'yellow', + 'CLOSED': 'green', + } + + + def _colored(self, value, colormapping, defaultcolor='red'): + v = '>> ' + value + if not self.options.color: + return v + color = colormapping.get(value, defaultcolor) + return termcolor.colored(v, color) + def run(self, argv): self.options = self.parser.parse_args(argv) + global config + if not os.path.exists(self.options.conf): + self.parser.error('config file does not exist: %s' % self.options.conf) + config = _read_config_file(self.options.conf) + + result_man = [] + obzi = 0 + for obz in self.options.bz: + bz_tuples = [bz for bz in re.split('[,; ]', obz) if bz] + result_man2 = [] + for bzii in bz_tuples: + bzi = bzii.partition(':')[::2] + if not bzi[0] or not bzi[1] or not re.match('^[0-9]{4,7}$', bzi[1]): + raise self.parser.error('Invalid bugzilla option --bz \"%s\" (%s)' % (obz, bzii)) + if bzi[0] == 'rhbz' or bzi[0] == 'rh': + result_man2.append(BzInfoRhbz(bzi[1])) + elif bzi[0] == 'bgo' or bzi[0] == 'bg': + result_man2.append(BzInfoBgo(bzi[1])) + else: + raise self.parser.error('Invalid bugzilla option --bz \"%s\"' % obz) + if not result_man2: + raise self.parser.error('Invalid bugzilla option --bz \"%s\"' % obz) + result_man.append(UtilParseCommitMessage('bz \"%s\"' % obz, result_man2, git_backend=False, commit_date=-obzi)) + obzi = obzi + 1 + result_all = [ (ref, [UtilParseCommitMessage(commit) for commit in git_ref_list(ref)]) for ref in self.options.commit] for ref_data in result_all: print("ref: %s" % ref_data[0]) for commit_data in ref_data[1]: - print(" %s" % git_summary(commit_data.commit, self.options.color)) + print(" %s" % commit_data.commit_summary(self.options.color)) for result in commit_data.result: print(" %-4s #%-8s %s" % (result.bztype, result.bzid, result.url)) + result_reduced = [ commit_data for ref_data in result_all for commit_data in ref_data[1] if commit_data.result ] + result_reduced = result_reduced + result_man + result_reduced = sorted(set(result_reduced), key=lambda commit_data: commit_data.get_commit_date(), reverse=True) print print('sorted:') - for commit_data in sorted(set(result_reduced), key=lambda commit_data: git_get_commit_date(commit_data.commit), reverse=True): - print(" %s" % git_summary(commit_data.commit, self.options.color)) + for commit_data in result_reduced: + print(" %s" % commit_data.commit_summary(self.options.color)) for result in commit_data.result: print(" %-4s #%-8s %s" % (result.bztype, result.bzid, result.url)) + bzdata = result.getBZData() + for k in CmdParseCommitMessage._order_keys(bzdata.keys(), ['status', 'flags']): + if k == 'flags': + for flag in bzdata[k]: + print(" %-20s = %s" % ('#'+flag['name'], self._colored(flag['status'], CmdParseCommitMessage._colormap_flag))) + elif k == 'status': + print(" %-20s = %s" % (k, self._colored(bzdata[k], CmdParseCommitMessage._colormap_status))) print