Importing from Mantis

I just made the switch from Mantis (mantisbt.sf.net) to RT, and I’m
loving RT more and more every day. To switch over, I had to write a
convert script which I present below. Use it as you like.

xoxo,
Andy

#!/usr/bin/perl -w
#$Id: import-mantis,v 1.18 2002/08/02 01:51:19 alester Exp $

import-mantis

Created by: Andy Lester andy@petdance.com

Hacked from Jesse’s import-gnats script

Please go thru and change all the values to what’s appropriate.

use strict;
use Carp qw( cluck confess );

my $EMAILDOMAIN = “flr.follett.com”; # user_id@EMAILDOMAIN for missing RT1 emails
my $DEFAULTQUEUE = ‘general’; # Qeuue to put tickets from deleted queues in.

IF YOU DO NOT SET THE ABOVE VALUE TO A VALID QUEUE NAME, YOUR IMPORT WILL

FAIL AND YOU’LL BE REALLY REALLY UNHAPPY.

Mantis DB variables

my $dbname = “bugtracker”;
my $dbuser = “root”;
my $dbpass = “fish”;

my $Debug = 1;

our %mantis_name_by_id;

Replace this with the path to your RT lib directory

use lib “/usr/local/rt2/lib/”;

Replace this with the path to your RT etc directory

use lib “/usr/local/rt2/etc/”;

use Data::Dumper;
use RT::Interface::CLI qw( CleanEnv LoadConfig DBConnect GetCurrentUser GetMessageContent );
use Date::Format;
use MIME::Entity;
use Mail::Internet;
use MIME::Parser;
use RT::User;
use RT::Queue;
use RT::Ticket;
use RT::Transaction;
warn “Loaded modules\n”;
#Clean out all the nasties from the environment
CleanEnv();
warn “CleanEnv() done\n”;

#Load etc/config.pm and drop privs
LoadConfig();
warn “LoadConfig() done\n”;

This is an inlined version of RT::Init().

Calling RT::Init() would lock up, but pulling the guts out here

worked. I don’t know why.

use RT;
#Get a database connection
$RT::Handle = new RT::Handle($RT::DatabaseType);
$RT::Handle->Connect();

#RT's system user is a genuine database user. its id lives here
$RT::SystemUser = new RT::CurrentUser();
$RT::SystemUser->LoadByName('RT_System');

#RT's "nobody user" is a genuine database user. its ID lives here.
$RT::Nobody = new RT::CurrentUser();
$RT::Nobody->LoadByName('Nobody');

warn “Did DBConnect();\n”;

Mantis-specific handle

our $dbh =
DBI->connect(
“DBI:mysql:host=localhost;database=$dbname”,
$dbuser, $dbpass,
{PrintError => 0, RaiseError => 1}
);
warn “Created Mantis DBH\n”;

Returns a queue object based on a Mantis project name

sub get_queue {
my $name = shift;

my $queue = new RT::Queue($RT::SystemUser);
$queue->Load($name);

my ($val,$msg);
unless ($queue->Id) {
($val, $msg) =
    $queue->Create( 
	Name => $name, 
	Description => $name . " imported from Mantis",
	CorrespondAddress => $name.'@'. $EMAILDOMAIN ); 
warn "Fired up the RT queue $name\n";
}


unless ($queue->Id) {
die "Couldn't create a queue called '$name'\n$msg";
} 

return $queue;

}

IMPORT_USERS: {
my $sql = q{
select id, username, email
from mantis_user_table
};
my $sth = $dbh->prepare( $sql ) or die;
$sth->execute() or die;

my $root = new RT::User($RT::SystemUser);
$root->Load('root');

while ( my $row = $sth->fetchrow_hashref ) {
my $name = $row->{username};
my $result = $root->Create( 
    Name => $name,
    RealName => $name,
    Gecos => $name,
    EmailAddress => $row->{email},
    Privileged => 1,
);
$mantis_name_by_id{ $row->{id} } = $name;

warn "Created $name: $result\n";
}
$sth->finish;

}

Import all the top-level bugs

IMPORT_BUGS: {
my $sql = q{
select B., T., P.name as project_name
from mantis_bug_table B, mantis_bug_text_table T, mantis_project_table P
where B.id = T.id
and
P.id = B.project_id
order by B.id
};
my $sth = $dbh->prepare( $sql ) or die;
$sth->execute() or die;

while ( my $row = $sth->fetchrow_hashref ) {
my $ticket = bug_to_ticket( $row );
}
$sth->finish;

}

