Migrating from Trac to Redmine

In the past few days, I needed to migrate from a 6 years old server with CentOS 5 (it’s end of life is this month) to a shiny new one with Ubuntu 16.04. One of the services that was running in the old server was Trac 0.11 and I had several issues to move it to version 1.2. I like Trac and I’ve used it without any issues until now. After a while I decided to try Redmine 3.3.2, but I was not sure about the migration process.

After installing a large amount of requirements (a lot of Ruby packages), I followed this migration procedure that seemed straight-forward and should work with Trac 0.11, but it failed almost immediately for a very simple issue: The timestamps in all the database tables in Trac were in microseconds, whilst the script was expecting them in seconds.

I wrote this simple Perl script that changes the timestamps in all the Trac database tables:

#!/usr/bin/perl

use strict;
use DBI;
use POSIX;

my $dbh = DBI->connect(
    "dbi:SQLite:dbname=trac.db",
    "",
    "",
    { RaiseError => 1 },
) or die $DBI::errstr;

# attachment
print "Processing 'attachment' table ";
my $sth = $dbh->prepare("SELECT COUNT(*) FROM attachment");
$sth->execute() or die $DBI::errstr;
my $rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT type, id, filename, time FROM attachment");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($type, $id, $filename, $time) = $sth->fetchrow();

    $time = ceil($time / 1000000);
    my $sth2 = $dbh->prepare("UPDATE attachment SET time=$time WHERE type='$type' AND id='$id' AND filename='$filename'");
    $sth2->execute();
    $sth2->finish();

    print ".";
    $rows--;
}

$sth->finish();
print "\nDone\n\n";

# revision
print "Processing 'revision' table ";
$sth = $dbh->prepare("SELECT COUNT(*) FROM revision");
$sth->execute() or die $DBI::errstr;
$rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT repos, rev, time FROM revision");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($repos, $rev, $time) = $sth->fetchrow();

    $time = ceil($time / 1000000);
    my $sth2 = $dbh->prepare("UPDATE revision SET time=$time WHERE repos='$repos' AND rev='$rev'");
    $sth2->execute();
    $sth2->finish();

    print ".";
    $rows--;
}

$sth->finish();
print "\nDone\n\n";


# ticket
printf "Processing 'ticket' table";
$sth = $dbh->prepare("SELECT COUNT(*) FROM ticket");
$sth->execute() or die $DBI::errstr;
$rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT id, time, changetime FROM ticket");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($id, $time, $changetime) = $sth->fetchrow();

    $time = ceil($time / 1000000);
    $changetime = ceil($changetime / 1000000);
    my $sth2 = $dbh->prepare("UPDATE ticket SET time=$time, changetime=$changetime WHERE id='$id'");
    $sth2->execute();
    $sth2->finish();

    printf ".";
    $rows--;
}

$sth->finish();
printf "\nDone\n\n";

# ticket_change
print "Processing 'ticket_change' table ";
$sth = $dbh->prepare("SELECT COUNT(*) FROM ticket_change");
$sth->execute() or die $DBI::errstr;
$rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT ticket, time, author, field, oldvalue, newvalue FROM ticket_change");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($ticket, $time, $author, $field, $oldvalue, $newvalue) = $sth->fetchrow();

    #my $sth2 = $dbh->prepare("DELETE FROM ticket_change WHERE ticket='$ticket' AND time='$time' AND field='$field'");
    #$sth2->execute();

    my $prev_time = $time;
    $time = ceil($time / 1000000);
    #my $query = "INSERT INTO ticket_change (ticket, time, author, field, oldvalue, newvalue) VALUES ('$ticket', '$time', '$author', '$field', '".quotemeta($oldvalue)."', '".quotemeta($newvalue)."'";
    #printf("query: $query\n");
    #my $sth2 = $dbh->prepare($query);
    my $sth2 = $dbh->prepare("UPDATE ticket_change SET time='$time' WHERE ticket='$ticket' AND time='$prev_time' AND field='$field'");
    $sth2->execute();
    $sth2->finish();

    print ".";
    $rows--;
}

$sth->finish();
print "\nDone\n\n";

# version
print "Processing 'version' table ";
$sth = $dbh->prepare("SELECT COUNT(*) FROM version");
$sth->execute() or die $DBI::errstr;
$rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT name, time FROM version");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($name, $time) = $sth->fetchrow();

    $time = ceil($time / 1000000);
    my $sth2 = $dbh->prepare("UPDATE version SET time=$time WHERE name='$name'");
    $sth2->execute();
    $sth2->finish();

    print ".";
    $rows--;
}

$sth->finish();
print "\nDone\n\n";

# wiki
print "Processing 'wiki' table ";
$sth = $dbh->prepare("SELECT COUNT(*) FROM wiki");
$sth->execute() or die $DBI::errstr;
$rows = $sth->fetchrow();
$sth->finish();

$sth = $dbh->prepare("SELECT name, version, time FROM wiki");
$sth->execute() or die $DBI::errstr;

while ($rows) {
    my ($name, $version, $time) = $sth->fetchrow();

    $time = ceil($time / 1000000);
    my $sth2 = $dbh->prepare("UPDATE wiki SET time=$time WHERE name='$name' AND version='$version'");
    $sth2->execute();
    $sth2->finish();

    print ".";
    $rows--;
}

