Migrating project management software from Redmine to GitLab

19 minute read

Migrating tickets, comments, roadmaps and wiki pages from Redmine to GitLab is a lot of work if you do it by hand. Fortunately, one can automate this process by using the Redmine to GitLab migrator.

Migrating Redmine to GitLab

At Drift+Noise we’ve decided to use GitLab in preference to Redmine. Although Redmine had served us very faithfully for several years, we found the Git integration as well as the merge request and code review functionality in GitLab to be sufficiently valuable to justify migrating between the systems.

Since we already had a lot of data stored in our Redmine instance, one necessary prerequisite of the migration was the availability of an automated migration tool which would copy historical data across to the new GitLab instance (I mean, who wants to copy by hand discussions from old tickets from one system to another?). Luckily such a tool exists: the Redmine to GitLab migrator1.

Below I’m going to describe how to set up the Redmine to GitLab migrator, how to use it to migrate Redmine roadmaps, issues and wikis across to GitLab milestones, issues and wikis, as well as some of the gotchas I noticed while performing the migration.

For this discussion I’m going to assume that the Redmine and GitLab instances are self-hosted. Theoretically the migration should work when migrating data from a Redmine instance to projects hosted on gitlab.com, however I haven’t tested that configuration.

Let’s go!

Initial setup

Since there are a couple of files that need to be used as configuration for the migration, it’s a good idea to create a directory for the migration you’re going to perform, e.g.:

$ mkdir redmine-to-gitlab-migration
$ cd redmine-to-gitlab-migration

Now that that’s set up, we can install redmine-gitlab-migrator.

Installing the Redmine to GitLab migrator

The redmine-gitlab-migrator project is written in Python, so it’s a good idea to create and activate a virtual environment2 first before running the installation:

$ virtualenv --python=/usr/bin/python3 venv
$ source venv/bin/activate

Now we can install the package with pip:

$ pip install redmine-gitlab-migrator

After the installation has completed successfully, the migrate-rg command will be available. To get quick help about this command, run it with the --help option:

$ migrate-rg --help
usage: migrate-rg [-h] {issues,pages,roadmap,redirect,iid,delete-issues} ...

positional arguments:
  {issues,pages,roadmap,redirect,iid,delete-issues}
    issues
    pages
    roadmap
    redirect
    iid                 Should occur after the issues migration
    delete-issues       Delete all issues in the gitlab repo

optional arguments:
  -h, --help            show this help message and exit

The subcommands also have their own help output, just pass the --help option to the appropriate subcommand, for example:

$ migrate-rg issues --help

Reading the docs considered useful

Note that reading the project’s README is a very good idea before starting with an actual migration. It contains lots of information about the process and what the automatic migrator can and cannot do. This is important, because some of the features of (and concepts in) Redmine aren’t available in GitLab, therefore it’s a good idea to be clear about whether or not this is the right tool for the job you’re trying to perform.

In order for the migration to work, it’s not necessary for the Redmine and GitLab instances to be running on the same host (in fact, it’s probably better if they aren’t). The data migration takes place between the Redmine and GitLab REST APIs (across the network), hence one only needs to create (or simply look up) the API key for a user with appropriate privileges on Redmine and on GitLab.

Setting up the Redmine API

Since the data is migrated across the REST APIs of the two services, you will need to ensure that the REST web service is activated. To activate this, go to the Administration link at the top of the page3; then choose the Settings link; and finally choose the API tab. Now ensure the Enable REST web service checkbox is checked.

Enable REST web service in Redmine

Now navigate to your user account by clicking on the My Account link in the top right-hand corner of the page. When you visit your My Account page you should find the heading API access key in the panel to the right. Click on Show to see the key:

View API access key in Redmine

Copy this information somewhere for safe keeping; you’ll need it when running the migrate-rg command.

Setting up the GitLab API

It’s also necessary to ensure that access to the API on GitLab is set up correctly and that your user has the correct privileges.

Note that you need be an “Owner” of the project4 you’re migrating to on GitLab, otherwise you’ll get the following error:

    raise ValueError('Could not get project_id for path_with_namespace: {}'.format(path_with_namespace))
ValueError: Could not get project_id for path_with_namespace: <group-name>/<project-name>

which admittedly isn’t very helpful in debugging the situation, nevertheless what the message is trying to say is that the user accessing the API endpoint doesn’t have enough privileges to access everything it needs to, hence the need for the user to have the role permission of “Owner” on the GitLab project.

