DevSecOps & Platform

Comprehensive Migration from Redmine to GitLab

How EasyLauncher engineered an automated data migration platform to transfer 10+ years of legacy project tracking, 25,000+ tickets, attachments, and complex wikis from Redmine into a unified enterprise GitLab environment.

6 min read
Comprehensive Migration from Redmine to GitLab

Executive Summary

Legacy project tracking systems often suffer from organizational inertia. For a leading enterprise engineering organization, Redmine had served as the central ticket repository, defect tracker, and documentation wiki for over a decade. However, maintaining Redmine alongside modern git version control systems created massive friction.

Developers worked in two disconnected worlds: writing and reviewing code in modern git workflows while manually updating status notes, pasting URLs, and referencing defect IDs in an aging Ruby on Rails Redmine application. Crucial context was routinely lost in context switching, plugin breakdowns, and broken database attachments.

“When source code, CI/CD pipelines, and issue trackers are separated by different tools, developers spend more time syncing status than shipping software.”

EasyLauncher DevOps Engineering Practice

EasyLauncher was commissioned to execute an automated, high-fidelity migration from Redmine to GitLab Enterprise. The project unified source code, automated pipelines, issues, milestones, and documentation into a single enterprise pane of glass while maintaining 100% fidelity across 25,000+ historical issues, comment threads, attachments, and user attributions.

The Legacy Redmine Fragmentation Challenge

Prior to engineering automated data extractors, EasyLauncher audited the Redmine database schema and operational dependencies:

Disjointed Workflows

No Native Code Integration

Engineers spent hours manually cross-referencing commit hashes and pull requests with Redmine issue IDs, causing compliance gaps and missed bug tracking.

Attachment Storage Risks

60+ GB of Orphaned Assets

Historical screenshots, architecture specs, and log dumps were scattered across unindexed server directories with inconsistent filename encoding and broken database pointers.

Markup Incompatibility

Textile Markup & Raw HTML

Over 1,400 wiki pages used legacy Textile syntax and nested HTML tags that rendered as broken, unreadable code when pasted directly into modern markdown systems.

Identity & Account Drift

Inactive & Renamed Users

Over a decade, hundreds of developers had departed, changed email addresses, or used varied usernames across different Redmine project boards.

Automated Relational Mapping Engine

To prevent data truncation or lost relationships, EasyLauncher developed a custom Python-based migration pipeline utilizing the Redmine REST API and direct MySQL read replicas:

  • Issue Hierarchy & Epics: Extracted hierarchical parent-child ticket trees and mapped them cleanly to GitLab Epics, Issues, and Sub-tasks with preserved nesting.
  • Comments & Audit Logs: Transferred full journal histories, including author name, original timestamp, status changes, and assigned field transitions into formatted GitLab discussion threads.
  • Custom Field Translation: Converted 40+ project-specific custom fields into scoped GitLab labels (e.g., type::bug, priority::high), ensuring searchability across filtered issue boards.

User Identity Normalization & SSO Mapping

A primary risk in issue migrations is attributing historical tickets to a single generic administrative user. EasyLauncher engineered an automated user reconciliation pipeline:

Three-Tier Identity Resolution Strategy

  1. Exact SAML / Active Directory Match: Active employees were matched via enterprise corporate email addresses and mapped directly to authenticated GitLab accounts.
  2. Historical Alias Resolution: Mapped legacy usernames, secondary email addresses, and name formatting variations using a deterministic JSON reconciliation dictionary.
  3. Archival Attribution Footnote: For departed employees without GitLab accounts, tickets were authored with a clear, audited provenance header: “Originally authored by John Doe (jdoe_legacy) on October 14, 2018”, preserving regulatory audit compliance.

Wiki HTML to GitLab Markdown Transpilation

Over 1,400 engineering wikis, architectural runbooks, and onboarding guides existed inside Redmine formatted in complex Textile markup. We deployed an automated compiler:

  • Parsed Textile table syntax (|_. Header 1 |_. Header 2 |) into clean GitHub/GitLab Flavored Markdown tables.
  • Converted internal Redmine page links ([[Project Architecture]]) into relative GitLab wiki links ([Project Architecture](project-architecture)).
  • Extracted all inline screenshots, uploaded them into GitLab git-backed wiki storage, and updated image paths automatically.