Import all the notes

IMPORT_NOTES: {
my $sql = q{
select *
from mantis_bugnote_table N, mantis_bugnote_text_table T
where N.id = T.id
order by bug_id
};
my $sth = $dbh->prepare( $sql ) or die;
$sth->execute() or die;

while ( my $row = $sth->fetchrow_hashref ) {
my $id = $row->{bug_id};
my $ticket = new RT::Ticket( $RT::SystemUser );
if ( $ticket->Load( $id ) ) {
    my $email = username_from_mantis_id( $row->{reporter_id} );
    add_comment( $ticket, $email, $row->{note}, $row->{date_submitted} );
    warn "Attached to bug $id\n";
} else {
    warn "***>>> Couldn't load $id";
}
}
$sth->finish;

}

sub bug_to_ticket {
my $bug = shift;

my $queue = get_queue( $bug->{project_name} );

my $ticket = new RT::Ticket( $RT::SystemUser );

my $status = status_convert( $bug->{status} );
my ($id,$error);

{ # Needed a more verbose dump if anything went wrong here.
local $SIG{WARN} = &confess;
($id,$error) =
$ticket->Import(
id => $bug->{id},
Subject => clean( $bug->{summary} ),
Queue => $queue->id,
Priority => $bug->{priority},
Status => $status,
);
}

warn $error unless $id;
warn "Created ticket #$id as $status\n" if $id;

my $email = username_from_mantis_id( $bug->{reporter_id} );
for my $field ( qw( description steps_to_reproduce additional_information ) ) {
my $text = $bug->{$field};
if ( $text ) {
    my $header = ucfirst $field;
    $header =~ s/_/ /g;
    add_comment( $ticket, $email, "$header:\n$text", $bug->{date_submitted} );
    warn "  Added $header\n";
}
}
warn " \n";

return $ticket;

}

sub add_comment {
my $ticket = shift;
my $email = shift;
my $text = shift;
my $date = shift;

$text = clean( $text );

# Squeeze the user info into the top of the text
my $mime = MIME::Entity->build( 
Data => "From: $email\nDate: $date\n$text" 
);
$ticket->Comment( MIMEObj => $mime );

}

sub username_from_mantis_id {
my $mantis_id = shift;

return $mantis_name_by_id{ $mantis_id } || "Unknown Mantis user #$mantis_id";

}

Undo all the goofy stuff that Mantis does with its text fields.

sub clean {
my $str = shift;

$str =~ s/&lt;/</g;
$str =~ s/&gt;/>/g;
$str =~ s/&quot;/"/g;
$str =~ s/&amp;/\&/g;

# The order of translation is important here.
$str =~ s/\\\\/\\/g;
$str =~ s/\\'/'/g;
$str =~ s/\\"/"/g;

return $str;

}

#$g_access_levels_enum_string = “10:viewer,25:reporter,40:updater,55:developer,70:manager,90:administrator”;
#$g_project_status_enum_string = “10:development,30:release,50:stable,70:obsolete”;
#$g_priority_enum_string = “10:none,20:low,30:normal,40:high,50:urgent,60:immediate”;
#$g_severity_enum_string = “10:feature,20:trivial,30:text,40:tweak,50:minor,60:major,70:crash,80:block”;
#$g_reproducibility_enum_string = “10:always,30:sometimes,50:random,70:have not tried,90:unable to duplicate,100:N/A”;
#$g_status_enum_string = “10:new,20:feedback,30:acknowledged,40:confirmed,50:assigned,80:resolved,90:closed”;
#$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:won’t fix”;

sub status_convert {
my $status = shift;

my %map = (
10 => 'new',
20 => 'open',
30 => 'open',
40 => 'open',
50 => 'open',
80 => 'resolved',
90 => 'resolved',
);

return $map{$status} or die "Unknown status \"$status\"";

}

‘Andy Lester andy@petdance.com
Programmer/author petdance.com
Daddy parsley.org Jk’=~/.+/s;print((split//,$&)
[unpack’C*‘,"n2]3%+>"34.’%&.‘^%4+!o.’"])