| try: |
| # Using Psyco makes it about 25% faster, but there's a bug in psyco in |
| # handling of eval causing it to use unlimited memory with the magic |
| # file enabled. |
| # import psyco |
| # psyco.full() |
| # from psyco.classes import * |
| pass |
| except: |
| pass |
| import re |
| import base64 |
| import cStringIO |
| import specialuu |
| import array |
| import email.Utils |
| import zlib |
| import magic |
| |
| # Comment out if you don't want magic detection |
| magicf = magic.MagicFile() |
| |
| # Open our output file |
| outfile = open("gnats2bz_data.sql", "w") |
| |
| # List of GNATS fields |
| fieldnames = ("Number", "Category", "Synopsis", "Confidential", "Severity", |
| "Priority", "Responsible", "State", "Quarter", "Keywords", |
| "Date-Required", "Class", "Submitter-Id", "Arrival-Date", |
| "Closed-Date", "Last-Modified", "Originator", "Release", |
| "Organization", "Environment", "Description", "How-To-Repeat", |
| "Fix", "Release-Note", "Audit-Trail", "Unformatted") |
| |
| # Dictionary telling us which GNATS fields are multiline |
| multilinefields = {"Organization":1, "Environment":1, "Description":1, |
| "How-To-Repeat":1, "Fix":1, "Release-Note":1, |
| "Audit-Trail":1, "Unformatted":1} |
| |
| # Mapping of GCC release to version. Our version string is updated every |
| # so we need to funnel all release's with 3.4 in the string to be version |
| # 3.4 for bug tracking purposes |
| # The key is a regex to match, the value is the version it corresponds |
| # with |
| releasetovermap = {r"3\.4":"3.4", r"3\.3":"3.3", r"3\.2\.2":"3.2.2", |
| r"3\.2\.1":"3.2.1", r"3\.2":"3.2", r"3\.1\.2":"3.1.2", |
| r"3\.1\.1":"3.1.1", r"3\.1":"3.1", r"3\.0\.4":"3.0.4", |
| r"3\.0\.3":"3.0.3", r"3\.0\.2":"3.0.2", r"3\.0\.1":"3.0.1", |
| r"3\.0":"3.0", r"2\.95\.4":"2.95.4", r"2\.95\.3":"2.95.3", |
| r"2\.95\.2":"2.95.2", r"2\.95\.1":"2.95.1", |
| r"2\.95":"2.95", r"2\.97":"2.97", |
| r"2\.96.*[rR][eE][dD].*[hH][aA][tT]":"2.96 (redhat)", |
| r"2\.96":"2.96"} |
| |
| # These map the field name to the field id bugzilla assigns. We need |
| # the id when doing bug activity. |
| fieldids = {"State":8, "Responsible":15} |
| |
| # These are the keywords we use in gcc bug tracking. They are transformed |
| # into bugzilla keywords. The format here is <keyword>-><bugzilla keyword id> |
| keywordids = {"wrong-code":1, "ice-on-legal-code":2, "ice-on-illegal-code":3, |
| "rejects-legal":4, "accepts-illegal":5, "pessimizes-code":6} |
| |
| # Map from GNATS states to Bugzilla states. Duplicates and reopened bugs |
| # are handled when parsing the audit trail, so no need for them here. |
| state_lookup = {"":"NEW", "open":"ASSIGNED", "analyzed":"ASSIGNED", |
| "feedback":"WAITING", "closed":"CLOSED", |
| "suspended":"SUSPENDED"} |
| |
| # Table of versions that exist in the bugs, built up as we go along |
| versions_table = {} |
| |
| # Delimiter gnatsweb uses for attachments |
| attachment_delimiter = "----gnatsweb-attachment----\n" |
| |
| # Here starts the various regular expressions we use |
| # Matches an entire GNATS single line field |
| gnatfieldre = re.compile(r"""^([>\w\-]+)\s*:\s*(.*)\s*$""") |
| |
| # Matches the name of a GNATS field |
| fieldnamere = re.compile(r"""^>(.*)$""") |
| |
| # Matches the useless part of an envelope |
| uselessre = re.compile(r"""^(\S*?):\s*""", re.MULTILINE) |
| |
| # Matches the filename in a content disposition |
| dispositionre = re.compile("(\\S+);\\s*filename=\"([^\"]+)\"") |
| |
| # Matches the last changed date in the entire text of a bug |
| # If you have other editable fields that get audit trail entries, modify this |
| # The field names are explicitly listed in order to speed up matching |
| lastdatere = re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-When: )(.+?)$""", re.MULTILINE) |
| |
| # Matches the From line of an email or the first line of an audit trail entry |
| # We use this re to find the begin lines of all the audit trail entries |
| # The field names are explicitly listed in order to speed up matching |
| fromtore=re.compile(r"""^(?:(?:State|Responsible|Priority|Severity)-Changed-From-To: |From: )""", re.MULTILINE) |
| |
| # These re's match the various parts of an audit trail entry |
| changedfromtore=re.compile(r"""^(\w+?)-Changed-From-To: (.+?)$""", re.MULTILINE) |
| changedbyre=re.compile(r"""^\w+?-Changed-By: (.+?)$""", re.MULTILINE) |
| changedwhenre=re.compile(r"""^\w+?-Changed-When: (.+?)$""", re.MULTILINE) |
| changedwhyre=re.compile(r"""^\w+?-Changed-Why:\s*(.*?)$""", re.MULTILINE) |
| |
| # This re matches audit trail text saying that the current bug is a duplicate of another |
| duplicatere=re.compile(r"""(?:")?Dup(?:licate)?(?:d)?(?:")? of .*?(\d+)""", re.IGNORECASE | re.MULTILINE) |
| |
| # Get the text of a From: line |
| fromre=re.compile(r"""^From: (.*?)$""", re.MULTILINE) |
| |
| # Get the text of a Date: Line |
| datere=re.compile(r"""^Date: (.*?)$""", re.MULTILINE) |
| |
| # Map of the responsible file to email addresses |
| responsible_map = {} |
| # List of records in the responsible file |
| responsible_list = [] |
| # List of records in the categories file |
| categories_list = [] |
| # List of pr's in the index |
| pr_list = [] |
| # Map usernames to user ids |
| usermapping = {} |
| # Start with this user id |
| userid_base = 2 |
| |
| # Name of gnats user |
| gnats_username = "gnats@gcc.gnu.org" |
| # Name of unassigned user |
| unassigned_username = "unassigned@gcc.gnu.org" |
| |
| gnats_db_dir = "." |
| product = "gcc" |
| productdesc = "GNU Compiler Connection" |
| milestoneurl = "http://gcc/gnu.org" |
| defaultmilestone = "3.4" |
| |
| def write_non_bug_tables(): |
| """ Write out the non-bug related tables, such as products, profiles, etc.""" |
| # Set all non-unconfirmed bugs's everconfirmed flag |
| print >>outfile, "update bugs set everconfirmed=1 where bug_status != 'UNCONFIRMED';" |
| |
| # Set all bugs assigned to the unassigned user to NEW |
| print >>outfile, "update bugs set bug_status='NEW',assigned_to='NULL' where bug_status='ASSIGNED' AND assigned_to=3;" |
| |
| # Insert the products |
| print >>outfile, "\ninsert into products (" |
| print >>outfile, " product, description, milestoneurl, disallownew," |
| print >>outfile, " defaultmilestone, votestoconfirm) values (" |
| print >>outfile, " '%s', '%s', '%s', 0, '%s', 1);" % (product, |
| productdesc, |
| milestoneurl, |
| defaultmilestone) |
| |
| # Insert the components |
| for category in categories_list: |
| component = SqlQuote(category[0]) |
| productstr = SqlQuote(product) |
| description = SqlQuote(category[1]) |
| initialowner = SqlQuote("3") |
| print >>outfile, "\ninsert into components ("; |
| print >>outfile, " value, program, initialowner, initialqacontact," |
| print >>outfile, " description) values (" |
| print >>outfile, " %s, %s, %s, '', %s);" % (component, productstr, |
| initialowner, description) |
| |
| # Insert the versions |
| for productstr, version_list in versions_table.items(): |
| productstr = SqlQuote(productstr) |
| for version in version_list: |
| version = SqlQuote(version) |
| print >>outfile, "\ninsert into versions (value, program) " |
| print >>outfile, " values (%s, %s);" % (version, productstr) |
| |
| # Insert the users |
| for username, userid in usermapping.items(): |
| realname = map_username_to_realname(username) |
| username = SqlQuote(username) |
| realname = SqlQuote(realname) |
| print >>outfile, "\ninsert into profiles (" |
| print >>outfile, " userid, login_name, password, cryptpassword, realname, groupset" |
| print >>outfile, ") values (" |
| print >>outfile, "%s,%s,'password',encrypt('password'), %s, 0);" % (userid, username, realname) |
| print >>outfile, "update profiles set groupset=1 << 32 where login_name like '%\@gcc.gnu.org';" |
| |
| def unixdate2datetime(unixdate): |
| """ Convert a unix date to a datetime value """ |
| year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate) |
| return "%d-%02d-%02d %02d:%02d:%02d" % (year,month,day,hour,min,sec) |
| |
| def unixdate2timestamp(unixdate): |
| """ Convert a unix date to a timestamp value """ |
| year, month, day, hour, min, sec, x, x, x, x = email.Utils.parsedate_tz(unixdate) |
| return "%d%02d%02d%02d%02d%02d" % (year,month,day,hour,min,sec) |
| |
| def SqlQuote(str): |
| """ Perform SQL quoting on a string """ |
| return "'%s'" % str.replace("'", """''""").replace("\\", "\\\\").replace("\0","\\0") |
| |
| def convert_gccver_to_ver(gccver): |
| """ Given a gcc version, convert it to a Bugzilla version. """ |
| for k in releasetovermap.keys(): |
| if re.search(".*%s.*" % k, gccver) is not None: |
| return releasetovermap[k] |
| result = re.search(r""".*(\d\.\d) \d+ \(experimental\).*""", gccver) |
| if result is not None: |
| return result.group(1) |
| return "unknown" |
| |
| def load_index(fname): |
| """ Load in the GNATS index file """ |
| global pr_list |
| ifp = open(fname) |
| for record in ifp.xreadlines(): |
| fields = record.split("|") |
| pr_list.append(fields[0]) |
| ifp.close() |
| |
| def load_categories(fname): |
| """ Load in the GNATS categories file """ |
| global categories_list |
| cfp = open(fname) |
| for record in cfp.xreadlines(): |
| if re.search("^#", record) is not None: |
| continue |
| categories_list.append(record.split(":")) |
| cfp.close() |
| |
| def map_username_to_realname(username): |
| """ Given a username, find the real name """ |
| name = username |
| name = re.sub("@.*", "", name) |
| for responsible_record in responsible_list: |
| if responsible_record[0] == name: |
| return responsible_record[1] |
| if len(responsible_record) > 2: |
| if responsible_record[2] == username: |
| return responsible_record[1] |
| return "" |
| |
| |
| def get_userid(responsible): |
| """ Given an email address, get the user id """ |
| global responsible_map |
| global usermapping |
| global userid_base |
| if responsible is None: |
| return -1 |
| responsible = responsible.lower() |
| responsible = re.sub("sources.redhat.com", "gcc.gnu.org", responsible) |
| if responsible_map.has_key(responsible): |
| responsible = responsible_map[responsible] |
| if usermapping.has_key(responsible): |
| return usermapping[responsible] |
| else: |
| usermapping[responsible] = userid_base |
| userid_base += 1 |
| return usermapping[responsible] |
| |
| def load_responsible(fname): |
| """ Load in the GNATS responsible file """ |
| global responsible_map |
| global responsible_list |
| rfp = open(fname) |
| for record in rfp.xreadlines(): |
| if re.search("^#", record) is not None: |
| continue |
| split_record = record.split(":") |
| responsible_map[split_record[0]] = split_record[2].rstrip() |
| responsible_list.append(record.split(":")) |
| rfp.close() |
| |
| def split_csl(list): |
| """ Split a comma separated list """ |
| newlist = re.split(r"""\s*,\s*""", list) |
| return newlist |
| |
| def fix_email_addrs(addrs): |
| """ Perform various fixups and cleaning on an e-mail address """ |
| addrs = split_csl(addrs) |
| trimmed_addrs = [] |
| for addr in addrs: |
| addr = re.sub(r"""\(.*\)""","",addr) |
| addr = re.sub(r""".*<(.*)>.*""","\\1",addr) |
| addr = addr.rstrip() |
| addr = addr.lstrip() |
| trimmed_addrs.append(addr) |
| addrs = ", ".join(trimmed_addrs) |
| return addrs |
| |
| class Bugzillabug(object): |
| """ Class representing a bugzilla bug """ |
| def __init__(self, gbug): |
| """ Initialize a bugzilla bug from a GNATS bug. """ |
| self.bug_id = gbug.bug_id |
| self.long_descs = [] |
| self.bug_ccs = [get_userid("gcc-bugs@gcc.gnu.org")] |
| self.bug_activity = [] |
| self.attachments = gbug.attachments |
| self.gnatsfields = gbug.fields |
| self.need_unformatted = gbug.has_unformatted_attach == 0 |
| self.need_unformatted &= gbug.fields.has_key("Unformatted") |
| self.translate_pr() |
| self.update_versions() |
| if self.fields.has_key("Audit-Trail"): |
| self.parse_audit_trail() |
| self.write_bug() |
| |
| def parse_fromto(type, string): |
| """ Parses the from and to parts of a changed-from-to line """ |
| fromstr = "" |
| tostr = "" |
| |
| # Some slightly messed up changed lines have unassigned-new, |
| # instead of unassigned->new. So we make the > optional. |
| result = re.search(r"""(.*)-(?:>?)(.*)""", string) |
| |
| # Only know how to handle parsing of State and Responsible |
| # changed-from-to right now |
| if type == "State": |
| fromstr = state_lookup[result.group(1)] |
| tostr = state_lookup[result.group(2)] |
| elif type == "Responsible": |
| if result.group(1) != "": |
| fromstr = result.group(1) |
| if result.group(2) != "": |
| tostr = result.group(2) |
| if responsible_map.has_key(fromstr): |
| fromstr = responsible_map[fromstr] |
| if responsible_map.has_key(tostr): |
| tostr = responsible_map[tostr] |
| return (fromstr, tostr) |
| parse_fromto = staticmethod(parse_fromto) |
| |
| def parse_audit_trail(self): |
| """ Parse a GNATS audit trail """ |
| trail = self.fields["Audit-Trail"] |
| # Begin to split the audit trail into pieces |
| result = fromtore.finditer(trail) |
| starts = [] |
| ends = [] |
| pieces = [] |
| # Make a list of the pieces |
| for x in result: |
| pieces.append (x) |
| # Find the start and end of each piece |
| if len(pieces) > 0: |
| for x in xrange(len(pieces)-1): |
| starts.append(pieces[x].start()) |
| ends.append(pieces[x+1].start()) |
| starts.append(pieces[-1].start()) |
| ends.append(len(trail)) |
| pieces = [] |
| # Now make the list of actual text of the pieces |
| for x in xrange(len(starts)): |
| pieces.append(trail[starts[x]:ends[x]]) |
| # And parse the actual pieces |
| for piece in pieces: |
| result = changedfromtore.search(piece) |
| # See what things we actually have inside this entry, and |
| # handle them appropriately |
| if result is not None: |
| type = result.group(1) |
| changedfromto = result.group(2) |
| # If the bug was reopened, mark it as such |
| if changedfromto.find("closed->analyzed") != -1: |
| if self.fields["bug_status"] == "'NEW'": |
| self.fields["bug_status"] = "'REOPENED'" |
| if type == "State" or type == "Responsible": |
| oldstate, newstate = self.parse_fromto (type, changedfromto) |
| result = changedbyre.search(piece) |
| if result is not None: |
| changedby = result.group(1) |
| result = changedwhenre.search(piece) |
| if result is not None: |
| changedwhen = result.group(1) |
| changedwhen = unixdate2datetime(changedwhen) |
| changedwhen = SqlQuote(changedwhen) |
| result = changedwhyre.search(piece) |
| changedwhy = piece[result.start(1):] |
| #changedwhy = changedwhy.lstrip() |
| changedwhy = changedwhy.rstrip() |
| changedby = get_userid(changedby) |
| # Put us on the cc list if we aren't there already |
| if changedby != self.fields["userid"] \ |
| and changedby not in self.bug_ccs: |
| self.bug_ccs.append(changedby) |
| # If it's a duplicate, mark it as such |
| result = duplicatere.search(changedwhy) |
| if result is not None: |
| newtext = "*** This bug has been marked as a duplicate of %s ***" % result.group(1) |
| newtext = SqlQuote(newtext) |
| self.long_descs.append((self.bug_id, changedby, |
| changedwhen, newtext)) |
| self.fields["bug_status"] = "'RESOLVED'" |
| self.fields["resolution"] = "'DUPLICATE'" |
| self.fields["userid"] = changedby |
| else: |
| newtext = "%s-Changed-From-To: %s\n%s-Changed-Why: %s\n" % (type, changedfromto, type, changedwhy) |
| newtext = SqlQuote(newtext) |
| self.long_descs.append((self.bug_id, changedby, |
| changedwhen, newtext)) |
| if type == "State" or type == "Responsible": |
| newstate = SqlQuote("%s" % newstate) |
| oldstate = SqlQuote("%s" % oldstate) |
| fieldid = fieldids[type] |
| self.bug_activity.append((newstate, oldstate, fieldid, changedby, changedwhen)) |
| |
| else: |
| # It's an email |
| result = fromre.search(piece) |
| if result is None: |
| continue |
| fromstr = result.group(1) |
| fromstr = fix_email_addrs(fromstr) |
| fromstr = get_userid(fromstr) |
| result = datere.search(piece) |
| if result is None: |
| continue |
| datestr = result.group(1) |
| datestr = SqlQuote(unixdate2timestamp(datestr)) |
| if fromstr != self.fields["userid"] \ |
| and fromstr not in self.bug_ccs: |
| self.bug_ccs.append(fromstr) |
| self.long_descs.append((self.bug_id, fromstr, datestr, |
| SqlQuote(piece))) |
| |
| |
| |
| def write_bug(self): |
| """ Output a bug to the data file """ |
| fields = self.fields |
| print >>outfile, "\ninsert into bugs(" |
| print >>outfile, " bug_id, assigned_to, bug_severity, priority, bug_status, creation_ts, delta_ts," |
| print >>outfile, " short_desc," |
| print >>outfile, " reporter, version," |
| print >>outfile, " product, component, resolution, target_milestone, qa_contact," |
| print >>outfile, " gccbuild, gcctarget, gcchost, keywords" |
| print >>outfile, " ) values (" |
| print >>outfile, "%s, %s, %s, %s, %s, %s, %s," % (self.bug_id, fields["userid"], fields["bug_severity"], fields["priority"], fields["bug_status"], fields["creation_ts"], fields["delta_ts"]) |
| print >>outfile, "%s," % (fields["short_desc"]) |
| print >>outfile, "%s, %s," % (fields["reporter"], fields["version"]) |
| print >>outfile, "%s, %s, %s, %s, 0," %(fields["product"], fields["component"], fields["resolution"], fields["target_milestone"]) |
| print >>outfile, "%s, %s, %s, %s" % (fields["gccbuild"], fields["gcctarget"], fields["gcchost"], fields["keywords"]) |
| print >>outfile, ");" |
| if self.fields["keywords"] != 0: |
| print >>outfile, "\ninsert into keywords (bug_id, keywordid) values (" |
| print >>outfile, " %s, %s);" % (self.bug_id, fields["keywordid"]) |
| for id, who, when, text in self.long_descs: |
| print >>outfile, "\ninsert into longdescs (" |
| print >>outfile, " bug_id, who, bug_when, thetext) values(" |
| print >>outfile, " %s, %s, %s, %s);" % (id, who, when, text) |
| for name, data, who in self.attachments: |
| print >>outfile, "\ninsert into attachments (" |
| print >>outfile, " bug_id, filename, description, mimetype, ispatch, submitter_id) values (" |
| ftype = None |
| # It's *magic*! |
| if name.endswith(".ii") == 1: |
| ftype = "text/x-c++" |
| elif name.endswith(".i") == 1: |
| ftype = "text/x-c" |
| else: |
| ftype = magicf.detect(cStringIO.StringIO(data)) |
| if ftype is None: |
| ftype = "application/octet-stream" |
| |
| print >>outfile, "%s,%s,%s, %s,0, %s,%s);" %(self.bug_id, SqlQuote(name), SqlQuote(name), SqlQuote (ftype), who) |
| print >>outfile, "\ninsert into attach_data (" |
| print >>outfile, "\n(id, thedata) values (last_insert_id()," |
| print >>outfile, "%s);" % (SqlQuote(zlib.compress(data))) |
| for newstate, oldstate, fieldid, changedby, changedwhen in self.bug_activity: |
| print >>outfile, "\ninsert into bugs_activity (" |
| print >>outfile, " bug_id, who, bug_when, fieldid, added, removed) values (" |
| print >>outfile, " %s, %s, %s, %s, %s, %s);" % (self.bug_id, |
| changedby, |
| changedwhen, |
| fieldid, |
| newstate, |
| oldstate) |
| for cc in self.bug_ccs: |
| print >>outfile, "\ninsert into cc(bug_id, who) values (%s, %s);" %(self.bug_id, cc) |
| def update_versions(self): |
| """ Update the versions table to account for the version on this bug """ |
| global versions_table |
| if self.fields.has_key("Release") == 0 \ |
| or self.fields.has_key("Category") == 0: |
| return |
| curr_product = "gcc" |
| curr_version = self.fields["Release"] |
| if curr_version == "": |
| return |
| curr_version = convert_gccver_to_ver (curr_version) |
| if versions_table.has_key(curr_product) == 0: |
| versions_table[curr_product] = [] |
| for version in versions_table[curr_product]: |
| if version == curr_version: |
| return |
| versions_table[curr_product].append(curr_version) |
| def translate_pr(self): |
| """ Transform a GNATS PR into a Bugzilla bug """ |
| self.fields = self.gnatsfields |
| if (self.fields.has_key("Organization") == 0) \ |
| or self.fields["Organization"].find("GCC"): |
| self.fields["Originator"] = "" |
| self.fields["Organization"] = "" |
| self.fields["Organization"].lstrip() |
| if (self.fields.has_key("Release") == 0) \ |
| or self.fields["Release"] == "" \ |
| or self.fields["Release"].find("unknown-1.0") != -1: |
| self.fields["Release"]="unknown" |
| if self.fields.has_key("Responsible"): |
| result = re.search(r"""\w+""", self.fields["Responsible"]) |
| self.fields["Responsible"] = "%s%s" % (result.group(0), "@gcc.gnu.org") |
| self.fields["gcchost"] = "" |
| self.fields["gcctarget"] = "" |
| self.fields["gccbuild"] = "" |
| if self.fields.has_key("Environment"): |
| result = re.search("^host: (.+?)$", self.fields["Environment"], |
| re.MULTILINE) |
| if result is not None: |
| self.fields["gcchost"] = result.group(1) |
| result = re.search("^target: (.+?)$", self.fields["Environment"], |
| re.MULTILINE) |
| if result is not None: |
| self.fields["gcctarget"] = result.group(1) |
| result = re.search("^build: (.+?)$", self.fields["Environment"], |
| re.MULTILINE) |
| if result is not None: |
| self.fields["gccbuild"] = result.group(1) |
| self.fields["userid"] = get_userid(self.fields["Responsible"]) |
| self.fields["bug_severity"] = "normal" |
| if self.fields["Class"] == "change-request": |
| self.fields["bug_severity"] = "enhancement" |
| elif self.fields.has_key("Severity"): |
| if self.fields["Severity"] == "critical": |
| self.fields["bug_severity"] = "critical" |
| elif self.fields["Severity"] == "serious": |
| self.fields["bug_severity"] = "major" |
| elif self.fields.has_key("Synopsis"): |
| if re.search("crash|assert", self.fields["Synopsis"]): |
| self.fields["bug_severity"] = "critical" |
| elif re.search("wrong|error", self.fields["Synopsis"]): |
| self.fields["bug_severity"] = "major" |
| self.fields["bug_severity"] = SqlQuote(self.fields["bug_severity"]) |
| self.fields["keywords"] = 0 |
| if keywordids.has_key(self.fields["Class"]): |
| self.fields["keywords"] = self.fields["Class"] |
| self.fields["keywordid"] = keywordids[self.fields["Class"]] |
| self.fields["keywords"] = SqlQuote(self.fields["keywords"]) |
| self.fields["priority"] = "P1" |
| if self.fields.has_key("Severity") and self.fields.has_key("Priority"): |
| severity = self.fields["Severity"] |
| priority = self.fields["Priority"] |
| if severity == "critical": |
| if priority == "high": |
| self.fields["priority"] = "P1" |
| else: |
| self.fields["priority"] = "P2" |
| elif severity == "serious": |
| if priority == "low": |
| self.fields["priority"] = "P4" |
| else: |
| self.fields["priority"] = "P3" |
| else: |
| if priority == "high": |
| self.fields["priority"] = "P4" |
| else: |
| self.fields["priority"] = "P5" |
| self.fields["priority"] = SqlQuote(self.fields["priority"]) |
| state = self.fields["State"] |
| if (state == "open" or state == "analyzed") and self.fields["userid"] != 3: |
| self.fields["bug_status"] = "ASSIGNED" |
| self.fields["resolution"] = "" |
| elif state == "feedback": |
| self.fields["bug_status"] = "WAITING" |
| self.fields["resolution"] = "" |
| elif state == "closed": |
| self.fields["bug_status"] = "CLOSED" |
| if self.fields.has_key("Class"): |
| theclass = self.fields["Class"] |
| if theclass.find("duplicate") != -1: |
| self.fields["resolution"]="DUPLICATE" |
| elif theclass.find("mistaken") != -1: |
| self.fields["resolution"]="INVALID" |
| else: |
| self.fields["resolution"]="FIXED" |
| else: |
| self.fields["resolution"]="FIXED" |
| elif state == "suspended": |
| self.fields["bug_status"] = "SUSPENDED" |
| self.fields["resolution"] = "" |
| elif state == "analyzed" and self.fields["userid"] == 3: |
| self.fields["bug_status"] = "NEW" |
| self.fields["resolution"] = "" |
| else: |
| self.fields["bug_status"] = "UNCONFIRMED" |
| self.fields["resolution"] = "" |
| self.fields["bug_status"] = SqlQuote(self.fields["bug_status"]) |
| self.fields["resolution"] = SqlQuote(self.fields["resolution"]) |
| self.fields["creation_ts"] = "" |
| if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "": |
| self.fields["creation_ts"] = unixdate2datetime(self.fields["Arrival-Date"]) |
| self.fields["creation_ts"] = SqlQuote(self.fields["creation_ts"]) |
| self.fields["delta_ts"] = "" |
| if self.fields.has_key("Audit-Trail"): |
| result = lastdatere.findall(self.fields["Audit-Trail"]) |
| result.reverse() |
| if len(result) > 0: |
| self.fields["delta_ts"] = unixdate2timestamp(result[0]) |
| if self.fields["delta_ts"] == "": |
| if self.fields.has_key("Arrival-Date") and self.fields["Arrival-Date"] != "": |
| self.fields["delta_ts"] = unixdate2timestamp(self.fields["Arrival-Date"]) |
| self.fields["delta_ts"] = SqlQuote(self.fields["delta_ts"]) |
| self.fields["short_desc"] = SqlQuote(self.fields["Synopsis"]) |
| if self.fields.has_key("Reply-To") and self.fields["Reply-To"] != "": |
| self.fields["reporter"] = get_userid(self.fields["Reply-To"]) |
| elif self.fields.has_key("Mail-Header"): |
| result = re.search(r"""From .*?([\w.]+@[\w.]+)""", self.fields["Mail-Header"]) |
| if result: |
| self.fields["reporter"] = get_userid(result.group(1)) |
| else: |
| self.fields["reporter"] = get_userid(gnats_username) |
| else: |
| self.fields["reporter"] = get_userid(gnats_username) |
| long_desc = self.fields["Description"] |
| long_desc2 = "" |
| for field in ["Release", "Environment", "How-To-Repeat"]: |
| if self.fields.has_key(field) and self.fields[field] != "": |
| long_desc += ("\n\n%s:\n" % field) + self.fields[field] |
| if self.fields.has_key("Fix") and self.fields["Fix"] != "": |
| long_desc2 = "Fix:\n" + self.fields["Fix"] |
| if self.need_unformatted == 1 and self.fields["Unformatted"] != "": |
| long_desc += "\n\nUnformatted:\n" + self.fields["Unformatted"] |
| if long_desc != "": |
| self.long_descs.append((self.bug_id, self.fields["reporter"], |
| self.fields["creation_ts"], |
| SqlQuote(long_desc))) |
| if long_desc2 != "": |
| self.long_descs.append((self.bug_id, self.fields["reporter"], |
| self.fields["creation_ts"], |
| SqlQuote(long_desc2))) |
| for field in ["gcchost", "gccbuild", "gcctarget"]: |
| self.fields[field] = SqlQuote(self.fields[field]) |
| self.fields["version"] = "" |
| if self.fields["Release"] != "": |
| self.fields["version"] = convert_gccver_to_ver (self.fields["Release"]) |
| self.fields["version"] = SqlQuote(self.fields["version"]) |
| self.fields["product"] = SqlQuote("gcc") |
| self.fields["component"] = "invalid" |
| if self.fields.has_key("Category"): |
| self.fields["component"] = self.fields["Category"] |
| self.fields["component"] = SqlQuote(self.fields["component"]) |
| self.fields["target_milestone"] = "---" |
| if self.fields["version"].find("3.4") != -1: |
| self.fields["target_milestone"] = "3.4" |
| self.fields["target_milestone"] = SqlQuote(self.fields["target_milestone"]) |
| if self.fields["userid"] == 2: |
| self.fields["userid"] = "\'NULL\'" |
| |
| class GNATSbug(object): |
| """ Represents a single GNATS PR """ |
| def __init__(self, filename): |
| self.attachments = [] |
| self.has_unformatted_attach = 0 |
| fp = open (filename) |
| self.fields = self.parse_pr(fp.xreadlines()) |
| self.bug_id = int(self.fields["Number"]) |
| if self.fields.has_key("Unformatted"): |
| self.find_gnatsweb_attachments() |
| if self.fields.has_key("How-To-Repeat"): |
| self.find_regular_attachments("How-To-Repeat") |
| if self.fields.has_key("Fix"): |
| self.find_regular_attachments("Fix") |
| |
| def get_attacher(fields): |
| if fields.has_key("Reply-To") and fields["Reply-To"] != "": |
| return get_userid(fields["Reply-To"]) |
| else: |
| result = None |
| if fields.has_key("Mail-Header"): |
| result = re.search(r"""From .*?([\w.]+\@[\w.]+)""", |
| fields["Mail-Header"]) |
| if result is not None: |
| reporter = get_userid(result.group(1)) |
| else: |
| reporter = get_userid(gnats_username) |
| get_attacher = staticmethod(get_attacher) |
| def find_regular_attachments(self, which): |
| fields = self.fields |
| while re.search("^begin [0-7]{3}", fields[which], |
| re.DOTALL | re.MULTILINE): |
| outfp = cStringIO.StringIO() |
| infp = cStringIO.StringIO(fields[which]) |
| filename, start, end = specialuu.decode(infp, outfp, quiet=0) |
| fields[which]=fields[which].replace(fields[which][start:end], |
| "See attachments for %s\n" % filename) |
| self.attachments.append((filename, outfp.getvalue(), |
| self.get_attacher(fields))) |
| |
| def decode_gnatsweb_attachment(self, attachment): |
| result = re.split(r"""\n\n""", attachment, 1) |
| if len(result) == 1: |
| return -1 |
| envelope, body = result |
| envelope = uselessre.split(envelope) |
| envelope.pop(0) |
| # Turn the list of key, value into a dict of key => value |
| attachinfo = dict([(envelope[i], envelope[i+1]) for i in xrange(0,len(envelope),2)]) |
| for x in attachinfo.keys(): |
| attachinfo[x] = attachinfo[x].rstrip() |
| if (attachinfo.has_key("Content-Type") == 0) or \ |
| (attachinfo.has_key("Content-Disposition") == 0): |
| raise ValueError, "Unable to parse file attachment" |
| result = dispositionre.search(attachinfo["Content-Disposition"]) |
| filename = result.group(2) |
| filename = re.sub(".*/","", filename) |
| filename = re.sub(".*\\\\","", filename) |
| attachinfo["filename"]=filename |
| result = re.search("""(\S+);.*""", attachinfo["Content-Type"]) |
| if result is not None: |
| attachinfo["Content-Type"] = result.group(1) |
| if attachinfo.has_key("Content-Transfer-Encoding"): |
| if attachinfo["Content-Transfer-Encoding"] == "base64": |
| attachinfo["data"] = base64.decodestring(body) |
| else: |
| attachinfo["data"]=body |
| |
| return (attachinfo["filename"], attachinfo["data"], |
| self.get_attacher(self.fields)) |
| |
| def find_gnatsweb_attachments(self): |
| fields = self.fields |
| attachments = re.split(attachment_delimiter, fields["Unformatted"]) |
| fields["Unformatted"] = attachments.pop(0) |
| for attachment in attachments: |
| result = self.decode_gnatsweb_attachment (attachment) |
| if result != -1: |
| self.attachments.append(result) |
| self.has_unformatted_attach = 1 |
| def parse_pr(lines): |
| #fields = {"envelope":[]} |
| fields = {"envelope":array.array("c")} |
| hdrmulti = "envelope" |
| for line in lines: |
| line = line.rstrip('\n') |
| line += '\n' |
| result = gnatfieldre.search(line) |
| if result is None: |
| if hdrmulti != "": |
| if fields.has_key(hdrmulti): |
| #fields[hdrmulti].append(line) |
| fields[hdrmulti].fromstring(line) |
| else: |
| #fields[hdrmulti] = [line] |
| fields[hdrmulti] = array.array("c", line) |
| continue |
| hdr, arg = result.groups() |
| ghdr = "*not valid*" |
| result = fieldnamere.search(hdr) |
| if result != None: |
| ghdr = result.groups()[0] |
| if ghdr in fieldnames: |
| if multilinefields.has_key(ghdr): |
| hdrmulti = ghdr |
| #fields[ghdr] = [""] |
| fields[ghdr] = array.array("c") |
| else: |
| hdrmulti = "" |
| #fields[ghdr] = [arg] |
| fields[ghdr] = array.array("c", arg) |
| elif hdrmulti != "": |
| #fields[hdrmulti].append(line) |
| fields[hdrmulti].fromstring(line) |
| if hdrmulti == "envelope" and \ |
| (hdr == "Reply-To" or hdr == "From" \ |
| or hdr == "X-GNATS-Notify"): |
| arg = fix_email_addrs(arg) |
| #fields[hdr] = [arg] |
| fields[hdr] = array.array("c", arg) |
| if fields.has_key("Reply-To") and len(fields["Reply-To"]) > 0: |
| fields["Reply-To"] = fields["Reply-To"] |
| else: |
| fields["Reply-To"] = fields["From"] |
| if fields.has_key("From"): |
| del fields["From"] |
| if fields.has_key("X-GNATS-Notify") == 0: |
| fields["X-GNATS-Notify"] = array.array("c") |
| #fields["X-GNATS-Notify"] = "" |
| for x in fields.keys(): |
| fields[x] = fields[x].tostring() |
| #fields[x] = "".join(fields[x]) |
| for x in fields.keys(): |
| if multilinefields.has_key(x): |
| fields[x] = fields[x].rstrip() |
| |
| return fields |
| parse_pr = staticmethod(parse_pr) |
| load_index("%s/gnats-adm/index" % gnats_db_dir) |
| load_categories("%s/gnats-adm/categories" % gnats_db_dir) |
| load_responsible("%s/gnats-adm/responsible" % gnats_db_dir) |
| get_userid(gnats_username) |
| get_userid(unassigned_username) |
| for x in pr_list: |
| print "Processing %s..." % x |
| a = GNATSbug ("%s/%s" % (gnats_db_dir, x)) |
| b = Bugzillabug(a) |
| write_non_bug_tables() |
| outfile.close() |