Mirroring a repository with issues, labels & milestones from one GitHub server to another
require 'pry' | |
require 'octokit' | |
require 'json' | |
# Part 0: Extract bare repo and push it to GH: | |
# Follow https://help.github.com/enterprise/2.2/admin/articles/moving-a-repository-from-github-com-to-github-enterprise/ | |
# Part 1: Extract issues & everything else from the source repo | |
## Setup | |
Octokit.configure do |c| | |
c.api_endpoint = 'https://git.yale.edu/api/v3/' | |
c.auto_paginate = true | |
end | |
# set ENTERPRISE_TOKEN prior to this line | |
yalegit = Octokit::Client.new(:access_token => ENTERPRISE_TOKEN) | |
repoName = 'levylab/RNA_PTB_task' | |
## Action | |
opts = {:state => :all, :sort => :created, :direction => :asc} | |
labels = yalegit.labels(repoName, {:state => :all}) | |
issuesAndPRs = yalegit.issues(repoName, opts) | |
pulls = yalegit.pull_requests(repoName, opts) | |
milestones = yalegit.milestones(repoName, opts) | |
comments = yalegit.issues_comments(repoName, opts) | |
## Intermediate save | |
# Returned objects are Sawyer resources; we need | |
# `sawyer_resource.map(&:to_h)` to serialize them. | |
File.open('labels.json', 'w') do |f| | |
f.write(labels.map(&:to_h).to_json) | |
end | |
File.open('issuesAndPRs.json', 'w') do |f| | |
f.write(issuesAndPRs.map(&:to_h).to_json) | |
end | |
File.open('milestones.json', 'w') do |f| | |
f.write(milestones.map(&:to_h).to_json) | |
end | |
File.open('comments.json', 'w') do |f| | |
f.write(comments.map(&:to_h).to_json) | |
end | |
File.open('pulls.json', 'w') do |f| | |
f.write(pulls.map(&:to_h).to_json) | |
end | |
## Investigate | |
binding.pry |
require 'octokit' | |
require 'json' | |
# Part 3: Upload everything to target repo on GitHub | |
## Setup | |
Octokit.configure do |c| | |
c.api_endpoint = 'https://api.github.com/' | |
c.auto_paginate = true | |
end | |
# set GITHUB_TOKEN prior to this line | |
github = Octokit::Client.new(:access_token => GITHUB_TOKEN) | |
repo = 'shippy/test_PTF' | |
## 1. Labels | |
### 1a. Delete default labels | |
github.labels(repo).each do |l| | |
github.delete_label!(repo, l[:name]) | |
end | |
### 1b. Upload labels to GH | |
labels = JSON.parse(File.read('labels.json'), {symbolize_names: true}) | |
labels.each do |l| | |
begin | |
github.add_label(repo, l[:name], l[:color]) | |
puts "Added #{l[:name]} - ##{l[:color]}" | |
rescue Exception => e | |
puts "#{l[:name]} already exists, updating:" if e.class == Octokit::UnprocessableEntity | |
github.update_label(repo, l[:name], {color: l[:color]}) | |
end | |
end | |
## 2. Upload milestones to GH with the right numbering | |
### 2a. Create milestones, including placeholders | |
milestones = JSON.parse(File.read('milestones.json'), {symbolize_names: true}).sort_by {|m| m[:number]} | |
current_milestone = 0 | |
fake_milestones = [] | |
milestones.each do |m| | |
current_milestone = current_milestone + 1 | |
while m[:number] > current_milestone | |
github.create_milestone(repo, "fake #{current_milestone}") | |
fake_milestones << current_milestone | |
current_milestone = current_milestone + 1 | |
end | |
github.create_milestone(repo, m[:title], {state: m[:state], description: m[:description]}) | |
end | |
### 2b. Remove the placeholder milestones | |
fake_milestones.each do |fake| | |
github.delete_milestone(repo, fake) | |
end | |
## 3. Upload issues/PRs with the right labels and milestones | |
issuesAndPRs = JSON.parse(File.read('issuesAndPRs.json'), {symbolize_names: true}).sort_by { |p| p[:number] } | |
pulls = JSON.parse(File.read('pulls.json'), {symbolize_names: true}).sort_by { |p| p[:number] } | |
comments = JSON.parse(File.read('comments.json'), {symbolize_names: true}).sort_by { |p| p[:id] } | |
# In case uploading was interrupted, note the uploaded issues | |
issues_uploaded = github.issues(repo, {state: :all, sort: :created, direction: :desc}) | |
issuesAndPRs.each do |i| | |
### 3a. Extract identifiers from the issue | |
# Skip existing issues | |
issue_number = i[:number] | |
unless issues_uploaded.empty? | |
last_issue_id = issues_uploaded[0][:number] | |
if issue_number <= last_issue_id | |
next | |
end | |
end | |
issue_url = i[:url] | |
issue_labels = i[:labels].map { |l| l[:name] } | |
begin | |
issue_milestone = i[:milestone][:number] | |
rescue Exception | |
issue_milestone = nil | |
end | |
### 3b. create issue | |
sleep(3) # to avoid rate limiting | |
github.create_issue(repo, i[:title], i[:body], {milestone: issue_milestone, labels: issue_labels}) | |
### 3c. convert into open PR / note the history for closed PR | |
if i.key?(:pull_request) | |
current_pull = pulls.select { |p| p[:number] == issue_number }[0] | |
base = current_pull[:base][:ref] | |
head = current_pull[:head][:ref] | |
if i[:state] == "open" | |
github.create_pull_request_for_issue(repo, base, head, issue_number) | |
else | |
merge_commit_sha = current_pull[:merge_commit_sha] | |
base_sha = current_pull[:base][:sha] | |
head_sha = current_pull[:head][:sha] | |
pull_note = "**Migration note**: This was a pull request to merge " | |
pull_note << "`#{head}` at #{head_sha} into `#{base}` at #{base_sha}. " | |
pull_note << "It was merged in #{merge_commit_sha}.\n\n" | |
new_body = pull_note + current_pull[:body] | |
github.update_issue(repo, issue_number, { body: new_body }) | |
end | |
end | |
### 4d. Add all comments left in the issue | |
comments.select { |c| c[:issue_url] == issue_url }.each do |c| | |
github.add_comment(repo, issue_number, c[:body]) | |
end | |
### 4e. close if appropriate | |
if i[:state] != 'open' | |
github.close_issue(repo, issue_number) | |
end | |
end |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment