github_changelog_generator 1.3.11 → 1.4.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 +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
|