summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
Diffstat (limited to 'bugzilla-viewer.py')
-rwxr-xr-xbugzilla-viewer.py252
1 files changed, 252 insertions, 0 deletions
diff --git a/bugzilla-viewer.py b/bugzilla-viewer.py
new file mode 100755
index 0000000..bf034cc
--- /dev/null
+++ b/bugzilla-viewer.py
@@ -0,0 +1,252 @@
+#!/usr/bin/env python
+# Copyright 2011 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import curses.ascii
+import itertools
+import optparse
+import os
+import re
+import subprocess
+import sys
+import textwrap
+import xml.etree
+
+import bugz.bugzilla
+import portage.versions
+
+CPV_REGEX = re.compile("[A-Za-z0-9+_.-]+/[A-Za-z0-9+_-]+-[0-9]+(?:\.[0-9]+)*[a-z0-9_]*(?:-r[0-9]+)?")
+
+class TermTooSmall(Exception):
+ pass
+
+# Main class (called with curses.wrapper later).
+class MainWindow:
+ def __init__(self, screen, bugs, bugs_dict, packages_dict, related_bugs, repoman_dict):
+ self.bugs = bugs
+ self.bugs_dict = bugs_dict
+ self.packages_dict = packages_dict
+ self.related_bugs = related_bugs
+ self.repoman_dict = repoman_dict
+
+ curses.curs_set(0)
+ self.screen = screen
+
+ curses.use_default_colors()
+ self.init_screen()
+
+ c = self.screen.getch()
+ while c not in ( ord("q"), curses.ascii.ESC):
+ if c == ord("j"):
+ self.scroll_bugs_pad(1)
+ elif c == ord("k"):
+ self.scroll_bugs_pad(-1)
+ elif c == curses.KEY_DOWN:
+ self.scroll_contents_pad(1)
+ elif c == curses.KEY_UP:
+ self.scroll_contents_pad(-1)
+ elif c == curses.KEY_RESIZE:
+ self.init_screen()
+
+ c = self.screen.getch()
+
+ def init_screen(self):
+ (self.height, self.width) = self.screen.getmaxyx()
+
+ if self.height < 12 or self.width < 80:
+ raise TermTooSmall()
+
+ self.screen.border()
+ self.screen.vline(1, self.width / 3, curses.ACS_VLINE, self.height - 2)
+ self.screen.refresh()
+
+ self.fill_bugs_pad()
+ self.refresh_bugs_pad()
+
+ self.fill_contents_pad()
+ self.refresh_contents_pad()
+
+ def fill_bugs_pad(self):
+ self.bugs_pad = curses.newpad(len(self.bugs),self.width)
+ self.bugs_pad.erase()
+
+ self.bugs_pad_pos = 0
+
+ for i in range(len(self.bugs)):
+ self.bugs_pad.addstr(i, 0,
+ " " + self.bugs[i].find('bug_id').text + " " + self.bugs[i].find('short_desc').text)
+
+ def scroll_bugs_pad(self, amount):
+ height = len(self.bugs)
+
+ self.bugs_pad_pos += amount
+ if self.bugs_pad_pos < 0:
+ self.bugs_pad_pos = 0
+ if self.bugs_pad_pos >= height:
+ self.bugs_pad_pos = height - 1
+ self.refresh_bugs_pad()
+
+ self.fill_contents_pad()
+ self.refresh_contents_pad()
+
+ def refresh_bugs_pad(self):
+ (height, width) = self.bugs_pad.getmaxyx()
+ for i in range(height):
+ self.bugs_pad.addch(i, 0, " ")
+ self.bugs_pad.addch(self.bugs_pad_pos, 0, "*")
+ pos = min(height - self.height + 2, max(0, self.bugs_pad_pos - (self.height / 2)))
+ self.bugs_pad.refresh(
+ pos, 0,
+ 1, 1,
+ self.height - 2, self.width / 3 - 1)
+
+ def fill_contents_pad(self):
+ width = 2 * self.width / 3
+
+ bug = self.bugs[self.bugs_pad_pos]
+
+ output = []
+ output += textwrap.wrap(bug.find("short_desc").text, width=width-2)
+ output.append("-" * (width - 2))
+
+ cpvs = self.packages_dict[bug.find("bug_id").text]
+ if cpvs:
+ output += textwrap.wrap("Found package cpvs:", width=width-2)
+ for cpv in cpvs:
+ output += textwrap.wrap(cpv, width=width-2)
+ output.append("-" * (width - 2))
+
+ deps = bug.findall("dependson")
+ if deps:
+ output += textwrap.wrap("Depends on:", width=width-2)
+ for dep in deps:
+ dep_bug = self.bugs_dict[dep.text]
+ desc = dep.text + " " + dep_bug.find("bug_status").text + " " + dep_bug.find("short_desc").text
+ output += textwrap.wrap(desc, width=width-2)
+ output.append("-" * (width - 2))
+
+ related = self.related_bugs[bug.find("bug_id").text]
+ if related:
+ output += textwrap.wrap("Related bugs:", width=width-2)
+ for related_bug in related:
+ if related_bug['bugid'] == bug.find("bug_id").text:
+ continue
+ desc = related_bug['bugid'] + " " + related_bug['desc']
+ output += textwrap.wrap(desc, width=width-2)
+ output.append("-" * (width - 2))
+
+ if bug.find("bug_id").text in repoman_dict and repoman_dict[bug.find("bug_id").text]:
+ output += textwrap.wrap("Repoman output:", width=width-2)
+ lines = repoman_dict[bug.find("bug_id").text].split("\n")
+ for line in lines:
+ output += textwrap.wrap(line, width=width-2)
+ output.append("-" * (width - 2))
+
+ for elem in bug.findall("long_desc"):
+ output += textwrap.wrap("%s:" % elem.find("who").text, width=width-2)
+ lines = elem.find("thetext").text.split("\n")
+ for line in lines:
+ output += textwrap.wrap(line, width=width-2)
+ output.append("-" * (width - 2))
+
+ self.contents_pad_length = len(output)
+
+ self.contents_pad = curses.newpad(max(self.contents_pad_length, self.height), width)
+ self.contents_pad.erase()
+
+ self.contents_pad_pos = 0
+
+ for i in range(len(output)):
+ if type(output[i]) == unicode:
+ real_output = output[i]
+ else:
+ real_output = unicode(output[i], errors='replace')
+ self.contents_pad.addstr(i, 0, real_output.encode("utf-8"))
+
+ def scroll_contents_pad(self, amount):
+ height = self.contents_pad_length - self.height + 5
+
+ self.contents_pad_pos += amount
+ if self.contents_pad_pos < 0:
+ self.contents_pad_pos = 0
+ if self.contents_pad_pos >= height:
+ self.contents_pad_pos = height - 1
+ self.refresh_contents_pad()
+
+ def refresh_contents_pad(self):
+ self.contents_pad.refresh(
+ self.contents_pad_pos, 0,
+ 1, self.width / 3 + 1,
+ self.height - 2, self.width - 2)
+ self.screen.refresh()
+
+if __name__ == "__main__":
+ parser = optparse.OptionParser()
+ parser.add_option("--arch", dest="arch", help="Gentoo arch to use, e.g. x86, amd64, ...")
+ parser.add_option("--repo", dest="repo", help="Path to portage CVS repository")
+ parser.add_option("-v", "--verbose", dest="verbose", action="store_true", default=False, help="Include more output, e.g. related bugs")
+
+ (options, args) = parser.parse_args()
+ if not options.arch:
+ parser.error("--arch option is required")
+ if args:
+ parser.error("unrecognized command-line args")
+
+ bugzilla = bugz.bugzilla.Bugz('http://bugs.gentoo.org', skip_auth=True)
+
+ print "Searching for arch bugs..."
+ raw_bugs = bugzilla.search("", cc="%s@gentoo.org" % options.arch, keywords="STABLEREQ")
+ bugs = bugzilla.get([bug['bugid'] for bug in raw_bugs]).findall("bug")
+
+ dep_bug_ids = []
+
+ bugs_dict = {}
+ packages_dict = {}
+ related_bugs = {}
+ repoman_dict = {}
+ for bug in bugs:
+ print "Processing bug %s: %s" % (bug.find("bug_id").text, bug.find("short_desc").text)
+ bugs_dict[bug.find("bug_id").text] = bug
+ packages_dict[bug.find("bug_id").text] = []
+ related_bugs[bug.find("bug_id").text] = []
+ repoman_dict[bug.find("bug_id").text] = ""
+ for cpv_candidate in CPV_REGEX.findall(bug.find("short_desc").text):
+ if portage.db["/"]["porttree"].dbapi.cpv_exists(cpv_candidate):
+ packages_dict[bug.find("bug_id").text].append(cpv_candidate)
+ pv = portage.versions.cpv_getkey(cpv_candidate)
+ if options.verbose:
+ related_bugs[bug.find("bug_id").text] += bugzilla.search(pv)
+
+ if options.repo:
+ cvs_path = os.path.join(options.repo, pv)
+ ebuild_name = portage.versions.catsplit(cpv_candidate)[1] + ".ebuild"
+ ebuild_path = os.path.join(cvs_path, ebuild_name)
+ manifest_path = os.path.join(cvs_path, 'Manifest')
+ if os.path.exists(ebuild_path):
+ original_contents = open(ebuild_path).read()
+ manifest_contents = open(manifest_path).read()
+ try:
+ output = repoman_dict[bug.find("bug_id").text]
+ output += subprocess.Popen(["ekeyword", options.arch, ebuild_name], cwd=cvs_path, stdout=subprocess.PIPE).communicate()[0]
+ subprocess.check_call(["repoman", "manifest"], cwd=cvs_path)
+ output += subprocess.Popen(["repoman", "full"], cwd=cvs_path, stdout=subprocess.PIPE).communicate()[0]
+ repoman_dict[bug.find("bug_id").text] = output
+ finally:
+ f = open(ebuild_path, "w")
+ f.write(original_contents)
+ f.close()
+ f = open(manifest_path, "w")
+ f.write(manifest_contents)
+ f.close()
+ dep_bug_ids += [dep.text for dep in bug.findall("dependson")]
+
+ dep_bug_ids = list(set(dep_bug_ids))
+ dep_bugs = bugzilla.get(dep_bug_ids).findall("bug")
+ for bug in dep_bugs:
+ bugs_dict[bug.find("bug_id").text] = bug
+
+ try:
+ curses.wrapper(MainWindow, bugs=bugs, bugs_dict=bugs_dict, packages_dict=packages_dict, related_bugs=related_bugs, repoman_dict=repoman_dict)
+ except TermTooSmall:
+ print "Your terminal window is too small, please try to enlarge it"
+ sys.exit(1)