Files

222 lines
8.5 KiB
Ruby

require 'octokit'
require_relative 'gapi_config'
############################################################################
# Copy issues and comments from one repository to another (sort of). #
# Because there is no way to copy issues directly, this script will #
# create new issues with the original metadata in the issue's body. #
# #
# NOTE: Assignees and issue status are included in the metadata block, #
# but the new issue itself will be open and unassigned. #
# #
# Inspired by https://github.com/IQAndreas/github-issues-import, #
# the layout of issues and comments is very similar. #
# #
# Requires GitHub's official OctoKit gem: http://octokit.github.io/ #
# #
# Expects a configuration file named "gapi_config.rb" in this format: #
# #
# @gapi_config = { #
# :api_key => "YOUR_API_KEY", #
# :origin_repo => "WHERE_THE_ISSUES_ARE_COMING_FROM", #
# :dest_repo => "WHERE_THE_ISSUES_ARE_GOING", #
# :source_labels => "LABELS_TO_FILTER_BY", #
# :page_count => "HOW_MANY_PAGES_OF_ISSUES_TO_FETCH" #
# } #
# #
# #
# LICENSE: Public Domain #
# #
# USAGE: #
# Run as file from the command line #
# To copy a specific list of issues, change Line 162 to #
# issue_list = get_issues([COMMA_SEPARATED_LIST_OF_INTEGERS]) #
# (keep the brackets, it expects an array) #
############################################################################
def create_client
@client = Octokit::Client.new(:access_token => @gapi_config[:api_key])
user = @client.user
user.login
end
def get_issues(iss_numbers = nil)
issue_list = []
if iss_numbers.nil?
1.upto(@gapi_config[:page_count]) do |pg|
issue_list << @client.list_issues(@gapi_config[:origin_repo], options = { :labels => @gapi_config[:source_labels], :state => "all", :page => pg })
end
else
@gapi_config[:source_labels] = nil
iss_numbers.each do |isn|
begin
issue_list << @client.issue(@gapi_config[:origin_repo], isn)
rescue => e
puts "***\nSkipping #{isn}:\n #{e}\n***"
next
end
end
end
return issue_list.flatten(1)
end
def get_new_issues
issue_list = []
1.upto(@gapi_config[:page_count]) do |pg|
issue_list << @client.list_issues(@gapi_config[:dest_repo], options = { :labels => @gapi_config[:source_labels], :state => "open", :page => pg })
end
return issue_list.flatten(1)
end
def get_comments(issue_id)
comment_list = @client.issue_comments(@gapi_config[:origin_repo], issue_id)
end
def issuemd(iss_data)
issue_markdown = []
issue_markdown << "**Issue by:** <a href='https://github.com/#{iss_data[:user][:login]}'>#{iss_data[:user][:login]}</a>"
issue_markdown << "**Original date:** #{iss_data[:created_at]}"
issue_markdown << "**Originally opened as:** <a href='https://github.com/#{@gapi_config[:origin_repo]}/issues/#{iss_data[:number]}'>#{@gapi_config[:origin_repo]}/issues/#{iss_data[:number]}</a>"
if !iss_data[:assignees].nil?
assignees = []
iss_data[:assignees].each { |ase| assignees << "<a href='https://github.com/#{ase[:login]}'>#{ase[:login]}</a>" unless ase[:login].nil? }
issue_markdown << "**Original assignees:** #{assignees.join(', ')}"
end
issue_markdown << "**Status on #{Time.now.strftime('%Y-%m-%d')}:** #{iss_data[:state]}"
issue_markdown << "<hr>\r\n"
issue_markdown << iss_data[:body] unless iss_data[:body].nil?
return issue_markdown.join("\n")
end
def commentmd(comm)
comment_markdown = []
comment_markdown << "**Comment by:** <a href='https://github.com/#{comm[:user][:login]}'>#{comm[:user][:login]}</a>"
comment_markdown << "**Original date:** #{comm[:created_at]}"
comment_markdown << "<hr>\r\n"
comment_markdown << comm[:body] unless comm[:body].nil?
return comment_markdown.join("\n")
end
def milestone_id(ms_name = nil)
ms_list = (@client.list_milestones(@gapi_config[:dest_repo]))
ms_id = nil
ms_list.each do |ms|
ms_id = ms[:number] if ms[:title] == ms_name
end
return ms_id
end
def copy_issue(this_iss)
puts " Copying issue #{this_iss[:number]}"
iss_body = issuemd(this_iss)
iss_labels = []
this_iss[:labels].each { |il| iss_labels << il[:name] }
ms_title = this_iss[:milestone][:title] unless this_iss[:milestone].nil?
ms_id = milestone_id(ms_title) unless ms_title.nil?
if ms_id.nil? && !this_iss[:milestone].nil?
@client.create_milestone(@gapi_config[:dest_repo], ms_title)
ms_id = milestone_id(ms_title)
end
@client.create_issue(@gapi_config[:dest_repo], this_iss[:title], iss_body, options = { :labels => iss_labels.join(','), :milestone => ms_id })
puts "\t--copy complete."
end
def copy_issue_comments(old_id, new_id)
puts "Copying comments from #{old_id} to #{new_id}"
comm_list = @client.issue_comments(@gapi_config[:origin_repo], old_id)
comm_list.each do |comm|
@client.add_comment(@gapi_config[:dest_repo], new_id, commentmd(comm))
end
puts "\t--copy complete."
end
if __FILE__ == $0
# Set up error counts
iss_errors = 0
num_errors = 0
comm_errors = 0
# Login with API Token
create_client
# Gets list of issues based on options in gapi_config
# To get specific issues, instead, use get_issues([1,2,3])
puts "\r\nGetting original issues"
issue_list = get_issues
puts "\r\nCopying issues to new repo"
# Copies to target repository specified in @gapi_config[:dest_repo]
issue_list.reverse.each do |iss|
begin
copy_issue(iss)
rescue => e
iss_errors += 1
puts " ***\nFailed to copy issue #{iss[:number]}:\n #{e}\n***"
next
end
end
# Get data for the new issues
puts "\r\nGetting new issue data"
new_list = get_new_issues
issue_numbers = []
puts "\r\nGetting issues with comments"
# Using title, match old issue numbers to new issue numbers
issue_list.each do |oiss|
if oiss[:comments] > 0
puts " Matching titles for #{oiss[:number]}: #{oiss[:title]}"
begin
new_iss = new_list.select { |iss| iss[:title] == oiss[:title] unless iss[:title].nil? }
rescue => e
num_errors += 1
puts " ***\nFailed to match #{oiss[:number]} with an issue in the destination repository: \n#{e}\n***"
end
puts " Adding issue number pair to array:"
issue_numbers << [oiss[:number], new_iss[0][:number]] if !new_iss.nil?
puts " Added [#{oiss[:number]}, #{new_iss[0][:number]}]"
end
end
puts " ...no issues with comments found." if issue_numbers.empty?
# Adds comments to the issues in the target repository
puts "\r\nCopying comments to new issues" unless issue_numbers.empty?
issue_numbers.each do |iss|
begin
copy_issue_comments(iss[0], iss[1])
rescue => e
comm_errors += 1
puts " ***\nFailed to copy comments from issue number #{old_id} to issue #{new_id}:\n #{e}\n***"
next
end
end
puts "\r\n---------------"
puts "Issue errors: #{iss_errors} for #{issue_list.count} total issues"
puts "Pair matching errors: #{num_errors}"
puts "Comment errors: #{comm_errors}"
end