| | 1 | #!/usr/bin/env python |
| | 2 | |
| | 3 | """ |
| | 4 | Import Mantis bugs into a Trac database. |
| | 5 | |
| | 6 | Requires: Trac 0.9.X or newer from http://trac.edgewall.com/ |
| | 7 | Python 2.4 from http://www.python.org/ |
| | 8 | MySQL >= 3.23 from http://www.mysql.org/ |
| | 9 | |
| | 10 | Version 1.4 |
| | 11 | Author: John Lichovnik (licho@ufo.cz) |
| | 12 | Date: 10.9.2007 |
| | 13 | |
| | 14 | Version 1.3 |
| | 15 | Author: Anton Stroganov (stroganov.a@gmail.com) |
| | 16 | Date: December 19, 2006 |
| | 17 | |
| | 18 | Based on version 1.1 from: |
| | 19 | Author: Joao Prado Maia (jpm@pessoal.org) |
| | 20 | |
| | 21 | Based on version 1.0 from: |
| | 22 | Paul Baranowski (paul@paulbaranowski.org) |
| | 23 | |
| | 24 | Based on bugzilla2trac.py by these guys (thank you!): |
| | 25 | Dmitry Yusupov <dmitry_yus@yahoo.com> - bugzilla2trac.py |
| | 26 | Mark Rowe <mrowe@bluewire.net.nz> - original TracDatabase class |
| | 27 | Bill Soudan <bill@soudan.net> - Many enhancements |
| | 28 | |
| | 29 | Example use: |
| | 30 | python mantis2trac.py --db mantis --tracenv /usr/local/trac-projects/myproj/ \ |
| | 31 | --host localhost --user root --clean --products foo,bar |
| | 32 | |
| | 33 | Changes in version 1.4: |
| | 34 | - fixed strftime for Python 2.4 |
| | 35 | - fixed Mantis text_id in ticket and comment queries (original version was sometimes adding mismatched descriptions and comments) |
| | 36 | - added IGNORE_VERSION switch |
| | 37 | |
| | 38 | Changes since version 1.2: |
| | 39 | - better join in the attachment author finding query |
| | 40 | - changed default encoding to be utf8 |
| | 41 | - added working status->keyword migration for statuses that don't have exact Trac equivalents |
| | 42 | |
| | 43 | Changes since version 1.1: |
| | 44 | - Made it work against Trac running on MySQL (specifically, changes to the |
| | 45 | LAST_INSERT_ID() call on line 382 (in the addTicket function)) |
| | 46 | - Couple of bugfixes |
| | 47 | - Works fine against 10.2 |
| | 48 | - Modified to allow specifying product list on command line |
| | 49 | - Modified to migrate database-stored mantis attachments correctly. |
| | 50 | Nota Bene!!! The script requires write access to the attachments |
| | 51 | directory of the trac env. So, suggested sequence of actions: |
| | 52 | - chmod -R 777 /usr/local/trac-projects/myproj/attachments/ |
| | 53 | - run the script |
| | 54 | - chown -R apache /usr/local/trac-projects/myproj/attachments/ |
| | 55 | - chgrp -R webuser /usr/local/trac-projects/myproj/attachments/ |
| | 56 | - chmod -R 755 /usr/local/trac-projects/myproj/attachments/ |
| | 57 | |
| | 58 | Changes since version 1.0: |
| | 59 | - Made it to work against Trac 0.9.3 (tweaks to make the Environment class work) |
| | 60 | - Re-did all prepared statements-like queries to avoid a DB error |
| | 61 | - Fixed a reference to the wrong variable name when adding a comment |
| | 62 | |
| | 63 | Notes: |
| | 64 | - Private bugs will become public |
| | 65 | - Some ticket changes will not be preserved since they have no |
| | 66 | equivalents in Trac. |
| | 67 | - I consider milestones and versions to be the same thing (actually, |
| | 68 | I dont really care about the version, because for our project, bugs are |
| | 69 | only in the 'previous version'). |
| | 70 | - Importing attachments is not implemented (couldnt get it to work, |
| | 71 | and we didnt have enough attachments to justify spending time on this) |
| | 72 | "Clean" will not delete your existing attachments. There is code in here |
| | 73 | to support adding attachments, but you will have to play with it to |
| | 74 | make it work. If you search for the word "attachment" you will find |
| | 75 | all the code related to this. |
| | 76 | - Ticket descriptions & comments will be re-wrapped to 70 characters. |
| | 77 | This may mess up your formatting for your bugs. If you dont want to do |
| | 78 | this, search for textwrap.fill() and fix it. |
| | 79 | - You will probably want to change "report.css" in trac to handle one more |
| | 80 | level of priorities (default trac has 6 levels of priorities, while Mantis |
| | 81 | has 7). When you look at your reports, the color schemes will look wrong. |
| | 82 | |
| | 83 | The lines that control the priority color scheme look like this: |
| | 84 | #tktlist tr.color1-odd { background: #fdc; border-color: #e88; color: #a22 } |
| | 85 | #tktlist tr.color1-even { background: #fed; border-color: #e99; color: #a22 } |
| | 86 | |
| | 87 | I added a new level 2 ("urgent") with an orange color, |
| | 88 | and incremented all the rest of the levels: |
| | 89 | #tktlist tr.color2-odd { background: #FFE08F; border-color: #e88; color: #a22 } |
| | 90 | #tktlist tr.color2-even { background: #FFE59F; border-color: #e99; color: #a22 } |
| | 91 | |
| | 92 | """ |
| | 93 | from urllib import quote |
| | 94 | import datetime |
| | 95 | import time |
| | 96 | |
| | 97 | ### |
| | 98 | ### Conversion Settings -- edit these before running if desired |
| | 99 | ### |
| | 100 | |
| | 101 | # Mantis version. |
| | 102 | # |
| | 103 | # Currently, the following mantis versions are known to work: |
| | 104 | # 0.19.X |
| | 105 | # |
| | 106 | # If you run this script on a version not listed here and it is successful, |
| | 107 | # please report it to the Trac mailing list so we can update the list. |
| | 108 | MANTIS_VERSION = '0.19' |
| | 109 | |
| | 110 | # MySQL connection parameters for the Mantis database. These can also |
| | 111 | # be specified on the command line. |
| | 112 | MANTIS_DB = 'mantis' |
| | 113 | MANTIS_HOST = 'localhost' |
| | 114 | MANTIS_USER = 'root' |
| | 115 | MANTIS_PASSWORD = '' |
| | 116 | |
| | 117 | # Path to the Trac environment. |
| | 118 | TRAC_ENV = '' |
| | 119 | |
| | 120 | # If true, all existing Trac tickets will be removed |
| | 121 | # prior to import. |
| | 122 | TRAC_CLEAN = True |
| | 123 | |
| | 124 | # Enclose imported ticket description and comments in a {{{ }}} |
| | 125 | # preformat block? This formats the text in a fixed-point font. |
| | 126 | PREFORMAT_COMMENTS = False |
| | 127 | |
| | 128 | # Products are now specified on command line. |
| | 129 | # By default, all bugs are imported from Mantis. If you add a list |
| | 130 | # of products here, only bugs from those products will be imported. |
| | 131 | # Warning: I have not tested this script where this field is blank! |
| | 132 | # default products to ignore: |
| | 133 | PRODUCTS = [ ] |
| | 134 | |
| | 135 | # Trac doesn't have the concept of a product. Instead, this script can |
| | 136 | # assign keywords in the ticket entry to represent products. |
| | 137 | # |
| | 138 | # ex. PRODUCT_KEYWORDS = { 'product1' : 'PRODUCT1_KEYWORD' } |
| | 139 | PRODUCT_KEYWORDS = {} |
| | 140 | |
| | 141 | # Bug comments that should not be imported. Each entry in list should |
| | 142 | # be a regular expression. |
| | 143 | IGNORE_COMMENTS = [ |
| | 144 | # '^Created an attachment \(id=' |
| | 145 | ] |
| | 146 | |
| | 147 | # Ticket changes in Trac have the restriction where the |
| | 148 | # bug ID, field, and time must be unique for all entries in the ticket |
| | 149 | # changes table. |
| | 150 | # Mantis, for unknown reasons, has fields that can change two states |
| | 151 | # in under a second (e.g. "milestone":""->"1.0", "milestone":"1.0"->"2.0"). |
| | 152 | # Setting this to true will attempt to fix these cases by adjusting the |
| | 153 | # time for the 2nd change to be one second more than the original time. |
| | 154 | # I dont know why you'd want to turn this off, but I give you the option |
| | 155 | # anyhow. :) |
| | 156 | TIME_ADJUSTMENT_HACK = True |
| | 157 | |
| | 158 | # If set to true, version numbers wont be assigned to tickets (just milestones) |
| | 159 | IGNORE_VERSION = True |
| | 160 | |
| | 161 | ########################################################################### |
| | 162 | ### You probably don't need to change any configuration past this line. ### |
| | 163 | ########################################################################### |
| | 164 | |
| | 165 | # Mantis status to Trac status translation map. |
| | 166 | # |
| | 167 | # NOTE: bug activity is translated as well, which may cause bug |
| | 168 | # activity to be deleted (e.g. resolved -> closed in Mantis |
| | 169 | # would translate into closed -> closed in Trac, so we just ignore the |
| | 170 | # change). |
| | 171 | # |
| | 172 | # Possible Trac 'status' values: 'new', 'assigned', 'reopened', 'closed' |
| | 173 | STATUS_TRANSLATE = { |
| | 174 | 10 : 'new', # 10 == 'new' in mantis |
| | 175 | 20 : 'assigned', # 20 == 'feedback' |
| | 176 | 30 : 'new', # 30 == 'acknowledged' |
| | 177 | 40 : 'new', # 40 == 'confirmed' |
| | 178 | 50 : 'assigned', # 50 == 'assigned' |
| | 179 | 60 : 'assigned', # 60 == 'QA' |
| | 180 | 80 : 'closed', # 80 == 'resolved' |
| | 181 | 90 : 'closed' # 90 == 'closed' |
| | 182 | } |
| | 183 | |
| | 184 | # Unused: |
| | 185 | # Translate Mantis statuses into Trac keywords. This provides a way |
| | 186 | # to retain the Mantis statuses in Trac. e.g. when a bug is marked |
| | 187 | # 'verified' in Mantis it will be assigned a VERIFIED keyword. |
| | 188 | # STATUS_KEYWORDS = { |
| | 189 | |
| | 190 | STATUS_KEYWORDS = { |
| | 191 | 20 : 'FEEDBACK', |
| | 192 | 30 : 'ACKNOWLEDGED', |
| | 193 | 40 : 'CONFIRMED', |
| | 194 | 60 : 'QA', |
| | 195 | 80 : 'RESOLVED' |
| | 196 | } |
| | 197 | |
| | 198 | # Possible Trac resolutions are 'fixed', 'invalid', 'wontfix', 'duplicate', 'worksforme' |
| | 199 | RESOLUTION_TRANSLATE = { |
| | 200 | 10 : '', # 10 == 'open' in mantis |
| | 201 | 20 : 'fixed', # 20 == 'fixed' |
| | 202 | 30 : '', # 30 == 'reopened' (TODO: 'reopened' needs to be mapped to a status event) |
| | 203 | 40 : 'invalid', # 40 == 'unable to duplicate' |
| | 204 | 50 : 'wontfix', # 50 == 'not fixable' |
| | 205 | 60 : 'duplicate', # 60 == 'duplicate' |
| | 206 | 70 : 'invalid', # 70 == 'not an issue' |
| | 207 | 80 : '', # 80 == 'suspended' |
| | 208 | 90 : 'wontfix', # 90 == 'wont fix' |
| | 209 | } |
| | 210 | |
| | 211 | # Mantis severities (which will also become equivalent Trac severities) |
| | 212 | ##SEVERITY_LIST = (('block', '80'), |
| | 213 | ## ('crash', '70'), |
| | 214 | ## ('major', '60'), |
| | 215 | ## ('minor', '50'), |
| | 216 | ## ('tweak', '40'), |
| | 217 | ## ('text', '30'), |
| | 218 | ## ('trivial', '20'), |
| | 219 | ## ('feature', '10')) |
| | 220 | SEVERITY_LIST = (('block', '1'), |
| | 221 | ('crash', '2'), |
| | 222 | ('major', '3'), |
| | 223 | ('minor', '4'), |
| | 224 | ('tweak', '5'), |
| | 225 | ('text', '6'), |
| | 226 | ('trivial', '7'), |
| | 227 | ('feature', '8')) |
| | 228 | |
| | 229 | # Translate severity numbers into their text equivalents |
| | 230 | SEVERITY_TRANSLATE = { |
| | 231 | 80 : 'block', |
| | 232 | 70 : 'crash', |
| | 233 | 60 : 'major', |
| | 234 | 50 : 'minor', |
| | 235 | 40 : 'tweak', |
| | 236 | 30 : 'text', |
| | 237 | 20 : 'trivial', |
| | 238 | 10 : 'feature' |
| | 239 | } |
| | 240 | |
| | 241 | # Mantis priorities (which will also become Trac priorities) |
| | 242 | ##PRIORITY_LIST = (('immediate', '60'), |
| | 243 | ## ('urgent', '50'), |
| | 244 | ## ('high', '40'), |
| | 245 | ## ('normal', '30'), |
| | 246 | ## ('low', '20'), |
| | 247 | ## ('none', '10')) |
| | 248 | PRIORITY_LIST = (('immediate', '1'), |
| | 249 | ('urgent', '2'), |
| | 250 | ('high', '3'), |
| | 251 | ('normal', '4'), |
| | 252 | ('low', '5'), |
| | 253 | ('none', '6')) |
| | 254 | |
| | 255 | # Translate priority numbers into their text equivalent |
| | 256 | PRIORITY_TRANSLATE = { |
| | 257 | 60 : 'immediate', |
| | 258 | 50 : 'urgent', |
| | 259 | 40 : 'high', |
| | 260 | 30 : 'normal', |
| | 261 | 20 : 'low', |
| | 262 | 10 : 'none' |
| | 263 | } |
| | 264 | |
| | 265 | |
| | 266 | # Some fields in Mantis do not have equivalents in Trac. Changes in |
| | 267 | # fields listed here will not be imported into the ticket change history, |
| | 268 | # otherwise you'd see changes for fields that don't exist in Trac. |
| | 269 | IGNORED_ACTIVITY_FIELDS = ['', 'project_id', 'reproducibility', 'view_state', 'os', 'os_build', 'duplicate_id'] |
| | 270 | |
| | 271 | ### |
| | 272 | ### Script begins here |
| | 273 | ### |
| | 274 | |
| | 275 | import os |
| | 276 | import re |
| | 277 | import sys |
| | 278 | import string |
| | 279 | import StringIO |
| | 280 | |
| | 281 | import MySQLdb |
| | 282 | import MySQLdb.cursors |
| | 283 | from trac.env import Environment |
| | 284 | |
| | 285 | if not hasattr(sys, 'setdefaultencoding'): |
| | 286 | reload(sys) |
| | 287 | |
| | 288 | #sys.setdefaultencoding('utf-8') |
| | 289 | |
| | 290 | # simulated Attachment class for trac.add |
| | 291 | # unused in 1.2 |
| | 292 | class Attachment: |
| | 293 | def __init__(self, name, data): |
| | 294 | self.filename = name |
| | 295 | self.file = StringIO.StringIO(data.tostring()) |
| | 296 | |
| | 297 | # simple field translation mapping. if string not in |
| | 298 | # mapping, just return string, otherwise return value |
| | 299 | class FieldTranslator(dict): |
| | 300 | def __getitem__(self, item): |
| | 301 | if not dict.has_key(self, item): |
| | 302 | return item |
| | 303 | |
| | 304 | return dict.__getitem__(self, item) |
| | 305 | |
| | 306 | statusXlator = FieldTranslator(STATUS_TRANSLATE) |
| | 307 | |
| | 308 | class TracDatabase(object): |
| | 309 | def __init__(self, path): |
| | 310 | self.env = Environment(path) |
| | 311 | self._db = self.env.get_db_cnx() |
| | 312 | self._db.autocommit = False |
| | 313 | self.loginNameCache = {} |
| | 314 | self.fieldNameCache = {} |
| | 315 | |
| | 316 | def db(self): |
| | 317 | return self._db |
| | 318 | |
| | 319 | def hasTickets(self): |
| | 320 | c = self.db().cursor() |
| | 321 | c.execute('''SELECT count(*) FROM ticket''') |
| | 322 | return int(c.fetchall()[0][0]) > 0 |
| | 323 | |
| | 324 | def assertNoTickets(self): |
| | 325 | if self.hasTickets(): |
| | 326 | raise Exception("Will not modify database with existing tickets!") |
| | 327 | |
| | 328 | def setSeverityList(self, s): |
| | 329 | """Remove all severities, set them to `s`""" |
| | 330 | self.assertNoTickets() |
| | 331 | |
| | 332 | c = self.db().cursor() |
| | 333 | c.execute("""DELETE FROM enum WHERE type='severity'""") |
| | 334 | for value, i in s: |
| | 335 | print "inserting severity ", value, " ", i |
| | 336 | c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", |
| | 337 | ("severity", value, i,)) |
| | 338 | self.db().commit() |
| | 339 | |
| | 340 | def setPriorityList(self, s): |
| | 341 | """Remove all priorities, set them to `s`""" |
| | 342 | self.assertNoTickets() |
| | 343 | |
| | 344 | c = self.db().cursor() |
| | 345 | c.execute("""DELETE FROM enum WHERE type='priority'""") |
| | 346 | for value, i in s: |
| | 347 | print "inserting priority ", value, " ", i |
| | 348 | c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""", |
| | 349 | ("priority", value, i,)) |
| | 350 | self.db().commit() |
| | 351 | |
| | 352 | |
| | 353 | def setComponentList(self, l, key): |
| | 354 | """Remove all components, set them to `l`""" |
| | 355 | self.assertNoTickets() |
| | 356 | |
| | 357 | c = self.db().cursor() |
| | 358 | c.execute("""DELETE FROM component""") |
| | 359 | for comp in l: |
| | 360 | print "inserting component '",comp[key],"', owner", comp['owner'] |
| | 361 | c.execute("""INSERT INTO component (name, owner) VALUES (%s, %s)""", |
| | 362 | (comp[key], comp['owner'],)) |
| | 363 | self.db().commit() |
| | 364 | |
| | 365 | def setVersionList(self, v, key): |
| | 366 | """Remove all versions, set them to `v`""" |
| | 367 | self.assertNoTickets() |
| | 368 | |
| | 369 | c = self.db().cursor() |
| | 370 | c.execute("""DELETE FROM version""") |
| | 371 | for vers in v: |
| | 372 | print "inserting version ", vers[key] |
| | 373 | c.execute("""INSERT INTO version (name) VALUES (%s)""", |
| | 374 | (vers[key],)) |
| | 375 | self.db().commit() |
| | 376 | |
| | 377 | def setMilestoneList(self, m, key): |
| | 378 | """Remove all milestones, set them to `m`""" |
| | 379 | self.assertNoTickets() |
| | 380 | |
| | 381 | c = self.db().cursor() |
| | 382 | c.execute("""DELETE FROM milestone""") |
| | 383 | for ms in m: |
| | 384 | print "inserting milestone ", ms[key] |
| | 385 | c.execute("""INSERT INTO milestone (name) VALUES (%s)""", |
| | 386 | (ms[key],)) |
| | 387 | self.db().commit() |
| | 388 | |
| | 389 | def addTicket(self, id, time, changetime, component, |
| | 390 | severity, priority, owner, reporter, cc, |
| | 391 | version, milestone, status, resolution, |
| | 392 | summary, description, keywords): |
| | 393 | c = self.db().cursor() |
| | 394 | if IGNORE_VERSION: |
| | 395 | version='' |
| | 396 | |
| | 397 | desc = description |
| | 398 | |
| | 399 | if PREFORMAT_COMMENTS: |
| | 400 | desc = '{{{\n%s\n}}}' % desc |
| | 401 | |
| | 402 | print "inserting ticket %s -- \"%s\"" % (id, summary[0:40].replace("\n", " ")) |
| | 403 | c.execute("""INSERT INTO ticket (id, time, changetime, component, |
| | 404 | severity, priority, owner, reporter, cc, |
| | 405 | version, milestone, status, resolution, |
| | 406 | summary, description, keywords) |
| | 407 | VALUES (%s, %s, %s, %s, |
| | 408 | %s, %s, %s, %s, %s, |
| | 409 | %s, %s, %s, %s, |
| | 410 | %s, %s, %s)""", |
| | 411 | (id, self.convertTime(time), self.convertTime(changetime), component, |
| | 412 | severity, priority, owner, reporter, cc, |
| | 413 | version, milestone, status.lower(), resolution, |
| | 414 | summary, desc, keywords)) |
| | 415 | |
| | 416 | self.db().commit() |
| | 417 | |
| | 418 | ## TODO: add database-specific methods to get the last inserted ticket's id... |
| | 419 | ## PostgreSQL: |
| | 420 | # c.execute('''SELECT currval("ticket_id_seq")''') |
| | 421 | ## SQLite: |
| | 422 | # c.execute('''SELECT last_insert_rowid()''') |
| | 423 | ## MySQL: |
| | 424 | # c.execute('''SELECT LAST_INSERT_ID()''') |
| | 425 | # Oh, Trac db abstraction layer already has a function for this... |
| | 426 | return self.db().get_last_id(c,'ticket') |
| | 427 | |
| | 428 | def convertTime(self,time2): |
| | 429 | return time.mktime(time2.timetuple())+1e-6*time2.microsecond |
| | 430 | |
| | 431 | def addTicketComment(self, ticket, time, author, value): |
| | 432 | print " * adding comment \"%s...\"" % value[0:40] |
| | 433 | comment = value |
| | 434 | |
| | 435 | if PREFORMAT_COMMENTS: |
| | 436 | comment = '{{{\n%s\n}}}' % comment |
| | 437 | |
| | 438 | c = self.db().cursor() |
| | 439 | c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) |
| | 440 | VALUES (%s, %s, %s, %s, %s, %s)""", |
| | 441 | (ticket, self.convertTime(time), author, 'comment', '', comment)) |
| | 442 | self.db().commit() |
| | 443 | |
| | 444 | def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue): |
| | 445 | if (field[0:4]=='doba'): |
| | 446 | return |
| | 447 | |
| | 448 | print " * adding ticket change \"%s\": \"%s\" -> \"%s\" (%s)" % (field, oldvalue[0:20], newvalue[0:20], time) |
| | 449 | c = self.db().cursor() |
| | 450 | c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) |
| | 451 | VALUES (%s, %s, %s, %s, %s, %s)""", |
| | 452 | (ticket, self.convertTime(time), author, field, oldvalue, newvalue)) |
| | 453 | self.db().commit() |
| | 454 | # Now actually change the ticket because the ticket wont update itself! |
| | 455 | sql = "UPDATE ticket SET %s='%s' WHERE id=%s" % (field, newvalue, ticket) |
| | 456 | c.execute(sql) |
| | 457 | self.db().commit() |
| | 458 | |
| | 459 | # unused in 1.2 |
| | 460 | def addAttachment(self, id, attachment, description, author): |
| | 461 | print 'inserting attachment for ticket %s -- %s' % (id, description) |
| | 462 | attachment.filename = attachment.filename |
| | 463 | self.env.create_attachment(self.db(), 'ticket', str(id), attachment, description, |
| | 464 | author, 'unknown') |
| | 465 | |
| | 466 | def getLoginName(self, cursor, userid): |
| | 467 | if userid not in self.loginNameCache: |
| | 468 | cursor.execute("SELECT username,email,realname,last_visit FROM mantis_user_table WHERE id = %i" % int(userid)) |
| | 469 | result = cursor.fetchall() |
| | 470 | |
| | 471 | if result: |
| | 472 | loginName = result[0]['username'] |
| | 473 | print 'Adding user %s to sessions table' % loginName |
| | 474 | c = self.db().cursor() |
| | 475 | |
| | 476 | # check if user is already in the sessions table |
| | 477 | c.execute("SELECT sid FROM session WHERE sid = '%s'" % result[0]['username']) |
| | 478 | r = c.fetchall() |
| | 479 | |
| | 480 | # if there was no user sid in the database already |
| | 481 | if not r: |
| | 482 | # pre-populate the session table and the realname/email table with user data |
| | 483 | try: |
| | 484 | c.execute( |
| | 485 | """INSERT INTO session |
| | 486 | (sid, authenticated, last_visit) |
| | 487 | VALUES (%s, %s, %s)""",(result[0]['username'], '1', self.convertTime(result[0]['last_visit']))) |
| | 488 | except: |
| | 489 | print 'failed executing sql: ' |
| | 490 | print """INSERT INTO session |
| | 491 | (sid, authenticated, last_visit) |
| | 492 | VALUES """, (result[0]['username'], '1', self.convertTime(result[0]['last_visit'])) |
| | 493 | print 'could not insert %s into sessions table: sql error %s ' % (loginName, self.db().error()) |
| | 494 | self.db().commit() |
| | 495 | |
| | 496 | # insert the user's real name into session attribute table |
| | 497 | c.execute( |
| | 498 | """INSERT INTO session_attribute |
| | 499 | (sid, authenticated, name, value) |
| | 500 | VALUES |
| | 501 | (%s, %s, %s, %s)""", (result[0]['username'], '1', 'name', result[0]['realname'])) |
| | 502 | self.db().commit() |
| | 503 | |
| | 504 | # insert the user's email into session attribute table |
| | 505 | c.execute( |
| | 506 | """INSERT INTO session_attribute |
| | 507 | (sid, authenticated, name, value) |
| | 508 | VALUES |
| | 509 | (%s, %s, %s, %s)""", (result[0]['username'], '1', 'email', result[0]['email'])) |
| | 510 | self.db().commit() |
| | 511 | else: |
| | 512 | print 'warning: unknown mantis userid %d, recording as anonymous' % userid |
| | 513 | loginName = '' |
| | 514 | |
| | 515 | self.loginNameCache[userid] = loginName |
| | 516 | |
| | 517 | return self.loginNameCache[userid] |
| | 518 | |
| | 519 | def get_attachments_dir(self,bugid=0): |
| | 520 | if bugid > 0: |
| | 521 | return self.env.path + 'attachments/ticket/%i/' % bugid |
| | 522 | else: |
| | 523 | return self.env.path + 'attachments/ticket/' |
| | 524 | |
| | 525 | def _mkdir(newdir): |
| | 526 | """works the way a good mkdir should :) |
| | 527 | - already exists, silently complete |
| | 528 | - regular file in the way, raise an exception |
| | 529 | - parent directory(ies) does not exist, make them as well |
| | 530 | """ |
| | 531 | if os.path.isdir(newdir): |
| | 532 | pass |
| | 533 | elif os.path.isfile(newdir): |
| | 534 | raise OSError("a file with the same name as the desired " \ |
| | 535 | "dir, '%s', already exists." % newdir) |
| | 536 | else: |
| | 537 | head, tail = os.path.split(newdir) |
| | 538 | if head and not os.path.isdir(head): |
| | 539 | _mkdir(head) |
| | 540 | #print "_mkdir %s" % repr(newdir) |
| | 541 | if tail: |
| | 542 | os.mkdir(newdir) |
| | 543 | |
| | 544 | def productFilter(fieldName, products): |
| | 545 | first = True |
| | 546 | result = '' |
| | 547 | for product in products: |
| | 548 | if not first: |
| | 549 | result += " or " |
| | 550 | first = False |
| | 551 | result += "%s = '%s'" % (fieldName, product) |
| | 552 | return result |
| | 553 | |
| | 554 | def convert(_db, _host, _user, _password, _env, _force): |
| | 555 | activityFields = FieldTranslator() |
| | 556 | |
| | 557 | # account for older versions of mantis |
| | 558 | if MANTIS_VERSION == '0.19': |
| | 559 | print 'Using Mantis v%s schema.' % MANTIS_VERSION |
| | 560 | activityFields['removed'] = 'oldvalue' |
| | 561 | activityFields['added'] = 'newvalue' |
| | 562 | |
| | 563 | # init Mantis environment |
| | 564 | print "Mantis MySQL('%s':'%s':'%s':'%s'): connecting..." % (_db, _host, _user, _password) |
| | 565 | mysql_con = MySQLdb.connect(host=_host, |
| | 566 | user=_user, passwd=_password, db=_db, compress=1, |
| | 567 | cursorclass=MySQLdb.cursors.DictCursor) |
| | 568 | mysql_cur = mysql_con.cursor() |
| | 569 | |
| | 570 | # init Trac environment |
| | 571 | print "Trac database('%s'): connecting..." % (_env) |
| | 572 | trac = TracDatabase(_env) |
| | 573 | |
| | 574 | # force mode... |
| | 575 | if _force == 1: |
| | 576 | print "cleaning all tickets..." |
| | 577 | c = trac.db().cursor() |
| | 578 | c.execute("""DELETE FROM ticket_change""") |
| | 579 | trac.db().commit() |
| | 580 | c.execute("""DELETE FROM ticket""") |
| | 581 | trac.db().commit() |
| | 582 | c.execute("""DELETE FROM attachment""") |
| | 583 | os.system('rm -rf %s' % trac.get_attachments_dir()) |
| | 584 | os.mkdir(trac.get_attachments_dir()) |
| | 585 | trac.db().commit() |
| | 586 | |
| | 587 | print |
| | 588 | print '0. Finding project IDs...' |
| | 589 | sql = "SELECT id, name FROM mantis_project_table" |
| | 590 | if PRODUCTS: |
| | 591 | sql += " WHERE %s" % productFilter('name', PRODUCTS) |
| | 592 | mysql_cur.execute(sql) |
| | 593 | project_list = mysql_cur.fetchall() |
| | 594 | project_dict = dict() |
| | 595 | for project_id in project_list: |
| | 596 | print "Mantis project name '%s' has project ID %s" % (project_id['name'], project_id['id']) |
| | 597 | project_dict[project_id['id']] = project_id['id'] |
| | 598 | |
| | 599 | print |
| | 600 | print "1. import severities..." |
| | 601 | trac.setSeverityList(SEVERITY_LIST) |
| | 602 | |
| | 603 | print |
| | 604 | print "2. import components..." |
| | 605 | sql = "SELECT category, user_id as owner FROM mantis_project_category_table" |
| | 606 | if PRODUCTS: |
| | 607 | sql += " WHERE %s" % productFilter('project_id', project_dict) |
| | 608 | print "sql: %s" % sql |
| | 609 | mysql_cur.execute(sql) |
| | 610 | components = mysql_cur.fetchall() |
| | 611 | for component in components: |
| | 612 | component['owner'] = trac.getLoginName(mysql_cur, component['owner']) |
| | 613 | trac.setComponentList(components, 'category') |
| | 614 | |
| | 615 | print |
| | 616 | print "3. import priorities..." |
| | 617 | trac.setPriorityList(PRIORITY_LIST) |
| | 618 | |
| | 619 | print |
| | 620 | print "4. import versions..." |
| | 621 | sql = "SELECT DISTINCTROW version FROM mantis_project_version_table" |
| | 622 | if PRODUCTS: |
| | 623 | sql += " WHERE %s" % productFilter('project_id', project_dict) |
| | 624 | mysql_cur.execute(sql) |
| | 625 | versions = mysql_cur.fetchall() |
| | 626 | trac.setVersionList(versions, 'version') |
| | 627 | |
| | 628 | print |
| | 629 | print "5. import milestones..." |
| | 630 | sql = "SELECT version FROM mantis_project_version_table" |
| | 631 | if PRODUCTS: |
| | 632 | sql += " WHERE %s" % productFilter('project_id', project_dict) |
| | 633 | mysql_cur.execute(sql) |
| | 634 | milestones = mysql_cur.fetchall() |
| | 635 | trac.setMilestoneList(milestones, 'version') |
| | 636 | |
| | 637 | print |
| | 638 | print '6. retrieving bugs...' |
| | 639 | sql = "SELECT * FROM mantis_bug_table " |
| | 640 | if PRODUCTS: |
| | 641 | sql += " WHERE %s" % productFilter('project_id', project_dict) |
| | 642 | sql += " ORDER BY id" |
| | 643 | mysql_cur.execute(sql) |
| | 644 | bugs = mysql_cur.fetchall() |
| | 645 | |
| | 646 | print |
| | 647 | print "7. import bugs and bug activity..." |
| | 648 | totalComments = 0 |
| | 649 | totalTicketChanges = 0 |
| | 650 | totalAttachments = 0 |
| | 651 | errors = [] |
| | 652 | timeAdjustmentHacks = [] |
| | 653 | for bug in bugs: |
| | 654 | bugid = bug['id'] |
| | 655 | |
| | 656 | ticket = {} |
| | 657 | keywords = [] |
| | 658 | ticket['id'] = bugid |
| | 659 | ticket['time'] = bug['date_submitted'] |
| | 660 | ticket['changetime'] = bug['last_updated'] |
| | 661 | ticket['component'] = bug['category'] |
| | 662 | ticket['severity'] = SEVERITY_TRANSLATE[bug['severity']] |
| | 663 | ticket['priority'] = PRIORITY_TRANSLATE[bug['priority']] |
| | 664 | ticket['owner'] = trac.getLoginName(mysql_cur, bug['handler_id']) |
| | 665 | ticket['reporter'] = trac.getLoginName(mysql_cur, bug['reporter_id']) |
| | 666 | ticket['version'] = bug['version'] |
| | 667 | if IGNORE_VERSION: |
| | 668 | ticket['version'] = '' |
| | 669 | ticket['milestone'] = bug['version'] |
| | 670 | ticket['summary'] = bug['summary'] |
| | 671 | ticket['status'] = STATUS_TRANSLATE[bug['status']] |
| | 672 | ticket['cc'] = '' |
| | 673 | ticket['keywords'] = '' |
| | 674 | |
| | 675 | # Special case for 'reopened' resolution in mantis - |
| | 676 | # it maps to a status type in Trac. |
| | 677 | if (bug['resolution'] == 30): |
| | 678 | ticket['status'] = 'reopened' |
| | 679 | ticket['resolution'] = RESOLUTION_TRANSLATE[bug['resolution']] |
| | 680 | |
| | 681 | # Compose the description from the three text fields in Mantis: |
| | 682 | # 'description', 'steps_to_reproduce', 'additional_information' |
| | 683 | mysql_cur.execute("SELECT * FROM mantis_bug_text_table WHERE id = %s" % bug['bug_text_id']) |
| | 684 | longdescs = list(mysql_cur.fetchall()) |
| | 685 | |
| | 686 | # check for empty 'longdescs[0]' field... |
| | 687 | if len(longdescs) == 0: |
| | 688 | ticket['description'] = '' |
| | 689 | else: |
| | 690 | tmpDescr = longdescs[0]['description'] |
| | 691 | if (longdescs[0]['steps_to_reproduce'].strip() != ''): |
| | 692 | tmpDescr = ('%s\n\nSTEPS TO REPRODUCE:\n%s') % (tmpDescr, longdescs[0]['steps_to_reproduce']) |
| | 693 | if (longdescs[0]['additional_information'].strip() != ''): |
| | 694 | tmpDescr = ('%s\n\nADDITIONAL INFORMATION:\n%s') % (tmpDescr, longdescs[0]['additional_information']) |
| | 695 | ticket['description'] = tmpDescr |
| | 696 | del longdescs[0] |
| | 697 | |
| | 698 | # Add the ticket to the Trac database |
| | 699 | trac.addTicket(**ticket) |
| | 700 | |
| | 701 | # |
| | 702 | # Add ticket comments |
| | 703 | # |
| | 704 | mysql_cur.execute("SELECT * FROM mantis_bugnote_table, mantis_bugnote_text_table WHERE bug_id = %s AND mantis_bugnote_table.bugnote_text_id = mantis_bugnote_text_table.id ORDER BY date_submitted" % bugid) |
| | 705 | bug_notes = mysql_cur.fetchall() |
| | 706 | totalComments += len(bug_notes) |
| | 707 | for note in bug_notes: |
| | 708 | trac.addTicketComment(bugid, note['date_submitted'], trac.getLoginName(mysql_cur, note['reporter_id']), note['note']) |
| | 709 | |
| | 710 | # |
| | 711 | # Convert ticket changes |
| | 712 | # |
| | 713 | mysql_cur.execute("SELECT * FROM mantis_bug_history_table WHERE bug_id = %s ORDER BY date_modified" % bugid) |
| | 714 | bugs_activity = mysql_cur.fetchall() |
| | 715 | resolution = '' |
| | 716 | ticketChanges = [] |
| | 717 | keywords = [] |
| | 718 | for activity in bugs_activity: |
| | 719 | field_name = activity['field_name'].lower() |
| | 720 | # Convert Mantis field names... |
| | 721 | # The following fields are the same in Mantis and Trac: |
| | 722 | # - 'status' |
| | 723 | # - 'priority' |
| | 724 | # - 'summary' |
| | 725 | # - 'resolution' |
| | 726 | # - 'severity' |
| | 727 | # - 'version' |
| | 728 | # |
| | 729 | # Ignore the following changes: |
| | 730 | # - project_id |
| | 731 | # - reproducibility |
| | 732 | # - view_state |
| | 733 | # - os |
| | 734 | # - os_build |
| | 735 | # - duplicate_id |
| | 736 | # |
| | 737 | # Convert Mantis -> Trac: |
| | 738 | # - 'handler_id' -> 'owner' |
| | 739 | # - 'fixed_in_version' -> 'milestone' |
| | 740 | # - 'category' -> 'component' |
| | 741 | # - 'version' -> 'milestone' |
| | 742 | |
| | 743 | ticketChange = {} |
| | 744 | ticketChange['ticket'] = bugid |
| | 745 | ticketChange['oldvalue'] = activity['old_value'] |
| | 746 | ticketChange['newvalue'] = activity['new_value'] |
| | 747 | ticketChange['time'] = activity['date_modified'] |
| | 748 | ticketChange['author'] = trac.getLoginName(mysql_cur, activity['user_id']) |
| | 749 | ticketChange['field'] = field_name |
| | 750 | |
| | 751 | add_keywords = [] |
| | 752 | remove_keywords = [] |
| | 753 | |
| | 754 | if field_name == 'handler_id': |
| | 755 | ticketChange['field'] = 'owner' |
| | 756 | ticketChange['oldvalue'] = trac.getLoginName(mysql_cur, int(activity['old_value'])) |
| | 757 | ticketChange['newvalue'] = trac.getLoginName(mysql_cur, int(activity['new_value'])) |
| | 758 | elif field_name == 'fixed_in_version': |
| | 759 | ticketChange['field'] = 'milestone' |
| | 760 | elif field_name == 'category': |
| | 761 | ticketChange['field'] = 'component' |
| | 762 | elif field_name == 'version': |
| | 763 | ticketChange['field'] = 'milestone' |
| | 764 | elif field_name == 'status': |
| | 765 | ticketChange['oldvalue'] = STATUS_TRANSLATE[int(activity['old_value'])] |
| | 766 | ticketChange['newvalue'] = STATUS_TRANSLATE[int(activity['new_value'])] |
| | 767 | if int(activity['old_value']) in STATUS_KEYWORDS: |
| | 768 | remove_keywords.append(STATUS_KEYWORDS[int(activity['old_value'])]) |
| | 769 | if int(activity['new_value']) in STATUS_KEYWORDS: |
| | 770 | add_keywords.append(STATUS_KEYWORDS[int(activity['new_value'])]) |
| | 771 | |
| | 772 | elif field_name == 'priority': |
| | 773 | ticketChange['oldvalue'] = PRIORITY_TRANSLATE[int(activity['old_value'])] |
| | 774 | ticketChange['newvalue'] = PRIORITY_TRANSLATE[int(activity['new_value'])] |
| | 775 | elif field_name == 'resolution': |
| | 776 | ticketChange['oldvalue'] = RESOLUTION_TRANSLATE[int(activity['old_value'])] |
| | 777 | ticketChange['newvalue'] = RESOLUTION_TRANSLATE[int(activity['new_value'])] |
| | 778 | elif field_name == 'severity': |
| | 779 | ticketChange['oldvalue'] = SEVERITY_TRANSLATE[int(activity['old_value'])] |
| | 780 | ticketChange['newvalue'] = SEVERITY_TRANSLATE[int(activity['new_value'])] |
| | 781 | |
| | 782 | if add_keywords or remove_keywords: |
| | 783 | # ensure removed ones are in old |
| | 784 | old_keywords = keywords + [kw for kw in remove_keywords if kw not in keywords] |
| | 785 | # remove from new |
| | 786 | keywords = [kw for kw in keywords if kw not in remove_keywords] |
| | 787 | # add to new |
| | 788 | keywords += [kw for kw in add_keywords if kw not in keywords] |
| | 789 | if old_keywords != keywords: |
| | 790 | ticketChangeKw = ticketChange.copy() |
| | 791 | ticketChangeKw['field'] = "keywords" |
| | 792 | ticketChangeKw['oldvalue'] = ' '.join(old_keywords) |
| | 793 | ticketChangeKw['newvalue'] = ' '.join(keywords) |
| | 794 | ticketChanges.append(ticketChangeKw) |
| | 795 | |
| | 796 | if field_name in IGNORED_ACTIVITY_FIELDS: |
| | 797 | continue |
| | 798 | |
| | 799 | # skip changes that have no effect (think translation!) |
| | 800 | if ticketChange['oldvalue'] == ticketChange['newvalue']: |
| | 801 | continue |
| | 802 | |
| | 803 | ticketChanges.append (ticketChange) |
| | 804 | |
| | 805 | totalTicketChanges += len(ticketChanges) |
| | 806 | for ticketChange in ticketChanges: |
| | 807 | try: |
| | 808 | trac.addTicketChange (**ticketChange) |
| | 809 | except: |
| | 810 | if TIME_ADJUSTMENT_HACK: |
| | 811 | addTime = datetime.timedelta(seconds=1) |
| | 812 | originalTime = ticketChange['time'] |
| | 813 | ticketChange['time'] += addTime |
| | 814 | try: |
| | 815 | trac.addTicketChange(**ticketChange) |
| | 816 | noticeStr = " ~ Successfully adjusted time for ticket(#%s) change \"%s\": \"%s\" -> \"%s\" (%s)" % (bugid, ticketChange['field'], ticketChange['oldvalue'], ticketChange['newvalue'], ticketChange['time']) |
| | 817 | noticeStr += "\n Original time: %s" % originalTime |
| | 818 | timeAdjustmentHacks.append(noticeStr) |
| | 819 | except: |
| | 820 | errorStr = " * ERROR: unable to add ticket(#%s) change \"%s\": \"%s\" -> \"%s\" (%s)" % (bugid, ticketChange['field'], ticketChange['oldvalue'], ticketChange['newvalue'], ticketChange['time']) |
| | 821 | errorStr += "\n The bug id, field name, and time must be unique" |
| | 822 | errors.append(errorStr) |
| | 823 | print errorStr |
| | 824 | else: |
| | 825 | errorStr = " * ERROR: unable to add ticket(#%s) change \"%s\": \"%s\" -> \"%s\" (%s)" % (bugid, ticketChange['field'], ticketChange['oldvalue'], ticketChange['newvalue'], ticketChange['time']) |
| | 826 | errorStr += "\n The bug id, field name, and time must be unique" |
| | 827 | errors.append(errorStr) |
| | 828 | print errorStr |
| | 829 | |
| | 830 | |
| | 831 | # |
| | 832 | # Add ticket file attachments |
| | 833 | # |
| | 834 | attachment_sql = "SELECT b.id,b.bug_id,b.title,b.description,b.filename,b.filesize,b.file_type,UNIX_TIMESTAMP(b.date_added) AS date_added, b.content, h.user_id FROM mantis_bug_file_table AS b LEFT JOIN mantis_bug_history_table AS h ON (h.type = 9 AND h.old_value = b.filename AND h.bug_id = b.bug_id) WHERE b.bug_id = %s" % bugid |
| | 835 | # print attachment_sql |
| | 836 | mysql_cur.execute(attachment_sql) |
| | 837 | attachments = mysql_cur.fetchall() |
| | 838 | for attachment in attachments: |
| | 839 | author = trac.getLoginName(mysql_cur, attachment['user_id']) |
| | 840 | |
| | 841 | # Old attachment stuff that never worked... |
| | 842 | # attachmentFile = open(attachment['diskfile'], 'r') |
| | 843 | # attachmentData = attachmentFile.read() |
| | 844 | # tracAttachment = Attachment(attachment['filename'], attachmentData) |
| | 845 | # trac.addAttachment(bugid, tracAttachment, attachment['description'], author) |
| | 846 | |
| | 847 | try: |
| | 848 | try: |
| | 849 | if(os.path.isdir(trac.get_attachments_dir(bugid)) == False): |
| | 850 | try: |
| | 851 | os.mkdir(trac.get_attachments_dir(bugid)) |
| | 852 | except: |
| | 853 | errorStr = " * ERROR: couldnt create attachment directory in filesystem at %s" % trac.get_attachments_dir(bugid) |
| | 854 | errors.append(errorStr) |
| | 855 | print errorStr |
| | 856 | # trac stores the files with the special characters like spaces in the filename encoded to the url |
| | 857 | # equivalents, so we have to urllib.quote() the filename we're saving. |
| | 858 | attachmentFile = open(trac.get_attachments_dir(bugid) + quote(attachment['filename']),'wb') |
| | 859 | attachmentFile.write(attachment['content']) |
| | 860 | attachmentFile.close() |
| | 861 | except: |
| | 862 | errorStr = " * ERROR: couldnt dump attachment data into filesystem at %s" % trac.get_attachments_dir(bugid) + attachment['filename'] |
| | 863 | errors.append(errorStr) |
| | 864 | print errorStr |
| | 865 | else: |
| | 866 | attach_sql = """INSERT INTO attachment (type,id,filename,size,time,description,author,ipnr) VALUES ('ticket',%s,'%s',%i,%i,'%s','%s','127.0.0.1')""" % (bugid,attachment['filename'],attachment['filesize'],attachment['date_added'],attachment['description'],author) |
| | 867 | try: |
| | 868 | c = trac.db().cursor() |
| | 869 | c.execute(attach_sql) |
| | 870 | trac.db().commit() |
| | 871 | except: |
| | 872 | errorStr = " * ERROR: couldnt insert attachment data into database with %s" % attach_sql |
| | 873 | errors.append(errorStr) |
| | 874 | print errorStr |
| | 875 | else: |
| | 876 | print 'inserting attachment for ticket %s -- %s, added by %s' % (bugid, attachment['description'], author) |
| | 877 | |
| | 878 | totalAttachments += 1 |
| | 879 | except: |
| | 880 | errorStr = " * ERROR: couldn't migrate attachment %s" % attachment['filename'] |
| | 881 | errors.append(errorStr) |
| | 882 | print errorStr |
| | 883 | |
| | 884 | print |
| | 885 | if TIME_ADJUSTMENT_HACK: |
| | 886 | for adjustment in timeAdjustmentHacks: |
| | 887 | print adjustment |
| | 888 | if len(errors) != 0: |
| | 889 | print "Some errors occurred while importing:" |
| | 890 | for error in errors: |
| | 891 | print error |
| | 892 | else: |
| | 893 | print "Success!" |
| | 894 | print |
| | 895 | print "Total tickets imported: %d" % len(bugs) |
| | 896 | print "Total ticket comments: %d" % totalComments |
| | 897 | print "Total ticket changes: %d" % totalTicketChanges |
| | 898 | print "Total attachments: %d" % totalAttachments |
| | 899 | print |
| | 900 | |
| | 901 | def usage(): |
| | 902 | print "mantis2trac - Imports a bug database from Mantis into Trac." |
| | 903 | print |
| | 904 | print "Usage: mantis2trac.py [options]" |
| | 905 | print |
| | 906 | print "Available Options:" |
| | 907 | print " --db <MySQL dbname> - Mantis database" |
| | 908 | print " --tracenv /path/to/trac/env/ - Full path to Trac environment" |
| | 909 | print " -h | --host <MySQL hostname> - Mantis DNS host name" |
| | 910 | print " -u | --user <MySQL username> - Effective Mantis database user" |
| | 911 | print " -p | --passwd <MySQL password> - Mantis database user password" |
| | 912 | print " -c | --clean - Remove current Trac tickets before importing" |
| | 913 | print " --products <product1,product2> - List of products to import from mantis" |
| | 914 | print " --help | help - This help info" |
| | 915 | print |
| | 916 | print "Note: If you want the ticket attachments to be converted, you MUST run the script" |
| | 917 | print " as a user who has write permissions to the trac env attachments directory." |
| | 918 | print "Note 2: Attachment conversion only works for attachments stored directly in the mantis" |
| | 919 | print " database at this point." |
| | 920 | print |
| | 921 | print "Additional configuration options can be defined directly in the script." |
| | 922 | print |
| | 923 | sys.exit(0) |
| | 924 | |
| | 925 | def main(): |
| | 926 | global MANTIS_DB, MANTIS_HOST, MANTIS_USER, MANTIS_PASSWORD, TRAC_ENV, TRAC_CLEAN, PRODUCTS |
| | 927 | if len (sys.argv) > 1: |
| | 928 | if sys.argv[1] in ['--help','help'] or len(sys.argv) < 4: |
| | 929 | usage() |
| | 930 | iter = 1 |
| | 931 | while iter < len(sys.argv): |
| | 932 | if sys.argv[iter] in ['--db'] and iter+1 < len(sys.argv): |
| | 933 | MANTIS_DB = sys.argv[iter+1] |
| | 934 | iter = iter + 1 |
| | 935 | elif sys.argv[iter] in ['-h', '--host'] and iter+1 < len(sys.argv): |
| | 936 | MANTIS_HOST = sys.argv[iter+1] |
| | 937 | iter = iter + 1 |
| | 938 | elif sys.argv[iter] in ['-u', '--user'] and iter+1 < len(sys.argv): |
| | 939 | MANTIS_USER = sys.argv[iter+1] |
| | 940 | iter = iter + 1 |
| | 941 | elif sys.argv[iter] in ['-p', '--passwd'] and iter+1 < len(sys.argv): |
| | 942 | MANTIS_PASSWORD = sys.argv[iter+1] |
| | 943 | iter = iter + 1 |
| | 944 | elif sys.argv[iter] in ['--tracenv'] and iter+1 < len(sys.argv): |
| | 945 | TRAC_ENV = sys.argv[iter+1] |
| | 946 | iter = iter + 1 |
| | 947 | elif sys.argv[iter] in ['-c', '--clean']: |
| | 948 | TRAC_CLEAN = 1 |
| | 949 | elif sys.argv[iter] in ['--products'] and iter+1 < len(sys.argv): |
| | 950 | PRODUCTS = sys.argv[iter+1].split(',') |
| | 951 | iter = iter + 1 |
| | 952 | else: |
| | 953 | print "Error: unknown parameter: " + sys.argv[iter] |
| | 954 | sys.exit(0) |
| | 955 | iter = iter + 1 |
| | 956 | else: |
| | 957 | usage() |
| | 958 | |
| | 959 | convert(MANTIS_DB, MANTIS_HOST, MANTIS_USER, MANTIS_PASSWORD, TRAC_ENV, TRAC_CLEAN) |
| | 960 | |
| | 961 | if __name__ == '__main__': |
| | 962 | main() |