github_changelog_generator 1.3.11 → 1.4.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.coveralls.yml +1 -0
- data/.hound.yml +2 -0
- data/.rspec +2 -0
- data/.rubocop.yml +8 -0
- data/.rubocop_todo.yml +81 -0
- data/.travis.yml +12 -10
- data/CHANGELOG.md +145 -113
- data/Gemfile +14 -4
- data/Gemfile.lock +75 -11
- data/README.md +17 -1
- data/Rakefile +5 -7
- data/bin/github_changelog_generator +2 -2
- data/bump_gemfile.rb +42 -48
- data/github_changelog_generator.gemspec +9 -10
- data/lib/CHANGELOG.md +2 -0
- data/lib/github_changelog_generator.rb +194 -346
- data/lib/github_changelog_generator/fetcher.rb +209 -0
- data/lib/github_changelog_generator/generator.rb +11 -8
- data/lib/github_changelog_generator/parser.rb +99 -86
- data/lib/github_changelog_generator/reader.rb +87 -0
- data/lib/github_changelog_generator/version.rb +1 -1
- data/spec/files/angular.js.md +9395 -0
- data/spec/files/bundler.md +1911 -0
- data/spec/files/github-changelog-generator.md +305 -0
- data/spec/spec_helper.rb +117 -0
- data/spec/unit/reader_spec.rb +113 -0
- metadata +22 -4
@@ -1,21 +1,21 @@
|
|
1
1
|
# coding: utf-8
|
2
2
|
|
3
|
-
lib = File.expand_path(
|
3
|
+
lib = File.expand_path("../lib", __FILE__)
|
4
4
|
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
5
|
-
require
|
5
|
+
require "github_changelog_generator/version"
|
6
6
|
|
7
7
|
Gem::Specification.new do |spec|
|
8
8
|
spec.name = "github_changelog_generator"
|
9
9
|
spec.version = GitHubChangelogGenerator::VERSION
|
10
10
|
spec.default_executable = "github_changelog_generator"
|
11
11
|
|
12
|
-
spec.required_ruby_version =
|
12
|
+
spec.required_ruby_version = ">= 1.9.3"
|
13
13
|
spec.authors = ["Petr Korolev"]
|
14
|
-
spec.email =
|
14
|
+
spec.email = "sky4winder+github_changelog_generator@gmail.com"
|
15
15
|
spec.date = `date +"%Y-%m-%d"`.strip!
|
16
|
-
spec.summary =
|
17
|
-
spec.description =
|
18
|
-
spec.homepage =
|
16
|
+
spec.summary = "Script, that automatically generate changelog from your tags, issues, labels and pull requests."
|
17
|
+
spec.description = "Changelog generation has never been so easy. Fully automate changelog generation - this gem generate change log file based on tags, issues and merged pull requests from Github issue tracker."
|
18
|
+
spec.homepage = "https://github.com/skywinder/Github-Changelog-Generator"
|
19
19
|
spec.license = "MIT"
|
20
20
|
|
21
21
|
spec.files = `git ls-files -z`.split("\x0")
|
@@ -26,7 +26,6 @@ Gem::Specification.new do |spec|
|
|
26
26
|
spec.add_development_dependency "bundler", "~> 1.7"
|
27
27
|
spec.add_development_dependency "rake", "~> 10.0"
|
28
28
|
|
29
|
-
spec.add_runtime_dependency(
|
30
|
-
spec.add_runtime_dependency(
|
31
|
-
|
29
|
+
spec.add_runtime_dependency("github_api", ["~> 0.12"])
|
30
|
+
spec.add_runtime_dependency("colorize", ["~> 0.7"])
|
32
31
|
end
|
data/lib/CHANGELOG.md
CHANGED
@@ -6,6 +6,8 @@
|
|
6
6
|
|
7
7
|
**Merged pull requests:**
|
8
8
|
|
9
|
+
- This PR SHOULD NOT appear in change log! [\#6](https://github.com/skywinder/changelog_test/pull/6) ([skywinder](https://github.com/skywinder))
|
10
|
+
|
9
11
|
- Add automatically generated change log file. [\#5](https://github.com/skywinder/changelog_test/pull/5) ([skywinder](https://github.com/skywinder))
|
10
12
|
|
11
13
|
## [v0.0.3](https://github.com/skywinder/changelog_test/tree/v0.0.3) (2015-03-04)
|
@@ -1,62 +1,65 @@
|
|
1
1
|
#!/usr/bin/env ruby
|
2
2
|
|
3
|
-
require
|
4
|
-
require
|
5
|
-
require
|
6
|
-
require
|
3
|
+
require "github_api"
|
4
|
+
require "json"
|
5
|
+
require "colorize"
|
6
|
+
require "benchmark"
|
7
7
|
|
8
|
-
require_relative
|
9
|
-
require_relative
|
10
|
-
require_relative
|
8
|
+
require_relative "github_changelog_generator/parser"
|
9
|
+
require_relative "github_changelog_generator/generator"
|
10
|
+
require_relative "github_changelog_generator/version"
|
11
|
+
require_relative "github_changelog_generator/reader"
|
12
|
+
require_relative "github_changelog_generator/fetcher"
|
11
13
|
|
12
14
|
module GitHubChangelogGenerator
|
13
|
-
|
15
|
+
# Default error for ChangelogGenerator
|
16
|
+
class ChangelogGeneratorError < StandardError
|
17
|
+
end
|
14
18
|
|
19
|
+
# Main class and entry point for this script.
|
20
|
+
class ChangelogGenerator
|
15
21
|
attr_accessor :options, :all_tags, :github
|
16
22
|
|
17
|
-
|
18
|
-
|
23
|
+
# Class, responsible for whole change log generation cycle
|
24
|
+
# @return initialised instance of ChangelogGenerator
|
19
25
|
def initialize
|
20
|
-
|
21
26
|
@options = Parser.parse_options
|
22
27
|
|
23
|
-
|
28
|
+
@fetcher = GitHubChangelogGenerator::Fetcher.new @options
|
24
29
|
|
25
|
-
|
26
|
-
github_options[:oauth_token] = @github_token unless @github_token.nil?
|
27
|
-
github_options[:endpoint] = options[:github_endpoint] unless options[:github_endpoint].nil?
|
28
|
-
github_options[:site] = options[:github_endpoint] unless options[:github_site].nil?
|
30
|
+
@generator = Generator.new @options
|
29
31
|
|
30
|
-
|
31
|
-
|
32
|
-
rescue
|
33
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
34
|
-
end
|
32
|
+
# @all_tags = get_filtered_tags
|
33
|
+
@all_tags = @fetcher.get_all_tags
|
35
34
|
|
36
|
-
@
|
35
|
+
@issues, @pull_requests = @fetcher.fetch_issues_and_pull_requests
|
37
36
|
|
38
|
-
@
|
39
|
-
@issues, @pull_requests = self.fetch_issues_and_pull_requests
|
37
|
+
@pull_requests = @options[:pulls] ? get_filtered_pull_requests : []
|
40
38
|
|
41
|
-
|
42
|
-
@pull_requests = self.get_filtered_pull_requests
|
43
|
-
else
|
44
|
-
@pull_requests = []
|
45
|
-
end
|
46
|
-
|
47
|
-
if @options[:issues]
|
48
|
-
@issues = self.get_filtered_issues
|
49
|
-
else
|
50
|
-
@issues = []
|
51
|
-
end
|
39
|
+
@issues = @options[:issues] ? get_filtered_issues : []
|
52
40
|
|
53
41
|
fetch_event_for_issues_and_pr
|
54
42
|
detect_actual_closed_dates
|
55
|
-
@tag_times_hash = {}
|
56
43
|
end
|
57
44
|
|
58
|
-
|
45
|
+
# Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
|
46
|
+
#
|
47
|
+
# @return [Array]
|
48
|
+
def get_filtered_tags
|
49
|
+
all_tags = @fetcher.get_all_tags
|
50
|
+
filtered_tags = []
|
51
|
+
if @options[:between_tags]
|
52
|
+
@options[:between_tags].each do |tag|
|
53
|
+
unless all_tags.include? tag
|
54
|
+
puts "Warning: can't find tag #{tag}, specified with --between-tags option.".yellow
|
55
|
+
end
|
56
|
+
end
|
57
|
+
filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag }
|
58
|
+
end
|
59
|
+
filtered_tags
|
60
|
+
end
|
59
61
|
|
62
|
+
def detect_actual_closed_dates
|
60
63
|
if @options[:verbose]
|
61
64
|
print "Fetching closed dates for issues...\r"
|
62
65
|
end
|
@@ -74,32 +77,30 @@ rescue
|
|
74
77
|
find_closed_date_by_commit(pull_request)
|
75
78
|
}
|
76
79
|
}
|
77
|
-
threads.each
|
80
|
+
threads.each(&:join)
|
78
81
|
|
79
82
|
if @options[:verbose]
|
80
|
-
puts
|
83
|
+
puts "Fetching closed dates for issues: Done!"
|
81
84
|
end
|
82
85
|
end
|
83
86
|
|
87
|
+
# Fill :actual_date parameter of specified issue by closed date of the commit, it it was closed by commit.
|
88
|
+
# @param [Hash] issue
|
84
89
|
def find_closed_date_by_commit(issue)
|
85
|
-
unless issue[
|
86
|
-
#if it's PR -> then find "merged event", in case of usual issue -> fond closed date
|
87
|
-
compare_string = issue[:merged_at].nil? ?
|
90
|
+
unless issue["events"].nil?
|
91
|
+
# if it's PR -> then find "merged event", in case of usual issue -> fond closed date
|
92
|
+
compare_string = issue[:merged_at].nil? ? "closed" : "merged"
|
88
93
|
# reverse! - to find latest closed event. (event goes in date order)
|
89
|
-
issue[
|
94
|
+
issue["events"].reverse!.each { |event|
|
90
95
|
if event[:event].eql? compare_string
|
91
96
|
if event[:commit_id].nil?
|
92
97
|
issue[:actual_date] = issue[:closed_at]
|
93
98
|
else
|
94
99
|
begin
|
95
|
-
|
96
|
-
commit = @github.git_data.commits.get @options[:user], @options[:project], event[:commit_id]
|
97
|
-
rescue
|
98
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
99
|
-
end
|
100
|
+
commit = @fetcher.fetch_commit(event)
|
100
101
|
issue[:actual_date] = commit[:author][:date]
|
101
102
|
rescue
|
102
|
-
puts "Warning:
|
103
|
+
puts "Warning: Can't fetch commit #{event[:commit_id]}. It is probably referenced from another repo.".yellow
|
103
104
|
issue[:actual_date] = issue[:closed_at]
|
104
105
|
end
|
105
106
|
end
|
@@ -107,95 +108,92 @@ rescue
|
|
107
108
|
end
|
108
109
|
}
|
109
110
|
end
|
110
|
-
#TODO: assert issues, that remain without 'actual_date' hash for some reason.
|
111
|
+
# TODO: assert issues, that remain without 'actual_date' hash for some reason.
|
111
112
|
end
|
112
113
|
|
113
114
|
def print_json(json)
|
114
115
|
puts JSON.pretty_generate(json)
|
115
116
|
end
|
116
117
|
|
117
|
-
|
118
|
+
# This method fetches missing params for PR and filter them by specified options
|
119
|
+
# It include add all PR's with labels from @options[:include_labels] array
|
120
|
+
# And exclude all from :exclude_labels array.
|
121
|
+
# @return [Array] filtered PR's
|
122
|
+
def get_filtered_pull_requests
|
123
|
+
fetch_merged_at_pull_requests
|
124
|
+
|
125
|
+
filtered_pull_requests = include_issues_by_labels(@pull_requests)
|
126
|
+
|
127
|
+
filtered_pull_requests = exclude_issues_by_labels(filtered_pull_requests)
|
128
|
+
|
118
129
|
if @options[:verbose]
|
119
|
-
|
130
|
+
puts "Filtered pull requests: #{filtered_pull_requests.count}"
|
120
131
|
end
|
121
|
-
pull_requests = []
|
122
|
-
begin
|
123
|
-
response = @github.pull_requests.list @options[:user], @options[:project], :state => 'closed'
|
124
|
-
page_i = 0
|
125
|
-
response.each_page do |page|
|
126
|
-
page_i += PER_PAGE_NUMBER
|
127
|
-
count_pages = response.count_pages
|
128
|
-
print "Fetching merged dates... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
129
|
-
pull_requests.concat(page)
|
130
|
-
end
|
131
|
-
rescue
|
132
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
133
|
-
end
|
134
|
-
|
135
132
|
|
133
|
+
filtered_pull_requests
|
134
|
+
end
|
136
135
|
|
137
|
-
|
136
|
+
# This method fetch missing required attributes for pull requests
|
137
|
+
# :merged_at - is a date, when issue PR was merged.
|
138
|
+
# More correct to use this date, not closed date.
|
139
|
+
def fetch_merged_at_pull_requests
|
140
|
+
if @options[:verbose]
|
141
|
+
print "Fetching merged dates...\r"
|
142
|
+
end
|
143
|
+
pull_requests = @fetcher.fetch_pull_requests
|
138
144
|
|
139
145
|
@pull_requests.each { |pr|
|
140
146
|
fetched_pr = pull_requests.find { |fpr|
|
141
|
-
fpr.number == pr.number
|
147
|
+
fpr.number == pr.number
|
148
|
+
}
|
142
149
|
pr[:merged_at] = fetched_pr[:merged_at]
|
143
150
|
pull_requests.delete(fetched_pr)
|
144
151
|
}
|
145
152
|
|
146
153
|
if @options[:verbose]
|
147
|
-
puts
|
154
|
+
puts "Fetching merged dates: Done!"
|
148
155
|
end
|
149
|
-
|
150
156
|
end
|
151
157
|
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
158
|
+
# Include issues with labels, specified in :include_labels
|
159
|
+
# @param [Array] issues to filter
|
160
|
+
# @return [Array] filtered array of issues
|
161
|
+
def include_issues_by_labels(issues)
|
162
|
+
filtered_issues = @options[:include_labels].nil? ? issues : issues.select { |issue| (issue.labels.map(&:name) & @options[:include_labels]).any? }
|
157
163
|
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
(issue.labels.map { |label| label.name } & @options[:include_labels]).any?
|
164
|
+
if @options[:add_issues_wo_labels]
|
165
|
+
issues_wo_labels = issues.select { |issue|
|
166
|
+
!issue.labels.map(&:name).any?
|
162
167
|
}
|
168
|
+
filtered_issues |= issues_wo_labels
|
163
169
|
end
|
170
|
+
filtered_issues
|
171
|
+
end
|
164
172
|
|
173
|
+
# delete all labels with labels from @options[:exclude_labels] array
|
174
|
+
# @param [Array] issues
|
175
|
+
# @return [Array] filtered array
|
176
|
+
def exclude_issues_by_labels(issues)
|
165
177
|
unless @options[:exclude_labels].nil?
|
166
|
-
|
167
|
-
|
168
|
-
!(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any?
|
178
|
+
issues = issues.select { |issue|
|
179
|
+
!(issue.labels.map(&:name) & @options[:exclude_labels]).any?
|
169
180
|
}
|
170
181
|
end
|
171
|
-
|
172
|
-
if @options[:add_issues_wo_labels]
|
173
|
-
issues_wo_labels = @pull_requests.select {
|
174
|
-
# add issues without any labels
|
175
|
-
|issue| !issue.labels.map { |label| label.name }.any?
|
176
|
-
}
|
177
|
-
filtered_pull_requests |= issues_wo_labels
|
178
|
-
end
|
179
|
-
|
180
|
-
|
181
|
-
if @options[:verbose]
|
182
|
-
puts "Filtered pull requests: #{filtered_pull_requests.count}"
|
183
|
-
end
|
184
|
-
|
185
|
-
filtered_pull_requests
|
182
|
+
issues
|
186
183
|
end
|
187
184
|
|
188
|
-
|
189
|
-
|
185
|
+
# The entry point of this script to generate change log
|
186
|
+
# @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags.
|
187
|
+
def compound_changelog
|
190
188
|
log = "# Change Log\n\n"
|
191
189
|
|
192
190
|
if @options[:unreleased_only]
|
193
|
-
log +=
|
191
|
+
log += generate_log_between_tags(all_tags[0], nil)
|
194
192
|
elsif @options[:tag1] and @options[:tag2]
|
195
193
|
tag1 = @options[:tag1]
|
196
194
|
tag2 = @options[:tag2]
|
197
195
|
tags_strings = []
|
198
|
-
|
196
|
+
all_tags.each { |x| tags_strings.push(x["name"]) }
|
199
197
|
|
200
198
|
if tags_strings.include?(tag1)
|
201
199
|
if tags_strings.include?(tag2)
|
@@ -203,65 +201,63 @@ rescue
|
|
203
201
|
hash = Hash[to_a]
|
204
202
|
index1 = hash[tag1]
|
205
203
|
index2 = hash[tag2]
|
206
|
-
log +=
|
204
|
+
log += generate_log_between_tags(all_tags[index1], all_tags[index2])
|
207
205
|
else
|
208
|
-
|
209
|
-
exit
|
206
|
+
fail ChangelogGeneratorError, "Can't find tag #{tag2} -> exit".red
|
210
207
|
end
|
211
208
|
else
|
212
|
-
|
213
|
-
exit
|
209
|
+
fail ChangelogGeneratorError, "Can't find tag #{tag1} -> exit".red
|
214
210
|
end
|
215
211
|
else
|
216
|
-
log +=
|
212
|
+
log += generate_log_for_all_tags
|
217
213
|
end
|
218
214
|
|
219
215
|
log += "\n\n\\* *This Change Log was automatically generated by [github_changelog_generator](https://github.com/skywinder/Github-Changelog-Generator)*"
|
220
216
|
|
221
217
|
output_filename = "#{@options[:output]}"
|
222
|
-
File.open(output_filename,
|
223
|
-
puts
|
218
|
+
File.open(output_filename, "w") { |file| file.write(log) }
|
219
|
+
puts "Done!"
|
224
220
|
puts "Generated log placed in #{`pwd`.strip!}/#{output_filename}"
|
225
|
-
|
226
221
|
end
|
227
222
|
|
223
|
+
# The full cycle of generation for whole project
|
224
|
+
# @return [String] The complete change log
|
228
225
|
def generate_log_for_all_tags
|
229
|
-
|
230
226
|
fetch_tags_dates
|
231
227
|
|
232
228
|
if @options[:verbose]
|
233
|
-
puts "Sorting tags
|
229
|
+
puts "Sorting tags..."
|
234
230
|
end
|
235
231
|
|
236
|
-
@all_tags.sort_by! { |x|
|
232
|
+
@all_tags.sort_by! { |x| @fetcher.get_time_of_tag(x) }.reverse!
|
237
233
|
|
238
234
|
if @options[:verbose]
|
239
|
-
puts "Generating log
|
235
|
+
puts "Generating log..."
|
240
236
|
end
|
241
237
|
|
242
|
-
|
243
|
-
log = ''
|
238
|
+
log = ""
|
244
239
|
|
245
240
|
if @options[:unreleased] && @all_tags.count != 0
|
246
|
-
unreleased_log =
|
241
|
+
unreleased_log = generate_log_between_tags(all_tags[0], nil)
|
247
242
|
if unreleased_log
|
248
243
|
log += unreleased_log
|
249
244
|
end
|
250
245
|
end
|
251
246
|
|
252
|
-
(1
|
253
|
-
log +=
|
247
|
+
(1...all_tags.size).each { |index|
|
248
|
+
log += generate_log_between_tags(all_tags[index], all_tags[index - 1])
|
254
249
|
}
|
255
250
|
if @all_tags.count != 0
|
256
|
-
log += generate_log_between_tags(nil,
|
251
|
+
log += generate_log_between_tags(nil, all_tags.last)
|
257
252
|
end
|
258
253
|
|
259
254
|
log
|
260
255
|
end
|
261
256
|
|
257
|
+
# Async fetching of all tags dates
|
262
258
|
def fetch_tags_dates
|
263
259
|
if @options[:verbose]
|
264
|
-
print "Fetching
|
260
|
+
print "Fetching tag dates...\r"
|
265
261
|
end
|
266
262
|
|
267
263
|
# Async fetching tags:
|
@@ -269,96 +265,46 @@ rescue
|
|
269
265
|
i = 0
|
270
266
|
all = @all_tags.count
|
271
267
|
@all_tags.each { |tag|
|
272
|
-
# explicit set @tag_times_hash to write data safety.
|
273
268
|
threads << Thread.new {
|
274
|
-
|
269
|
+
@fetcher.get_time_of_tag(tag)
|
275
270
|
if @options[:verbose]
|
276
|
-
print "Fetching tags dates: #{i+1}/#{all}\r"
|
277
|
-
i+=1
|
271
|
+
print "Fetching tags dates: #{i + 1}/#{all}\r"
|
272
|
+
i += 1
|
278
273
|
end
|
279
|
-
|
280
274
|
}
|
281
275
|
}
|
282
276
|
|
283
277
|
print " \r"
|
284
278
|
|
285
|
-
threads.each
|
286
|
-
|
287
|
-
if @options[:verbose]
|
288
|
-
puts 'Fetching tags: Done!'
|
289
|
-
end
|
290
|
-
end
|
291
|
-
|
292
|
-
def is_megred(number)
|
293
|
-
begin
|
294
|
-
@github.pull_requests.merged? @options[:user], @options[:project], number
|
295
|
-
rescue
|
296
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
297
|
-
end
|
298
|
-
end
|
299
|
-
|
300
|
-
def get_all_tags
|
279
|
+
threads.each(&:join)
|
301
280
|
|
302
281
|
if @options[:verbose]
|
303
|
-
|
304
|
-
end
|
305
|
-
|
306
|
-
tags = []
|
307
|
-
|
308
|
-
begin
|
309
|
-
response = @github.repos.tags @options[:user], @options[:project]
|
310
|
-
page_i = 0
|
311
|
-
count_pages = response.count_pages
|
312
|
-
response.each_page do |page|
|
313
|
-
page_i += PER_PAGE_NUMBER
|
314
|
-
print "Fetching tags... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
315
|
-
tags.concat(page)
|
316
|
-
end
|
317
|
-
print " \r"
|
318
|
-
if @options[:verbose]
|
319
|
-
puts "Found #{tags.count} tags"
|
320
|
-
end
|
321
|
-
rescue
|
322
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
323
|
-
end
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
tags
|
328
|
-
end
|
329
|
-
|
330
|
-
def fetch_github_token
|
331
|
-
env_var = @options[:token] ? @options[:token] : (ENV.fetch 'CHANGELOG_GITHUB_TOKEN', nil)
|
332
|
-
|
333
|
-
unless env_var
|
334
|
-
puts "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found.".yellow
|
335
|
-
puts "This script can make only 50 requests to GitHub API per hour without token!".yellow
|
282
|
+
puts "Fetching tags dates: #{i} Done!"
|
336
283
|
end
|
337
|
-
|
338
|
-
@github_token ||= env_var
|
339
|
-
|
340
284
|
end
|
341
285
|
|
286
|
+
# Generate log only between 2 specified tags
|
287
|
+
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
288
|
+
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
342
289
|
def generate_log_between_tags(older_tag, newer_tag)
|
343
|
-
# older_tag nil - means it's first tag, newer_tag nil - means it unreleased section
|
344
290
|
filtered_pull_requests = delete_by_time(@pull_requests, :actual_date, older_tag, newer_tag)
|
345
291
|
filtered_issues = delete_by_time(@issues, :actual_date, older_tag, newer_tag)
|
346
292
|
|
347
|
-
newer_tag_name = newer_tag.nil? ? nil : newer_tag[
|
348
|
-
older_tag_name = older_tag.nil? ? nil : older_tag[
|
293
|
+
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
294
|
+
older_tag_name = older_tag.nil? ? nil : older_tag["name"]
|
349
295
|
|
350
296
|
if @options[:filter_issues_by_milestone]
|
351
|
-
#delete excess irrelevant issues (according milestones)
|
297
|
+
# delete excess irrelevant issues (according milestones)
|
352
298
|
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
353
299
|
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
354
300
|
end
|
355
301
|
|
356
302
|
if filtered_issues.empty? && filtered_pull_requests.empty? && newer_tag.nil?
|
357
303
|
# do not generate empty unreleased section
|
358
|
-
return
|
304
|
+
return ""
|
359
305
|
end
|
360
306
|
|
361
|
-
|
307
|
+
create_log(filtered_pull_requests, filtered_issues, newer_tag, older_tag_name)
|
362
308
|
end
|
363
309
|
|
364
310
|
def filter_by_milestone(filtered_issues, newer_tag_name, src_array)
|
@@ -367,18 +313,18 @@ rescue
|
|
367
313
|
if issue.milestone.nil?
|
368
314
|
true
|
369
315
|
else
|
370
|
-
#check, that this milestone in tag list:
|
316
|
+
# check, that this milestone in tag list:
|
371
317
|
@all_tags.find { |tag| tag.name == issue.milestone.title }.nil?
|
372
318
|
end
|
373
319
|
}
|
374
320
|
unless newer_tag_name.nil?
|
375
321
|
|
376
|
-
#add missed issues (according milestones)
|
322
|
+
# add missed issues (according milestones)
|
377
323
|
issues_to_add = src_array.select { |issue|
|
378
324
|
if issue.milestone.nil?
|
379
325
|
false
|
380
326
|
else
|
381
|
-
#check, that this milestone in tag list:
|
327
|
+
# check, that this milestone in tag list:
|
382
328
|
milestone_is_tag = @all_tags.find { |tag|
|
383
329
|
tag.name == issue.milestone.title
|
384
330
|
}
|
@@ -396,12 +342,17 @@ rescue
|
|
396
342
|
filtered_issues
|
397
343
|
end
|
398
344
|
|
399
|
-
|
345
|
+
# Method filter issues, that belong only specified tag range
|
346
|
+
# @param [Array] array of issues to filter
|
347
|
+
# @param [Symbol] hash_key key of date value default is :actual_date
|
348
|
+
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
349
|
+
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
350
|
+
# @return [Array] filtered issues
|
351
|
+
def delete_by_time(array, hash_key = :actual_date, older_tag = nil, newer_tag = nil)
|
352
|
+
fail ChangelogGeneratorError, "At least one of the tags should be not nil!".red if older_tag.nil? && newer_tag.nil?
|
400
353
|
|
401
|
-
|
402
|
-
|
403
|
-
newer_tag_time = self.get_time_of_tag(newer_tag)
|
404
|
-
older_tag_time = self.get_time_of_tag(older_tag)
|
354
|
+
newer_tag_time = newer_tag && @fetcher.get_time_of_tag(newer_tag)
|
355
|
+
older_tag_time = older_tag && @fetcher.get_time_of_tag(older_tag)
|
405
356
|
|
406
357
|
array.select { |req|
|
407
358
|
if req[hash_key]
|
@@ -419,7 +370,6 @@ rescue
|
|
419
370
|
tag_in_range_new = t <= newer_tag_time
|
420
371
|
end
|
421
372
|
|
422
|
-
|
423
373
|
tag_in_range = (tag_in_range_old) && (tag_in_range_new)
|
424
374
|
|
425
375
|
tag_in_range
|
@@ -429,45 +379,38 @@ rescue
|
|
429
379
|
}
|
430
380
|
end
|
431
381
|
|
432
|
-
#
|
433
|
-
#
|
434
|
-
# @param [
|
435
|
-
# @
|
382
|
+
# Generates log for section with header and body
|
383
|
+
#
|
384
|
+
# @param [Array] pull_requests List or PR's in new section
|
385
|
+
# @param [Array] issues List of issues in new section
|
386
|
+
# @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
|
387
|
+
# @param [String] older_tag_name Older tag, used for the links. Could be nil for last tag.
|
388
|
+
# @return [String] Ready and parsed section
|
436
389
|
def create_log(pull_requests, issues, newer_tag, older_tag_name = nil)
|
390
|
+
newer_tag_time = newer_tag.nil? ? Time.new : @fetcher.get_time_of_tag(newer_tag)
|
391
|
+
newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
|
392
|
+
newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
|
437
393
|
|
438
|
-
|
439
|
-
newer_tag_name = newer_tag.nil? ? nil : newer_tag['name']
|
440
|
-
|
441
|
-
github_site = options[:github_site] || 'https://github.com'
|
394
|
+
github_site = options[:github_site] || "https://github.com"
|
442
395
|
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
|
443
396
|
|
444
|
-
|
445
|
-
newer_tag_name = @options[:unreleased_label]
|
446
|
-
newer_tag_link = 'HEAD'
|
447
|
-
newer_tag_time = Time.new
|
448
|
-
else
|
449
|
-
newer_tag_link = newer_tag_name
|
450
|
-
end
|
451
|
-
|
452
|
-
log = ''
|
453
|
-
|
454
|
-
log += generate_header(log, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
397
|
+
log = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
455
398
|
|
456
399
|
if @options[:issues]
|
457
400
|
# Generate issues:
|
458
401
|
issues_a = []
|
459
402
|
enhancement_a = []
|
460
|
-
bugs_a =[]
|
403
|
+
bugs_a = []
|
461
404
|
|
462
405
|
issues.each { |dict|
|
463
406
|
added = false
|
464
407
|
dict.labels.each { |label|
|
465
|
-
if label.name ==
|
408
|
+
if label.name == "bug"
|
466
409
|
bugs_a.push dict
|
467
410
|
added = true
|
468
411
|
next
|
469
412
|
end
|
470
|
-
if label.name ==
|
413
|
+
if label.name == "enhancement"
|
471
414
|
enhancement_a.push dict
|
472
415
|
added = true
|
473
416
|
next
|
@@ -478,22 +421,26 @@ rescue
|
|
478
421
|
end
|
479
422
|
}
|
480
423
|
|
481
|
-
log +=
|
482
|
-
log +=
|
483
|
-
log +=
|
424
|
+
log += generate_sub_section(enhancement_a, @options[:enhancement_prefix])
|
425
|
+
log += generate_sub_section(bugs_a, @options[:bug_prefix])
|
426
|
+
log += generate_sub_section(issues_a, @options[:issue_prefix])
|
484
427
|
end
|
485
428
|
|
486
429
|
if @options[:pulls]
|
487
430
|
# Generate pull requests:
|
488
|
-
log +=
|
431
|
+
log += generate_sub_section(pull_requests, @options[:merge_prefix])
|
489
432
|
end
|
490
433
|
|
491
434
|
log
|
492
435
|
end
|
493
436
|
|
494
|
-
|
495
|
-
|
496
|
-
|
437
|
+
# @param [Array] issues List of issues on sub-section
|
438
|
+
# @param [String] prefix Nae of sub-section
|
439
|
+
# @return [String] Generate ready-to-go sub-section
|
440
|
+
def generate_sub_section(issues, prefix)
|
441
|
+
log = ""
|
442
|
+
|
443
|
+
if options[:simple_list] != true && issues.any?
|
497
444
|
log += "#{prefix}\n\n"
|
498
445
|
end
|
499
446
|
|
@@ -506,119 +453,51 @@ rescue
|
|
506
453
|
log
|
507
454
|
end
|
508
455
|
|
509
|
-
|
456
|
+
# It generate one header for section with specific parameters.
|
457
|
+
#
|
458
|
+
# @param [String] newer_tag_name - name of newer tag
|
459
|
+
# @param [String] newer_tag_link - used for links. Could be same as #newer_tag_name or some specific value, like HEAD
|
460
|
+
# @param [Time] newer_tag_time - time, when newer tag created
|
461
|
+
# @param [String] older_tag_link - tag name, used for links.
|
462
|
+
# @param [String] project_url - url for current project.
|
463
|
+
# @return [String] - Generate one ready-to-add section.
|
464
|
+
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_link, project_url)
|
465
|
+
log = ""
|
510
466
|
|
511
|
-
#Generate date string:
|
512
|
-
time_string = newer_tag_time.strftime @options[:
|
467
|
+
# Generate date string:
|
468
|
+
time_string = newer_tag_time.strftime @options[:dateformat]
|
513
469
|
|
514
470
|
# Generate tag name and link
|
515
471
|
if newer_tag_name.equal? @options[:unreleased_label]
|
516
|
-
log += "## [#{newer_tag_name}](#{project_url}/tree/#{
|
472
|
+
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link})\n\n"
|
517
473
|
else
|
518
|
-
log += "## [#{newer_tag_name}](#{project_url}/tree/#{
|
474
|
+
log += "## [#{newer_tag_name}](#{project_url}/tree/#{newer_tag_link}) (#{time_string})\n\n"
|
519
475
|
end
|
520
476
|
|
521
|
-
if @options[:compare_link] &&
|
477
|
+
if @options[:compare_link] && older_tag_link
|
522
478
|
# Generate compare link
|
523
|
-
log += "[Full Changelog](#{project_url}/compare/#{
|
479
|
+
log += "[Full Changelog](#{project_url}/compare/#{older_tag_link}...#{newer_tag_link})\n\n"
|
524
480
|
end
|
525
481
|
|
526
482
|
log
|
527
483
|
end
|
528
484
|
|
529
|
-
|
530
|
-
|
531
|
-
if tag_name.nil?
|
532
|
-
return nil
|
533
|
-
end
|
534
|
-
|
535
|
-
if tag_times_hash[tag_name['name']]
|
536
|
-
return @tag_times_hash[tag_name['name']]
|
537
|
-
end
|
538
|
-
|
539
|
-
begin
|
540
|
-
github_git_data_commits_get = @github.git_data.commits.get @options[:user], @options[:project], tag_name['commit']['sha']
|
541
|
-
rescue
|
542
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
543
|
-
end
|
544
|
-
time_string = github_git_data_commits_get['committer']['date']
|
545
|
-
@tag_times_hash[tag_name['name']] = Time.parse(time_string)
|
546
|
-
end
|
547
|
-
|
485
|
+
# Filter issues according labels
|
486
|
+
# @return [Array] Filtered issues
|
548
487
|
def get_filtered_issues
|
488
|
+
filtered_issues = include_issues_by_labels(@issues)
|
549
489
|
|
550
|
-
|
551
|
-
|
552
|
-
filtered_issues = issues
|
553
|
-
|
554
|
-
unless @options[:include_labels].nil?
|
555
|
-
filtered_issues = issues.select { |issue|
|
556
|
-
#add all labels from @options[:incluse_labels] array
|
557
|
-
(issue.labels.map { |label| label.name } & @options[:include_labels]).any?
|
558
|
-
}
|
559
|
-
end
|
560
|
-
|
561
|
-
unless @options[:exclude_labels].nil?
|
562
|
-
filtered_issues = filtered_issues.select { |issue|
|
563
|
-
#delete all labels from @options[:exclude_labels] array
|
564
|
-
!(issue.labels.map { |label| label.name } & @options[:exclude_labels]).any?
|
565
|
-
}
|
566
|
-
end
|
567
|
-
|
568
|
-
if @options[:add_issues_wo_labels]
|
569
|
-
issues_wo_labels = issues.select {
|
570
|
-
# add issues without any labels
|
571
|
-
|issue| !issue.labels.map { |label| label.name }.any?
|
572
|
-
}
|
573
|
-
filtered_issues |= issues_wo_labels
|
574
|
-
end
|
575
|
-
|
490
|
+
filtered_issues = exclude_issues_by_labels(filtered_issues)
|
576
491
|
|
577
492
|
if @options[:verbose]
|
578
493
|
puts "Filtered issues: #{filtered_issues.count}"
|
579
494
|
end
|
580
495
|
|
581
496
|
filtered_issues
|
582
|
-
|
583
|
-
end
|
584
|
-
|
585
|
-
def fetch_issues_and_pull_requests
|
586
|
-
if @options[:verbose]
|
587
|
-
print "Fetching closed issues...\r"
|
588
|
-
end
|
589
|
-
issues = []
|
590
|
-
|
591
|
-
begin
|
592
|
-
response = @github.issues.list user: @options[:user], repo: @options[:project], state: 'closed', filter: 'all', labels: nil
|
593
|
-
page_i = 0
|
594
|
-
count_pages = response.count_pages
|
595
|
-
response.each_page do |page|
|
596
|
-
page_i += PER_PAGE_NUMBER
|
597
|
-
print "Fetching issues... #{page_i}/#{count_pages * PER_PAGE_NUMBER}\r"
|
598
|
-
issues.concat(page)
|
599
|
-
end
|
600
|
-
rescue
|
601
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
602
|
-
end
|
603
|
-
|
604
|
-
|
605
|
-
|
606
|
-
print " \r"
|
607
|
-
|
608
|
-
if @options[:verbose]
|
609
|
-
puts "Received issues: #{issues.count}"
|
610
|
-
end
|
611
|
-
|
612
|
-
# remove pull request from issues:
|
613
|
-
issues_wo_pr = issues.select { |x|
|
614
|
-
x.pull_request == nil
|
615
|
-
}
|
616
|
-
pull_requests = issues.select { |x|
|
617
|
-
x.pull_request != nil
|
618
|
-
}
|
619
|
-
return issues_wo_pr, pull_requests
|
620
497
|
end
|
621
498
|
|
499
|
+
# Fetch event for issues and pull requests
|
500
|
+
# @return [Array] array of fetched issues
|
622
501
|
def fetch_event_for_issues_and_pr
|
623
502
|
if @options[:verbose]
|
624
503
|
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r"
|
@@ -626,42 +505,11 @@ rescue
|
|
626
505
|
|
627
506
|
# Async fetching events:
|
628
507
|
|
629
|
-
fetch_events_async(@issues + @pull_requests)
|
630
|
-
|
631
|
-
#to clear line from prev print
|
632
|
-
print " \r"
|
633
|
-
|
634
|
-
if @options[:verbose]
|
635
|
-
puts 'Fetching events for issues and PR: Done!'
|
636
|
-
end
|
637
|
-
end
|
638
|
-
|
639
|
-
def fetch_events_async(issues)
|
640
|
-
i = 0
|
641
|
-
max_thread_number = 50
|
642
|
-
threads = []
|
643
|
-
issues.each_slice(max_thread_number) { |issues_slice|
|
644
|
-
issues_slice.each { |issue|
|
645
|
-
threads << Thread.new {
|
646
|
-
begin
|
647
|
-
obj = @github.issues.events.list user: @options[:user], repo: @options[:project], issue_number: issue['number']
|
648
|
-
rescue
|
649
|
-
puts "Warning: GitHub API rate limit exceed (5000 per hour), change log may not contain some issues.".yellow
|
650
|
-
end
|
651
|
-
issue[:events] = obj.body
|
652
|
-
print "Fetching events for issues and PR: #{i+1}/#{@issues.count + @pull_requests.count}\r"
|
653
|
-
i +=1
|
654
|
-
}
|
655
|
-
}
|
656
|
-
threads.each { |thr| thr.join }
|
657
|
-
threads = []
|
658
|
-
}
|
508
|
+
@fetcher.fetch_events_async(@issues + @pull_requests)
|
659
509
|
end
|
660
|
-
|
661
510
|
end
|
662
511
|
|
663
|
-
if __FILE__ == $
|
664
|
-
GitHubChangelogGenerator::ChangelogGenerator.new.
|
512
|
+
if __FILE__ == $PROGRAM_NAME
|
513
|
+
GitHubChangelogGenerator::ChangelogGenerator.new.compound_changelog
|
665
514
|
end
|
666
|
-
|
667
515
|
end
|