Skip to content

Instantly share code, notes, and snippets.

@brodygov
Created May 30, 2017 18:46
Show Gist options
  • Star 3 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save brodygov/583c26a7db1cbf0d11549ceb84ac5bcf to your computer and use it in GitHub Desktop.
Save brodygov/583c26a7db1cbf0d11549ceb84ac5bcf to your computer and use it in GitHub Desktop.
Copy issues from one github repository to another
#!/usr/bin/env ruby
# Copy a list of issues from one repository to another.
#
# This whole script is a poorly documented mess. Sorry.
require 'date'
require 'yaml'
require 'pp'
require 'json'
require 'rest-client'
require 'netrc'
def main(args)
if args.length < 2
puts <<-EOS
#{File.basename($0)} NETRC_FILE SOURCE_REPO TARGET_REPO MOVED_LABEL ISSUE_IDS
Copy a list of github issues from SOURCE_REPO to TARGET_REPO.
MOVED_LABEL should be a marker like copied-to-target to indicate an issue has
been processed. This label must exist on the source repo.
For example, to copy issues 123 124 and 125 from source-repo to target-repo:
#{File.basename($0)} secret.netrc myorg/source-repo myorg/target-repo '123 124 125'
EOS
exit 1
end
netrc = args.fetch(0)
sourcerepo = args.fetch(1)
targetrepo = args.fetch(2)
sentinel_label = args.fetch(3)
ids_string = args.fetch(4)
issue_ids = ids_string.split
r = Runner.new(netrc, sourcerepo, targetrepo, sentinel_label, issue_ids)
r.dry_run
puts
puts "Dry run is complete. Check above to see that all looks good."
puts "About to destructively update data!"
if STDIN.tty?
puts "Press enter to continue..."
STDIN.gets
end
r.run!
end
class Runner
def initialize(netrc, source_repo, target_repo, moved_label, issue_ids)
@user, @password = load_creds_from_netrc(netrc)
@source_repo = source_repo
@target_repo = target_repo
@moved_label = moved_label
@issue_ids = issue_ids
RestClient.log ||= STDOUT
end
def run!
run(false)
end
def dry_run
run(true)
end
def run(dry_run)
if dry_run
puts "Starting dry run migration from #{@source_repo} to #{@target_repo}"
else
puts "Starting LIVE RUN migration from #{@source_repo} to #{@target_repo}"
end
@issue_ids.each do |id|
migrate_issue(dry_run, id)
end
end
def load_creds_from_netrc(path)
netrc = Netrc.read(path)
rec = netrc['api.github.com']
raise "No rec for api.github.com in netrc" unless rec
[rec.login, rec.password]
end
def migrate_issue(dry_run, issue_id, copy_labels=true)
puts "migrate_issue #{issue_id} (dry_run: #{dry_run})"
issue_data = get_issue(@source_repo, issue_id)
state = issue_data['state']
if state != 'open'
warn "skipping issue #{issue_id}, is in state #{state.inspect}, must be 'open' to migrate"
return
end
labels = issue_data.fetch('labels')
if labels.find {|label| label.fetch('name') == @moved_label}
warn "skipping issue #{issue_id} already has label #{@moved_label}, will not move"
return
end
source_url = "https://github.com/#{@source_repo}/issues/#{issue_id}"
body_prefix = "## This issue was migrated from #{source_url}\n\n"
new_issue_hash = {
'title' => issue_data.fetch('title') + ' (migrated)',
'body' => body_prefix + issue_data.fetch('body'),
'assignees' => issue_data.fetch('assignees').map {|assignee| assignee.fetch('login')}
}
if copy_labels
new_issue_hash['labels'] = issue_data.fetch('labels').map{|label| label.fetch('name')}
end
if dry_run
puts "DRY RUN: would have created issue with data:"
pp new_issue_hash
else
resp = create_issue(@target_repo, new_issue_hash)
target_url = resp.fetch('url')
puts "Adding migrated label #{@moved_label} to source issue"
add_label(@source_repo, issue_id, @moved_label)
puts "Done migrating #{issue_id} from #{source_url} to #{target_url}"
end
end
def get_issue(repo, issue_id)
github_get_json("/repos/#{repo}/issues/#{issue_id}")
end
def add_label(repo, issue_id, label_name)
puts "Adding label #{label_name} to #{repo} issue ##{issue_id}"
res = RestClient::Request.execute(
method: :post,
url: "https://api.github.com/repos/#{repo}/issues/#{issue_id}/labels",
user: @user,
password: @password,
payload: [label_name].to_json,
headers: {content_type: :json})
data = JSON.parse(res.body)
#puts "Response:"
#pp data
data
end
def create_issue(repo, payload_hash)
res = RestClient::Request.execute(
method: :post,
url: "https://api.github.com/repos/#{repo}/issues",
user: @user,
password: @password,
payload: payload_hash.to_json,
headers: {content_type: :json})
data = JSON.parse(res.body)
puts "Created issue: #{data.fetch('url')}"
data
end
def github_get_json(path)
uri = URI.parse('https://api.github.com')
uri.path = path
resp = RestClient::Request.execute(
method: :get,
url: uri.to_s,
user: @user,
password: @password
)
JSON.parse(resp.body)
end
end
if $0 == __FILE__
main(ARGV)
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment