Edgewall Software

Ticket #1514: mantis2trac.py

File mantis2trac.py, 25.6 KB (added by Tarjei Knapstad <tarjei.knapstad@…>, 3 years ago)

Mantis to Trac DB conversion script by Blake Watters

Line 
1#!/usr/bin/env python
2
3"""
4Import a Mantis bug database into Trac
5
6Requires:  Trac 0.8 from http://trac.edgewall.com/
7           Python 2.3 from http://www.python.org/
8           MySQL >= 3.23 from http://www.mysql.org/
9
10Thanks: Dmitry Yusupov, author of bugzilla2trac on which this
11    script is based
12
13Copyright 2005, Blake Watters <blake@near-time.com>
14"""
15
16###
17### Conversion Settings -- edit these before running if desired
18###
19
20# Mantis version.
21#
22# Currently, the following Mantis versions are known to work:
23#   2.11
24#
25# If you run this script on a version not listed here and it is successful,
26# please report it to the Trac mailing list so we can update the list.
27MT_VERSION = '0.19.2'
28
29# MySQL connection parameters for the Mantis database.  These can also
30# be specified on the command line.
31MT_DB = ''
32MT_HOST = 'localhost'
33MT_USER = ''
34MT_PASSWORD = ''
35
36# Path to the Trac environment.
37TRAC_ENV = ''
38
39# If true, all existing Trac tickets and attachments will be removed
40# prior to import.
41TRAC_CLEAN = False
42
43# Enclose imported ticket description and comments in a {{{ }}}
44# preformat block?  This formats the text in a fixed-point font.
45PREFORMAT_COMMENTS = False
46
47# By default, all bugs are imported from Mantis.  If you add a list
48# of products here, only bugs from those projects will be imported.
49PROJECTS = [2]
50
51# Trac doesn't have the concept of a project.  Instead, this script can
52# assign keywords in the ticket entry to represent projects.
53#
54# ex. PROJECT_KEYWORDS = { 'project1' : 'PROJECT_KEYWORD' }
55PROJECT_KEYWORDS = {}
56
57# Bug comments that should not be imported.  Each entry in list should
58# be a regular expression.
59IGNORE_COMMENTS = []
60
61###########################################################################
62### You probably don't need to change any configuration past this line. ###
63###########################################################################
64
65# Mantis status to Trac status translation map.
66#
67# NOTE: bug activity is translated as well, which may cause bug
68# activity to be deleted (e.g. resolved -> closed in Mantis
69# would translate into closed -> closed in Trac, so we just ignore the
70# change).
71#
72# There is some special magic for open in the code:  if there is no
73# Mantis owner, open is mapped to 'new' instead.
74STATUS_TRANSLATE = {
75    10 : 'new',
76    20 : 'assigned',   
77    30 : 'assigned',
78    40 : 'assigned',
79    50 : 'assigned',
80    80 : 'closed',
81    90 : 'closed'
82}
83
84#
85# Mantis resolution to Trac resolution translator
86#
87# $g_resolution_enum_string                       = '10:open,20:fixed,30:reopened,40:unable to duplicate,50:not fixable,60:duplicate,70:not a bug,80:suspended,90:wont fix';
88RESOLUTION_TRANSLATE = {
89    10 : '',
90    20 : 'fixed',
91    30 : '',
92    40 : 'worksforme',
93    50 : 'wontfix',
94    60 : 'duplicate',
95    70 : 'invalid',
96    80 : '',
97    90 : 'wontfix'
98}
99
100#
101# Mantis severity to Trac severity translation map.
102#
103SEVERITY_TRANSLATE = {
104    '10' : 'enhancement',
105    '20' : 'trivial',
106    '30' : 'trivial',
107    '40' : 'trivial',
108    '50' : 'minor',
109    '60' : 'major',
110    '70' : 'critical',
111    '80' : 'blocker'
112}
113
114#
115# Mantis priority to Trac priority
116#
117PRIORITY_TRANSLATE = {
118    '10' : 'lowest',
119    '20' : 'low',
120    '30' : 'normal',
121    '40' : 'high',
122    '50' : 'highest',
123    '60' : 'highest'
124}
125
126# Translate Mantis statuses into Trac keywords.  This provides a way
127# to retain the Mantis statuses in Trac.  e.g. when a bug is marked
128# 'verified' in Mantis it will be assigned a VERIFIED keyword.
129STATUS_KEYWORDS = {
130  'verified' : 'VERIFIED',
131  'released' : 'RELEASED'
132}
133
134# Some fields in Mantis do not have equivalents in Trac.  Changes in
135# fields listed here will not be imported into the ticket change history,
136# otherwise you'd see changes for fields that don't exist in Trac.
137IGNORED_ACTIVITY_FIELDS = ['everconfirmed']
138
139###
140### Script begins here
141###
142
143import os
144import re
145import sys
146import string
147import StringIO
148
149import MySQLdb
150import MySQLdb.cursors
151import trac.Environment
152import sqlite
153
154if not hasattr(sys, 'setdefaultencoding'):
155    reload(sys)
156
157sys.setdefaultencoding('latin1')
158
159# simulated Attachment class for trac.add
160class Attachment:
161    def __init__(self, name, data):
162        self.filename = name
163        self.file = StringIO.StringIO(data.tostring())
164 
165# simple field translation mapping.  if string not in
166# mapping, just return string, otherwise return value
167class FieldTranslator(dict):
168    def __getitem__(self, item):
169        if not dict.has_key(self, item):
170            return item
171           
172        return dict.__getitem__(self, item)
173
174statusXlator = FieldTranslator(STATUS_TRANSLATE)
175severityXlator = FieldTranslator(SEVERITY_TRANSLATE)
176priorityXlator = FieldTranslator(PRIORITY_TRANSLATE)
177resolutionXlator = FieldTranslator(RESOLUTION_TRANSLATE)
178
179class TracDatabase(object):
180    def __init__(self, path):
181        self.env = trac.Environment.Environment(path)
182        self._db = self.env.get_db_cnx()
183        self._db.autocommit = False
184        self.mysql_cur = False
185        self.loginNameCache = {}
186        self.fieldNameCache = {}
187   
188    def db(self):
189        return self._db
190   
191    def hasTickets(self):
192        c = self.db().cursor()
193        c.execute('''SELECT count(*) FROM Ticket''')
194        return int(c.fetchall()[0][0]) > 0
195
196    def assertNoTickets(self):
197        if self.hasTickets():
198            raise Exception("Will not modify database with existing tickets!")
199   
200    def setSeverityList(self, s):
201        """Remove all severities, set them to `s`"""
202        self.assertNoTickets()
203       
204        c = self.db().cursor()
205        c.execute("""DELETE FROM enum WHERE type='severity'""")
206        for value, i in s:
207            print "inserting severity ", value, " ", i
208            c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""",
209                      "severity", value.encode('utf-8'), i)
210        self.db().commit()
211   
212    def setPriorityList(self, s):
213        """Remove all priorities, set them to `s`"""
214        self.assertNoTickets()
215       
216        c = self.db().cursor()
217        c.execute("""DELETE FROM enum WHERE type='priority'""")
218        for value, i in s:
219            print "inserting priority ", value, " ", i
220            c.execute("""INSERT INTO enum (type, name, value) VALUES (%s, %s, %s)""",
221                      "priority",
222                      value.encode('utf-8'),
223                      i)
224        self.db().commit()
225
226   
227    def setComponentList(self, l, key):
228        """Remove all components, set them to `l`"""
229        self.assertNoTickets()
230       
231        c = self.db().cursor()
232        c.execute("""DELETE FROM component""")
233        for comp in l:
234            name = comp[key].strip()
235            print "inserting component '",name,"', owner",  comp['owner']
236            c.execute("""INSERT INTO component (name, owner) VALUES (%s, %s)""",
237                      name.encode('utf-8'), comp['owner'].encode('utf-8'))
238        self.db().commit()
239   
240    def setVersionList(self, v, key):
241        """Remove all versions, set them to `v`"""
242        self.assertNoTickets()
243       
244        c = self.db().cursor()
245        c.execute("""DELETE FROM version""")
246        for vers in v:
247            print "inserting version ", vers[key]
248            c.execute("""INSERT INTO version (name) VALUES (%s)""",
249                      vers[key].encode('utf-8'))
250        self.db().commit()
251       
252    def setMilestoneList(self, m, key):
253        """Remove all milestones, set them to `m`"""
254        self.assertNoTickets()
255       
256        c = self.db().cursor()
257        c.execute("""DELETE FROM milestone""")
258        for ms in m:
259            print "inserting milestone ", ms[key]
260            c.execute("""INSERT INTO milestone (name) VALUES (%s)""",
261                      ms[key].encode('utf-8'))
262        self.db().commit()
263   
264    def addTicket(self, id, time, changetime, component,
265                  severity, priority, owner, reporter, cc,
266                  version, milestone, status, resolution,
267                  summary, description, keywords):
268        c = self.db().cursor()
269       
270        desc = description.encode('utf-8')
271       
272        if PREFORMAT_COMMENTS:
273          desc = '{{{\n%s\n}}}' % desc
274
275        print "inserting ticket %s -- %s" % (id, summary)
276        c.execute("""INSERT INTO ticket (id, time, changetime, component,
277                                         severity, priority, owner, reporter, cc,
278                                         version, milestone, status, resolution,
279                                         summary, description, keywords)
280                                 VALUES (%s, %s, %s, %s,
281                                         %s, %s, %s, %s, %s,
282                                         %s, %s, %s, %s,
283                                         %s, %s, %s)""",
284                  id, time.strftime('%s'), changetime.strftime('%s'), component.encode('utf-8'),
285                  severity.encode('utf-8'), priority.encode('utf-8'), owner, reporter, cc,
286                  version, milestone.encode('utf-8'), status.lower(), resolution,
287                  summary.encode('utf-8'), desc, keywords)
288       
289        self.db().commit()
290        return self.db().db.sqlite_last_insert_rowid()
291   
292    def addTicketComment(self, ticket, time, author, value):
293        comment = value.encode('utf-8')
294       
295        if PREFORMAT_COMMENTS:
296          comment = '{{{\n%s\n}}}' % comment
297
298        c = self.db().cursor()
299        c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue)
300                                 VALUES        (%s, %s, %s, %s, %s, %s)""",
301                  ticket, time.strftime('%s'), author, 'comment', '', comment)
302        self.db().commit()
303
304    def addTicketChange(self, ticket, time, author, field, oldvalue, newvalue):
305        c = self.db().cursor()
306       
307        if field == 'status':
308            oldvalue = statusXlator[int(oldvalue)]
309            newvalue = statusXlator[int(newvalue)]
310       
311        if field == 'severity':
312            oldvalue = severityXlator[oldvalue]
313            newvalue = severityXlator[newvalue]
314       
315        if field == 'owner':
316            if oldvalue == 0:
317                oldvalue = 'unassigned'
318            else:
319                oldvalue = self.getLoginName(self.mysql_cur, oldvalue)
320               
321            if newvalue == 0:
322                newvalue = 'unassigned'
323            else:
324                newvalue = self.getLoginName(self.mysql_cur, newvalue)
325       
326        if field == 'resolution':
327            oldvalue = resolutionXlator[oldvalue]
328            newvalue = resolutionXlator[newvalue]
329       
330        if field == 'priority':
331            oldvalue = priorityXlator[oldvalue]
332            newvalue = priorityXlator[newvalue]           
333           
334        try:
335            c.execute("""INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue)
336                                 VALUES        (%s, %s, %s, %s, %s, %s)""",
337                  ticket, time.strftime('%s'), author, field, oldvalue.encode('utf-8'), newvalue.encode('utf-8'))
338            self.db().commit()
339       
340        except sqlite.IntegrityError, excp:
341            print 'Warning : Ticket ID ' + str(ticket) + ' ' + str(excp)
342           
343       
344    def addAttachment(self, id, attachment, description, author):
345        print 'inserting attachment for ticket %s -- %s' % (id, description)
346        attachment.filename = attachment.filename.encode('utf-8')
347        try:
348            self.env.create_attachment(self.db(), 'ticket', str(id), attachment, description.encode('utf-8'),
349            author, 'unknown')
350       
351        except trac.util.TracError, excp:
352            print 'Warning : Ticket ID ' + str(id) + ' ' + str(excp)
353       
354    def getLoginName(self, cursor, userid):
355        if userid not in self.loginNameCache:
356            cursor.execute("SELECT * FROM mantis_user_table WHERE id = %s" % userid)
357            loginName = cursor.fetchall()
358
359            if loginName:
360                loginName = loginName[0]['username']
361            else:
362                #print 'warning: unknown Mantis userid %i, recording as anonymous' % userid
363                loginName = 'anonymous'
364
365            self.loginNameCache[userid] = loginName
366
367        return self.loginNameCache[userid]
368   
369    def setMysqlCursor(self, cursor):
370        self.mysql_cur = cursor
371       
372    def getFieldName(self, cursor, fieldid):
373        if fieldid not in self.fieldNameCache:
374            cursor.execute("SELECT * FROM fielddefs WHERE fieldid = %s" % fieldid)
375            fieldName = cursor.fetchall()
376
377            if fieldName:
378                fieldName = fieldName[0]['name'].lower()
379            else:
380                print 'warning: unknown Mantis fieldid %d, recording as unknown' % userid
381                fieldName = 'unknown'
382
383            self.fieldNameCache[fieldid] = fieldName
384
385        return self.fieldNameCache[fieldid]
386
387def projectFilter(fieldName, projects):
388    first = True
389    result = ''
390    for project in projects:
391        if not first: 
392            result += " or "
393        first = False
394        result += "%s = '%s'" % (fieldName, project)
395    return result
396
397def convert(_db, _host, _user, _password, _env, _force):
398    # init Mantis environment
399    print "Mantis MySQL('%s':'%s':'%s':'%s'): connecting..." % (_db, _host, _user, _password)
400    mysql_con = MySQLdb.connect(host=_host, 
401                user=_user, passwd=_password, db=_db, compress=1, 
402                cursorclass=MySQLdb.cursors.DictCursor)
403    mysql_cur = mysql_con.cursor()
404
405    # init Trac environment
406    print "Trac SQLite('%s'): connecting..." % (_env)
407    trac = TracDatabase(_env)
408    trac.setMysqlCursor(mysql_cur)
409   
410    # force mode...
411    if _force == 1:
412        print "cleaning all tickets..."
413        c = trac.db().cursor()
414        c.execute("""DELETE FROM ticket_change""")
415        trac.db().commit()
416        c.execute("""DELETE FROM ticket""")
417        trac.db().commit()
418        c.execute("""DELETE FROM attachment""")
419        os.system('rm -rf %s' % trac.env.get_attachments_dir())
420        os.mkdir(trac.env.get_attachments_dir())
421        trac.db().commit()
422
423
424    print
425    print "1. import severities..."
426    severities = (('blocker', '1'), ('critical', '2'), ('major', '3'), ('normal', '4'),
427        ('minor', '5'), ('trivial', '6'), ('enhancement', '7'))
428    trac.setSeverityList(severities)
429
430    print
431    print "2. import components..."
432    sql = "SELECT category, user_id AS owner FROM mantis_project_category_table"
433    if PROJECTS:
434       sql += " WHERE %s" % projectFilter('project_id', PROJECTS)
435    mysql_cur.execute(sql)
436    components = mysql_cur.fetchall()
437    for component in components:
438                component['owner'] = trac.getLoginName(mysql_cur, component['owner'])
439    trac.setComponentList(components, 'category')
440
441    print
442    print "3. import priorities..."
443    priorities = (('lowest', '1'), ('low', '2'), ('normal', '3'), ('high', '4'), ('highest', '5'))
444    trac.setPriorityList(priorities)
445
446    print
447    print "4. import versions..."
448    sql = "SELECT DISTINCTROW version FROM mantis_project_version_table"
449    if PROJECTS:
450       sql += " WHERE %s" % projectFilter('project_id', PROJECTS)
451    mysql_cur.execute(sql)
452    versions = mysql_cur.fetchall()
453    trac.setVersionList(versions, 'version')
454
455    print
456    print '5. retrieving bugs...'
457    sql = "SELECT * FROM mantis_bug_table "
458    if PROJECTS:
459       sql += " WHERE %s" % projectFilter('project_id', PROJECTS)
460    sql += " ORDER BY id"
461    mysql_cur.execute(sql)
462    bugs = mysql_cur.fetchall()
463   
464    print
465    print "6. import bugs and bug activity..."
466    for bug in bugs:
467        bugid = bug['id']
468       
469        ticket = {}
470        keywords = []
471        ticket['id'] = bugid
472        ticket['time'] = bug['date_submitted']
473        ticket['changetime'] = bug['last_updated']
474        ticket['component'] = bug['category']
475       
476        severity = str(bug['severity'])
477        ticket['severity'] = severityXlator[severity]
478       
479        priority = str(bug['priority'])
480        ticket['priority'] = priorityXlator[priority]
481
482        ticket['owner'] = trac.getLoginName(mysql_cur, bug['handler_id'])
483        ticket['reporter'] = trac.getLoginName(mysql_cur, bug['reporter_id'])
484
485        mysql_cur.execute("SELECT * FROM mantis_bug_monitor_table WHERE bug_id = %s" % bugid)
486        cc_records = mysql_cur.fetchall()
487        cc_list = []
488        for cc in cc_records:
489            cc_list.append(trac.getLoginName(mysql_cur, cc['user_id']))
490        ticket['cc'] = string.join(cc_list, ', ')
491
492        ticket['version'] = bug['version']
493        ticket['milestone'] = ''
494
495        bug_status = bug['status']
496        ticket['status'] = statusXlator[bug_status]
497        ticket['resolution'] = resolutionXlator[bug['resolution']]
498
499        # a bit of extra work to do open tickets
500        if bug_status == 'open':
501            if owner != '':
502                ticket['status'] = 'assigned'
503            else:
504                ticket['status'] = 'new'
505
506        ticket['summary'] = bug['summary']
507
508        # TODO: Figure out keywords
509        #keywords = string.split(bug['keywords'], ' ')
510        keywords = ''
511
512        mysql_cur.execute("SELECT * FROM mantis_bug_text_table WHERE id = %s" % bugid) 
513        texts = mysql_cur.fetchone()
514        ticket['description'] = texts['description']
515       
516        # Add the additional info and steps to reproduce as comments
517        trac.addTicketComment(bugid,
518            bug['date_submitted'],
519            trac.getLoginName(mysql_cur, bug['reporter_id']),
520            texts['steps_to_reproduce'])
521       
522#        trac.addTicketComment(bugid,
523#            bug['date_submitted'],
524#            trac.getLoginName(mysql_cur, bug['reporter_id']),
525#            texts['additional_information'])
526
527        # Grab the bug notes as comments
528        mysql_cur.execute("SELECT * FROM mantis_bugnote_table WHERE bug_id = %s" % bugid)
529        bugnotes = mysql_cur.fetchall()
530       
531        for desc in bugnotes:           
532            ignore = False
533            mysql_cur.execute("SELECT * FROM mantis_bugnote_text_table WHERE id = %s" % desc['id'])
534            text = mysql_cur.fetchone()
535            try:
536                note = str(text['note'])
537            except TypeError:
538                continue
539               
540            for comment in IGNORE_COMMENTS:
541                if re.match(comment, note):
542                    ignore = True
543                   
544            if ignore: continue
545           
546            trac.addTicketComment(bugid,
547                desc['date_submitted'],
548                trac.getLoginName(mysql_cur, desc['reporter_id']),
549                note)
550
551        # Import the bug history
552        mysql_cur.execute("SELECT * FROM mantis_bug_history_table WHERE bug_id = %s ORDER BY date_modified" % bugid)
553        bugs_activity = mysql_cur.fetchall()
554        resolution = ''
555        ticketChanges = []
556        for activity in bugs_activity:
557            field_name = activity['field_name']           
558            removed = activity['old_value']
559            added = activity['new_value']
560
561            # remember most recent resolution, we need this later
562            if field_name == 'resolution':
563                resolution = resolutionXlator[added.lower()]
564
565            keywordChange = False
566            oldKeywords = string.join(keywords, " ")
567
568            # convert Mantis field names...
569            if field_name == 'handler_id':
570                field_name = 'owner'
571            elif field_name == 'status':
572                if removed in STATUS_KEYWORDS:
573                    kw = STATUS_KEYWORDS[removed]
574                    if kw in keywords:
575                        keywords.remove(kw)
576                    else:
577                        oldKeywords = string.join(keywords + [ kw ], " ")
578                    keywordChange = True
579                if added in STATUS_KEYWORDS:
580                    kw = STATUS_KEYWORDS[added]
581                    keywords.append(kw)
582                    keywordChange = True
583                added = statusXlator[added]
584                removed = statusXlator[removed]
585            elif field_name == 'project_id':
586                if removed in PROJECT_KEYWORDS:
587                    kw = PROJECT_KEYWORDS[removed]
588                    if kw in keywords:
589                        keywords.remove(kw)
590                    else:
591                        oldKeywords = string.join(keywords + [ kw ], " ")
592                    keywordChange = True
593                if added in PROJECT_KEYWORDS:
594                    kw = PROJECT_KEYWORDS[added]
595                    keywords.append(kw)
596                    keywordChange = True
597
598            ticketChange = {}
599            ticketChange['ticket'] = bugid
600            ticketChange['time'] = activity['date_modified']
601            ticketChange['author'] = trac.getLoginName(mysql_cur, activity['user_id'])
602            ticketChange['field'] = field_name
603            #ticketChange['oldvalue'] = removed
604            ticketChange['oldvalue'] = activity['old_value']
605            #ticketChange['newvalue'] = added
606            ticketChange['newvalue'] = activity['new_value']
607
608            if keywordChange:
609                newKeywords = string.join(keywords, " ")
610                ticketChangeKw = ticketChange
611                ticketChangeKw['field'] = 'keywords'
612                ticketChangeKw['oldvalue'] = oldKeywords
613                ticketChangeKw['newvalue'] = newKeywords
614                #trac.addTicketChange(ticket=bugid, time=activity['bug_when'],
615                #    author=trac.getLoginName(mysql_cur, activity['who']),
616                #    field='keywords', oldvalue=oldKeywords, newvalue=newKeywords)
617                ticketChanges.append(ticketChangeKw)
618
619            if field_name in IGNORED_ACTIVITY_FIELDS:
620                continue
621
622            # skip changes that have no effect (think translation!)
623            if added == removed:
624                continue
625               
626            # Mantis splits large summary changes into two records
627            for oldChange in ticketChanges:
628              if (field_name == 'summary'
629                  and oldChange['field'] == ticketChange['field'] 
630                  and oldChange['time'] == ticketChange['time'] 
631                  and oldChange['author'] == ticketChange['author']):
632                  oldChange['oldvalue'] += " " + ticketChange['oldvalue'] 
633                  oldChange['newvalue'] += " " + ticketChange['newvalue']
634                  break
635            else:
636                #trac.addTicketChange(ticket=bugid, time=activity['bug_when'],
637                #    author=trac.getLoginName(mysql_cur, activity['who']),
638                #    field=field_name, oldvalue=removed, newvalue=added)
639                ticketChanges.append (ticketChange)
640
641        for ticketChange in ticketChanges:
642            trac.addTicketChange (**ticketChange)
643
644        if bug['status'] in STATUS_KEYWORDS:
645            kw = STATUS_KEYWORDS[bug['status']]
646            # may have already been added during activity import
647            if kw not in keywords:
648                keywords.append(kw)
649
650        if bug['project_id'] in PROJECT_KEYWORDS:
651            kw = PROJECT_KEYWORDS[bug['project_id']]
652            # may have already been added during activity import
653            if kw not in keywords:
654                keywords.append(kw)
655
656        mysql_cur.execute("SELECT * FROM mantis_bug_file_table WHERE bug_id = %s" % bugid)
657        attachments = mysql_cur.fetchall()
658        for a in attachments:
659            author = trac.getLoginName(mysql_cur, bug['reporter_id'])
660           
661            tracAttachment = Attachment(a['filename'], a['content'])
662            trac.addAttachment(bugid, tracAttachment, a['description'], author)
663           
664        ticket['keywords'] = string.join(keywords)               
665        ticketid = trac.addTicket(**ticket)
666
667    print "Success!"
668
669def usage():
670    print "Mantis2trac - Imports a bug database from Mantis into Trac."
671    print
672    print "Usage: mantis2trac.py [options]"
673    print
674    print "Available Options:"
675    print "  --db <MySQL dbname>              - Mantis's database"
676    print "  --tracenv /path/to/trac/env      - full path to Trac db environment"
677    print "  -h | --host <MySQL hostname>     - Mantis's DNS host name"
678    print "  -u | --user <MySQL username>     - effective Mantis's database user"
679    print "  -p | --passwd <MySQL password>   - Mantis's user password"
680    print "  -c | --clean                     - remove current Trac tickets before importing"
681    print "  --help | help                    - this help info"
682    print
683    print "Additional configuration options can be defined directly in the script."
684    print
685    sys.exit(0)
686
687def main():
688    global MT_DB, MT_HOST, MT_USER, MT_PASSWORD, TRAC_ENV, TRAC_CLEAN
689    if len (sys.argv) > 1:
690        if sys.argv[1] in ['--help','help'] or len(sys.argv) < 4:
691            usage()
692        iter = 1
693        while iter < len(sys.argv):
694            if sys.argv[iter] in ['--db'] and iter+1 < len(sys.argv):
695                MT_DB = sys.argv[iter+1]
696                iter = iter + 1
697            elif sys.argv[iter] in ['-h', '--host'] and iter+1 < len(sys.argv):
698                MT_HOST = sys.argv[iter+1]
699                iter = iter + 1
700            elif sys.argv[iter] in ['-u', '--user'] and iter+1 < len(sys.argv):
701                MT_USER = sys.argv[iter+1]
702                iter = iter + 1
703            elif sys.argv[iter] in ['-p', '--passwd'] and iter+1 <