Migration Tooling Comparison

Why the EasyLauncher custom migration suite delivered superior data fidelity compared to open-source scripts:

Feature DimensionStandard CSV ExportGeneric Community ScriptsEasyLauncher Migration Suite
Creation Date PreservationOverwritten with import dateOverwritten with import date100% exact historical timestamps
Comment Author AttributionSingle admin userPartial username tagsSudo impersonation + SAML mapping
File AttachmentsExcluded completelyFrequent HTTP timeoutsSHA-256 validated direct upload
Wiki & Markup FormattingNot supportedRaw broken Textile stringsFull GFM Markdown transpilation

Relational Extraction Blueprint

Representative extraction snippet demonstrating timestamp preservation and administrative impersonation:

migrate_issue.py (GitLab Impersonation API)Python
import requests

def migrate_redmine_issue(issue_data, gitlab_project_id, admin_token):
    url = f"https://gitlab.enterprise.internal/api/v4/projects/{gitlab_project_id}/issues"
    
    headers = {
        "Private-Token": admin_token,
        # Sudo impersonation preserves authentic author attribution
        "Sudo": issue_data["author_gitlab_username"]
    }
    
    payload = {
        "title": issue_data["subject"],
        "description": issue_data["converted_markdown_body"],
        "created_at": issue_data["created_on"],  # Preserves historical ISO 8601 timestamp
        "labels": issue_data["scoped_labels"],
        "milestone_id": issue_data["matched_milestone_id"],
        "weight": issue_data["story_points"]
    }
    
    response = requests.post(url, headers=headers, json=payload)
    response.raise_for_status()
    return response.json()

Verified Outcomes & Productivity ROI

Consolidating into GitLab Enterprise delivered sweeping productivity improvements across all engineering pods:

25,000+
Issues Successfully Transferred

Every defect, feature request, and customer issue transferred with full comment chains and attachments.

4.8x
Faster Issue-to-PR Velocity

Native branch creation directly from tickets and automatic issue closing via commit keywords eliminated administrative tracking overhead.

0 Lost Files
100% Asset Parity

Over 60 GB of design specs, PDFs, and diagnostic logs preserved and relinked seamlessly into GitLab storage.

Single Pane
Unified Toolchain

Decommissioned 4 legacy Ruby application servers and eliminated recurring Redmine hosting, backup, and plugin licensing costs.

Frequently Asked Questions

Common questions regarding legacy issue tracking migrations and GitLab consolidation:

How do you preserve original creation dates and author attributions when migrating into GitLab?

Standard API creation calls overwrite the author and creation timestamp with the API token owner and current time. We utilized GitLab sudo and administrative impersonation tokens along with direct created_at overrides via the GitLab Issues API to preserve original author usernames, creation dates, and full historical comment timelines.

How are complex Redmine textile formatting and raw HTML tables converted into GitLab markdown?

We built an automated syntax converter using regular expressions and an AST parser. It translates Redmine Textile syntax (such as h1., *bold*, @code@, and inline tables) into standard GitLab Flavored Markdown (GFM), including syntax-highlighted code fences and native markdown tables.

What happens to large file attachments, design PDFs, and screenshots stored in Redmine?

Attachments were extracted from Redmine disk storage, hashed using SHA-256 for integrity verification, and uploaded via GitLab project upload endpoints. Every issue description and comment containing an inline image reference was rewritten to link to the new authenticated GitLab upload URL.

How are Redmine subtasks, related issues, and blockers mapped into GitLab?

Redmine parent-child relationships and issue relations (blocks, precedes, relates_to) were extracted into a relational graph. We mapped parent-child tickets into GitLab Epics and Tasks, and translated issue dependencies into GitLab Linked Issues ('is blocked by', 'relates to').

Can custom fields and status workflows from Redmine be preserved in GitLab?

Yes. Redmine custom fields (such as 'Root Cause', 'Customer Severity', or 'Billable') were mapped into scoped GitLab labels (e.g., 'severity::p1', 'category::backend') and GitLab Issue Custom Fields, ensuring searchability and reporting continuity without data truncation.

DevSecOps & Platform Architecture

Consolidating Legacy Toolchains into Modern DevOps?

EasyLauncher helps technology organizations unify fragmented project trackers, git repositories, and CI/CD pipelines into modern, high-velocity developer platforms.