Merge Trac tickets

Below (open post to see) is a quickly hacked together script to copy tickets from one Trac database to another. It’s sets a new component on each ticket and removes a milestone, if set, so if you don’t want that, you’ll have to change that part (it’s pretty easy).

Public domain, use at your own risk.

"""
    Usage: %s source.db target.db new_component

    Copies all tickets, ticket changes and ticket attachments from the source database
    to the target database, and updates the 'component' field on each ticket.

    * 'component' is replaced and 'milestone' removed from tickets. Customize the
      script if you don't / need this behaviour.
    * Make backups yourself!
    * No attempt is made to preserve ticket IDs from the source.
    * Ignores the ticket_custom table - not sure what it's for, it always
      seems to be empty.
    * Tries to be as generic, but makes some assumptions about the
      database schema when accessing columns via indices.
    * Expects both databases to be based on the same trac-version, i.e.
      have the same schema.
"""

import sys, os
from pysqlite2 import dbapi2 as sqlite

def main(argv):
    try:
        source_file, dest_file, new_component = argv
    except ValueError:
        print "Usage: %s source.db target.db new_component" % os.path.basename(sys.argv[0])
        return 1

    # connect to databases
    source_conn = sqlite.connect(source_file)
    source_cur = source_conn.cursor()
    dest_conn = sqlite.connect(dest_file)
    dest_cur = dest_conn.cursor()

    qmarks = lambda seq: ','.join(['?' for r in seq])
    try:
        # go through tickets in source
        tickets = source_cur.execute('SELECT * FROM ticket;')
        for ticket in tickets:
            # delete the id column - will get a new id
            old_id = ticket[0]
            ticket = list(ticket[1:])
            # reset values of component and milestone rows
            ticket[4-1] = new_component     # component
            ticket[11-1] = None               # milestone
            # insert ticket into target db
            print "copying ticket #%s" % old_id
            dest_cur.execute('INSERT INTO ticket '+
                                '('+(','.join([f[0] for f in source_cur.description[1:]]))+') '+
                                'VALUES('+qmarks(ticket)+')',
                            ticket)
            new_id = dest_cur.lastrowid

            # parameters: table name, where clause, query params, id column index, table repr, row repr index
            def copy_table(table, whereq, params, id_idx, trepr, rrepr_idx):
                cur = source_conn.cursor()
                try:
                    cur.execute('SELECT * FROM %s WHERE %s'%(table, whereq), params)
                    for row in cur:
                        row = list(row)
                        row [id_idx] = new_id
                        print "tcopying %s #%s"%(trepr, row [rrepr_idx])
                        dest_cur.execute('INSERT INTO %s VALUES(%s)'%(table,qmarks(row)), row)
                finally:
                    cur.close()

            # copy ticket changes
            copy_table('ticket_change',
                       'ticket=?', (old_id,),
                       0, 'ticket change', 1)
            # copy attachments
            copy_table('attachment',
                       'type="ticket" AND id=?', (old_id,),
                       1, 'attachment', 2)

        # commit changes
        dest_conn.commit()
    finally:
        dest_conn.close()

if __name__ == '__main__':
    sys.exit(main(sys.argv[1:]))

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s