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