Edgewall Software

Ticket #1462: bugzilla2trac.py.6.diff

File bugzilla2trac.py.6.diff, 43.6 KB (added by asmodai@…, 3 years ago)

Continue building on Guillaume's work and support BZ 2.1[89] as well as PySQLite2

  • bugzilla2trac.py

     
    33""" 
    44Import a Bugzilla items into a Trac database. 
    55 
    6 Requires:  Trac 0.7.1 from http://trac.edgewall.com/ 
     6Requires:  Trac 0.9b1 from http://trac.edgewall.com/ 
    77           Python 2.3 from http://www.python.org/ 
    88           MySQL >= 3.23 from http://www.mysql.org/ 
    99 
     
    1313Copyright 2004, Dmitry Yusupov <dmitry_yus@yahoo.com> 
    1414 
    1515Many enhancements, Bill Soudan <bill@soudan.net> 
     16Other enhancements, Florent Guillaume <fg@nuxeo.com> 
     17Reworked, Jeroen Ruigrok van der Werven <asmodai@tendra.org> 
     18 
     19$Id$ 
    1620""" 
    1721 
     22import re 
     23 
    1824### 
    1925### Conversion Settings -- edit these before running if desired 
    2026### 
     
    2228# Bugzilla version.  You can find this in Bugzilla's globals.pl file. 
    2329# 
    2430# Currently, the following bugzilla versions are known to work: 
    25 #   2.11 
     31#   2.11 (2110), 2.16.5 (2165), 2.18.3 (2183), 2.19.1 (2191) 
    2632# 
    2733# If you run this script on a version not listed here and it is successful, 
    28 # please report it to the Trac mailing list so we can update the list. 
    29 BZ_VERSION = '2.16.5' 
     34# please report it to the Trac mailing list and drop a note to 
     35# asmodai@tendra.org so we can update the list. 
     36BZ_VERSION = 2180 
    3037 
    3138# MySQL connection parameters for the Bugzilla database.  These can also  
    3239# be specified on the command line. 
    33 BZ_DB = '' 
    34 BZ_HOST = 'localhost' 
    35 BZ_USER = '' 
    36 BZ_PASSWORD = '' 
     40BZ_DB = "" 
     41BZ_HOST = "" 
     42BZ_USER = "" 
     43BZ_PASSWORD = "" 
    3744 
    3845# Path to the Trac environment. 
    39 TRAC_ENV = '' 
     46TRAC_ENV = "/usr/local/trac" 
    4047 
    4148# If true, all existing Trac tickets and attachments will be removed  
    4249# prior to import. 
    43 TRAC_CLEAN = False 
     50TRAC_CLEAN = True 
    4451 
    4552# Enclose imported ticket description and comments in a {{{ }}}  
    4653# preformat block?  This formats the text in a fixed-point font. 
    4754PREFORMAT_COMMENTS = False 
    4855 
     56# Replace bug numbers in comments with #xyz 
     57REPLACE_BUG_NO = False 
     58 
     59# Severities 
     60SEVERITIES = [ 
     61    ("blocker",  "1"), 
     62    ("critical", "2"), 
     63    ("major",    "3"), 
     64    ("normal",   "4"), 
     65    ("minor",    "5"), 
     66    ("trivial",  "6") 
     67] 
     68 
     69# Priorities 
     70# If using the default Bugzilla priorities of P1 - P5, do not change anything 
     71# here. 
     72# If you have other priorities defined please change the P1 - P5 mapping to 
     73# the order you want.  You can also collapse multiple priorities on bugzilla's 
     74# side into the same priority on Trac's side, simply adjust PRIORITIES_MAP. 
     75PRIORITIES = [ 
     76    ("highest", "1"), 
     77    ("high",    "2"), 
     78    ("normal",  "3"), 
     79    ("low",     "4"), 
     80    ("lowest",  "5") 
     81] 
     82 
     83# Bugzilla: Trac 
     84# NOTE: Use lowercase. 
     85PRIORITIES_MAP = { 
     86    "p1": "highest", 
     87    "p2": "high", 
     88    "p3": "normal", 
     89    "p4": "low", 
     90    "p5": "lowest" 
     91} 
     92 
    4993# By default, all bugs are imported from Bugzilla.  If you add a list 
    5094# of products here, only bugs from those products will be imported. 
    5195PRODUCTS = [] 
     96# These Bugzilla products will be ignored during import. 
     97IGNORE_PRODUCTS = [] 
    5298 
    53 # Trac doesn't have the concept of a product.  Instead, this script can 
    54 # assign keywords in the ticket entry to represent products. 
    55 # 
    56 # ex. PRODUCT_KEYWORDS = { 'product1' : 'PRODUCT1_KEYWORD' } 
    57 PRODUCT_KEYWORDS = {} 
     99# These milestones are ignored 
     100IGNORE_MILESTONES = ["---"] 
    58101 
     102# These logins are converted to these user ids 
     103LOGIN_MAP = { 
     104    #'some.user@example.com': 'someuser', 
     105} 
     106 
     107# These emails are removed from CC list 
     108IGNORE_CC = [ 
     109    #'loser@example.com', 
     110] 
     111 
     112# The 'component' field in Trac can come either from the Product or 
     113# or from the Component field of Bugzilla. COMPONENTS_FROM_PRODUCTS 
     114# switches the behavior. 
     115# If COMPONENTS_FROM_PRODUCTS is True: 
     116# - Bugzilla Product -> Trac Component 
     117# - Bugzilla Component -> Trac Keyword 
     118# IF COMPONENTS_FROM_PRODUCTS is False: 
     119# - Bugzilla Product -> Trac Keyword 
     120# - Bugzilla Component -> Trac Component 
     121COMPONENTS_FROM_PRODUCTS = False 
     122 
     123# If COMPONENTS_FROM_PRODUCTS is True, the default owner for each 
     124# Trac component is inferred from a default Bugzilla component. 
     125DEFAULT_COMPONENTS = ["default", "misc", "main"] 
     126 
     127# This mapping can assign keywords in the ticket entry to represent 
     128# products or components (depending on COMPONENTS_FROM_PRODUCTS). 
     129# The keyword will be ignored if empty. 
     130KEYWORDS_MAPPING = { 
     131    #'Bugzilla_product_or_component': 'Keyword', 
     132    "default": "", 
     133    "misc": "", 
     134    } 
     135 
     136# If this is True, products or components are all set as keywords 
     137# even if not mentionned in KEYWORDS_MAPPING. 
     138MAP_ALL_KEYWORDS = True 
     139 
     140 
    59141# Bug comments that should not be imported.  Each entry in list should 
    60142# be a regular expression. 
    61143IGNORE_COMMENTS = [ 
    62    '^Created an attachment \(id=' 
     144   "^Created an attachment \(id=" 
    63145] 
    64146 
    65147########################################################################### 
     
    76158# There is some special magic for open in the code:  if there is no 
    77159# Bugzilla owner, open is mapped to 'new' instead. 
    78160STATUS_TRANSLATE = { 
    79   'unconfirmed' : 'new', 
    80   'open' : 'assigned', 
    81   'resolved' : 'closed', 
    82   'verified' : 'closed', 
    83   'released' : 'closed' 
     161  "unconfirmed": "new", 
     162  "open":        "assigned", 
     163  "resolved":    "closed", 
     164  "verified":    "closed", 
     165  "released":    "closed" 
    84166} 
    85167 
    86168# Translate Bugzilla statuses into Trac keywords.  This provides a way  
    87169# to retain the Bugzilla statuses in Trac.  e.g. when a bug is marked  
    88170# 'verified' in Bugzilla it will be assigned a VERIFIED keyword. 
    89171STATUS_KEYWORDS = { 
    90   'verified' : 'VERIFIED', 
    91   'released' : 'RELEASED' 
     172  "verified": "VERIFIED", 
     173  "released": "RELEASED" 
    92174} 
    93175 
    94176# Some fields in Bugzilla do not have equivalents in Trac.  Changes in 
    95177# fields listed here will not be imported into the ticket change history, 
    96178# otherwise you'd see changes for fields that don't exist in Trac. 
    97 IGNORED_ACTIVITY_FIELDS = ['everconfirmed'] 
     179IGNORED_ACTIVITY_FIELDS = ["everconfirmed"] 
    98180 
     181# Regular expression and its replacement 
     182BUG_NO_RE = re.compile(r"\b(bug #?)([0-9])") 
     183BUG_NO_REPL = r"#\2" 
     184 
    99185### 
    100186### Script begins here 
    101187### 
    102188 
    103189import os 
    104 import re 
    105190import sys 
    106191import string 
    107192import StringIO 
    108193 
    109194import MySQLdb 
    110195import MySQLdb.cursors 
    111 import trac.env 
     196try: 
     197    from trac.env import Environment 
     198except: 
     199    from trac.Environment import Environment 
     200from trac.attachment import Attachment 
    112201 
    113202if not hasattr(sys, 'setdefaultencoding'): 
    114203    reload(sys) 
     
    116205sys.setdefaultencoding('latin1') 
    117206 
    118207# simulated Attachment class for trac.add 
    119 class Attachment: 
    120     def __init__(self, name, data): 
    121         self.filename = name 
    122         self.file = StringIO.StringIO(data.tostring()) 
     208#class Attachment: 
     209#    def __init__(self, name, data): 
     210#        self.filename = name 
     211#        self.file = StringIO.StringIO(data.tostring()) 
    123212   
    124213# simple field translation mapping.  if string not in 
    125214# mapping, just return string, otherwise return value 
     
    134223 
    135224class TracDatabase(object): 
    136225    def __init__(self, path): 
    137         self.env = trac.env.Environment(path) 
     226        self.env = Environment(path) 
    138227        self._db = self.env.get_db_cnx() 
    139228        self._db.autocommit = False 
    140229        self.loginNameCache = {} 
     
    145234     
    146235    def hasTickets(self): 
    147236        c = self.db().cursor() 
    148         c.execute('''SELECT count(*) FROM Ticket''') 
     237        c.execute("SELECT count(*) FROM Ticket") 
    149238        return int(c.fetchall()[0][0]) > 0 
    150239 
    151240    def assertNoTickets(self): 
     
    157246        self.assertNoTickets() 
    158247         
    159248        c = self.db().cursor() 
    160         c.execute("""DELETE FROM enum WHERE type='severity'""") 
     249        c.execute("DELETE FROM enum WHERE type='severity'") 
    161250        for value, i in s: 
    162             print "inserting severity ", value, " ", i 
    163             c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", 
    164                       "severity", value.encode('utf-8'), i) 
     251            print "  inserting severity '%s' - '%s'" % (value, i) 
     252            c.execute("""INSERT INTO enum (type, name, value) 
     253                                   VALUES (%s, %s, %s)""", 
     254                      ("severity", value.encode('utf-8'), i)) 
    165255        self.db().commit() 
    166256     
    167257    def setPriorityList(self, s): 
     
    169259        self.assertNoTickets() 
    170260         
    171261        c = self.db().cursor() 
    172         c.execute("""DELETE FROM enum WHERE type='priority'""") 
     262        c.execute("DELETE FROM enum WHERE type='priority'") 
    173263        for value, i in s: 
    174             print "inserting priority ", value, " ", i 
    175             c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", 
    176                       "priority", 
    177                       value.encode('utf-8'), 
    178                       i) 
     264            print "  inserting priority '%s' - '%s'" % (value, i) 
     265            c.execute("""INSERT INTO enum (type, name, value) 
     266                                   VALUES (%s, %s, %s)""", 
     267                      ("priority", value.encode('utf-8'), i)) 
    179268        self.db().commit() 
    180269 
    181270     
     
    184273        self.assertNoTickets() 
    185274         
    186275        c = self.db().cursor() 
    187         c.execute("""DELETE FROM component""") 
     276        c.execute("DELETE FROM component") 
    188277        for comp in l: 
    189             print "inserting component '",comp[key],"', owner",  comp['owner'] 
    190             c.execute("""INSERT INTO component (name, owner) VALUES (%s, %s)""", 
    191                       comp[key].encode('utf-8'), comp['owner'].encode('utf-8')) 
     278            print "  inserting component '%s', owner '%s'" % \ 
     279                            (comp[key], comp['owner']) 
     280            c.execute("INSERT INTO component (name, owner) VALUES (%s, %s)", 
     281                      (comp[key].encode('utf-8'), 
     282                       comp['owner'].encode('utf-8'))) 
    192283        self.db().commit() 
    193284     
    194285    def setVersionList(self, v, key): 
     
    196287        self.assertNoTickets() 
    197288         
    198289        c = self.db().cursor() 
    199         c.execute("""DELETE FROM version""") 
     290        c.execute("DELETE FROM version") 
    200291        for vers in v: 
    201             print "inserting version ", vers[key] 
    202             c.execute("""INSERT INTO version (name) VALUES (%s)""", 
    203                       vers[key].encode('utf-8')) 
     292            print "  inserting version '%s'" % (vers[key]) 
     293            c.execute("INSERT INTO version (name) VALUES (%s)", (vers[key],)) 
    204294        self.db().commit() 
    205295         
    206296    def setMilestoneList(self, m, key): 
     
    208298        self.assertNoTickets() 
    209299         
    210300        c = self.db().cursor() 
    211         c.execute("""DELETE FROM milestone""") 
     301        c.execute("DELETE FROM milestone") 
    212302        for ms in m: 
    213             print "inserting milestone ", ms[key] 
    214             c.execute("""INSERT INTO milestone (name) VALUES (%s)""", 
    215                       ms[key].encode('utf-8')) 
     303            milestone = ms[key] 
     304            print "  inserting milestone '%s'" % (milestone) 
     305            c.execute("INSERT INTO milestone (name) VALUES (%s)", 
     306                      (milestone.encode('utf-8'))) 
    216307        self.db().commit() 
    217308     
    218     def addTicket(self, id, time, changetime, component, 
    219                   severity, priority, owner, reporter, cc, 
    220                   version, milestone, status, resolution, 
     309    def addTicket(self, id, time, changetime, component, severity, priority, 
     310                  owner, reporter, cc, version, milestone, status, resolution, 
    221311                  summary, description, keywords): 
    222312        c = self.db().cursor() 
    223313         
    224314        desc = description.encode('utf-8') 
     315        type = "defect" 
    225316         
     317        if severity.lower() == "enhancement": 
     318                severity = "minor" 
     319                type = "enhancement" 
     320         
    226321        if PREFORMAT_COMMENTS: 
    227322          desc = '{{{\n%s\n}}}' % desc 
    228323 
    229         print "inserting ticket %s -- %s" % (id, summary) 
    230         c.execute("""INSERT INTO ticket (id, time, changetime, component, 
    231                                          severity, priority, owner, reporter, cc, 
    232                                          version, milestone, status, resolution, 
    233                                          summary, description, keywords) 
    234                                  VALUES (%s, %s, %s, %s, 
    235                                          %s, %s, %s, %s, %s, 
    236                                          %s, %s, %s, %s, 
    237                                          %s, %s, %s)""", 
    238                   id, time.strftime('%s'), changetime.strftime('%s'), component.encode('utf-8'), 
    239                   severity.encode('utf-8'), priority.encode('utf-8'), owner, reporter, cc, 
    240                   version, milestone.encode('utf-8'), status.lower(), resolution, 
    241                   summary.encode('utf-8'), desc, keywords) 
     324        if REPLACE_BUG_NO: 
     325            if BUG_NO_RE.search(desc): 
     326                desc = re.sub(BUG_NO_RE, BUG_NO_REPL, desc) 
     327 
     328        if PRIORITIES_MAP.has_key(priority): 
     329            priority = PRIORITIES_MAP[priority] 
     330 
     331        print "  inserting ticket %s -- %s" % (id, summary) 
     332 
     333        c.execute("""INSERT INTO ticket (id, type, time, changetime, component, 
     334                                         severity, priority, owner, reporter, 
     335                                         cc, version, milestone, status, 
     336                                         resolution, summary, description, 
     337                                         keywords) 
     338                                 VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, 
     339                                         %s, %s, %s, %s, %s, %s, %s, %s)""", 
     340                  (id, type.encode('utf-8'), time.strftime('%s'), 
     341                   changetime.strftime('%s'), component.encode('utf-8'), 
     342                   severity.encode('utf-8'), priority.encode('utf-8'), owner, 
     343                   reporter, cc, version, milestone.encode('utf-8'), 
     344                   status.lower(), resolution, summary.encode('utf-8'), desc, 
     345                   keywords)) 
    242346         
    243347        self.db().commit() 
    244         return self.db().db.sqlite_last_insert_rowid() 
     348        return self.db().get_last_id(c, 'ticket') 
    245349     
    246350    def addTicketComment(self, ticket, time, author, value): 
    247351        comment = value.encode('utf-8') 
     
    249353        if PREFORMAT_COMMENTS: 
    250354          comment = '{{{\n%s\n}}}' % comment 
    251355 
     356        if REPLACE_BUG_NO: 
     357            if BUG_NO_RE.search(comment): 
     358                comment = re.sub(BUG_NO_RE, BUG_NO_REPL, comment) 
     359 
    252360        c = self.db().cursor() 
    253         c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) 
    254                                  VALUES        (%s, %s, %s, %s, %s, %s)""", 
    255                   ticket, time.strftime('%s'), author, 'comment', '', comment) 
     361        c.execute("""INSERT INTO ticket_change (ticket, time, author, field, 
     362                                                oldvalue, newvalue) 
     363                                        VALUES (%s, %s, %s, %s, %s, %s)""", 
     364                  (ticket, time.strftime('%s'), author, 'comment', '', comment)) 
    256365        self.db().commit() 
    257366 
    258367    def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): 
    259368        c = self.db().cursor() 
    260         c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) 
    261                                  VALUES        (%s, %s, %s, %s, %s, %s)""", 
    262                   ticket, time.strftime('%s'), author, field, oldvalue.encode('utf-8'), newvalue.encode('utf-8')) 
     369 
     370        if field == "priority": 
     371            if PRIORITIES_MAP.has_key(oldvalue.lower()): 
     372                oldvalue = PRIORITIES_MAP[oldvalue.lower()] 
     373            if PRIORITIES_MAP.has_key(newvalue.lower()): 
     374                newvalue = PRIORITIES_MAP[newvalue.lower()] 
     375 
     376        # Doesn't make sense if we go from highest -> highest, for example. 
     377        if oldvalue == newvalue: 
     378            return 
     379         
     380        c.execute("""INSERT INTO ticket_change (ticket, time, author, field, 
     381                                                oldvalue, newvalue) 
     382                                        VALUES (%s, %s, %s, %s, %s, %s)""", 
     383                  (ticket, time.strftime('%s'), author, field, 
     384                   oldvalue.encode('utf-8'), newvalue.encode('utf-8'))) 
    263385        self.db().commit() 
    264386         
    265     def addAttachment(self, id, attachment, description, author): 
    266         print 'inserting attachment for ticket %s -- %s' % (id, description) 
    267         attachment.filename = attachment.filename.encode('utf-8') 
    268         self.env.create_attachment(self.db(), 'ticket', str(id), attachment, description.encode('utf-8'), 
    269             author, 'unknown') 
     387    def addAttachment(self, author, a): 
     388        description = a['description'].encode('utf-8') 
     389        id = a['bug_id'] 
     390        filename = a['filename'].encode('utf-8') 
     391        filedata = StringIO.StringIO(a['thedata'].tostring()) 
     392        filesize = len(filedata.getvalue()) 
     393        time = a['creation_ts'] 
     394        print "    ->inserting attachment '%s' for ticket %s -- %s" % \ 
     395                (filename, id, description) 
     396 
     397        attachment = Attachment(self.env, 'ticket', id) 
     398        attachment.author = author 
     399        attachment.description = description 
     400        attachment.insert(filename, filedata, filesize, time.strftime('%s')) 
     401        del attachment 
    270402         
    271403    def getLoginName(self, cursor, userid): 
    272404        if userid not in self.loginNameCache: 
    273             cursor.execute("SELECT * FROM profiles WHERE userid = %s" % userid) 
     405            cursor.execute("SELECT * FROM profiles WHERE userid = %s", (userid)) 
    274406            loginName = cursor.fetchall() 
    275407 
    276408            if loginName: 
    277409                loginName = loginName[0]['login_name'] 
    278410            else: 
    279                 print 'warning: unknown bugzilla userid %d, recording as anonymous' % userid 
    280                 loginName = 'anonymous' 
     411                print """WARNING: unknown bugzilla userid %d, recording as 
     412                         anonymous""" % (userid) 
     413                loginName = "anonymous" 
    281414 
     415            loginName = LOGIN_MAP.get(loginName, loginName) 
     416 
    282417            self.loginNameCache[userid] = loginName 
    283418 
    284419        return self.loginNameCache[userid] 
    285420 
    286421    def getFieldName(self, cursor, fieldid): 
    287422        if fieldid not in self.fieldNameCache: 
    288             cursor.execute("SELECT * FROM fielddefs WHERE fieldid = %s" % fieldid) 
     423            cursor.execute("SELECT * FROM fielddefs WHERE fieldid = %s", 
     424                           (fieldid)) 
    289425            fieldName = cursor.fetchall() 
    290426 
    291427            if fieldName: 
    292428                fieldName = fieldName[0]['name'].lower() 
    293429            else: 
    294                 print 'warning: unknown bugzilla fieldid %d, recording as unknown' % userid 
    295                 fieldName = 'unknown' 
     430                print "WARNING: unknown bugzilla fieldid %d, \ 
     431                                recording as unknown" % (userid) 
     432                fieldName = "unknown" 
    296433 
    297434            self.fieldNameCache[fieldid] = fieldName 
    298435 
    299436        return self.fieldNameCache[fieldid] 
    300437 
    301 def productFilter(fieldName, products): 
    302     first = True 
    303     result = '' 
    304     for product in products: 
    305         if not first:  
    306             result += " or " 
    307         first = False 
    308         result += "%s = '%s'" % (fieldName, product) 
    309     return result 
     438def makeWhereClause(fieldName, values, negative=False): 
     439    if not values: 
     440        return '' 
     441    if negative: 
     442        connector, op = ' AND ', '!=' 
     443    else: 
     444        connector, op = ' OR ', '=' 
     445    clause = connector.join(["%s %s '%s'" % (fieldName, op, value) for value in values]) 
     446    return ' ' + clause 
    310447 
    311448def convert(_db, _host, _user, _password, _env, _force): 
    312449    activityFields = FieldTranslator() 
    313450 
    314451    # account for older versions of bugzilla 
    315     if BZ_VERSION == '2.11': 
    316         print 'Using Buzvilla v%s schema.' % BZ_VERSION 
    317         activityFields['removed'] = 'oldvalue' 
    318         activityFields['added'] = 'newvalue' 
     452    print "Using Bugzilla v%s schema." % BZ_VERSION 
     453    if BZ_VERSION == 2110: 
     454        activityFields['removed'] = "oldvalue" 
     455        activityFields['added'] = "newvalue" 
    319456 
    320457    # init Bugzilla environment 
    321     print "Bugzilla MySQL('%s':'%s':'%s':'%s'): connecting..." % (_db, _host, _user, _password) 
     458    print "Bugzilla MySQL('%s':'%s':'%s':'%s'): connecting..." % \ 
     459            (_db, _host, _user, ("*" * len(_password))) 
    322460    mysql_con = MySQLdb.connect(host=_host,  
    323461                user=_user, passwd=_password, db=_db, compress=1,  
    324462                cursorclass=MySQLdb.cursors.DictCursor) 
     
    330468 
    331469    # force mode... 
    332470    if _force == 1: 
    333         print "cleaning all tickets..." 
     471        print "\nCleaning all tickets..." 
    334472        c = trac.db().cursor() 
    335         c.execute("""DELETE FROM ticket_change""") 
     473        c.execute("DELETE FROM ticket_change") 
    336474        trac.db().commit() 
    337         c.execute("""DELETE FROM ticket""") 
     475         
     476        c.execute("DELETE FROM ticket") 
    338477        trac.db().commit() 
    339         c.execute("""DELETE FROM attachment""") 
    340         os.system('rm -rf %s' % trac.env.get_attachments_dir()) 
    341         os.mkdir(trac.env.get_attachments_dir()) 
     478         
     479        c.execute("DELETE FROM attachment") 
     480        attachments_dir = os.path.join(os.path.normpath(trac.env.path), 
     481                                "attachments") 
     482        # Straight from the Python documentation. 
     483        for root, dirs, files in os.walk(attachments_dir, topdown=False): 
     484            for name in files: 
     485                os.remove(os.path.join(root, name)) 
     486            for name in dirs: 
     487                os.rmdir(os.path.join(root, name)) 
     488        if not os.stat(attachments_dir): 
     489            os.mkdir(attachments_dir) 
    342490        trac.db().commit() 
     491        print "All tickets cleaned..." 
    343492 
    344493 
    345     print 
    346     print "1. import severities..." 
    347     severities = (('blocker', '1'), ('critical', '2'), ('major', '3'), ('normal', '4'), 
    348         ('minor', '5'), ('trivial', '6'), ('enhancement', '7')) 
    349     trac.setSeverityList(severities) 
     494    print "\n0. Filtering products..." 
     495    mysql_cur.execute("SELECT name FROM products") 
     496    products = [] 
     497    for line in mysql_cur.fetchall(): 
     498        product = line['name'] 
     499        if PRODUCTS and product not in PRODUCTS: 
     500            continue 
     501        if product in IGNORE_PRODUCTS: 
     502            continue 
     503        products.append(product) 
     504    PRODUCTS[:] = products 
     505    print "  Using products", " ".join(PRODUCTS) 
    350506 
    351     print 
    352     print "2. import components..." 
    353     sql = "SELECT value, initialowner AS owner FROM components" 
    354     if PRODUCTS: 
    355        sql += " WHERE %s" % productFilter('program', PRODUCTS) 
    356     mysql_cur.execute(sql) 
    357     components = mysql_cur.fetchall() 
    358     for component in components: 
    359                 component['owner'] = trac.getLoginName(mysql_cur, component['owner']) 
    360     trac.setComponentList(components, 'value') 
     507    print "\n1. Import severities..." 
     508    trac.setSeverityList(SEVERITIES) 
    361509 
    362     print 
    363     print "3. import priorities..." 
    364     priorities = (('P1', '1'), ('P2', '2'), ('P3', '3'), ('P4', '4'), ('P5', '5')) 
    365     trac.setPriorityList(priorities) 
     510    print "\n2. Import components..." 
     511    if not COMPONENTS_FROM_PRODUCTS: 
     512        if BZ_VERSION >= 2180: 
     513            sql = """SELECT DISTINCT c.name AS name, c.initialowner AS owner 
     514                                FROM components AS c, products AS p 
     515                               WHERE c.product_id = p.id AND""" 
     516            sql += makeWhereClause('p.name', PRODUCTS) 
     517        else: 
     518            sql = "SELECT value AS name, initialowner AS owner FROM components" 
     519            sql += " WHERE" + makeWhereClause('program', PRODUCTS) 
     520        mysql_cur.execute(sql) 
     521        components = mysql_cur.fetchall() 
     522        for component in components: 
     523            component['owner'] = trac.getLoginName(mysql_cur, 
     524                                                   component['owner']) 
     525        trac.setComponentList(components, 'name') 
     526    else: 
     527        sql = """SELECT program AS product, value AS comp, initialowner AS owner 
     528                   FROM components""" 
     529        sql += " WHERE" + makeWhereClause('program', PRODUCTS) 
     530        mysql_cur.execute(sql) 
     531        lines = mysql_cur.fetchall() 
     532        all_components = {} # product -> components 
     533        all_owners = {} # product, component -> owner 
     534        for line in lines: 
     535            product = line['product'] 
     536            comp = line['comp'] 
     537            owner = line['owner'] 
     538            all_components.setdefault(product, []).append(comp) 
     539            all_owners[(product, comp)] = owner 
     540        component_list = [] 
     541        for product, components in all_components.items(): 
     542            # find best default owner 
     543            default = None 
     544            for comp in DEFAULT_COMPONENTS: 
     545                if comp in components: 
     546                    default = comp 
     547                    break 
     548            if default is None: 
     549                default = components[0] 
     550            owner = all_owners[(product, default)] 
     551            owner_name = trac.getLoginName(mysql_cur, owner) 
     552            component_list.append({'product': product, 'owner': owner_name}) 
     553        trac.setComponentList(component_list, 'product') 
    366554 
    367     print 
    368     print "4. import versions..." 
    369     sql = "SELECT DISTINCTROW value FROM versions" 
    370     if PRODUCTS: 
    371        sql += " WHERE %s" % productFilter('program', PRODUCTS) 
     555    print "\n3. Import priorities..." 
     556    trac.setPriorityList(PRIORITIES) 
     557 
     558    print "\n4. Import versions..." 
     559    if BZ_VERSION >= 2180: 
     560        sql = """SELECT DISTINCTROW versions.value AS value 
     561                               FROM products, versions""" 
     562        sql += " WHERE" + makeWhereClause('products.name', PRODUCTS) 
     563    else: 
     564