Ticket #1462: bugzilla2trac.py.6.diff
| File bugzilla2trac.py.6.diff, 43.6 KB (added by asmodai@…, 3 years ago) |
|---|
-
bugzilla2trac.py
3 3 """ 4 4 Import a Bugzilla items into a Trac database. 5 5 6 Requires: Trac 0. 7.1 from http://trac.edgewall.com/6 Requires: Trac 0.9b1 from http://trac.edgewall.com/ 7 7 Python 2.3 from http://www.python.org/ 8 8 MySQL >= 3.23 from http://www.mysql.org/ 9 9 … … 13 13 Copyright 2004, Dmitry Yusupov <dmitry_yus@yahoo.com> 14 14 15 15 Many enhancements, Bill Soudan <bill@soudan.net> 16 Other enhancements, Florent Guillaume <fg@nuxeo.com> 17 Reworked, Jeroen Ruigrok van der Werven <asmodai@tendra.org> 18 19 $Id$ 16 20 """ 17 21 22 import re 23 18 24 ### 19 25 ### Conversion Settings -- edit these before running if desired 20 26 ### … … 22 28 # Bugzilla version. You can find this in Bugzilla's globals.pl file. 23 29 # 24 30 # 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) 26 32 # 27 33 # 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. 36 BZ_VERSION = 2180 30 37 31 38 # MySQL connection parameters for the Bugzilla database. These can also 32 39 # be specified on the command line. 33 BZ_DB = ''34 BZ_HOST = 'localhost'35 BZ_USER = ''36 BZ_PASSWORD = ''40 BZ_DB = "" 41 BZ_HOST = "" 42 BZ_USER = "" 43 BZ_PASSWORD = "" 37 44 38 45 # Path to the Trac environment. 39 TRAC_ENV = ''46 TRAC_ENV = "/usr/local/trac" 40 47 41 48 # If true, all existing Trac tickets and attachments will be removed 42 49 # prior to import. 43 TRAC_CLEAN = False50 TRAC_CLEAN = True 44 51 45 52 # Enclose imported ticket description and comments in a {{{ }}} 46 53 # preformat block? This formats the text in a fixed-point font. 47 54 PREFORMAT_COMMENTS = False 48 55 56 # Replace bug numbers in comments with #xyz 57 REPLACE_BUG_NO = False 58 59 # Severities 60 SEVERITIES = [ 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. 75 PRIORITIES = [ 76 ("highest", "1"), 77 ("high", "2"), 78 ("normal", "3"), 79 ("low", "4"), 80 ("lowest", "5") 81 ] 82 83 # Bugzilla: Trac 84 # NOTE: Use lowercase. 85 PRIORITIES_MAP = { 86 "p1": "highest", 87 "p2": "high", 88 "p3": "normal", 89 "p4": "low", 90 "p5": "lowest" 91 } 92 49 93 # By default, all bugs are imported from Bugzilla. If you add a list 50 94 # of products here, only bugs from those products will be imported. 51 95 PRODUCTS = [] 96 # These Bugzilla products will be ignored during import. 97 IGNORE_PRODUCTS = [] 52 98 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 100 IGNORE_MILESTONES = ["---"] 58 101 102 # These logins are converted to these user ids 103 LOGIN_MAP = { 104 #'some.user@example.com': 'someuser', 105 } 106 107 # These emails are removed from CC list 108 IGNORE_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 121 COMPONENTS_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. 125 DEFAULT_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. 130 KEYWORDS_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. 138 MAP_ALL_KEYWORDS = True 139 140 59 141 # Bug comments that should not be imported. Each entry in list should 60 142 # be a regular expression. 61 143 IGNORE_COMMENTS = [ 62 '^Created an attachment \(id='144 "^Created an attachment \(id=" 63 145 ] 64 146 65 147 ########################################################################### … … 76 158 # There is some special magic for open in the code: if there is no 77 159 # Bugzilla owner, open is mapped to 'new' instead. 78 160 STATUS_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" 84 166 } 85 167 86 168 # Translate Bugzilla statuses into Trac keywords. This provides a way 87 169 # to retain the Bugzilla statuses in Trac. e.g. when a bug is marked 88 170 # 'verified' in Bugzilla it will be assigned a VERIFIED keyword. 89 171 STATUS_KEYWORDS = { 90 'verified' : 'VERIFIED',91 'released' : 'RELEASED'172 "verified": "VERIFIED", 173 "released": "RELEASED" 92 174 } 93 175 94 176 # Some fields in Bugzilla do not have equivalents in Trac. Changes in 95 177 # fields listed here will not be imported into the ticket change history, 96 178 # otherwise you'd see changes for fields that don't exist in Trac. 97 IGNORED_ACTIVITY_FIELDS = [ 'everconfirmed']179 IGNORED_ACTIVITY_FIELDS = ["everconfirmed"] 98 180 181 # Regular expression and its replacement 182 BUG_NO_RE = re.compile(r"\b(bug #?)([0-9])") 183 BUG_NO_REPL = r"#\2" 184 99 185 ### 100 186 ### Script begins here 101 187 ### 102 188 103 189 import os 104 import re105 190 import sys 106 191 import string 107 192 import StringIO 108 193 109 194 import MySQLdb 110 195 import MySQLdb.cursors 111 import trac.env 196 try: 197 from trac.env import Environment 198 except: 199 from trac.Environment import Environment 200 from trac.attachment import Attachment 112 201 113 202 if not hasattr(sys, 'setdefaultencoding'): 114 203 reload(sys) … … 116 205 sys.setdefaultencoding('latin1') 117 206 118 207 # simulated Attachment class for trac.add 119 class Attachment:120 def __init__(self, name, data):121 self.filename = name122 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()) 123 212 124 213 # simple field translation mapping. if string not in 125 214 # mapping, just return string, otherwise return value … … 134 223 135 224 class TracDatabase(object): 136 225 def __init__(self, path): 137 self.env = trac.env.Environment(path)226 self.env = Environment(path) 138 227 self._db = self.env.get_db_cnx() 139 228 self._db.autocommit = False 140 229 self.loginNameCache = {} … … 145 234 146 235 def hasTickets(self): 147 236 c = self.db().cursor() 148 c.execute( '''SELECT count(*) FROM Ticket''')237 c.execute("SELECT count(*) FROM Ticket") 149 238 return int(c.fetchall()[0][0]) > 0 150 239 151 240 def assertNoTickets(self): … … 157 246 self.assertNoTickets() 158 247 159 248 c = self.db().cursor() 160 c.execute(" ""DELETE FROM enum WHERE type='severity'""")249 c.execute("DELETE FROM enum WHERE type='severity'") 161 250 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)) 165 255 self.db().commit() 166 256 167 257 def setPriorityList(self, s): … … 169 259 self.assertNoTickets() 170 260 171 261 c = self.db().cursor() 172 c.execute(" ""DELETE FROM enum WHERE type='priority'""")262 c.execute("DELETE FROM enum WHERE type='priority'") 173 263 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)) 179 268 self.db().commit() 180 269 181 270 … … 184 273 self.assertNoTickets() 185 274 186 275 c = self.db().cursor() 187 c.execute(" ""DELETE FROM component""")276 c.execute("DELETE FROM component") 188 277 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'))) 192 283 self.db().commit() 193 284 194 285 def setVersionList(self, v, key): … … 196 287 self.assertNoTickets() 197 288 198 289 c = self.db().cursor() 199 c.execute(" ""DELETE FROM version""")290 c.execute("DELETE FROM version") 200 291 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],)) 204 294 self.db().commit() 205 295 206 296 def setMilestoneList(self, m, key): … … 208 298 self.assertNoTickets() 209 299 210 300 c = self.db().cursor() 211 c.execute(" ""DELETE FROM milestone""")301 c.execute("DELETE FROM milestone") 212 302 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'))) 216 307 self.db().commit() 217 308 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, 221 311 summary, description, keywords): 222 312 c = self.db().cursor() 223 313 224 314 desc = description.encode('utf-8') 315 type = "defect" 225 316 317 if severity.lower() == "enhancement": 318 severity = "minor" 319 type = "enhancement" 320 226 321 if PREFORMAT_COMMENTS: 227 322 desc = '{{{\n%s\n}}}' % desc 228 323 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)) 242 346 243 347 self.db().commit() 244 return self.db(). db.sqlite_last_insert_rowid()348 return self.db().get_last_id(c, 'ticket') 245 349 246 350 def addTicketComment(self, ticket, time, author, value): 247 351 comment = value.encode('utf-8') … … 249 353 if PREFORMAT_COMMENTS: 250 354 comment = '{{{\n%s\n}}}' % comment 251 355 356 if REPLACE_BUG_NO: 357 if BUG_NO_RE.search(comment): 358 comment = re.sub(BUG_NO_RE, BUG_NO_REPL, comment) 359 252 360 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)) 256 365 self.db().commit() 257 366 258 367 def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): 259 368 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'))) 263 385 self.db().commit() 264 386 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 270 402 271 403 def getLoginName(self, cursor, userid): 272 404 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)) 274 406 loginName = cursor.fetchall() 275 407 276 408 if loginName: 277 409 loginName = loginName[0]['login_name'] 278 410 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" 281 414 415 loginName = LOGIN_MAP.get(loginName, loginName) 416 282 417 self.loginNameCache[userid] = loginName 283 418 284 419 return self.loginNameCache[userid] 285 420 286 421 def getFieldName(self, cursor, fieldid): 287 422 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)) 289 425 fieldName = cursor.fetchall() 290 426 291 427 if fieldName: 292 428 fieldName = fieldName[0]['name'].lower() 293 429 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" 296 433 297 434 self.fieldNameCache[fieldid] = fieldName 298 435 299 436 return self.fieldNameCache[fieldid] 300 437 301 def productFilter(fieldName, products):302 first = True303 result =''304 for product in products:305 if not first:306 result += " or "307 first = False308 result += "%s = '%s'" % (fieldName, product)309 return result438 def 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 310 447 311 448 def convert(_db, _host, _user, _password, _env, _force): 312 449 activityFields = FieldTranslator() 313 450 314 451 # account for older versions of bugzilla 315 if BZ_VERSION == '2.11':316 print 'Using Buzvilla v%s schema.' % BZ_VERSION317 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" 319 456 320 457 # 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))) 322 460 mysql_con = MySQLdb.connect(host=_host, 323 461 user=_user, passwd=_password, db=_db, compress=1, 324 462 cursorclass=MySQLdb.cursors.DictCursor) … … 330 468 331 469 # force mode... 332 470 if _force == 1: 333 print " cleaning all tickets..."471 print "\nCleaning all tickets..." 334 472 c = trac.db().cursor() 335 c.execute(" ""DELETE FROM ticket_change""")473 c.execute("DELETE FROM ticket_change") 336 474 trac.db().commit() 337 c.execute("""DELETE FROM ticket""") 475 476 c.execute("DELETE FROM ticket") 338 477 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) 342 490 trac.db().commit() 491 print "All tickets cleaned..." 343 492 344 493 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) 350 506 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) 361 509 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') 366 554 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
