github_labeler 0.1.0
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/github_labeler +288 -0
- data/lib/github_labeler.rb +298 -0
- metadata +88 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 21e8c495727bbb1dedeb2c52ece44b3def6c0290
|
4
|
+
data.tar.gz: d18b4d9bf033db40d62841716d26515e7a147655
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e60f2a3d8177b3ff85229decb3ddab4f72dd681991fb6b38235cc267390974576b47ce014ebf58c8466c2627618df03d022c823dc18f8b44dec3fe0aa2123ef7
|
7
|
+
data.tar.gz: 7b8a60e750fc95e5b233898c54e4baf2d674511c3d2c5ce1e5ea652dedccc5014ebfb8f42756ab9001342361df5eb4c026e8161bb5e23c0125cb572427cf7d2b
|
data/bin/github_labeler
ADDED
@@ -0,0 +1,288 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require "commander"
|
4
|
+
require "json"
|
5
|
+
require "github_labeler"
|
6
|
+
|
7
|
+
ARGV.push('-h') if ARGV.empty?
|
8
|
+
|
9
|
+
cli = HighLine.new($stdin, $stderr)
|
10
|
+
|
11
|
+
Commander.configure do
|
12
|
+
program :name, 'GitHub Labeler'
|
13
|
+
program :version, '0.1.0'
|
14
|
+
program :description, 'Simple program for managing issue labels across GitHub repositories.'
|
15
|
+
|
16
|
+
global_option '--verbose', "Enable output of detailed debugging information to STDERR"
|
17
|
+
global_option '--execute', "Execute changes immediately, without asking for confirmation"
|
18
|
+
global_option '--token STRING', String, "GitHub OAuth token for making API calls. If not specified, the GITHUB_OAUTH_TOKEN environment variable is used. Create a token here: https://github.com/settings/token"
|
19
|
+
|
20
|
+
command :duplicate do |c|
|
21
|
+
c.syntax = 'github_labeler duplicate [options]'
|
22
|
+
c.description = 'Duplicate labels from one repository to another'
|
23
|
+
|
24
|
+
c.option '-s', '--source STRING', String, '(required) Source repository to copy labels from (USER/REPO)'
|
25
|
+
c.option '-d', '--destination STRING', String, '(required) Destination repository to copy labels to (USER/REPO), or name of JSON file with list of repositories'
|
26
|
+
|
27
|
+
c.example "Destination is repository name", "github_labeler duplicate -s github/linguist -d foo/bar"
|
28
|
+
c.example "Destinations are specified in JSON file", "github_labeler duplicate -s github/linguist -d dest.json"
|
29
|
+
|
30
|
+
c.action do |args, options|
|
31
|
+
options.default \
|
32
|
+
:verbose => false,
|
33
|
+
:execute => false,
|
34
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
35
|
+
|
36
|
+
raise ArgumentError.new("Token is required") unless options.token
|
37
|
+
raise ArgumentError.new("Source is required") unless options.source
|
38
|
+
raise ArgumentError.new("Destination is required") unless options.destination
|
39
|
+
|
40
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
41
|
+
destination = File.file?(options.destination) ? JSON.parse(IO.read(options.destination), {:symbolize_names => true}) : [options.destination]
|
42
|
+
changes = labeler.duplicate_labels_from_repo(options.source, destination)
|
43
|
+
|
44
|
+
puts JSON.pretty_generate(changes)
|
45
|
+
|
46
|
+
if changes.size == 0
|
47
|
+
cli.say("No changes to execute.")
|
48
|
+
elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
|
49
|
+
labeler.execute_changes(changes)
|
50
|
+
end
|
51
|
+
|
52
|
+
cli.say("Done!")
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
command :export do |c|
|
57
|
+
c.syntax = 'github_labeler export [options]'
|
58
|
+
c.description = 'Export a list of labels from a repository or list of repositories'
|
59
|
+
|
60
|
+
c.option '-r', '--repo STRING', String, '(required) Repository from which to extract labels (USER/REPO)'
|
61
|
+
|
62
|
+
c.example "Repository is repository name", "github_labeler export -r foo/bar"
|
63
|
+
|
64
|
+
c.action do |args, options|
|
65
|
+
options.default \
|
66
|
+
:verbose => false,
|
67
|
+
:execute => false,
|
68
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
69
|
+
|
70
|
+
raise ArgumentError.new("Token is required") unless options.token
|
71
|
+
raise ArgumentError.new("Repo is required") unless options.repo
|
72
|
+
raise ArgumentError.new("Token is required") unless options.token
|
73
|
+
|
74
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
75
|
+
labels = labeler.export_labels_from_repo(options.repo)
|
76
|
+
|
77
|
+
puts JSON.pretty_generate(labels)
|
78
|
+
|
79
|
+
cli.say("Done!")
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
command :execute do |c|
|
84
|
+
c.syntax = 'github_labeler execute [options]'
|
85
|
+
c.description = 'Execute changes previously created by the program'
|
86
|
+
|
87
|
+
c.option '-c', '--changes STRING', String, '(required) Name of JSON file with list of changes'
|
88
|
+
|
89
|
+
c.example "Changes are specified in JSON file", "github_labeler export -c changes.json"
|
90
|
+
|
91
|
+
c.action do |args, options|
|
92
|
+
options.default \
|
93
|
+
:verbose => false,
|
94
|
+
:execute => false,
|
95
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
96
|
+
|
97
|
+
raise ArgumentError.new("Token is required") unless options.token
|
98
|
+
raise ArgumentError.new("Changes are required") unless options.changes
|
99
|
+
|
100
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
101
|
+
changes = JSON.parse(IO.read(options.changes), {:symbolize_names => true})
|
102
|
+
result = labeler.execute_changes(changes)
|
103
|
+
|
104
|
+
puts JSON.pretty_generate(result)
|
105
|
+
|
106
|
+
cli.say("Done!")
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
command :add do |c|
|
111
|
+
c.syntax = 'github_labeler add [options]'
|
112
|
+
c.description = 'Add one or more label to one or more repositories'
|
113
|
+
|
114
|
+
c.option '-l', '--labels STRING', String, '(required) Label (NAME#COLOR) or name of JSON file with list of labels'
|
115
|
+
c.option '-r', '--repos STRING', String, '(required) Destination repository to add labels to (USER/REPO) or name of JSON file with list of repositories'
|
116
|
+
|
117
|
+
c.example "Label is a string and destination repo is repository name", "github_labeler add -r github/linguist -l api#c7def8"
|
118
|
+
c.example "Labels and repositories are specified in JSON files", "github_labeler add -r repos.json -l labels.json"
|
119
|
+
|
120
|
+
c.action do |args, options|
|
121
|
+
options.default \
|
122
|
+
:verbose => false,
|
123
|
+
:execute => false,
|
124
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
125
|
+
|
126
|
+
raise ArgumentError.new("Token is required") unless options.token
|
127
|
+
raise ArgumentError.new("Labels are required") unless options.labels
|
128
|
+
raise ArgumentError.new("Repos are required") unless options.repos
|
129
|
+
|
130
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
131
|
+
repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
|
132
|
+
labels = options.labels
|
133
|
+
|
134
|
+
if File.file?(options.labels)
|
135
|
+
labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
|
136
|
+
else
|
137
|
+
label_name, label_color = labels.split("#")
|
138
|
+
labels = [{:name => label_name, :color => label_color}]
|
139
|
+
end
|
140
|
+
|
141
|
+
changes = labeler.add_labels_to_repos(repos, labels)
|
142
|
+
|
143
|
+
puts JSON.pretty_generate(changes)
|
144
|
+
|
145
|
+
if changes.size == 0
|
146
|
+
cli.say("No changes to execute.")
|
147
|
+
elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
|
148
|
+
labeler.execute_changes(changes)
|
149
|
+
end
|
150
|
+
|
151
|
+
cli.say("Done!")
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
command :delete do |c|
|
156
|
+
c.syntax = 'github_labeler delete [options]'
|
157
|
+
c.description = 'Delete one or more label from one or more repositories'
|
158
|
+
|
159
|
+
c.option '-l', '--labels STRING', String, '(required) Label (NAME) or name of JSON file with list of labels'
|
160
|
+
c.option '-r', '--repos STRING', String, '(required) Destination repository to delete labels from (USER/REPO) or name of JSON file with list of repositories'
|
161
|
+
|
162
|
+
c.example "Label is a string and destination repo is repository name", "github_labeler delete -r github/linguist -l api"
|
163
|
+
c.example "Labels and repositories are specified in JSON files", "github_labeler delete -r repos.json -l labels.json"
|
164
|
+
|
165
|
+
c.action do |args, options|
|
166
|
+
options.default \
|
167
|
+
:verbose => false,
|
168
|
+
:execute => false,
|
169
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
170
|
+
|
171
|
+
raise ArgumentError.new("Token is required") unless options.token
|
172
|
+
raise ArgumentError.new("Labels are required") unless options.labels
|
173
|
+
raise ArgumentError.new("Repos are required") unless options.repos
|
174
|
+
|
175
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
176
|
+
repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
|
177
|
+
labels = options.labels
|
178
|
+
|
179
|
+
if File.file?(options.labels)
|
180
|
+
labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
|
181
|
+
else
|
182
|
+
labels = [{:name => labels, :color => nil}]
|
183
|
+
end
|
184
|
+
|
185
|
+
changes = labeler.delete_labels_from_repos(repos, labels)
|
186
|
+
|
187
|
+
puts JSON.pretty_generate(changes)
|
188
|
+
|
189
|
+
if changes.size == 0
|
190
|
+
cli.say("No changes to execute.")
|
191
|
+
elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
|
192
|
+
labeler.execute_changes(changes)
|
193
|
+
end
|
194
|
+
|
195
|
+
cli.say("Done!")
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
command :rename do |c|
|
200
|
+
c.syntax = 'github_labeler rename [options]'
|
201
|
+
c.description = 'Rename one or more labels in one or more repositories'
|
202
|
+
|
203
|
+
c.option '-l', '--labels STRING', String, '(required) Label (OLDNAME/NEWNAME) or name of JSON file with list of labels'
|
204
|
+
c.option '-r', '--repos STRING', String, '(required) Destination repository to rename labels in (USER/REPO) or name of JSON file with list of repositories'
|
205
|
+
|
206
|
+
c.example "Label is a string and destination repo is repository name", "github_labeler rename -r github/linguist -l design/ui"
|
207
|
+
c.example "Labels and repositories are specified in JSON files", "github_labeler rename -r repos.json -l labels.json"
|
208
|
+
|
209
|
+
c.action do |args, options|
|
210
|
+
options.default \
|
211
|
+
:verbose => false,
|
212
|
+
:execute => false,
|
213
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
214
|
+
|
215
|
+
raise ArgumentError.new("Token is required") unless options.token
|
216
|
+
raise ArgumentError.new("Labels are required") unless options.labels
|
217
|
+
raise ArgumentError.new("Repos are required") unless options.repos
|
218
|
+
|
219
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
220
|
+
repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
|
221
|
+
labels = options.labels
|
222
|
+
|
223
|
+
if File.file?(options.labels)
|
224
|
+
labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
|
225
|
+
else
|
226
|
+
old_name, new_name = labels.split("/")
|
227
|
+
labels = [{:name => old_name, :new_name => new_name}]
|
228
|
+
end
|
229
|
+
|
230
|
+
changes = labeler.rename_labels_in_repos(repos, labels)
|
231
|
+
|
232
|
+
puts JSON.pretty_generate(changes)
|
233
|
+
|
234
|
+
if changes.size == 0
|
235
|
+
cli.say("No changes to execute.")
|
236
|
+
elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
|
237
|
+
labeler.execute_changes(changes)
|
238
|
+
end
|
239
|
+
|
240
|
+
cli.say("Done!")
|
241
|
+
end
|
242
|
+
end
|
243
|
+
|
244
|
+
command :recolor do |c|
|
245
|
+
c.syntax = 'github_labeler recolor [options]'
|
246
|
+
c.description = 'Recolor one or more labels in one or more repositories'
|
247
|
+
|
248
|
+
c.option '-l', '--labels STRING', String, '(required) Label (NAME#COLOR) or name of JSON file with list of labels'
|
249
|
+
c.option '-r', '--repos STRING', String, '(required) Destination repository to recolor labels in (USER/REPO) or name of JSON file with list of repositories'
|
250
|
+
|
251
|
+
c.example "Label is a string and destination repo is repository name", "github_labeler recolor -r github/linguist -l api#c7def8"
|
252
|
+
c.example "Labels and repositories are specified in JSON files", "github_labeler recolor -r repos.json -l labels.json"
|
253
|
+
|
254
|
+
c.action do |args, options|
|
255
|
+
options.default \
|
256
|
+
:verbose => false,
|
257
|
+
:execute => false,
|
258
|
+
:token => ENV["GITHUB_OAUTH_TOKEN"]
|
259
|
+
|
260
|
+
raise ArgumentError.new("Token is required") unless options.token
|
261
|
+
raise ArgumentError.new("Labels are required") unless options.labels
|
262
|
+
raise ArgumentError.new("Repos are required") unless options.repos
|
263
|
+
|
264
|
+
labeler = GitHubLabeler.new(options.token, options.verbose)
|
265
|
+
repos = File.file?(options.repos) ? JSON.parse(IO.read(options.repos), {:symbolize_names => true}) : [options.repos]
|
266
|
+
labels = options.labels
|
267
|
+
|
268
|
+
if File.file?(options.labels)
|
269
|
+
labels = JSON.parse(IO.read(options.labels), {:symbolize_names => true})
|
270
|
+
else
|
271
|
+
label_name, label_color = labels.split("#")
|
272
|
+
labels = [{:name => label_name, :color => label_color}]
|
273
|
+
end
|
274
|
+
|
275
|
+
changes = labeler.recolor_labels_in_repos(repos, labels)
|
276
|
+
|
277
|
+
puts JSON.pretty_generate(changes)
|
278
|
+
|
279
|
+
if changes.size == 0
|
280
|
+
cli.say("No changes to execute.")
|
281
|
+
elsif options.execute or cli.agree("Execute #{changes.size} changes? [y/n]")
|
282
|
+
labeler.execute_changes(changes)
|
283
|
+
end
|
284
|
+
|
285
|
+
cli.say("Done!")
|
286
|
+
end
|
287
|
+
end
|
288
|
+
end
|
@@ -0,0 +1,298 @@
|
|
1
|
+
require "json"
|
2
|
+
require "logger"
|
3
|
+
require "Octokit"
|
4
|
+
|
5
|
+
Octokit.auto_paginate = true
|
6
|
+
|
7
|
+
class GitHubLabeler
|
8
|
+
attr_accessor :client, # Octokit client for acesing the API
|
9
|
+
:repo_labels, # Labels cache for repositories
|
10
|
+
:logger # Logger for writing debugging info
|
11
|
+
|
12
|
+
def initialize(token, verbose=false, labels=nil)
|
13
|
+
@logger = Logger.new(STDERR)
|
14
|
+
@logger.sev_threshold = verbose ? Logger::DEBUG : Logger::WARN
|
15
|
+
|
16
|
+
@logger.debug "Creating new GitHubLabeler instance."
|
17
|
+
|
18
|
+
@logger.debug "Creating a new Octokit client with token #{token[0..5]}"
|
19
|
+
|
20
|
+
begin
|
21
|
+
@client = Octokit::Client.new(:access_token => token)
|
22
|
+
@client.rate_limit
|
23
|
+
rescue Octokit::Unauthorized => exception
|
24
|
+
@logger.error "Token #{token[0..5]} is not valid"
|
25
|
+
raise ArgumentError.new("Token #{token[0..5]} is not valid")
|
26
|
+
end
|
27
|
+
|
28
|
+
@logger.debug "Token #{token[0..5]} is valid"
|
29
|
+
|
30
|
+
@repo_labels = !labels.nil? ? labels : {}
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Checks if the remaining rate limit allows us to make the specified number of
|
35
|
+
# changes
|
36
|
+
#
|
37
|
+
def is_remaining_rate_limit_sufficient?(expected_number_of_calls)
|
38
|
+
remaining_limit = @client.rate_limit.remaining
|
39
|
+
|
40
|
+
@logger.debug "There are #{expected_number_of_calls} API calls to be made, and the remaining API rate limit quota is #{remaining_limit}."
|
41
|
+
|
42
|
+
return expected_number_of_calls <= @client.rate_limit.remaining
|
43
|
+
end
|
44
|
+
|
45
|
+
#
|
46
|
+
# Executes a list of label changes. Each change has the following format:
|
47
|
+
#
|
48
|
+
# {
|
49
|
+
# :type => "update/create/delete",
|
50
|
+
# :repo => "testrename/testing",
|
51
|
+
# :label => {:color => "fc2929", :name => "design", :new_name => "ui"}
|
52
|
+
# }
|
53
|
+
#
|
54
|
+
def execute_changes(changes, options = {})
|
55
|
+
@logger.debug "Executing changes"
|
56
|
+
|
57
|
+
if !is_remaining_rate_limit_sufficient?(changes.size)
|
58
|
+
@logger.error "Remaining rate limit is not enough to make all changes. Wait for the limit to refresh and try again."
|
59
|
+
return []
|
60
|
+
end
|
61
|
+
|
62
|
+
changes = validate_changes(changes, options)
|
63
|
+
|
64
|
+
changes.each do |change|
|
65
|
+
@logger.debug "Executing change: #{change_string(change)}"
|
66
|
+
|
67
|
+
if change[:type] == "add"
|
68
|
+
success = @client.add_label(change[:repo], change[:label][:name], change[:label][:color])
|
69
|
+
|
70
|
+
if !@repo_labels[change[:repo]].nil?
|
71
|
+
@repo_labels[change[:repo]][success[:name].downcase] = {:name => success[:name], :color => success[:color]}
|
72
|
+
end
|
73
|
+
|
74
|
+
@logger.debug "Change succeded"
|
75
|
+
next
|
76
|
+
end
|
77
|
+
|
78
|
+
if change[:type] == "update"
|
79
|
+
new_label = {:name => change[:label][:new_name] || change[:label][:name]}
|
80
|
+
if !change[:label][:color].nil?
|
81
|
+
new_label[:color] = change[:label][:color]
|
82
|
+
end
|
83
|
+
|
84
|
+
success = @client.update_label(change[:repo], change[:label][:name], new_label)
|
85
|
+
|
86
|
+
if !@repo_labels[change[:repo]].nil?
|
87
|
+
@repo_labels[change[:repo]][success[:name].downcase] = {:name => success[:name], :color => success[:color]}
|
88
|
+
end
|
89
|
+
|
90
|
+
@logger.debug "Change succeded"
|
91
|
+
next
|
92
|
+
end
|
93
|
+
|
94
|
+
if change[:type] == "delete"
|
95
|
+
success = @client.delete_label!(change[:repo], change[:label][:name])
|
96
|
+
|
97
|
+
if !@repo_labels[change[:repo]].nil?
|
98
|
+
@repo_labels[change[:repo]][change[:label][:name].downcase] = nil
|
99
|
+
end
|
100
|
+
|
101
|
+
@logger.debug "Change succeded"
|
102
|
+
next
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
@logger.debug "Done executing changes"
|
107
|
+
|
108
|
+
return changes
|
109
|
+
end
|
110
|
+
|
111
|
+
#
|
112
|
+
# Updates repo labels cache for a specific label
|
113
|
+
#
|
114
|
+
def update_repos_labels_cache(repo, label)
|
115
|
+
@repo_labels[change[:repo]][change[:label][:name].downcase] = success
|
116
|
+
end
|
117
|
+
|
118
|
+
#
|
119
|
+
# Validates changes by merging multiple updates into one, removing
|
120
|
+
# duplicates, detecting conflicts, etc.
|
121
|
+
#
|
122
|
+
def validate_changes(changes, options = {})
|
123
|
+
return changes.sort_by { |hsh| [hsh["repo"], hsh["type"]] }
|
124
|
+
end
|
125
|
+
|
126
|
+
#
|
127
|
+
# Create changes for adding a list of labels to a list of repos
|
128
|
+
#
|
129
|
+
def add_labels_to_repos(repos, labels, options = {})
|
130
|
+
@logger.debug "Adding labels to repositories"
|
131
|
+
return process_labels_for_repos(repos, labels, method(:add_label_to_repo), options)
|
132
|
+
end
|
133
|
+
|
134
|
+
#
|
135
|
+
# Create changes for deleting a list of labels from a list of repos
|
136
|
+
#
|
137
|
+
def delete_labels_from_repos(repos, labels, options = {})
|
138
|
+
@logger.debug "Deleting labels from repositories"
|
139
|
+
return process_labels_for_repos(repos, labels, method(:delete_label_from_repo), options)
|
140
|
+
end
|
141
|
+
|
142
|
+
#
|
143
|
+
# Create changes for renaming a list of labels in a list of repos
|
144
|
+
#
|
145
|
+
def rename_labels_in_repos(repos, labels, options = {})
|
146
|
+
@logger.debug "Renaming labels in repositories"
|
147
|
+
return process_labels_for_repos(repos, labels, method(:rename_label_in_repo), options)
|
148
|
+
end
|
149
|
+
|
150
|
+
#
|
151
|
+
# Create changes for recoloring a list of labels in a list of repos
|
152
|
+
#
|
153
|
+
def recolor_labels_in_repos(repos, labels, options = {})
|
154
|
+
@logger.debug "Recoloring labels in repositories"
|
155
|
+
return process_labels_for_repos(repos, labels, method(:recolor_label_in_repo), options)
|
156
|
+
end
|
157
|
+
|
158
|
+
#
|
159
|
+
# Generic creation of changes for list of labels and list of repos
|
160
|
+
#
|
161
|
+
def process_labels_for_repos(repos, labels, change_creator, options = {})
|
162
|
+
if !is_remaining_rate_limit_sufficient?(repos.size)
|
163
|
+
@logger.error "Rate limit is not enough to process all labels in repositories"
|
164
|
+
return nil
|
165
|
+
end
|
166
|
+
|
167
|
+
changes = []
|
168
|
+
|
169
|
+
for repo in repos
|
170
|
+
repo_name = repo if repo.instance_of?(String)
|
171
|
+
repo_name = repo[:full_name] if repo.instance_of?(Hash)
|
172
|
+
|
173
|
+
@logger.debug "Processing labels for repository #{repo_name}"
|
174
|
+
|
175
|
+
refresh_repo_labels(repo_name, options)
|
176
|
+
|
177
|
+
for label in labels
|
178
|
+
@logger.debug "Processing label #{label[:name]}"
|
179
|
+
change = change_creator.call(repo_name, label, options)
|
180
|
+
changes << change if !change.nil?
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
return changes
|
185
|
+
end
|
186
|
+
|
187
|
+
#
|
188
|
+
# Fetches the list of labels in a repository and stores them
|
189
|
+
# in the labels cache
|
190
|
+
#
|
191
|
+
def refresh_repo_labels(repo, options = {})
|
192
|
+
@logger.debug "Fetching label information for #{repo}"
|
193
|
+
|
194
|
+
@repo_labels[repo] = {}
|
195
|
+
@client.labels(repo).each do |label|
|
196
|
+
@repo_labels[repo][label[:name].downcase] = label
|
197
|
+
end
|
198
|
+
end
|
199
|
+
|
200
|
+
#
|
201
|
+
# Create a single change for adding a label to a repo
|
202
|
+
#
|
203
|
+
def add_label_to_repo(repo, label, options = {})
|
204
|
+
existing_label = @repo_labels[repo][label[:name].downcase]
|
205
|
+
|
206
|
+
if existing_label
|
207
|
+
if existing_label[:color] != label[:color] or existing_label[:name] != label[:name]
|
208
|
+
@logger.warn "Label #{label[:name]} already exist, creating an update"
|
209
|
+
return { :type => "update", :repo => repo, :label => label }
|
210
|
+
end
|
211
|
+
else
|
212
|
+
return { :type => "add", :repo => repo, :label => label }
|
213
|
+
end
|
214
|
+
|
215
|
+
@logger.warn "Label #{label[:name]} already exist and is the same. No change created"
|
216
|
+
return nil
|
217
|
+
end
|
218
|
+
|
219
|
+
#
|
220
|
+
# Create a single change for deleting a label from a repo
|
221
|
+
#
|
222
|
+
def delete_label_from_repo(repo, label, options = {})
|
223
|
+
existing_label = @repo_labels[repo][label[:name].downcase]
|
224
|
+
|
225
|
+
if existing_label
|
226
|
+
return { :type => "delete", :repo => repo, :label => label }
|
227
|
+
end
|
228
|
+
|
229
|
+
@logger.warn "Label #{label[:name]} doesn't exist. No change created"
|
230
|
+
return nil
|
231
|
+
end
|
232
|
+
|
233
|
+
#
|
234
|
+
# Create a single change for renaming a label in a repo
|
235
|
+
#
|
236
|
+
def rename_label_in_repo(repo, label, options = {})
|
237
|
+
existing_label = @repo_labels[repo][label[:name].downcase]
|
238
|
+
|
239
|
+
if existing_label
|
240
|
+
if label[:new_name] and label[:new_name] != label[:name]
|
241
|
+
return { :type => "update", :repo => repo, :label => label }
|
242
|
+
end
|
243
|
+
else
|
244
|
+
@logger.warn "Label #{label[:name]} doesn't exist. Creating a create change"
|
245
|
+
newLabel = {:name => label[:new_name], :color => label[:color]}
|
246
|
+
return { :type => "add", :repo => repo, :label => newLabel }
|
247
|
+
end
|
248
|
+
|
249
|
+
@logger.warn "Label #{label[:name]} exist and is the same. No change created"
|
250
|
+
return nil
|
251
|
+
end
|
252
|
+
|
253
|
+
#
|
254
|
+
# Create a single change for recoloring a label in a repo
|
255
|
+
#
|
256
|
+
def recolor_label_in_repo(repo, label, options = {})
|
257
|
+
existing_label = @repo_labels[repo][label[:name].downcase]
|
258
|
+
|
259
|
+
if existing_label
|
260
|
+
if existing_label[:color] != label[:color]
|
261
|
+
return { :type => "update", :repo => repo, :label => label }
|
262
|
+
end
|
263
|
+
else
|
264
|
+
@logger.warn "Label #{label[:name]} doesn't exist. Creating a create change"
|
265
|
+
return { :type => "add", :repo => repo, :label => label }
|
266
|
+
end
|
267
|
+
|
268
|
+
@logger.warn "Label #{label[:name]} exist and is the same. No change created"
|
269
|
+
return nil
|
270
|
+
end
|
271
|
+
|
272
|
+
#
|
273
|
+
# Creates changes for duplicating labels from one repo to other repos
|
274
|
+
#
|
275
|
+
def duplicate_labels_from_repo(repo_source, repos_end, options = {})
|
276
|
+
@logger.debug "Duplicating labels from repo #{repo_source}"
|
277
|
+
source_repo_labels = export_labels_from_repo(repo_source, options)
|
278
|
+
return add_labels_to_repos(repos_end, source_repo_labels, options)
|
279
|
+
end
|
280
|
+
|
281
|
+
#
|
282
|
+
# Fetches a list of labels for a repository
|
283
|
+
#
|
284
|
+
def export_labels_from_repo(repo, options = {})
|
285
|
+
@logger.debug "Exporting labels from repo #{repo}"
|
286
|
+
|
287
|
+
@client.labels(repo).map do |label|
|
288
|
+
{ :name => label[:name], :color => label[:color] }
|
289
|
+
end
|
290
|
+
end
|
291
|
+
|
292
|
+
#
|
293
|
+
# Get a human readable string representation of a change
|
294
|
+
#
|
295
|
+
def change_string(change, options = {})
|
296
|
+
return "#{change[:repo]} - #{change[:type]} - #{change[:label][:name]} - color: #{change[:label][:color]} - new_name: #{change[:label][:new_name]}"
|
297
|
+
end
|
298
|
+
end
|
metadata
ADDED
@@ -0,0 +1,88 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: github_labeler
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ivan Zuzak
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2015-08-21 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: commander
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - "~>"
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '4.3'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '4.3'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: octokit
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - "~>"
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '4.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '4.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - "~>"
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '3.3'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - "~>"
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '3.3'
|
55
|
+
description:
|
56
|
+
email: izuzak@gmail.com
|
57
|
+
executables:
|
58
|
+
- github_labeler
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- bin/github_labeler
|
63
|
+
- lib/github_labeler.rb
|
64
|
+
homepage: https://github.com/izuzak/github_labeler
|
65
|
+
licenses:
|
66
|
+
- MIT
|
67
|
+
metadata: {}
|
68
|
+
post_install_message:
|
69
|
+
rdoc_options: []
|
70
|
+
require_paths:
|
71
|
+
- lib
|
72
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
73
|
+
requirements:
|
74
|
+
- - ">="
|
75
|
+
- !ruby/object:Gem::Version
|
76
|
+
version: '0'
|
77
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: '0'
|
82
|
+
requirements: []
|
83
|
+
rubyforge_project:
|
84
|
+
rubygems_version: 2.2.3
|
85
|
+
signing_key:
|
86
|
+
specification_version: 4
|
87
|
+
summary: Utility for running actions on issue labels in groups of GitHub repositories
|
88
|
+
test_files: []
|