Many people have a body of issues in an existing tracker, BugZilla? being a very common issue tracker. I was faced with the task of moving all our issues from a bugzilla instance into my shiny new roundup instance. Bugzilla helpfully provides an option to format query results and bugs in RDF (an XML dialect) so using the wonders of ElementTree it isn't too hard to import issues into a roundup instance, provided you can either figure out how to map the information onto the roundup schema, or modify your roundup schema to match. I took the later course, and then modified my schema over time to work better with roundup.
The following is a simple script to chug through the bugzilla issues and then import them into roundup. I cache the issue downloads so I can experiment without killing the bugzilla instance, so it should be easy enough to tweak the script to your heart's content.
I should add the caveat that I haven't used this script in a while, so it might have bitrotted slightly over time. I'm confident that it should work for most people with only minimal tweaking.
bugzilla_to_roundup.py::
-
#!/usr/bin/env python """Bugzilla to roundup Converts entries in bugzilla to roundup issues """ import os from optparse import OptionParser? import urllib2 import sys import logging from elementtree import ElementTree from roundup import instance from roundup.date import Date logging.basicConfig() logger = logging.getLogger() logger.setLevel(logging.INFO) # Map bugzilla states to ours bug_status_mapping = {
-
"UNCONFIRMED": "open", "NEW": "open", "ASSIGNED": "accepted", "REOPENED": "open", # this our chatting equiv? "RESOLVED": "complete", "VERIFIED": "verified", "CLOSED": "cancelled", }
-
"blocker": "critical", "critical": "critical", "major": "urgent", "normal": "bug", "minor": "bug", "trivial": "bug", "enhancement": "feature", }
-
"product": "product", "version": "version", "assigned_to": "assignedto", "reporter": "creator", "creation_ts": "creation", "short_desc": "title" }
-
"""Uses bugzilla to get a list of bug ids. Uses the RDF output of bugzilla. """ result = [] url = "%sbuglist.cgi?short_desc_type=allwordssubstr&short_desc=&long_desc_type=allwordssubstr&long_desc=&bug_file_loc_type=allwordssubstr&bug_file_loc=&bug_status=UNCONFIRMED&bug_status=NEW&bug_status=ASSIGNED&bug_status=REOPENED&bug_status=RESOLVED&bug_status=VERIFIED&bug_status=CLOSED&emailtype1=substring&email1=&emailtype2=substring&email2=&bugidtype=include&bug_id=&votes=&changedin=&chfieldfrom=&chfieldto=Now&chfieldvalue=&cmdtype=doit&newqueryname=&order=Reuse+same+sort+as+last+time&field0-0-0=noop&type0-0-0=noop&value0-0-0=&format=rdf" % url_prefix f = urllib2.urlopen(url) rdf_text = f.read() fp = open("bugzilla.rdf", "w") fp.write(rdf_text) fp.close() tree = ElementTree.parse("bugzilla.rdf") root = tree.getroot() for bug in root.findall(".//{ http://www.bugzilla.org/rdf#}id"):
-
result.append(bug.text)
-
-
"""Downloads all the bugs given from bugzilla to XML files""" for bug_id in bug_ids:
-
f = urllib2.urlopen("%s/xml.cgi?id=%s" % (url_prefix, bug_id)) bug_text = f.read() fp = open(os.path.join(output_dir, "%s.xml" % bug_id), "w") fp.write(bug_text) fp.close()
-
-
def init(self, tree):
-
self._tree = tree self._root = self._tree.getroot()
-
# Special case for all the long_desc's there if name in ["long_desc",]?:
-
return self._root.findall("bug/%s" % name)
-
-
return self.getattr(key)
-
return [x.tag for x in self._root.find("bug")]?
-
-
"""Parses a bug""" logger.debug("Parsing %s" % bug_filename) tree = ElementTree.parse(bug_filename) return BugzillaBug?(tree)
-
"""Dumps out info on the given bugs""" for bug_id in bug_ids:
-
bug = parse_bug(os.path.join(cache_dir, "%s.xml" % bug_id)) for key in bug.keys():
-
if key == "long_desc":
-
continue
-
-
print "%s:long_desc %s = %s" % (bug_id, "who", long_desc.find("who").text) print "%s:long_desc %s = %s" % (bug_id, "bug_when", long_desc.find("bug_when").text) thetext = long_desc.find("thetext").text thetext = thetext.replace("\n", "\\n") print "%s:long_desc %s = %s" % (bug_id, "thetext", thetext)
-
-
-
"""Converts the bugzilla dumped XML into roundup bugs""" tracker = instance.open(roundup_instance) # NOTE: Is it worth sorting the bugs so the ids increase in chronological order? for bug_id in bug_ids:
-
filename = os.path.join(cache_dir, "%s.xml" % bug_id) logger.info("Processing bug %s" % bug_id) bz_bug = parse_bug(filename) roundup_bug = {} for bz_prop, roundup_prop in bug_property_mapping.items():
roundup_bug['status']? = bug_status_mapping[bz_bug.bug_status]? roundup_bug['priority']? = bug_severity_mapping[bz_bug.bug_severity]? roundup_bug['messages']? = [] for long_desc in bz_bug.long_desc:-
message = {} message['creator']? = long_desc.find('who').text message['author']? = long_desc.find('who').text message['creation']? = long_desc.find('bug_when').text message['file']? = long_desc.find('thetext').text roundup_bug['messages']?.append(message)
-
add_message_to_roundup(bug_id, message, tracker)
-
-
-
"""Add a roundup bug (dict) to a roundup instance :param bug: A dictionary of bug data. Pretty much a plain set of property -> value mappings, with the only exception being messages which maps to a list of message dictionaries. :param tracker: The roundup tracker instance, this is obtained via roundup.instance.open. """ try:
-
# Open up the db and get the user, creating if necessary
-
db = tracker.open('admin') username = get_roundup_user(db, bug['creator']?) db.commit() db.close() db = tracker.open(username) # Get the issue's properties product_id = get_roundup_property(db, 'product', bug['product']?) version_id = get_roundup_property(db, 'version', bug['version']?) status_id = get_roundup_property(db, 'status', bug['status']?) priority_id = get_roundup_property(db, 'priority', bug['priority']?) # Get the users relating to the issue # From bugzilla the username is the email, so can use that as a starting point creator_id = get_roundup_user(db, bug['creator']?) assignedto_id = get_roundup_user(db, bug['assignedto']?) bug_id = db.issue.create(
db.commit()
-
#Ensure the db is always closed no matter what db.close() logger.info("Roundup db connection closed")
-
-
"""Adds the given message to to roundup""" try:
-
# Open up the db and get the user, creating if necessary db = tracker.open('admin') username = get_roundup_user(db, message['creator']?) db.commit() db.close() # Re-open as the user db = tracker.open(username) # Create the author if not there get_roundup_user(db, message['author']?) # Create the message message_id = db.msg.create(
# Add the message to the bug bug = db.issue.getnode(bug_id) messages = bug.messages messages.append(message_id) bug.messages = messages # Force a setattr db.commit()
-
#Ensure the db is always closed no matter what db.close() logger.info("Roundup db connection closed")
-
-
filename = os.path.basename(filename) # ensure there are no directory parts try:
-
db = tracker.open("admin") # Create the file file_id = db.file.create(
-
content=content, name=filename, type=mimetype )
-
-
db.close() logger.debug("Closed db connection")
-
-
"""Obtains the id of the given property. If it doesn't exist it is created. """ klass = db.getclass(propname) try:
-
result = klass.lookup(value)
-
result = klass.create(name=value)
-
-
"""Find (or create) a user based on their email address This assumes everyone has a username which is also their email address prefix. """ username = email.split("@")[0]? try:
-
db.user.lookup(username)
-
db.user.create(username=username, address=email)
-
-
parser = OptionParser?(usage=usage) parser.add_option("", "--download", dest="download", default=False, action="store_true",
-
help="Download bugs only. Downloads the individual bug xml files to dir called 'cache'.")
-
help="Dumps out details on the parse entries")
-
parser.error("You need to give the bugzilla URL and the roundup instance home dir")
-
download_bugs(bugzilla_url, bug_ids, "cache") sys.exit(0)
-
dump_bugs(bug_ids, "cache") sys.exit(0)
-
-
main()
-
| subtopics: |
get the python code --wiki, Sat, 09 Feb 2008 00:38:56 +1100
the python code is available in a usable fashion via the diff page ( http://www.mechanicalcat.net/tech/roundup/wiki/ImportingFromBugzilla/diff)
Tobias