redmine-to-gitea/redmine-to-gitea.rb

272 lines
8.4 KiB
Ruby

require_relative 'redmine_api'
require_relative 'gitea_api'
module RedmineToGitea
class Mapping
attr_accessor :user_names_proc
attr_accessor :statuses_proc
attr_accessor :closed_statuses_proc
attr_accessor :priorities_proc
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
end
def user_name(redmine_user_id)
return nil if redmine_user_id.nil?
user_names_proc.call(redmine_user_id)
end
def status(redmine_status_id)
statuses_proc.call(redmine_status_id.to_i)
end
def status_closed?(redmine_status_id)
closed_statuses_proc.call(redmine_status_id.to_i)
end
def priority(redmine_priority_id)
priorities_proc.call(redmine_priority_id.to_i)
end
end
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
class Migrator
attr_accessor :redmine_api
attr_accessor :gitea_api
attr_accessor :settings
attr_accessor :issue_formatter
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
def reset_project(owner:, repo_payload:, collaborators: [])
repo = repo_payload.fetch(:name)
old_repository = gitea_api.get_repo(owner: owner, repo: repo)
if old_repository.id
gitea_api.delete_repository(owner: owner, repo: repo)
end
# gitea_api.create_org_repository(org: owner, repository: repo_payload)
gitea_api.create_user_repository(payload: repo_payload, user: owner)
collaborators.each do |collaborator|
gitea_api.add_collaborator(
owner: owner,
repo: repo,
collaborator: collaborator,
payload: { permission: "write" }
)
end
rescue => ex
binding.pry
end
def issues(redmine_project:, gitea_owner:, gitea_repo:)
remaining_results = true
offset = 0
limit = 25
while remaining_results do
result_set = redmine_api.get_issues(
project_id: redmine_project,
offset: offset,
limit: limit
)
result_set.results.each do |result|
import_issue(
redmine_issue: redmine_api.get_issue(id: result.id),
owner: gitea_owner,
repo: gitea_repo
)
end
offset = offset + result_set.results.size
remaining_results = result_set.remaining_results?
end
rescue => ex
binding.pry
end
private
def mapping
settings.mapping
end
def import_issue(redmine_issue:, owner:, repo:)
gitea_issue = {
"title" => redmine_issue.subject,
"body" => issue_formatter.description(redmine_issue),
"closed" => mapping.status_closed?(redmine_issue.status_id),
}
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)
created_gitea_issue = gitea_api.create_issue(
payload: gitea_issue,
owner: owner,
repo: repo,
user: user
)
gitea_issue_number = created_gitea_issue.fetch("number")
redmine_issue.journals.each do |journal|
gitea_comment = {
"body" => issue_formatter.comment(journal)
}
user_name = mapping.user_name(journal.user_id)
gitea_api.create_comment(
payload: gitea_comment,
owner: owner,
repo: repo,
issue: gitea_issue_number,
user: user_name
)
end
end
rescue => ex
binding.pry
end
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
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
comment_details(journal.details).each do |detail|
comment_body += "\n> * #{detail}"
end
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
end
comment_body
rescue => ex
binding.pry
end
private
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']}"
else
next
end
end
results
end
end
end