To enable access to GitLab, it’s necessary to create an access token with the appropriate permission scopes. To do this, go to your avatar at the top right-hand corner of the page and select Preferences from the menu.

Open user preferences page in GitLab

Then choose Access Tokens from the left hand sidebar.

Choose Access Tokens link from left hand sidebar

You will now be on the page to add a personal access token. Give the access token a name (e.g. redmine-import; specify api and sudo for the scopes (you will need to access the GitLab API, hence api, and the access token will need administrative privileges in order to carry out its tasks, hence sudo) and then click on the Create personal access token button.

Create access token

The access token will now be displayed; copy and paste this somewhere for safe keeping since you won’t be allowed to view it again, but you will need it in order to run the migrate-rg command.

Create the user account map file

User accounts in Redmine need to be mapped to their equivalent accounts in GitLab so that user information in tickets, comments, etc. can be converted correctly. Therefore, the user accounts need to be created in GitLab before the migration takes place. For instance, if a user is called “mihipeka” on the Redmine system, however uses the username “mihi” on GitLab, the mapping file will allow the migrator to correctly assign tickets, comments, etc. from Redmine into the appropriate issues, comments etc. for each mapped user in GitLab. Note that any references to users found in Redmine but that are unknown in GitLab will be automatically assigned to the GitLab Admin User in GitLab by the Redmine-GitLab Migrator.

Be careful with, however, deactivated accounts: if your user exists on both Redmine and GitLab, however has been deactivated, you will get read errors when migrating issues, therefore don’t map deactivated users in the map file.

The map file is a yaml-formatted file with the keys being the Redmine usernames and the values being the respective GitLab usernames. E.g.:

alice: alice
bob: robert
clair: claire@example.com

Give this file a sensible name (something like redmine-gitlab-user-map.yml should do) because you’re going to be referencing this file in almost all migrate-rg command calls.

Danger Will Robinson! My GitLab is spamming me!

One point to realise: the projects that you’ve set up in GitLab will have notifications turned on. This means that for each new issue (or change to an issue) an email will be sent to each user associated with that issue. In normal operations that is probably something that you want. However, when migrating potentially hundreds of issues from Redmine to GitLab, this will create a lot of notifications, so it’s a very good idea to turn off email notifications from your GitLab server otherwise you will spam yourself and could run into the situation that the email address your GitLab server uses to send notifications will be blocked by your internet service provider. It is very unlikely that you want to get into this situation as you probably want to keep sending notification emails after having migrated from Redmine to GitLab :smiley:.

One can turn off notifications on a project-by-project basis and since one can only migrate one project at a time, this is the way to go. To do this, go to the main project page in GitLab and select Settings from the left-hand navigation bar. Then select General from the subsequent menu.

Select general project settings

On the general settings page for the project, scroll down to the Visibility, project features, permissions section and click on the Expand button to show all of the options.

Visibility, project features, and permissions section

Close to the bottom of this section you will find a checkbox Disable email notifications. Check this box and click on the Save changes button to save the changes.

Disable email notifications

Now when you migrate your issues you shouldn’t produce any unnecessary emails.

Of course, if you want to be really, really sure, you could turn off your mail relay, by e.g. turning off the postfix service5:

$ sudo service postfix stop

Note that after you’ve migrated everything you will need to turn the postfix service back on and will need to turn email notifications back on so that your project works as normal.

Migrate Redmine roadmap versions to GitLab milestones

Now we’re at the point that we can migrate data from Redmine to GitLab, and it’s now especially important that one has read the Redmine to GitLab project’s README; in particular the initial comment in the Migration process section:

This process is for each project, order matters.

If you use roadmaps in Redmine you can migrate the roadmap versions to GitLab milestones with the roadmap subcommand to the migrate-rg command. If you don’t use roadmaps, it’s safe to skip this step and move on to migrating issues.

The roadmap subcommand requires the Redmine API key that was set up in the Setting up the Redmine API section, the GitLab access token that was created in the Setting up the GitLab API section, the URL of the Redmine project to be migrated, as well as the URL of the GitLab project into which the data is to be migrated. The general form of the command looks like this:

$ migrate-rg roadmap \
    --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>' \
   https://url/of/redmine/project https://url/of/gitlab/project

and you can get more detailed information about the command by using the --help option:

$ migrate-rg roadmap --help
usage: migrate-rg roadmap [-h] --redmine-key REDMINE_KEY --gitlab-key
                          GITLAB_KEY [--check] [--debug] [--no-verify]
                          redmine_project_url gitlab_project_url

positional arguments:
  redmine_project_url
  gitlab_project_url

optional arguments:
  -h, --help            show this help message and exit
  --redmine-key REDMINE_KEY
                        Redmine administrator API key
  --gitlab-key GITLAB_KEY
                        Gitlab administrator API key
  --check               do not perform any action, just check everything is
                        ready
  --debug               More output
  --no-verify           disable SSL certificate verification

Before migrating the roadmap in earnest, it’s a good idea to first check that everything works nicely by using the --check option:

$ migrate-rg roadmap --check \
    --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>' \
   https://url/of/redmine/project https://url/of/gitlab/project

This doesn’t actually migrate any data; it just checks to make sure that everything will work as soon as you run the command for real. Running this command gives output similar to this:

INFO: Redmine project contains versions...
INFO: Redmine project contains versions... OK
INFO: Would create version {'title': 'Version 1.0: Tobermory', 'description': 'Initial public release\n\n*(from redmine: created on 2016-04-29)*'}
INFO: Would create version {'title': 'Version 1.1 Bungo', 'description': 'Remove rubbish from Commmon\n\n*(from redmine: created on 2016-03-30)

What’s nice about this output is that it tells you what it “would” do, which is nice as it helps one to decide whether or not to run the actual migration.

To actually perform the operation, remove the --check option. As an example, consider a Redmine project called main-website being migrated to the GitLab project called website, which is in a group called public-web. This would result in a migrate-rg command that looks like this:

$ migrate-rg roadmap \
    --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>' \
   https://redmine.server.example.com/projects/main-website \
   https://gitlab.server.example.com/public-web/website

The output isn’t as verbose as with the --check option, however contains much of the same information:

INFO: Redmine project contains versions...
INFO: Redmine project contains versions... OK
INFO: Initial public release
INFO: Remove rubbish from Common

If you visit the project page on the GitLab instance (for the example here, https://gitlab.server.example.com/public-web/website) then you should find that the migrated roadmap versions have been created on the Milestones page. To get to the Milestones page from the main project page, hover your mouse over on the Issues link in the sidebar on the left-hand side of the page. In the submenu that appears, you will see the option Milestones; click on this link to see the newly-created project milestones.

Migrate Redmine tickets to GitLab issues

Migrating issues is much the same as migrating roadmaps; the main change is that the user account mapping file is used to ensure that issues (and the comments in discussions within issues) are assigned to the appropriate user.

As with the other commands, there’s a --help option which gives more details; this time with many more options:

$ migrate-rg issues --help
usage: migrate-rg issues [-h] --redmine-key REDMINE_KEY --gitlab-key
                         GITLAB_KEY [--check] [--debug] [--no-verify]
                         [--closed-states CLOSED_STATES]
                         [--custom-fields CUSTOM_FIELDS]
                         [--user-dict USER_DICT] [--project-members-only]
                         [--keep-id] [--keep-title] [--initial-id INITIAL_ID]
                         [--no-sudo] [--archive-account ARCHIVE_ACC]
                         redmine_project_url gitlab_project_url

positional arguments:
  redmine_project_url
  gitlab_project_url

optional arguments:
  -h, --help            show this help message and exit
  --redmine-key REDMINE_KEY
                        Redmine administrator API key
  --gitlab-key GITLAB_KEY
                        Gitlab administrator API key
  --check               do not perform any action, just check everything is
                        ready
  --debug               More output
  --no-verify           disable SSL certificate verification
  --closed-states CLOSED_STATES
                        comma seperated list of redmine states that close an
                        issue, default closed,rejected
  --custom-fields CUSTOM_FIELDS
                        comma seperated list of redmine custom filds to
                        migrate
  --user-dict USER_DICT
                        file path with redmine user mapping to gitlab user, in
                        YAML format
  --project-members-only
                        get project members instead of all users, useful for
                        gitlab.com
  --keep-id             create and delete empty issues for gaps, useful when
                        no ssh is possible (e.g. gitlab.com)
  --keep-title          migrate issues with same title, useful when no ssh is
                        possible (e.g. gitlab.com) and don't need to keep id
                        (faster)
  --initial-id INITIAL_ID
                        Initial issue ID, to skip some issues
  --no-sudo             do not use sudo, use if user is not admin (e.g.
                        gitlab.com)
  --archive-account ARCHIVE_ACC
                        if account doesn't exists in GitLab use this account
                        as default

It’s a very good idea to run the issues command with the --check option first to ensure that API authentication etc. works correctly.

$ migrate-rg issues --check --keep-title --user-dict redmine-gitlab-user-map.yml \
   --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>'
   https://url/of/redmine/project https://url/of/gitlab/project

Note that I’ve also added the --keep-title option here. If one omits this option, then all of the GitLab issues will have a text like -RM-1-MR- prepended to the title. Personally, I found this extra information in the issue title not to be that helpful, hence the use of the --keep-title option which ensures that the original title from Redmine is migrated to GitLab.

if you’ve got a lot of issues, this command can take a while to get started and can take a while to run once output is available. Once output does appear, you’ll see something along these lines:

INFO: Getting redmine issues
INFO: Converting issues
INFO: Creating gitlab issues
INFO: Would create issue "Go for a spin on a windy day" and 12 notes.
INFO: Would create issue "Repair Great Uncle Bulgaria's rocking chair" and 2 notes.
INFO: Would create issue "Invent telephone with cans and string" and 9 notes.

showing you what the system would do, had the --check option not been set. Now it’s time to go crazy and run the command for real:

$ migrate-rg issues --keep-title --user-dict redmine-gitlab-user-map.yml \
   --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>'
   https://url/of/redmine/project https://url/of/gitlab/project

The output now mentions the issue number of the issue it is creating, however no longer mentions how many notes were created as part of the issue.

INFO: Getting redmine issues
INFO: Converting issues
INFO: Creating gitlab issues
INFO: #10 Go for a spin on a windy day
INFO: #11 Repair Great Uncle Bulgaria's rocking chair
INFO: #12 Invent telephone with cans and string

What if something goes wrong when migrating issues?

Not everything in life goes to plan and it’s entirely possible that something goes awry when migrating issues, especially when migrating a lot of issues. What should one do?

Simply running the issues subcommand again is not a good idea as this will create duplicate issues on the destination GitLab system. What’s probably best is to delete the issues you created, work out what was going wrong at the point where things stopped working and then to repeat the issues subcommand, but this time with the --debug flag to get more information about what went wrong.

Let’s imagine that something went wrong when migrating issues and only half of the issues were migrated before the command returned with an error. We want to delete the issues that were migrated so that we can start again from a clean state. To do this, use the delete-issues subcommand:

$ migrate-rg delete-issues --gitlab-key '<gitlab-api-key>' \
    https://url/of/gitlab/project

Note that we only have to specify the GitLab project URL and the relevant GitLab API key as this operation only interacts with the GitLab server.

An oddity of the delete-issues subcommand is that it won’t produce any output (in contrast to the other subcommands), so don’t be surprised to just see your command prompt simply reappear after having run the command. You can, however, force it to show what it’s doing in much detail by passing the --debug option.

Now run migrate-rg issues with the --debug flag to get all of the available debugging information:

$ migrate-rg issues --debug --keep-title --user-dict redmine-gitlab-user-map.yml \
   --redmine-key '<redmine-api-key>' --gitlab-key '<gitlab-api-key>'
   https://url/of/redmine/project https://url/of/gitlab/project

This will produce a lot of output due to the --debug flag, however this is a good thing if you’re trying to work out why migration of issues didn’t work as expected.

Debugging migration failures are highly context dependent, so unfortunately I can’t give any guidance on how to proceed exactly. Sorry about that!

Migrate wiki pages

The last thing to do is to migrate any wiki pages that you might have in your Redmine project across to the equivalent space on GitLab.

First it’s necessary to create the wiki in GitLab. This will create a new Git repository, usually with a name of this form:

<group-name>/<project-name>.wiki.git

To find the Git repository associated with your wiki, click on the “Wiki” link in the left-hand sidebar on your project’s page in GitLab.

Wiki link from project page navbar

In the upper-right hand corner of the page there is a link called “Clone repository” (it’s not immediately obvious, so don’t worry if you don’t find it straight away).

Clone wiki repository link

Now you will be presented with details of how to clone the wiki’s git repository.

Clone wiki repository information

The only information that is really needed here is the URL to clone which is shown both at the top right-hand side of the page as well as within the “Clone your wiki” section of the main text. I’ve never needed to use the gollum program to edit my wiki pages, however your mileage may vary. If you want to edit files locally and would like to see how they will look when viewed on GitLab, using gollum might be a good idea.

We now just need to clone the wiki’s repository, which we can do with a command of this form:

$ git clone git@<gitlab-server>:<group-name>/<project-name>.wiki.git

For the example website project here, this would be:

git clone git@gitlab.example.com:public-web/website.wiki.git

Note that if your project name contains underscores, then these will be converted to hyphens when the wiki Git repository is created.

In contrast to other migrate-rg subcommands, migrating wiki pages doesn’t happen directly from Redmine to GitLab (for instance as in the case of migrating roadmaps or issues). The migration can only occur from a remote Redmine system to a local Git repository, hence the need to clone the wiki’s repository to your local system.

Note also that (unlike the other subcommands) the --check option doesn’t seem to work with the pages subcommand (which migrates wiki pages) and hence all changes will be fetched and added to the local repository even if you use the flag on the command line.

To migrate the wiki pages, use the pages subcommand of migrate-rg, which has this form:

$ migrate-rg pages --redmine-key '<redmine-api-key>' \
    --gitlab-wiki /path/to/cloned/gitlab/wiki/ https://url/of/redmine/project

Running this command on our example website project we have:

$ migrate-rg pages --redmine-key '<redmine-api-key>' \
    --gitlab-wiki /path/to/website.wiki/ \
    https://redmine.server.example.com/projects/main-website/

which gives this output:

Collecting Wimbledon_Common
Collecting Wombles_and_Ladders
Collecting Womblegarten
Converting home (Wimbledon_Common version 1)
Converting home (Wimbledon_Common version 2)
Converting home (Wimbledon_Common version 3)
Converting Wombles_and_Ladders (Wombles_and_Ladders version 1)
Converting Wombles_and_Ladders (Wombles_and_Ladders version 2)
Converting Womblegarten (Womblegarten version 1)
Converting Womblegarten (Womblegarten version 2)

Once the migration is complete (files will be created locally for the wiki pages and changes to those files will be made as commits to the repository) you can simply push your changes to the upstream repository for the wiki which will then update the wiki automatically in your GitLab instance, for example:

$ cd /path/to/website.wiki/
$ git push

Be careful though: the automated migration will overwrite any wiki pages previously created in your GitLab project (e.g. those created via the editor in the GitLab web interface) which have the same name as those being migrated. Most likely, however, only the home.md page will be affected. Hence you’ll need to put back any information that used to be on this page after the migration is complete.

One other issue is that the migration program assumes that the Redmine wiki pages are in Textile format (even if they were already written as Markdown in Redmine). Since it is possible to use Markdown in Redmine wikis and because all of the Markdown will be assumed to be Textile, all Markdown formatting will be escaped, meaning that one needs to fix the formatting of the GitLab wiki pages once the automated migration has taken place.

For instance, a Markdown-formatted heading from Redmine like this:

### Great Uncle Bulgaria's Keep Fit Lesson

will be converted to:

\#\#\# Great Uncle Bulgaria's Keep Fit Lesson

so you’ll have some editing to do (mostly just removing the backslashes) in order to get the formatting back to how it was originally.

A further issue to be aware of when migrating wiki pages from Redmine to GitLab is that any images that had been uploaded to the Redmine wiki won’t be migrated automatically, therefore this needs to be done by hand after the automated migration has been completed.

Wrapping up

And that’s basically it. Now it’s just a matter of repeating the above steps for each project that you want to migrate from Redmine to GitLab.

I hope that the information presented here was helpful! If you have any comments, questions or feedback, feel free to ping me on Twitter or drop me a line via email.


Support

If you liked this post and want to spur me on to write up more of my adventures in IT-land, please buy me a coffee or support me on Patreon!

buy me a coffee logo Become a patron
  1. I used version 1.0.3 when writing this post. 

  2. A Python virtual environment allows you to install and use packages in a local, encapsulated environment without requiring the packages to be installed system-wide. This way the package version requirements of separate projects do not conflict with one another. 

  3. Only single projects are migrated with the migrate-rg command, hence we consider migrations on a per-project basis. 

  4. Assuming, of course, that you use Postfix for your mail relay. 

Categories:

Updated: