github_changelog_generator 1.1.2 → 1.1.4

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.
@@ -0,0 +1,32 @@
1
+ # coding: utf-8
2
+
3
+ lib = File.expand_path('../lib', __FILE__)
4
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
5
+ require 'github_changelog_generator/version'
6
+
7
+ Gem::Specification.new do |spec|
8
+ spec.name = "github_changelog_generator"
9
+ spec.version = GitHubChangelogGenerator::VERSION
10
+ spec.default_executable = "github_changelog_generator"
11
+
12
+ spec.required_ruby_version = '>= 1.9.3'
13
+ spec.authors = ["Petr Korolev"]
14
+ spec.email = %q{sky4winder+github_changelog_generator@gmail.com}
15
+ spec.date = `date +"%Y-%m-%d"`.strip!
16
+ spec.summary = %q{Script, that automatically generate change-log from your tags and pull-requests.}
17
+ spec.description = %q{Script, that automatically generate change-log from your tags and pull-requests}
18
+ spec.homepage = %q{https://github.com/skywinder/Github-Changelog-Generator}
19
+ spec.license = "MIT"
20
+
21
+ spec.files = `git ls-files -z`.split("\x0")
22
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
23
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
24
+ spec.require_paths = ["lib"]
25
+
26
+ spec.add_development_dependency "bundler", "~> 1.7"
27
+ spec.add_development_dependency "rake", "~> 10.0"
28
+
29
+ spec.add_runtime_dependency(%q<github_api>, ["~> 0.12"])
30
+ spec.add_runtime_dependency(%q<colorize>, ["~> 0.7"])
31
+
32
+ end
@@ -2,329 +2,376 @@
2
2
 
3
3
  require 'github_api'
4
4
  require 'json'
5
- require 'httparty'
5
+ require 'colorize'
6
6
  require_relative 'github_changelog_generator/parser'
7
+ require_relative 'github_changelog_generator/version'
7
8
 
9
+ module GitHubChangelogGenerator
10
+ class ChangelogGenerator
8
11
 
9
- class ChangelogGenerator
12
+ attr_accessor :options, :all_tags, :github
10
13
 
11
- attr_accessor :options, :all_tags, :github
14
+ def initialize
12
15
 
13
- def initialize()
16
+ @options = Parser.parse_options
14
17
 
15
- @options = Parser.parse_options
16
- if @options[:token]
17
- @github = Github.new oauth_token: @options[:token]
18
- else
19
- @github = Github.new
20
- end
21
- @all_tags = self.get_all_tags
22
- @pull_requests = self.get_all_closed_pull_requests
23
- @issues = self.get_all_issues
24
-
25
- @tag_times_hash = {}
26
- end
18
+ github_token
27
19
 
28
- def print_json(json)
29
- puts JSON.pretty_generate(json)
30
- end
20
+ if @github_token.nil?
21
+ @github = Github.new
22
+ else
23
+ @github = Github.new oauth_token: @github_token
24
+ end
31
25
 
32
- def exec_command(cmd)
33
- exec_cmd = "cd #{$project_path} && #{cmd}"
34
- %x[#{exec_cmd}]
35
- end
26
+ @all_tags = self.get_all_tags
27
+ @pull_requests = self.get_all_closed_pull_requests
28
+ @issues = self.get_all_issues
36
29
 
30
+ @tag_times_hash = {}
31
+ end
37
32
 
38
- def get_all_closed_pull_requests
39
- request = @github.pull_requests.list @options[:user], @options[:project], :state => 'closed'
40
- pull_requests = request.body
33
+ def print_json(json)
34
+ puts JSON.pretty_generate(json)
35
+ end
41
36
 
42
- if @options[:verbose]
43
- puts 'Receive all pull requests'
37
+ def exec_command(cmd)
38
+ exec_cmd = "cd #{$project_path} && #{cmd}"
39
+ %x[#{exec_cmd}]
44
40
  end
45
41
 
46
- pull_requests
47
- end
48
42
 
49
- def compund_changelog
50
- if @options[:verbose]
51
- puts 'Generating changelog:'
43
+ def get_all_closed_pull_requests
44
+ response = @github.pull_requests.list @options[:user], @options[:project], :state => 'closed'
45
+
46
+ pull_requests = []
47
+ response.each_page do |page|
48
+ pull_requests.concat(page)
49
+ end
50
+
51
+ if @options[:verbose]
52
+ puts "Receive all pull requests: #{pull_requests.count}"
53
+ end
54
+
55
+ pull_requests
52
56
  end
53
57
 
54
- log = "# Changelog\n\n"
58
+ def compund_changelog
59
+ if @options[:verbose]
60
+ puts 'Generating changelog:'
61
+ end
62
+
63
+ log = "# Changelog\n\n"
55
64
 
56
- if @options[:last]
57
- log += self.generate_log_between_tags(self.all_tags[0], self.all_tags[1])
58
- elsif @options[:tag1] && @options[:tag2]
65
+ if @options[:last]
66
+ log += self.generate_log_between_tags(self.all_tags[0], self.all_tags[1])
67
+ elsif @options[:tag1] && @options[:tag2]
59
68
 
60
- tag1 = @options[:tag1]
61
- tag2 = @options[:tag2]
62
- tags_strings = []
63
- self.all_tags.each { |x| tags_strings.push(x['name']) }
69
+ tag1 = @options[:tag1]
70
+ tag2 = @options[:tag2]
71
+ tags_strings = []
72
+ self.all_tags.each { |x| tags_strings.push(x['name']) }
64
73
 
65
- if tags_strings.include?(tag1)
66
- if tags_strings.include?(tag2)
67
- hash = Hash[tags_strings.map.with_index.to_a]
68
- index1 = hash[tag1]
69
- index2 = hash[tag2]
70
- log += self.generate_log_between_tags(self.all_tags[index1], self.all_tags[index2])
74
+ if tags_strings.include?(tag1)
75
+ if tags_strings.include?(tag2)
76
+ hash = Hash[tags_strings.map.with_index.to_a]
77
+ index1 = hash[tag1]
78
+ index2 = hash[tag2]
79
+ log += self.generate_log_between_tags(self.all_tags[index1], self.all_tags[index2])
80
+ else
81
+ puts "Can't find tag #{tag2} -> exit"
82
+ exit
83
+ end
71
84
  else
72
- puts "Can't find tag #{tag2} -> exit"
85
+ puts "Can't find tag #{tag1} -> exit"
73
86
  exit
74
87
  end
75
88
  else
76
- puts "Can't find tag #{tag1} -> exit"
77
- exit
89
+ log += self.generate_log_for_all_tags
78
90
  end
79
- else
80
- log += self.generate_log_for_all_tags
81
- end
82
91
 
83
- log += "\n\n\\* *This changelog was generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
92
+ log += "\n\n\\* *This changelog was generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
84
93
 
85
- output_filename = "#{@options[:output]}"
86
- File.open(output_filename, 'w') { |file| file.write(log) }
94
+ output_filename = "#{@options[:output]}"
95
+ File.open(output_filename, 'w') { |file| file.write(log) }
87
96
 
88
- puts "Done! Generated log placed in #{output_filename}"
97
+ puts "Done! Generated log placed in #{output_filename}"
89
98
 
90
- end
91
-
92
- def generate_log_for_all_tags
93
- log = ''
94
- for index in 1 ... self.all_tags.size
95
- log += self.generate_log_between_tags(self.all_tags[index-1], self.all_tags[index])
96
99
  end
97
100
 
98
- log += self.generate_log_before_tag(self.all_tags.last)
101
+ def generate_log_for_all_tags
102
+ log = ''
103
+ @all_tags.each { |tag| self.get_time_of_tag(tag) }
99
104
 
100
- log
101
- end
102
105
 
103
- def is_megred(number)
104
- @github.pull_requests.merged? @options[:user], @options[:project], number
105
- end
106
+ if @options[:verbose]
107
+ puts "Sorting tags.."
108
+ end
109
+
110
+ @all_tags.sort_by! { |x| self.get_time_of_tag(x) }.reverse!
111
+
112
+ if @options[:verbose]
113
+ puts "Generating log.."
114
+ end
106
115
 
107
- def get_all_tags
116
+ for index in 1 ... self.all_tags.size
117
+ log += self.generate_log_between_tags(self.all_tags[index], self.all_tags[index-1])
118
+ end
108
119
 
109
- url = "https://api.github.com/repos/#{@options[:user]}/#{@options[:project]}/tags"
120
+ log += self.generate_log_before_tag(self.all_tags.last)
110
121
 
111
- if @options[:verbose]
112
- puts "Receive tags for repo #{url}"
122
+ log
113
123
  end
114
124
 
115
- if @options[:token]
116
- response = HTTParty.get(url,
117
- :headers => {'Authorization' => "token #{@options[:token]}",
118
- 'User-Agent' => 'Changelog-Generator'})
119
- else
120
- response = HTTParty.get(url,
121
- :headers => {'User-Agent' => 'Changelog-Generator'})
125
+ def is_megred(number)
126
+ @github.pull_requests.merged? @options[:user], @options[:project], number
122
127
  end
123
128
 
124
- json_parse = JSON.parse(response.body)
129
+ def get_all_tags
130
+
131
+ url = "https://api.github.com/repos/#{@options[:user]}/#{@options[:project]}/tags"
132
+
133
+ if @options[:verbose]
134
+
135
+ puts "Receive tags for repo #{url}"
136
+ end
137
+
138
+ response = @github.repos.tags @options[:user], @options[:project]
125
139
 
126
- if @options[:verbose]
127
- puts "Found #{json_parse.count} tags"
140
+ tags = []
141
+ response.each_page do |page|
142
+ tags.concat(page)
143
+ end
144
+
145
+ if @options[:verbose]
146
+ puts "Found #{tags.count} tags"
147
+ end
148
+
149
+ tags
128
150
  end
129
151
 
130
- json_parse
131
- end
152
+ def github_token
153
+ if @options[:token]
154
+ return @github_token ||= @options[:token]
155
+ end
132
156
 
133
- def generate_log_between_tags(since_tag, till_tag)
134
- since_tag_time = self.get_time_of_tag(since_tag)
135
- till_tag_time = self.get_time_of_tag(till_tag)
157
+ env_var = ENV.fetch 'CHANGELOG_GITHUB_TOKEN', nil
158
+
159
+ unless env_var
160
+ puts "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found.".yellow
161
+ puts "This script can make only 50 requests to GitHub API per hour without token!".yellow
162
+ end
163
+
164
+ @github_token ||= env_var
136
165
 
137
- # if we mix up tags order - lits fix it!
138
- if since_tag_time > till_tag_time
139
- since_tag, till_tag = till_tag, since_tag
140
- since_tag_time, till_tag_time = till_tag_time, since_tag_time
141
166
  end
142
167
 
143
- till_tag_name = till_tag['name']
144
168
 
145
- pull_requests = Array.new(@pull_requests)
169
+ def generate_log_between_tags(older_tag, newer_tag)
170
+ older_tag_time = self.get_time_of_tag(older_tag)
171
+ newer_tag_time = self.get_time_of_tag(newer_tag)
146
172
 
147
- pull_requests.delete_if { |req|
148
- if req[:merged_at]
149
- t = Time.parse(req[:merged_at]).utc
150
- tag_is_later_since = t > since_tag_time
151
- tag_is_before_till = t <= till_tag_time
173
+ # if we mix up tags order - lits fix it!
174
+ # if older_tag_time < newer_tag_time
175
+ # older_tag, newer_tag = newer_tag, older_tag
176
+ # older_tag_time, newer_tag_time = newer_tag_time, older_tag_time
177
+ # puts "Swap tags!"
178
+ # end
152
179
 
153
- in_range = (tag_is_later_since) && (tag_is_before_till)
154
- !in_range
155
- else
156
- true
157
- end
180
+ newer_tag_name = newer_tag['name']
158
181
 
159
- }
182
+ pull_requests = Array.new(@pull_requests)
160
183
 
161
- issues = Array.new(@issues)
184
+ pull_requests.delete_if { |req|
185
+ if req[:merged_at]
186
+ t = Time.parse(req[:merged_at]).utc
187
+ tag_is_older_of_older = t > older_tag_time
188
+ tag_is_newer_than_new = t <= newer_tag_time
162
189
 
163
- issues.delete_if { |issue|
164
- if issue[:closed_at]
165
- t = Time.parse(issue[:closed_at]).utc
166
- tag_is_later_since = t > since_tag_time
167
- tag_is_before_till = t <= till_tag_time
190
+ tag_not_in_range = (tag_is_older_of_older) && (tag_is_newer_than_new)
191
+ !tag_not_in_range
192
+ else
193
+ true
194
+ end
168
195
 
169
- in_range = (tag_is_later_since) && (tag_is_before_till)
170
- !in_range
171
- else
172
- true
173
- end
196
+ }
174
197
 
175
- }
198
+ issues = Array.new(@issues)
176
199
 
177
- self.create_log(pull_requests, issues, till_tag_name, till_tag_time)
200
+ issues.delete_if { |issue|
201
+ if issue[:closed_at]
202
+ t = Time.parse(issue[:closed_at]).utc
203
+ tag_is_later_since = t > older_tag_time
204
+ tag_is_before_till = t <= newer_tag_time
178
205
 
179
- end
206
+ in_range = (tag_is_later_since) && (tag_is_before_till)
207
+ !in_range
208
+ else
209
+ true
210
+ end
180
211
 
181
- def generate_log_before_tag(tag)
182
- tag_time = self.get_time_of_tag(tag)
183
- tag_name = tag['name']
212
+ }
184
213
 
185
- pull_requests = Array.new(@pull_requests)
214
+ self.create_log(pull_requests, issues, newer_tag_name, newer_tag_time)
186
215
 
187
- pull_requests.delete_if { |req|
188
- if req[:merged_at]
189
- t = Time.parse(req[:merged_at]).utc
190
- t > tag_time
191
- else
192
- true
193
- end
216
+ end
194
217
 
195
- }
218
+ def generate_log_before_tag(tag)
219
+ tag_time = self.get_time_of_tag(tag)
220
+ tag_name = tag['name']
196
221
 
197
- issues = Array.new(@issues)
222
+ pull_requests = Array.new(@pull_requests)
198
223
 
199
- issues.delete_if { |issue|
200
- if issue[:closed_at]
201
- t = Time.parse(issue[:closed_at]).utc
202
- t > tag_time
203
- else
204
- true
205
- end
206
- }
224
+ pull_requests.delete_if { |req|
225
+ if req[:merged_at]
226
+ t = Time.parse(req[:merged_at]).utc
227
+ t > tag_time
228
+ else
229
+ true
230
+ end
207
231
 
208
- self.create_log(pull_requests, issues, tag_name, tag_time)
232
+ }
209
233
 
210
- end
234
+ issues = Array.new(@issues)
211
235
 
212
- def create_log(pull_requests, issues, tag_name, tag_time)
236
+ issues.delete_if { |issue|
237
+ if issue[:closed_at]
238
+ t = Time.parse(issue[:closed_at]).utc
239
+ t > tag_time
240
+ else
241
+ true
242
+ end
243
+ }
213
244
 
214
- # Generate tag name and link
215
- trimmed_tag = tag_name.tr('v', '')
216
- log = "## [#{trimmed_tag}] (https://github.com/#{@options[:user]}/#{@options[:project]}/tree/#{tag_name})\n"
245
+ self.create_log(pull_requests, issues, tag_name, tag_time)
217
246
 
218
- #Generate date string:
219
- time_string = tag_time.strftime @options[:format]
220
- log += "#### #{time_string}\n"
247
+ end
221
248
 
222
- if @options[:pulls]
223
- # Generate pull requests:
224
- if pull_requests
225
- pull_requests.each { |dict|
226
- merge = "#{@options[:merge_prefix]}#{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n"
227
- log += "- #{merge}"
228
- }
249
+ def create_log(pull_requests, issues, tag_name, tag_time)
250
+
251
+ # Generate tag name and link
252
+ trimmed_tag = tag_name.tr('v', '')
253
+ log = "## [#{trimmed_tag}] (https://github.com/#{@options[:user]}/#{@options[:project]}/tree/#{tag_name})\n"
254
+
255
+ #Generate date string:
256
+ time_string = tag_time.strftime @options[:format]
257
+ log += "#### #{time_string}\n"
258
+
259
+ if @options[:pulls]
260
+ # Generate pull requests:
261
+ if pull_requests
262
+ pull_requests.each { |dict|
263
+ merge = "#{@options[:merge_prefix]}#{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n"
264
+ log += "- #{merge}"
265
+ }
266
+ end
229
267
  end
230
- end
231
268
 
232
- if @options[:issues]
233
- # Generate issues:
234
- if issues
235
- issues.sort! { |x, y|
236
- if x.labels.any? && y.labels.any?
237
- x.labels[0].name <=> y.labels[0].name
238
- else
239
- if x.labels.any?
240
- 1
269
+ if @options[:issues]
270
+ # Generate issues:
271
+ if issues
272
+ issues.sort! { |x, y|
273
+ if x.labels.any? && y.labels.any?
274
+ x.labels[0].name <=> y.labels[0].name
241
275
  else
242
- if y.labels.any?
243
- -1
276
+ if x.labels.any?
277
+ 1
244
278
  else
245
- 0
279
+ if y.labels.any?
280
+ -1
281
+ else
282
+ 0
283
+ end
246
284
  end
247
285
  end
286
+ }.reverse!
287
+ end
288
+ issues.each { |dict|
289
+ is_bug = false
290
+ is_enhancement = false
291
+ dict.labels.each { |label|
292
+ if label.name == 'bug'
293
+ is_bug = true
294
+ end
295
+ if label.name == 'enhancement'
296
+ is_enhancement = true
297
+ end
298
+ }
299
+
300
+ intro = 'Closed issue'
301
+ if is_bug
302
+ intro = 'Fixed bug'
248
303
  end
249
- }.reverse!
250
- end
251
- issues.each { |dict|
252
- is_bug = false
253
- is_enhancement = false
254
- dict.labels.each { |label|
255
- if label.name == 'bug'
256
- is_bug = true
257
- end
258
- if label.name == 'enhancement'
259
- is_enhancement = true
304
+
305
+ if is_enhancement
306
+ intro = 'Implemented enhancement'
260
307
  end
308
+
309
+ merge = "*#{intro}:* #{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n"
310
+ log += "- #{merge}"
261
311
  }
312
+ end
313
+ log
314
+ end
262
315
 
263
- intro = 'Closed issue'
264
- if is_bug
265
- intro = 'Fixed bug'
266
- end
316
+ def get_time_of_tag(prev_tag)
267
317
 
268
- if is_enhancement
269
- intro = 'Implemented enhancement'
270
- end
271
-
272
- merge = "*#{intro}:* #{dict[:title]} [\\##{dict[:number]}](#{dict.html_url})\n\n"
273
- log += "- #{merge}"
274
- }
275
- end
276
- log
277
- end
318
+ if @tag_times_hash[prev_tag['name']]
319
+ return @tag_times_hash[prev_tag['name']]
320
+ end
278
321
 
279
- def get_time_of_tag(prev_tag)
322
+ if @options[:verbose]
323
+ puts "Getting time for tag #{prev_tag['name']}"
324
+ end
280
325
 
281
- if @tag_times_hash[prev_tag['name']]
282
- return @tag_times_hash[prev_tag['name']]
326
+ github_git_data_commits_get = @github.git_data.commits.get @options[:user], @options[:project], prev_tag['commit']['sha']
327
+ time_string = github_git_data_commits_get['committer']['date']
328
+ Time.parse(time_string)
329
+ @tag_times_hash[prev_tag['name']] = Time.parse(time_string)
283
330
  end
284
331
 
285
- if @options[:verbose]
286
- puts "Get time for tag #{prev_tag['name']}"
287
- end
332
+ def get_all_issues
333
+ response = @github.issues.list user: @options[:user], repo: @options[:project], state: 'closed', filter: 'all', labels: nil
288
334
 
289
- github_git_data_commits_get = @github.git_data.commits.get @options[:user], @options[:project], prev_tag['commit']['sha']
290
- time_string = github_git_data_commits_get['committer']['date']
291
- Time.parse(time_string)
292
- @tag_times_hash[prev_tag['name']] = Time.parse(time_string)
293
- end
294
335
 
295
- def get_all_issues
296
- all_issues = []
336
+ issues = []
337
+ response.each_page do |page|
338
+ issues.concat(page)
339
+ end
297
340
 
298
- issues_req = @github.issues.list user: @options[:user], repo: @options[:project], state: 'closed', filter: 'all', labels: nil
341
+ # remove pull request from issues:
342
+ issues.select! { |x|
343
+ x.pull_request == nil
344
+ }
299
345
 
300
- filtered_issues = issues_req.body.select { |issues|
301
- #compare is there any labels from @options[:labels] array
302
- (issues.labels.map { |issue| issue.name } & @options[:labels]).any?
303
- }
346
+ if @options[:verbose]
347
+ puts "Receive all closed issues: #{issues.count}"
348
+ end
304
349
 
305
- if @options[:add_issues_wo_labels]
306
- issues_wo_labels = issues_req.body.select {
307
- |issues| !issues.labels.map { |issue| issue.name }.any?
350
+ filtered_issues = issues.select { |issue|
351
+ #compare is there any labels from @options[:labels] array
352
+ (issue.labels.map { |label| label.name } & @options[:labels]).any?
308
353
  }
309
- filtered_issues.concat(issues_wo_labels)
310
- end
311
354
 
312
- # remove pull request from issues:
313
- filtered_issues.select! { |x|
314
- x.pull_request == nil
315
- }
355
+ if @options[:add_issues_wo_labels]
356
+ issues_wo_labels = issues.select {
357
+ # add issues without any labels
358
+ |issue| !issue.labels.map { |label| label.name }.any?
359
+ }
360
+ filtered_issues.concat(issues_wo_labels)
361
+ end
316
362
 
317
- if @options[:verbose]
318
- puts "Receive all closed issues with labels #{@options[:labels]}: #{all_issues.count} issues"
319
- end
363
+ if @options[:verbose]
364
+ puts "Filter issues with labels #{@options[:labels]}#{@options[:add_issues_wo_labels] ? ' and w/o labels' : ''}: #{filtered_issues.count} issues"
365
+ end
320
366
 
321
- filtered_issues
367
+ filtered_issues
368
+
369
+ end
322
370
 
323
371
  end
324
372
 
325
- end
373
+ if __FILE__ == $0
374
+ GitHubChangelogGenerator::ChangelogGenerator.new.compund_changelog
375
+ end
326
376
 
327
- if __FILE__ == $0
328
- changelog_generator = ChangelogGenerator.new
329
- changelog_generator.compund_changelog
330
- end
377
+ end