$sth->finish();
print "\nDone\n\n";

$dbh->disconnect();

Assuming that you have installed all the requirements (including the Perl ones with CPAN) and that the SQLite3 Trac database is called trac.db, execute the above script followed by the Redmine migration script:

# Make a copy of the trac database just in case...
cp trac.db trac.db.back

# Execute the Perl script that fixes the timestamps
perl trac2redmine.pl 

# Execute the Ruby migration script. Adjust the target environment, normally it is 'production'
rake redmine:migrate_from_trac RAILS_ENV="production"

The Ruby (rake) script asks for your Trac settings:

Trac directory []: /var/trac/myproject
Trac database adapter (sqlite, sqlite3, mysql, postgresql) [sqlite]:
Database encoding [UTF-8]:
Target project identifier []: myproject

and outputs all the items that were migrated without any errors.

I checked immediately in Redmine if everything was migrated correctly and I noticed a couple of issues:

  • The attachments in the wiki and tickets are not migrated
  • The wiki links are corrupted. The whole wiki was migrated (all the pages and their content is correct), but the links got corrupted.

The attachments were not a big issue since I had a few of them.
The wiki links was a bit annoying, I went to the Index by title, check the new name and edit the pages that link the correspondent page. My wiki is large, but it’s not heavily linked, so it didn’t take too much time to fix it.

Now that I’ve been using Redmine for a week I feel quite comfortable with it and I think that its integration with Git is better than Trac’s.

Cross-building LibGTop

LibGTop is a library used to get system specific data such as CPU, memory usage and info about running processes.
Since it’s part of the GNOME desktop environment, it’s used by some system monitor applets, but it’s main interface is completely independent, so it can be used as a standalone library even for embedded system that already use GLib.

Normally, you should debug your system before deployment and be sure that there are no processes with memory leaks or that are consuming more CPU than they should, but sometimes there could be strange conditions in a process that at certain point start to eat too much CPU or just Valgrind doesn’t support your processor.

In these cases, the system needs to be closely monitored and a library like LibGTop with an API that you can easily use could be quite handy.

I normally use Buildroot, but it doesn’t include LibGTop, it seems that Yocto doesn’t have a recipe neither. I’ll try to explain briefly how to cross-build it for an ARM processor that I’m using with the Linaro hard-float toolchain.

Download:
libgtop-2.34.1.tar.xz

First, let’s export common environment variables that make our life easier:

export HOST=arm-linux
export BUILD=i386-linux
export PREFIX=/home/paguilar/rootfs

Configure remove unneeded things for an embedded system:

CC=arm-linux-gnueabihf-gcc ./configure --host=$HOST --build=$BUILD --prefix=$PREFIX --with-sysroot=$PREFIX --disable-gtk-doc-html --without-examples --without-libiconv-prefix --without-libintl-prefix --without-x 

Edit lib/Makefile and set the following variables:

GLIB_CFLAGS = -I/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/sysroot/usr/include 
GLIB_LIBS = -L/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabihf/sysroot/usr/lib -lgobject-2.0 -lglib-2.0 

Compile and install:

make
make DESTDIR=/home/paguilar/rootfs install

You can have a look at the GLibTop API here.
That’s it.

Skipping incompatible /lib/libc.so.6 when searching for /lib/libc.so.6

When cross-building a program (but this could also happen when building natively), it’s normal that we don’t have the environment variables and building flags set with the right paths that indicate where the toolchain and the sysroot are.

The wrong paths can lead to many types of building errors at different stages. This one happened to me recently:

arm-linux-gnueabi-gcc -L/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabi/qt5/arm-buildroot-linux-gnueabi/sysroot/lib -L/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabi/qt5/arm-buildroot-linux-gnueabi/sysroot/lib32 -L/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabi/qt5/arm-buildroot-linux-gnueabi/sysroot/usr/lib -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lz myfile.o -o myfile
/opt/gcc-linaro-4.9-2014.11-x86_64_arm-linux-gnueabi/bin/../lib/gcc/arm-linux-gnueabi/4.9.3/../../../../arm-linux-gnueabi/bin/ld: skipping incompatible /lib/libc.so.6 when searching for /lib/libc.so.6
collect2: error: ld returned 1 exit status

This linking error says that it is skipping an incompatible library that is searching in my host’s filesystem even though I explicitly indicated in the Makefile the paths to use in the environment variable LDFLAGS with the -L:

LDFLAGS = -L$(SYSROOT)/lib -L$(SYSROOT)/lib32 -L$(SYSROOT)/usr/lib -pthread -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lz 

The $SYSROOT environment variable was set correctly

To indicate the linker to use the right path you have to add the -Xlinker -rpath-link=/your/sysroot/path directives.

For adding the /lib and /usr/lib path that are in my sysroot, the previous setting of LDFLAGS becomes:

LDFLAGS = -L$(SYSROOT)/lib -L$(SYSROOT)/lib32 -L$(SYSROOT)/usr/lib -Xlinker -rpath-link=$(SYSROOT)/lib -Xlinker -rpath-link=$(SYSROOT)/usr/lib -pthread -lgobject-2.0 -lgmodule-2.0 -lgthread-2.0 -lglib-2.0 -lz 

Now the linker does not complain anymore.