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)(.+?)\)\]\]/', '', $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