2019-01-27 22:13:00 +01:00
|
|
|
require_relative 'redmine_api'
|
|
|
|
require_relative 'gitea_api'
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
module RedmineToGitea
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
class Mapping
|
2019-01-20 00:55:57 +01:00
|
|
|
attr_accessor :user_names_proc
|
|
|
|
attr_accessor :statuses_proc
|
|
|
|
attr_accessor :closed_statuses_proc
|
|
|
|
attr_accessor :priorities_proc
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-20 00:55:57 +01:00
|
|
|
def initialize(user_names_proc:, statuses_proc:, closed_statuses_proc:, priorities_proc: )
|
|
|
|
@user_names_proc = user_names_proc
|
|
|
|
@statuses_proc = statuses_proc
|
|
|
|
@closed_statuses_proc = closed_statuses_proc
|
|
|
|
@priorities_proc = priorities_proc
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
def user_name(redmine_user_id)
|
|
|
|
return nil if redmine_user_id.nil?
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
user_names_proc.call(redmine_user_id)
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
def status(redmine_status_id)
|
2019-01-20 00:55:57 +01:00
|
|
|
statuses_proc.call(redmine_status_id.to_i)
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
def status_closed?(redmine_status_id)
|
2019-01-20 00:55:57 +01:00
|
|
|
closed_statuses_proc.call(redmine_status_id.to_i)
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
def priority(redmine_priority_id)
|
2019-01-20 00:55:57 +01:00
|
|
|
priorities_proc.call(redmine_priority_id.to_i)
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-18 14:06:07 +01:00
|
|
|
end
|
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
class Settings
|
|
|
|
attr_accessor :mapping
|
|
|
|
attr_accessor :date_format
|
|
|
|
attr_accessor :textile_to_markdown
|
|
|
|
|
|
|
|
def initialize(mapping:, date_format:, textile_to_markdown: Proc.new { |x| x } )
|
|
|
|
@mapping = mapping
|
|
|
|
@date_format = date_format
|
|
|
|
@textile_to_markdown = textile_to_markdown
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
class Migrator
|
|
|
|
attr_accessor :redmine_api
|
|
|
|
attr_accessor :gitea_api
|
2019-01-27 22:13:00 +01:00
|
|
|
attr_accessor :settings
|
2019-01-20 00:24:20 +01:00
|
|
|
attr_accessor :issue_formatter
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
def initialize(redmine_api:, gitea_api:, settings: Settings.new)
|
|
|
|
@redmine_api = redmine_api
|
|
|
|
@gitea_api = gitea_api
|
|
|
|
@settings = settings
|
|
|
|
@issue_formatter = IssueFormatter.new(
|
|
|
|
mapping: mapping,
|
|
|
|
date_format: settings.date_format,
|
|
|
|
textile_to_markdown: settings.textile_to_markdown
|
|
|
|
)
|
|
|
|
end
|
|
|
|
|
2019-01-27 23:27:58 +01:00
|
|
|
def reset_project(owner:, repo_payload:, collaborators: [])
|
|
|
|
repo = repo_payload.fetch(:name)
|
|
|
|
old_repository = gitea_api.get_repo(owner: owner, repo: repo)
|
2019-01-27 22:13:00 +01:00
|
|
|
|
|
|
|
if old_repository.id
|
2019-01-27 23:27:58 +01:00
|
|
|
gitea_api.delete_repository(owner: owner, repo: repo)
|
2019-01-27 22:13:00 +01:00
|
|
|
end
|
|
|
|
|
2019-01-27 23:27:58 +01:00
|
|
|
# gitea_api.create_org_repository(org: owner, repository: repo_payload)
|
|
|
|
gitea_api.create_user_repository(payload: repo_payload, user: owner)
|
2019-01-27 22:13:00 +01:00
|
|
|
|
|
|
|
collaborators.each do |collaborator|
|
2019-01-27 23:27:58 +01:00
|
|
|
gitea_api.add_collaborator(
|
|
|
|
owner: owner,
|
|
|
|
repo: repo,
|
|
|
|
collaborator: collaborator,
|
|
|
|
payload: { permission: "write" }
|
|
|
|
)
|
2019-01-27 22:13:00 +01:00
|
|
|
end
|
|
|
|
rescue => ex
|
|
|
|
binding.pry
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-27 23:27:58 +01:00
|
|
|
def issues(redmine_project:, gitea_owner:, gitea_repo:)
|
2019-01-20 00:24:20 +01:00
|
|
|
remaining_results = true
|
|
|
|
offset = 0
|
|
|
|
limit = 25
|
|
|
|
|
|
|
|
while remaining_results do
|
|
|
|
result_set = redmine_api.get_issues(
|
|
|
|
project_id: redmine_project,
|
2019-01-20 21:48:20 +01:00
|
|
|
offset: offset,
|
|
|
|
limit: limit
|
2019-01-20 00:24:20 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
result_set.results.each do |result|
|
2019-01-27 23:27:58 +01:00
|
|
|
import_issue(
|
|
|
|
redmine_issue: redmine_api.get_issue(id: result.id),
|
|
|
|
owner: gitea_owner,
|
|
|
|
repo: gitea_repo
|
|
|
|
)
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
|
|
|
offset = offset + result_set.results.size
|
|
|
|
remaining_results = result_set.remaining_results?
|
|
|
|
end
|
2019-01-20 00:56:35 +01:00
|
|
|
rescue => ex
|
|
|
|
binding.pry
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-20 00:24:20 +01:00
|
|
|
private
|
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
def mapping
|
|
|
|
settings.mapping
|
|
|
|
end
|
|
|
|
|
2019-01-27 23:27:58 +01:00
|
|
|
def import_issue(redmine_issue:, owner:, repo:)
|
2019-01-20 00:24:20 +01:00
|
|
|
gitea_issue = {
|
|
|
|
"title" => redmine_issue.subject,
|
|
|
|
"body" => issue_formatter.description(redmine_issue),
|
|
|
|
"closed" => mapping.status_closed?(redmine_issue.status_id),
|
2019-01-19 01:22:42 +01:00
|
|
|
}
|
2019-01-20 00:24:20 +01:00
|
|
|
if redmine_issue.assigned_to
|
|
|
|
assignee = mapping.user_name(redmine_issue.assigned_to)
|
|
|
|
|
|
|
|
gitea_issue["assignee"] = assignee
|
|
|
|
gitea_issue["assignees"] = [assignee]
|
|
|
|
end
|
|
|
|
|
|
|
|
user = mapping.user_name(redmine_issue.author_id)
|
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
created_gitea_issue = gitea_api.create_issue(
|
2019-01-27 23:27:58 +01:00
|
|
|
payload: gitea_issue,
|
|
|
|
owner: owner,
|
|
|
|
repo: repo,
|
|
|
|
user: user
|
2019-01-20 00:24:20 +01:00
|
|
|
)
|
|
|
|
gitea_issue_number = created_gitea_issue.fetch("number")
|
|
|
|
|
|
|
|
redmine_issue.journals.each do |journal|
|
|
|
|
gitea_comment = {
|
|
|
|
"body" => issue_formatter.comment(journal)
|
|
|
|
}
|
2019-01-20 21:48:20 +01:00
|
|
|
user_name = mapping.user_name(journal.user_id)
|
2019-01-20 00:24:20 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
gitea_api.create_comment(
|
2019-01-27 23:27:58 +01:00
|
|
|
payload: gitea_comment,
|
|
|
|
owner: owner,
|
|
|
|
repo: repo,
|
|
|
|
issue: gitea_issue_number,
|
|
|
|
user: user_name
|
2019-01-20 00:24:20 +01:00
|
|
|
)
|
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
end
|
2019-01-20 00:56:35 +01:00
|
|
|
rescue => ex
|
|
|
|
binding.pry
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
class IssueFormatter
|
|
|
|
attr_accessor :mapping
|
|
|
|
attr_accessor :date_format
|
|
|
|
attr_accessor :textile_to_markdown
|
|
|
|
|
|
|
|
def initialize(mapping:, date_format: "%c", textile_to_markdown: )
|
|
|
|
@mapping = mapping
|
|
|
|
@date_format = date_format
|
|
|
|
@textile_to_markdown = textile_to_markdown
|
|
|
|
end
|
|
|
|
|
|
|
|
def description(issue)
|
|
|
|
issue_body = "> **Issue imported from forge.evolix.org on #{Time.now.strftime(date_format)}**"
|
|
|
|
issue_body += "\n> * Project: #{issue.project_name}"
|
|
|
|
issue_body += "\n> * Issue: #{issue.id}"
|
|
|
|
issue_body += "\n> * Tracker: #{issue.tracker_name}"
|
|
|
|
issue_body += "\n> * Status: #{issue.status_name}"
|
|
|
|
issue_body += "\n> * Author: #{issue.author_name}"
|
|
|
|
issue_body += "\n> * Category: #{issue.category_name}"
|
|
|
|
issue_body += "\n> * Started at: #{DateTime.parse(issue.start_date).strftime(date_format)}"
|
|
|
|
issue_body += "\n> * Done ratio: #{issue.done_ratio}"
|
|
|
|
issue_body += "\n> * Created at: #{DateTime.parse(issue.created_on).strftime(date_format)}"
|
|
|
|
issue_body += "\n> * Updated at: #{DateTime.parse(issue.updated_on).strftime(date_format)}"
|
|
|
|
issue_body += "\n\n"
|
|
|
|
issue_body += textile_to_markdown.call(issue.description)
|
|
|
|
|
|
|
|
issue_body
|
|
|
|
rescue => ex
|
|
|
|
binding.pry
|
|
|
|
end
|
2019-01-18 14:06:07 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
def comment(journal)
|
|
|
|
comment_body = "> **Comment imported from forge.evolix.org on #{Time.now.strftime(date_format)}**"
|
|
|
|
comment_body += "\n> * Author: #{journal.user_name}" if journal.user_name
|
|
|
|
comment_body += "\n> * Created at: #{DateTime.parse(journal.created_on).strftime(date_format)}" if journal.created_on
|
2019-01-20 00:24:20 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
comment_details(journal.details).each do |detail|
|
|
|
|
comment_body += "\n> * #{detail}"
|
|
|
|
end
|
2019-01-20 21:48:20 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
unless journal.notes.empty?
|
|
|
|
comment_body += "\n\n"
|
|
|
|
if journal.private_notes?
|
|
|
|
comment_body += "_This note is private its content has not been imported_"
|
|
|
|
else
|
|
|
|
comment_body += textile_to_markdown.call(journal.notes)
|
|
|
|
end
|
2019-01-20 21:48:20 +01:00
|
|
|
end
|
2019-01-27 22:13:00 +01:00
|
|
|
|
|
|
|
comment_body
|
|
|
|
rescue => ex
|
|
|
|
binding.pry
|
2019-01-20 21:48:20 +01:00
|
|
|
end
|
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
private
|
2019-01-20 21:48:20 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
def comment_details(details)
|
|
|
|
results = []
|
|
|
|
details.each do |detail|
|
|
|
|
if detail['property'] == "attr"
|
|
|
|
case detail['name']
|
|
|
|
when "status_id" then
|
|
|
|
old = mapping.status(detail['old_value'])
|
|
|
|
new = mapping.status(detail['new_value'])
|
|
|
|
results.push "Status: #{old} → #{new}"
|
|
|
|
when "priority_id" then
|
|
|
|
old = mapping.priority(detail['old_value'])
|
|
|
|
new = mapping.priority(detail['new_value'])
|
|
|
|
results.push "Priority: #{old} → #{new}"
|
|
|
|
when "done_ratio" then
|
|
|
|
old = detail['old_value']
|
|
|
|
new = detail['new_value']
|
|
|
|
results.push "Done ratio: #{old}% → #{new}%"
|
|
|
|
when "project_id" then
|
|
|
|
old = detail['old_value'] || "ø"
|
|
|
|
new = detail['new_value'] || "ø"
|
|
|
|
results.push "Project: #{old} → #{new}"
|
|
|
|
# when "tracker_id" then
|
|
|
|
# old = detail['old_value'] || "ø"
|
|
|
|
# new = detail['new_value'] || "ø"
|
|
|
|
# results.push "Tracker: #{old} → #{new}"
|
|
|
|
when "assigned_to_id" then
|
|
|
|
old = mapping.user_name(detail['old_value']) || "ø"
|
|
|
|
new = mapping.user_name(detail['new_value']) || "ø"
|
|
|
|
results.push "Assigned to: #{old} → #{new}"
|
|
|
|
when "fixed_version_id" then
|
|
|
|
old = detail['old_value'] || "ø"
|
|
|
|
new = detail['new_value'] || "ø"
|
|
|
|
results.push "Version: #{old} → #{new}"
|
|
|
|
when "subject", "description"
|
|
|
|
next
|
|
|
|
else
|
|
|
|
old = detail['old_value'] || "ø"
|
|
|
|
new = detail['new_value'] || "ø"
|
|
|
|
results.push "#{detail['name']}: #{old} → #{new}"
|
|
|
|
end
|
|
|
|
elsif detail['property'] == "relation"
|
|
|
|
results.push "Related to: #{detail['new_value']}"
|
2019-01-20 00:24:20 +01:00
|
|
|
else
|
2019-01-27 22:13:00 +01:00
|
|
|
next
|
2019-01-20 00:24:20 +01:00
|
|
|
end
|
|
|
|
end
|
2019-01-19 01:22:42 +01:00
|
|
|
|
2019-01-27 22:13:00 +01:00
|
|
|
results
|
|
|
|
end
|
2019-01-18 14:06:07 +01:00
|
|
|
end
|
2019-01-27 22:13:00 +01:00
|
|
|
|
2019-01-18 14:06:07 +01:00
|
|
|
end
|