From 7ae53f0f05be930db917f66311be8c9f0092cdf8 Mon Sep 17 00:00:00 2001
From: Jan-Hendrik Willms <tleilax+github@gmail.com>
Date: Sun, 15 Jul 2018 00:28:20 +0200
Subject: [PATCH] =?UTF-8?q?ticketzuweisung=20ist=20egal,=20hobeln=20und=20?=
 =?UTF-8?q?sp=C3=A4ne?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

---
 convert.php                         |  2 +
 includes/functions.php              | 80 +++++++++++++++++++++++++++++
 src/GitLab.php                      |  6 +--
 src/Migration.php                   |  2 +-
 src/Trac.php                        |  4 +-
 steps/convert-tickets-to-issues.php | 73 ++++++++++++++++++++++++--
 6 files changed, 158 insertions(+), 9 deletions(-)

diff --git a/convert.php b/convert.php
index cd14f5f..cd3ffda 100755
--- a/convert.php
+++ b/convert.php
@@ -4,6 +4,8 @@ require_once __DIR__ . '/vendor/autoload.php';
 require_once __DIR__ . '/includes/functions.php';
 
 $config = require __DIR__ . '/includes/config.php';
+$config['trac-clean-url'] = preg_replace('/(?<=:\/\/).*?:.*?@|\/login/', '${1}', $config['trac-url']);
+
 $steps  = [
     'issues'   => __DIR__ . '/steps/convert-tickets-to-issues.php',
     'commits'  => __DIR__ . '/steps/clone-svn-repository.php',
diff --git a/includes/functions.php b/includes/functions.php
index 7494944..308fe7a 100644
--- a/includes/functions.php
+++ b/includes/functions.php
@@ -38,3 +38,83 @@ function removeDirectory($directory)
     }
     rmdir($directory);
 }
+
+
+function translateTracToMarkdown($text, $trac_url) {
+    $text = str_replace("\r\n", "\n", $text);
+    // Inline code block
+    $text = preg_replace('/{{{(.*?)}}}/', '`$1`', $text);
+    // Multiline code block (optionally with language description)
+    $text = preg_replace("/{{{\n(?:#!(.+?)\n)?(.*?)\n}}}/s", "```\$1\n\$2\n```", $text);
+
+    // Headers
+    $text = preg_replace('/(?m)^======\s+(.*?)(\s+======)?$/', '###### $1', $text);
+    $text = preg_replace('/(?m)^=====\s+(.*?)(\s+=====)?$/', '##### $1', $text);
+    $text = preg_replace('/(?m)^====\s+(.*?)(\s+====)?$/', '#### $1', $text);
+    $text = preg_replace('/(?m)^===\s+(.*?)(\s+===)?$/', '### $1', $text);
+    $text = preg_replace('/(?m)^==\s+(.*?)(\s+==)?$/', '## $1', $text);
+    $text = preg_replace('/(?m)^=\s+(.*?)(\s+=)?$/', '# $1', $text);
+    // Bullet points
+    $text = preg_replace('/^             \* /', '****', $text);
+    $text = preg_replace('/^         \* /', '***', $text);
+    $text = preg_replace('/^     \* /', '**', $text);
+    $text = preg_replace('/^ \* /', '*', $text);
+    $text = preg_replace('/^ \d+\. /', '1.', $text);
+    // Make sure that horizontal rules have a line before them
+    $text = preg_replace("/(?m)^-{4,}$/", "\n----", $text);
+
+    $lines = array();
+    $isTable = false;
+    $isCode  = false;
+    foreach (explode("\n", $text) as $line) {
+        if (strpos($line, '```') === 0) {
+            $isCode = !$isCode;
+        }
+
+        // Don't mess with code
+        if (!$isCode) {
+            // External links
+            $line = preg_replace('/\[(https?:\/\/[^\s\[\]]+)\s([^\[\]]+)\]/', '[$2]($1)', $line);
+            // Plain images (not linking to something specific)
+            $line = preg_replace('/\[\[Image\((?!wiki|ticket|htdocs|source)(.+?)\)\]\]/', '![image]($1)', $line);
+            // Remove the unnecessary exclamation mark in !WikiLinkBreaker
+            $line = preg_replace('/\!(([A-Z][a-z0-9]+){2,})/', '$1', $line);
+            // '''bold'''
+            $line = preg_replace("/'''([^']*?)'''/", '**$1**', $line);
+            // ''italic''
+            $line = preg_replace("/''(.*?)''/", '_$1_', $line);
+            // //italic//
+            $line = preg_replace("/\/\/(.*?)\/\//", '_$1_', $line);
+            // #Ticket links
+            $line = preg_replace('/#(\d+)/', '[#$1](' . $trac_url . '/ticket/$1)', $line);
+            $line = preg_replace('/ticket:(\d+)/', '[ticket:$1](' . $trac_url . '/ticket/$1)', $line);
+            // [changeset] links
+            $line = preg_replace('/\[(\d+)\]/', '[[$1]](' . $trac_url . '/changeset/$1)', $line);
+            $line = preg_replace('/changeset:(\d+)/', '[changeset:$1](' . $trac_url . '/changeset/$1)', $line);
+            $line = preg_replace('/r(\d+)/', '[r$1](' . $trac_url . '/changeset/$1)', $line);
+            // {report} links
+            $line = preg_replace('/{(\d+)}/', '[{$1}](' . $trac_url . '/report/$1)', $line);
+            $line = preg_replace('/report:(\d+)/', '[report:$1](' . $trac_url . '/report/$1)', $line);
+
+            if (strpos($line, '||') !== 0) {
+                $isTable = false;
+            } else {
+                // Makes sure both that there's a new line before the table and that a table header is generated
+                if (!$isTable) {
+                    $sep = preg_replace('/[^|]/', '-', $line);
+                    $line = "\n$line\n$sep";
+                    $isTable = true;
+                }
+                // Makes sure that there's a space after the cell separator, since |cell| works in WikiFormatting but not in GFM
+                $line = preg_replace('/\|\|/', '| ', $line);
+                // Make trac headers bold
+                $line = preg_replace('/= (.+?) =/', '**$1**', $line);
+            }
+        }
+
+        $lines[] = $line;
+    }
+    $text = implode("\n", $lines);
+
+    return $text;
+}
diff --git a/src/GitLab.php b/src/GitLab.php
index c09ae53..143a954 100644
--- a/src/GitLab.php
+++ b/src/GitLab.php
@@ -96,13 +96,13 @@ class GitLab
      */
 	public function closeIssue($projectId, $issueId, $time = '', $author = '') {
 		$this->client->api('issues')->update($projectId, $issueId,
-			[
+			array_filter([
 				'state_event' => 'close',
 				'updated_at' => $time,
 				'closed_at' => $time,
 				'closed_by_id' => $author,
 				'closed_by' => $author
-			]);
+			]));
 	}
 
 	/**
@@ -161,7 +161,7 @@ class GitLab
 
         } catch (\Gitlab\Exception\RuntimeException $e) {
             // If adding has failed because of SUDO (author does not have access to the project), create an issue without SUDO (as the Admin user whose token is configured)
-            if ($this->isAdmin) {
+            if ($this->isAdmin && isset($data)) {
                 $note = $this->doCreateNote($projectId, $issueId, $data['markdown'], $authorId, false);
             } else {
                 // If adding has failed for some other reason, propagate the exception back
diff --git a/src/Migration.php b/src/Migration.php
index 6d9f180..1939135 100644
--- a/src/Migration.php
+++ b/src/Migration.php
@@ -96,7 +96,7 @@ class Migration
         $mapping = [];
 		foreach($openTickets as $ticket) {
 			$originalTicketId = $ticket[0];
-			$title = $ticket[3]['summary'];
+			$title = $ticket[3]['summary'] ?: '¯\_(ツ)_/¯';
 			$description = $this->translateTracToMarkdown($ticket[3]['description']);
 			if ($this->addLinkToOriginalTicket) {
 				$description .= "\n\n---\n\nOriginal ticket: " . $this->trac->getUrl() . '/ticket/' . $originalTicketId;
diff --git a/src/Trac.php b/src/Trac.php
index a07e4ab..92dcb6e 100644
--- a/src/Trac.php
+++ b/src/Trac.php
@@ -72,8 +72,8 @@ class Trac
 
             $result[] = [
                 'filename' => $attachment[0],
-                'content' => $data['__jsonclass__'][1],
-                'author' => $attachment[4]
+                'content'  => $data['__jsonclass__'][1],
+                'author'   => $attachment[4]
             ];
 
         }
diff --git a/steps/convert-tickets-to-issues.php b/steps/convert-tickets-to-issues.php
index 2307228..c3c6caf 100644
--- a/steps/convert-tickets-to-issues.php
+++ b/steps/convert-tickets-to-issues.php
@@ -12,20 +12,87 @@ $migration = new Migration(
     $config['create-trac-links'],
     $userMapping = []
 );
-$trac = $migration->trac->getClient();
+$trac = $migration->trac;
+
+$trac_client = $trac->getClient();
+$gitlab = $migration->gitLab;
+$gitlab_users = $gitlab->listUsers();
 
 $step_size = 50;
 $page = 1;
 do {
     $query = "{$config['trac-query']}&page={$page}&max={$step_size}";
     try {
-        $ticket_ids = $trac->execute('ticket.query', [$query]);
-        $migration->migrate($ticket_ids, $config['gitlab-project']);
+        $ticket_ids = $trac_client->execute('ticket.query', [$query]);
     } catch (Exception $e) {
         $ticket_ids = [];
     }
 
+    foreach ($ticket_ids as $ticket_id) {
+        try {
+            $ticket = $trac_client->execute('ticket.get', [$ticket_id]);
+
+            $title = $ticket[3]['summary'] ?: '¯\_(ツ)_/¯';
+            $description = translateTracToMarkdown($ticket[3]['description'], $config['trac-clean-url']);
+            $description .= "\n\n---\n\nOriginal ticket: {$config['trac-clean-url']}/ticket/{$ticket_id}";
+            $gitLabAssignee = isset($gitlab_users[$ticket[3]['owner']]) ? $gitlab_users[$ticket[3]['owner']] : null;
+            $gitLabCreator = isset($gitlab_users[$ticket[3]['reporter']]) ? $gitlab_users[$ticket[3]['reporter']] : null;
+            $assigneeId = is_array($gitLabAssignee) ? $gitLabAssignee['id'] : null;
+            $creatorId = is_array($gitLabCreator) ? $gitLabCreator['id'] : null;
+            $labels = $ticket[3]['keywords'];
+            $dateCreated = $ticket[3]['time']['__jsonclass__'][1];
+            $dateUpdated = $ticket[3]['_ts'];
+            $confidential = (bool) @$ticket[3]['sensitive'];
+
+            $issue = $gitlab->createIssue($config['gitlab-project'], $title,
+                $description, $dateCreated, $assigneeId, $creatorId, $labels,
+                $confidential);
+
+            echo "Created a GitLab issue #{$issue['iid']} for Trac ticket #{$ticket_id} : {$config['trac-clean-url']}/tickets/{$ticket_id}\n";
+
+            $mapping[$ticket_id] = $issue['iid'];
+
+            $attachments = $trac->getAttachments($ticket_id);
+
+            /*
+             * Add files attached to Trac ticket to new Gitlab issue.
+             */
+            foreach ($attachments as $a) {
+                $a['filename'] = str_replace(
+                    ['ä', 'ö', 'ü', 'Ä', 'Ö', 'Ü', 'ß'],
+                    ['ae', 'oe', 'ue', 'Ae', 'Oe', 'Ue', 'ss'],
+                    $a['filename']
+                );
+
+                // TODO: Thomas! WTF! FUCK! MACHEN! SOFORT!
+
+                file_put_contents($a['filename'], base64_decode($a['content']));
+
+                $gitlab->createIssueAttachment($config['gitlab-project'], $issue['iid'], $a['filename'], $a['author']);
+                unlink($a['filename']);
+
+                echo "\tAttached file " . $a['filename'] . " to issue " . $issue['iid'] . ".\n";
+            }
+
+            // Close issue if Trac ticket was closed.
+            if ($ticket[3]['status'] === 'closed') {
+                if (isset($ticket[4])) {
+                    $gitlab->closeIssue(
+                        $config['gitlab-project'], $issue['iid'],
+                        $ticket[4][0]['time']['__jsonclass__'][1], $ticket[4][0]['author']
+                    );
+                } else {
+                    $gitlab->closeIssue($config['gitlab-project'], $issue['iid']);
+                }
+            }
+        } catch (Exception $e) {
+            throw $e;
+            echo "Error creating issue for ticket #{$ticket_id}\n";
+        }
+    }
+
     $page += 1;
+
 } while (count($ticket_ids) > 0);
 
 return $migration->migrateQuery(
-- 
GitLab