missue 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/bin/missue.rb +400 -0
- metadata +46 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: fe559dea3d1c0dcb0acb013af221bbdc7ad57b294b332fa843bf3a6e8a26d674
|
4
|
+
data.tar.gz: 3c8e1d522d0d0f6ca058f1c6b986b89ac99e1a481fa623350019099f5194e183
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 7effa903cc1c278d319bd27f03ae7f761fe7e2a278a86a70665e6065319a0443aa18305e8002a8c9c64d84ed95e9776ebe7b4df0b55c1aad66a7dda1a696d304
|
7
|
+
data.tar.gz: 2c4fd26d7b741b4ca0e0c50fc2744de21a48b3ab67858be6ef373b41b34e36141b99b513a17d4e7f0d4b7ef410e6e1e06b7b0babd2af25c3f76c3aa9f76d593b
|
data/bin/missue.rb
ADDED
@@ -0,0 +1,400 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# gh-missue.rb -- A GitHub issue migration script written in Ruby
|
3
|
+
#==================================================================================================
|
4
|
+
# Author: E:V:A
|
5
|
+
# Date: 2018-04-10
|
6
|
+
# Change: 2022-01-25
|
7
|
+
# Version: 1.0.2
|
8
|
+
# License: ISC
|
9
|
+
# Formatting UTF-8 with 4-space TAB stops and no TAB chars.
|
10
|
+
# URL: https://github.com/E3V3A/gh-missue
|
11
|
+
# Based On: https://github.com/TimothyBritt/github-issue-migrate
|
12
|
+
#
|
13
|
+
# Description: A Ruby script for migrating selected GitHub issues to your own repository and
|
14
|
+
# using OAuth2 authentication to increase speed and prevent rate limiting.
|
15
|
+
#
|
16
|
+
# Dependencies:
|
17
|
+
# [1] docopt https://github.com/docopt/docopt.rb/ # option parser
|
18
|
+
# [2] octokit https://github.com/octokit/octokit.rb/ # GitHubs API library
|
19
|
+
#
|
20
|
+
# NOTE:
|
21
|
+
#
|
22
|
+
# 1. To make this run, you need to install Ruby with:
|
23
|
+
# (a) winget install ruby
|
24
|
+
# (b) gem install octokit
|
25
|
+
# (c) gem install docopt
|
26
|
+
#
|
27
|
+
# 2. Clone latest version of this file
|
28
|
+
#
|
29
|
+
# 3. You should also consider creating a personal authentication token on GitHub,
|
30
|
+
# to avoid getting rate-limited by a large number of requests in short time.
|
31
|
+
#
|
32
|
+
# ToDo:
|
33
|
+
#
|
34
|
+
# [ ] Add -a option to NOT copy original author & URL into migrated issue
|
35
|
+
# [ ] Fix username/password authentication ?? (Maybe Deprecated?)
|
36
|
+
# [ ] Check environment variable for OAUTH token:
|
37
|
+
# access_token = "#{ENV['GITHUB_OAUTH_TOKEN']}"
|
38
|
+
# [ ] Fix inclusion of CLI options: -d, -n
|
39
|
+
# -d - show debug info with full option list, raw requests & responses etc.
|
40
|
+
# -n <1,3-6,9> - only migrate issues given by the list. Can be a range.
|
41
|
+
# [/] Fix new Authentication issues
|
42
|
+
# [ ] Make the issue vs PR selection smarter!
|
43
|
+
# - Now it just takes ALL and filters using list_source_issues()
|
44
|
+
# [ ] ? Add <type> option to selec pr, vs issue: '-p <type>' where <type> = [issue, pr]
|
45
|
+
#
|
46
|
+
# References:
|
47
|
+
#
|
48
|
+
# [1] https://developer.github.com/changes/2020-02-10-deprecating-auth-through-query-param/
|
49
|
+
# [2] https://docs.github.com/en/developers/apps/building-oauth-apps/authorizing-oauth-apps#web-application-flow
|
50
|
+
# [3]
|
51
|
+
#
|
52
|
+
#==================================================================================================
|
53
|
+
require 'docopt'
|
54
|
+
require 'octokit'
|
55
|
+
require 'net/http'
|
56
|
+
require 'json'
|
57
|
+
|
58
|
+
VERSION = '1.0.2'
|
59
|
+
options = {}
|
60
|
+
|
61
|
+
#--------------------------------------------------------------------------------------------------
|
62
|
+
# The cli options parser
|
63
|
+
#--------------------------------------------------------------------------------------------------
|
64
|
+
doc = <<DOCOPT
|
65
|
+
|
66
|
+
Description:
|
67
|
+
|
68
|
+
gh-missue is a Ruby program that migrate issues from one github repository to another.
|
69
|
+
Please note that you can only migrate issues to your own repo, unless you have an OAuth2
|
70
|
+
authentication token.
|
71
|
+
|
72
|
+
Usage:
|
73
|
+
#{__FILE__} [-c | -n <ilist> | -t <itype>] <source_repo> <target_repo>
|
74
|
+
#{__FILE__} [-c | -n <ilist> | -t <itype>] <oauth2_token> <source_repo> <target_repo>
|
75
|
+
#{__FILE__} [-c | -n <ilist> | -t <itype>] <username> <password> <source_repo> <target_repo>
|
76
|
+
#{__FILE__} [-d] -l <itype> [<oauth2_token>] <repo>
|
77
|
+
#{__FILE__} -n <ilist>
|
78
|
+
#{__FILE__} -t <itype>
|
79
|
+
#{__FILE__} [-d] -r [<oauth2_token>]
|
80
|
+
#{__FILE__} -d
|
81
|
+
#{__FILE__} -v
|
82
|
+
#{__FILE__} -h
|
83
|
+
|
84
|
+
Options:
|
85
|
+
|
86
|
+
-c - only copy all issue labels from <source> to <target> repos, including name, color and description
|
87
|
+
-l <itype> <repo> - list available issues of type <itype> (all,open,closed) and all labels in repository <repo>
|
88
|
+
-t <itype> - specify what type (all,open,closed) of issues to migrate. [default: open]
|
89
|
+
-r - show current rate limit and authentication method for your IP
|
90
|
+
-d - show debug info with full option list, raw requests & responses etc.
|
91
|
+
-n <ilist> - only migrate issues with comma separated numbers given by the list. Can include a range.
|
92
|
+
-h, --help - show this help message and exit
|
93
|
+
-v, --version - show version and exit
|
94
|
+
|
95
|
+
Examples:
|
96
|
+
|
97
|
+
#{__FILE__} -r
|
98
|
+
#{__FILE__} -l open E3V3A/MMM-VOX
|
99
|
+
#{__FILE__} -t closed "E3V3A/TESTO" "USERNAME/REPO"
|
100
|
+
#{__FILE__} -n 1,4-5 "E3V3A/TESTO" "USERNAME/REPO"
|
101
|
+
|
102
|
+
Dependencies:
|
103
|
+
#{__FILE__} depends on the following gem packages: octokit, docopt.
|
104
|
+
|
105
|
+
DOCOPT
|
106
|
+
|
107
|
+
#--------------------------------------------------------------------------------------------------
|
108
|
+
# The IssueMigrator
|
109
|
+
#--------------------------------------------------------------------------------------------------
|
110
|
+
class IssueMigrator
|
111
|
+
|
112
|
+
# attr_accessor :issues, :client, :target_repo, :source_repo
|
113
|
+
attr_accessor :access_token, :issues, :ilist, :itype, :client, :target_repo, :source_repo
|
114
|
+
|
115
|
+
def hex2rgb(hex)
|
116
|
+
# Usage: print hex2rgb("d73a4a") - prints a RGB colored box from hex
|
117
|
+
r,g,b = hex.match(/^(..)(..)(..)$/).captures.map(&:hex)
|
118
|
+
s = "\e[48;2;#{r};#{g};#{b}m \e[0m"
|
119
|
+
end
|
120
|
+
|
121
|
+
# curl -v -H "Authorization: token <MY-40-CHAR-TOKEN>" \
|
122
|
+
# -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/E3V3A/gh-missue/issues
|
123
|
+
def initialize(access_token, source_repo, target_repo)
|
124
|
+
@client = Octokit::Client.new(
|
125
|
+
:access_token => access_token,
|
126
|
+
:accept => 'application/vnd.github.v3+json',
|
127
|
+
:headers => { "Authorization" => "token " + access_token },
|
128
|
+
# :headers => { "X-GitHub-OTP" => "<your 2FA token>" }
|
129
|
+
|
130
|
+
# // Personal OAuth2 Access Token
|
131
|
+
#:access_token => "YOUR_40_CHAR_OATH2_TOKEN"
|
132
|
+
|
133
|
+
# // OAuth2 App Credentials (DEPRECATED!)
|
134
|
+
# NEW use:
|
135
|
+
# curl -u my_client_id:my_client_secret https://api.github.com/user/repos
|
136
|
+
#:client_id => "<YOUR_20_CHAR_OATH2_ID>",
|
137
|
+
#:client_secret => "<YOUR_40_CHAR_OATH2_SECRET>"
|
138
|
+
|
139
|
+
per_page: 100
|
140
|
+
)
|
141
|
+
user = client.user
|
142
|
+
user.login
|
143
|
+
@source_repo = source_repo
|
144
|
+
@target_repo = target_repo
|
145
|
+
@itype = itype
|
146
|
+
end
|
147
|
+
|
148
|
+
def pull_source_issues(itype) # ilist => nil ??
|
149
|
+
@client.auto_paginate = true
|
150
|
+
@issues = @client.issues(@source_repo, :state => itype) # The issue type: <itype>: [open/closed/all]
|
151
|
+
# @issues = @client.issues(@source_repo, :issue => ilist) # The issue list: <ilist>: "1,2,5-7,19"
|
152
|
+
puts "Found #{issues.size} issues of type: #{itype}\n"
|
153
|
+
end
|
154
|
+
|
155
|
+
def list_source_issues(itype)
|
156
|
+
pull_source_issues(itype)
|
157
|
+
@issues.each do |source_issue|
|
158
|
+
#puts "[#{source_issue.number}]\t #{source_issue.title}"
|
159
|
+
puts "[#{source_issue.number}]".ljust(10) + "#{source_issue.title}"
|
160
|
+
end
|
161
|
+
puts
|
162
|
+
end
|
163
|
+
|
164
|
+
def list_source_labels
|
165
|
+
@client.auto_paginate = true
|
166
|
+
@labels = @client.labels(@source_repo.freeze, accept: 'application/vnd.github.symmetra-preview+json')
|
167
|
+
puts "Found #{@labels.size} issue labels:"
|
168
|
+
# ToDo: check and handle length (in case > 20)
|
169
|
+
@labels.each do |label|
|
170
|
+
# ToDo: Add " " colored "boxes" using the color of the tag.
|
171
|
+
color_box = hex2rgb("#{label.color}") + " "
|
172
|
+
#puts "[#{label.color}] " + "#{label.name}".ljust(20) + ": #{label.description}"
|
173
|
+
puts "[#{label.color}] " + color_box + "#{label.name}".ljust(20) + ": #{label.description}"
|
174
|
+
end
|
175
|
+
puts
|
176
|
+
end
|
177
|
+
|
178
|
+
def create_target_labels
|
179
|
+
@client.auto_paginate = true
|
180
|
+
@source_labels = @client.labels(@source_repo.freeze, accept: 'application/vnd.github.symmetra-preview+json')
|
181
|
+
# @target_labels = @client.add_label(@target_repo.freeze, accept: 'application/vnd.github.symmetra-preview+json')
|
182
|
+
puts "Found #{@source_labels.size} issue labels in <source_repo>:"
|
183
|
+
puts "Copying labels..."
|
184
|
+
tlabel = "" # nil
|
185
|
+
@source_labels.each do |lbl|
|
186
|
+
# ToDo: Add " " colored "boxes" using the color of the tag.
|
187
|
+
#puts "[#{lbl.color}] #{lbl.name} : #{lbl.description}"
|
188
|
+
puts "[#{lbl.color}] " + "#{lbl.name}".ljust(20) + ": #{lbl.description}"
|
189
|
+
#tlabel = {"name": lbl.name, "description": lbl.description, "color": lbl.color}
|
190
|
+
#tlabel = {lbl.name, lbl.color, description: lbl.description}
|
191
|
+
#lab = client.add_label(@target_repo.freeze, accept: 'application/vnd.github.symmetra-preview+json', tlabel)
|
192
|
+
lab = client.add_label(@target_repo.freeze, lbl.name, lbl.color, accept: 'application/vnd.github.symmetra-preview+json', description: lbl.description)
|
193
|
+
sleep(2)
|
194
|
+
end
|
195
|
+
puts "done."
|
196
|
+
end
|
197
|
+
|
198
|
+
def push_issues
|
199
|
+
@issues.reverse!
|
200
|
+
n = 0
|
201
|
+
@issues.each do |source_issue|
|
202
|
+
n += 1
|
203
|
+
print "Processing issue: #{source_issue.number} (#{n}/#{issues.size})\r"
|
204
|
+
source_labels = get_source_labels(source_issue)
|
205
|
+
source_comments = get_source_comments(source_issue)
|
206
|
+
if !source_issue.key?(:pull_request) || source_issue.pull_request.empty?
|
207
|
+
|
208
|
+
# PR#2
|
209
|
+
issue_body = "*Originally created by @#{source_issue.user[:login]} (#{source_issue.html_url}):*\n\n#{source_issue.body}"
|
210
|
+
target_issue = @client.create_issue(@target_repo, source_issue.title, issue_body, {labels: source_labels})
|
211
|
+
|
212
|
+
#target_issue = @client.create_issue(@target_repo, source_issue.title, source_issue.body, {labels: source_labels})
|
213
|
+
|
214
|
+
push_comments(target_issue, source_comments) unless source_comments.empty?
|
215
|
+
@client.close_issue(@target_repo, target_issue.number) if source_issue.state === 'closed'
|
216
|
+
end
|
217
|
+
# We need to set a rate limit, even for OA2, it is 0.5 [req/sec]
|
218
|
+
sleep(90) if ( issues.size > 1 ) # [sec]
|
219
|
+
end
|
220
|
+
puts "\n"
|
221
|
+
end
|
222
|
+
|
223
|
+
# API bug: missing color/description
|
224
|
+
def get_source_labels(source_issue)
|
225
|
+
labels = []
|
226
|
+
source_issue.labels.each do |lbl|
|
227
|
+
labels << {"name": lbl.name, "description": lbl.description, "color": lbl.color}
|
228
|
+
end
|
229
|
+
#puts "Labels: #{labels}"
|
230
|
+
labels
|
231
|
+
end
|
232
|
+
|
233
|
+
def get_source_comments(source_issue)
|
234
|
+
comments = []
|
235
|
+
source_comments = @client.issue_comments(@source_repo, source_issue.number)
|
236
|
+
source_comments.each do |cmt|
|
237
|
+
comments << cmt.body
|
238
|
+
end
|
239
|
+
comments
|
240
|
+
end
|
241
|
+
|
242
|
+
def push_comments(target_issue, source_comments)
|
243
|
+
source_comments.each do |cmt|
|
244
|
+
@client.add_comment(@target_repo, target_issue.number, cmt)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
end
|
248
|
+
|
249
|
+
#--------------------------------------------------------------------------------------------------
|
250
|
+
# MAIN
|
251
|
+
#--------------------------------------------------------------------------------------------------
|
252
|
+
|
253
|
+
#if __FILE__ == $0
|
254
|
+
begin
|
255
|
+
|
256
|
+
hLine = "-"*72
|
257
|
+
puts
|
258
|
+
|
259
|
+
def sort_list(ilist)
|
260
|
+
# "12,3-5,2,6,35-38" --> [2,3,4,5,6,12,35,36,37,38]
|
261
|
+
ilist.gsub(/(\d+)-(\d+)/) { ($1..$2).to_a.join(',') }.split(',').map(&:to_i).sort.uniq
|
262
|
+
end
|
263
|
+
|
264
|
+
#----------------------------------------------------------------------
|
265
|
+
# CLI Options
|
266
|
+
#----------------------------------------------------------------------
|
267
|
+
options = Docopt::docopt(doc, version: VERSION) # help: true
|
268
|
+
|
269
|
+
if options['-d']
|
270
|
+
debug = true
|
271
|
+
#pp Docopt::docopt(doc, version: VERSION)
|
272
|
+
puts "\nAvailable options are:\n#{options.inspect}\n"
|
273
|
+
puts "\nThe supplied CLI options were:\n#{ARGV.inspect}\n\n"
|
274
|
+
end
|
275
|
+
|
276
|
+
if options['<oauth2_token>']
|
277
|
+
access_token = options['<oauth2_token>']
|
278
|
+
if access_token.size != 40
|
279
|
+
puts "Error: The github access token has to be 40 characters long!"
|
280
|
+
puts " (Yours was: #{access_token.size} characters.)"
|
281
|
+
exit
|
282
|
+
else
|
283
|
+
puts "Using access_token: #{access_token}" if options['-d']
|
284
|
+
end
|
285
|
+
end
|
286
|
+
|
287
|
+
# -l <itype> <source_repo>
|
288
|
+
# https://docs.github.com/en/rest/reference/issues#list-repository-issues
|
289
|
+
# GET /repos/{owner}/{repo}/issues
|
290
|
+
# curl -v -H "Authorization: token <MY-40-CHAR-TOKEN>" \
|
291
|
+
# -H "Accept: application/vnd.github.v3+json" https://api.github.com/repos/E3V3A/gh-missue/issues
|
292
|
+
if ( options['-l'] )
|
293
|
+
itype = options['-l']
|
294
|
+
source_repo = options['<repo>']
|
295
|
+
target_repo = "E3V3A/TESTT" # a dummy repo
|
296
|
+
im = IssueMigrator.new("#{access_token}", "#{source_repo}", "#{target_repo}")
|
297
|
+
im.list_source_issues(itype)
|
298
|
+
im.list_source_labels
|
299
|
+
end
|
300
|
+
|
301
|
+
# -n <ilist>
|
302
|
+
if ( options['-n'] )
|
303
|
+
ilist = options['-n']
|
304
|
+
puts "The \"-n\" option has not yet been implemented!"
|
305
|
+
puts "The supplied issue list: #{ilist}"
|
306
|
+
#sorted = ilist.split(",").sort_by(&:to_i)
|
307
|
+
sorted = sort_list(ilist)
|
308
|
+
puts "The sorted issue list : #{sorted}"
|
309
|
+
end
|
310
|
+
|
311
|
+
# -r
|
312
|
+
# https://docs.github.com/en/rest/reference/rate-limit
|
313
|
+
# NEW: curl -H "Accept: application/vnd.github.v3+json" https://api.github.com/rate_limit
|
314
|
+
# curl -H "Accept: application/vnd.github.v3+json" \
|
315
|
+
# -H "Authorization: token <MY-40-CHAR-TOKEN>" https://api.github.com/rate_limit
|
316
|
+
|
317
|
+
if ( options['-r'] )
|
318
|
+
|
319
|
+
#access_token = "#{ENV['GITHUB_OAUTH_TOKEN']}"
|
320
|
+
#access_token = "<MY-40-CHAR-TOKEN>"
|
321
|
+
|
322
|
+
url = URI("https://api.github.com/rate_limit")
|
323
|
+
http = Net::HTTP.new(url.host, url.port)
|
324
|
+
http.use_ssl = true
|
325
|
+
|
326
|
+
req = Net::HTTP::Get.new(url)
|
327
|
+
req["User-Agent"] = "gh-missue"
|
328
|
+
req["Accept"] = "application/vnd.github.v3+json"
|
329
|
+
|
330
|
+
if (access_token)
|
331
|
+
puts "Using access_token: #{access_token}"
|
332
|
+
req["Authorization"] = "token #{access_token}"
|
333
|
+
end
|
334
|
+
|
335
|
+
res = http.request(req)
|
336
|
+
|
337
|
+
if (debug)
|
338
|
+
puts res.read_body
|
339
|
+
end
|
340
|
+
|
341
|
+
if (res.message != "OK") # 200
|
342
|
+
puts "ERROR: Bad reponse code: #{res.code}\n"
|
343
|
+
puts res.body
|
344
|
+
else
|
345
|
+
#debug = false
|
346
|
+
if (debug)
|
347
|
+
puts hLine + "\nResponse Headers:\n" + hLine
|
348
|
+
puts "#{res.to_hash.inspect}\n"
|
349
|
+
puts hLine + "\nBody:\n" + hLine
|
350
|
+
puts "#{res.body}\n" + hLine
|
351
|
+
end
|
352
|
+
|
353
|
+
#----------------------------------------------------------------------
|
354
|
+
# NEW: resources: {core, graphql, integration_manifest, search }
|
355
|
+
# (There are more!)
|
356
|
+
# Rate Limit Status:
|
357
|
+
# core : for all non-search-related resources in the REST API.
|
358
|
+
# search : for the Search API.
|
359
|
+
# graphql : for the GraphQL API.
|
360
|
+
# integration_manifest : for the GitHub App Manifest code conversion endpoint.
|
361
|
+
#----------------------------------------------------------------------
|
362
|
+
rbr = JSON.parse(res.body)['resources']['core']
|
363
|
+
RTc = Time.at(rbr['reset'])
|
364
|
+
puts "\nCore"
|
365
|
+
puts " Rate limit : #{rbr['limit']}"
|
366
|
+
puts " Remaining : #{rbr['remaining']}"
|
367
|
+
puts " Refresh at : #{RTc}"
|
368
|
+
puts "Search"
|
369
|
+
rbs = JSON.parse(res.body)['resources']['search']
|
370
|
+
RTs = Time.at(rbs['reset'])
|
371
|
+
puts " Search limit : #{rbs['limit']}"
|
372
|
+
puts " Remaining : #{rbs['remaining']}"
|
373
|
+
puts " Refresh at : #{RTs}"
|
374
|
+
end
|
375
|
+
end
|
376
|
+
|
377
|
+
# MAIN
|
378
|
+
if ( options['<source_repo>'] and options['<target_repo>'] )
|
379
|
+
itype = options['-t']
|
380
|
+
#ilist = options['-n']
|
381
|
+
#puts "<itype>: #{itype}" # debug
|
382
|
+
source_repo = options['<source_repo>']
|
383
|
+
target_repo = options['<target_repo>']
|
384
|
+
im = IssueMigrator.new("#{access_token}", "#{source_repo}", "#{target_repo}")
|
385
|
+
if options['-c']
|
386
|
+
im.create_target_labels
|
387
|
+
exit
|
388
|
+
end
|
389
|
+
#exit if options['-c']
|
390
|
+
im.pull_source_issues(itype) # add ilist
|
391
|
+
#im.list_source_issues(itype) #
|
392
|
+
im.push_issues
|
393
|
+
end
|
394
|
+
|
395
|
+
rescue Docopt::Exit => e
|
396
|
+
puts e.message
|
397
|
+
#puts e.backtrace.inspect
|
398
|
+
end
|
399
|
+
|
400
|
+
puts "\nDone!\n"
|
metadata
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: missue
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- E3V3A
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-01-26 00:00:00.000000000 Z
|
12
|
+
dependencies: []
|
13
|
+
description: This gem will list, migrate, and copy issues across github repos with
|
14
|
+
original authors and links.
|
15
|
+
email: xdae3v3a@gmail.com
|
16
|
+
executables:
|
17
|
+
- missue.rb
|
18
|
+
extensions: []
|
19
|
+
extra_rdoc_files: []
|
20
|
+
files:
|
21
|
+
- bin/missue.rb
|
22
|
+
homepage: https://github.com/E3V3A/gh-missue
|
23
|
+
licenses:
|
24
|
+
- MIT
|
25
|
+
metadata: {}
|
26
|
+
post_install_message: Thanks! Now you can migrate like a boss!
|
27
|
+
rdoc_options: []
|
28
|
+
require_paths:
|
29
|
+
- lib
|
30
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 2.7.0
|
35
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
36
|
+
requirements:
|
37
|
+
- - ">="
|
38
|
+
- !ruby/object:Gem::Version
|
39
|
+
version: '0'
|
40
|
+
requirements:
|
41
|
+
- docopt, oktokit
|
42
|
+
rubygems_version: 3.3.3
|
43
|
+
signing_key:
|
44
|
+
specification_version: 4
|
45
|
+
summary: Migrate github issues like a boss!
|
46
|
+
test_files: []
|