github_changelog_generator 1.16.4 → 1.18.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/README.md +52 -16
- data/bin/github_changelog_generator +1 -1
- data/lib/github_changelog_generator/argv_parser.rb +2 -2
- data/lib/github_changelog_generator/file_parser_chooser.rb +27 -0
- data/lib/github_changelog_generator/generator/entry.rb +8 -6
- data/lib/github_changelog_generator/generator/generator.rb +15 -10
- data/lib/github_changelog_generator/generator/generator_fetcher.rb +22 -24
- data/lib/github_changelog_generator/generator/generator_processor.rb +15 -25
- data/lib/github_changelog_generator/generator/generator_tags.rb +24 -34
- data/lib/github_changelog_generator/generator/section.rb +9 -4
- data/lib/github_changelog_generator/octo_fetcher.rb +30 -12
- data/lib/github_changelog_generator/options.rb +12 -2
- data/lib/github_changelog_generator/parser.rb +4 -4
- data/lib/github_changelog_generator/parser_file.rb +0 -24
- data/lib/github_changelog_generator/task.rb +2 -2
- data/lib/github_changelog_generator/version.rb +1 -1
- data/lib/github_changelog_generator.rb +18 -16
- data/man/git-generate-changelog.html +2 -2
- data/spec/github_changelog_generator_spec.rb +32 -0
- data/spec/unit/generator/entry_spec.rb +2 -2
- data/spec/unit/generator/generator_processor_spec.rb +61 -0
- data/spec/unit/generator/generator_spec.rb +151 -0
- data/spec/unit/generator/generator_tags_spec.rb +1 -1
- data/spec/unit/generator/section_spec.rb +9 -0
- data/spec/unit/octo_fetcher_spec.rb +8 -8
- data/spec/unit/{parse_file_spec.rb → parser_file_spec.rb} +35 -15
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits/when_API_is_valid/returns_commits.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits_before/when_API_is_valid/returns_commits.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issue_with_proper_key/values.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues_with_labels.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_request_with_proper_key/values.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_requests_with_labels.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_correct_pull_request_keys.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_pull_requests.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid/returns_commit.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid/returns_date.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid/populates_issues.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags_count.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid.json +1 -1
- metadata +12 -9
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "tmpdir"
|
|
4
|
-
require "set"
|
|
5
4
|
require "async"
|
|
6
5
|
require "async/barrier"
|
|
7
6
|
require "async/semaphore"
|
|
@@ -59,7 +58,7 @@ module GitHubChangelogGenerator
|
|
|
59
58
|
end
|
|
60
59
|
|
|
61
60
|
builder.use Octokit::Response::RaiseError
|
|
62
|
-
builder.adapter
|
|
61
|
+
builder.adapter Faraday.default_adapter
|
|
63
62
|
end
|
|
64
63
|
end
|
|
65
64
|
|
|
@@ -95,12 +94,17 @@ module GitHubChangelogGenerator
|
|
|
95
94
|
# Fetch all tags from repo
|
|
96
95
|
#
|
|
97
96
|
# @return [Array <Hash>] array of tags
|
|
98
|
-
def
|
|
97
|
+
def fetch_all_tags
|
|
99
98
|
print "Fetching tags...\r" if @options[:verbose]
|
|
100
99
|
|
|
101
100
|
check_github_response { github_fetch_tags }
|
|
102
101
|
end
|
|
103
102
|
|
|
103
|
+
def get_all_tags # rubocop:disable Naming/AccessorMethodName
|
|
104
|
+
warn("[DEPRECATED] GitHubChangelogGenerator::OctoFetcher#get_all_tags is deprecated; use fetch_all_tags instead.")
|
|
105
|
+
fetch_all_tags
|
|
106
|
+
end
|
|
107
|
+
|
|
104
108
|
# Returns the number of pages for a API call
|
|
105
109
|
#
|
|
106
110
|
# @return [Integer] number of pages for this API call in total
|
|
@@ -204,7 +208,8 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
204
208
|
# @param [Array] issues
|
|
205
209
|
# @return [Void]
|
|
206
210
|
def fetch_events_async(issues)
|
|
207
|
-
|
|
211
|
+
fetched_count = 0
|
|
212
|
+
mutex = Mutex.new
|
|
208
213
|
# Add accept option explicitly for disabling the warning of preview API.
|
|
209
214
|
preview = { accept: Octokit::Preview::PREVIEW_TYPES[:project_card_events] }
|
|
210
215
|
|
|
@@ -221,8 +226,8 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
221
226
|
issue["events"].concat(new_event)
|
|
222
227
|
end
|
|
223
228
|
issue["events"] = issue["events"].map { |event| stringify_keys_deep(event.to_hash) }
|
|
224
|
-
|
|
225
|
-
|
|
229
|
+
current = mutex.synchronize { fetched_count += 1 }
|
|
230
|
+
print_in_same_line("Fetching events for issues and PR: #{current}/#{issues.count}")
|
|
226
231
|
end
|
|
227
232
|
end
|
|
228
233
|
|
|
@@ -232,7 +237,7 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
232
237
|
print_empty_line
|
|
233
238
|
end
|
|
234
239
|
|
|
235
|
-
Helper.log.info "Fetching events for issues and PR: #{
|
|
240
|
+
Helper.log.info "Fetching events for issues and PR: #{fetched_count}"
|
|
236
241
|
end
|
|
237
242
|
|
|
238
243
|
# Fetch comments for PRs and add them to "comments"
|
|
@@ -305,12 +310,13 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
305
310
|
barrier = Async::Barrier.new
|
|
306
311
|
semaphore = Async::Semaphore.new(MAXIMUM_CONNECTIONS, parent: barrier)
|
|
307
312
|
|
|
313
|
+
branch = @options[:release_branch] || default_branch
|
|
308
314
|
if (since_commit = @options[:since_commit])
|
|
309
|
-
iterate_pages(client, "commits_since", since_commit, parent: semaphore) do |new_commits|
|
|
315
|
+
iterate_pages(client, "commits_since", since_commit, branch, parent: semaphore) do |new_commits|
|
|
310
316
|
@commits.concat(new_commits)
|
|
311
317
|
end
|
|
312
318
|
else
|
|
313
|
-
iterate_pages(client, "commits", parent: semaphore) do |new_commits|
|
|
319
|
+
iterate_pages(client, "commits", branch, parent: semaphore) do |new_commits|
|
|
314
320
|
@commits.concat(new_commits)
|
|
315
321
|
end
|
|
316
322
|
end
|
|
@@ -340,7 +346,13 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
340
346
|
# @param [String] name
|
|
341
347
|
# @return [Array<String>]
|
|
342
348
|
def commits_in_branch(name)
|
|
343
|
-
@branches ||=
|
|
349
|
+
@branches ||= begin
|
|
350
|
+
all_branches = {}
|
|
351
|
+
iterate_pages(client, "branches") do |branches|
|
|
352
|
+
branches.each { |branch| all_branches[branch[:name]] = branch }
|
|
353
|
+
end
|
|
354
|
+
all_branches
|
|
355
|
+
end
|
|
344
356
|
|
|
345
357
|
if (branch = @branches[name])
|
|
346
358
|
commits_in_tag(branch[:commit][:sha])
|
|
@@ -375,9 +387,15 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
375
387
|
queue = [current]
|
|
376
388
|
while queue.any?
|
|
377
389
|
commit = queue.shift
|
|
378
|
-
# If we've already processed this sha, just grab it's parents from the cache
|
|
379
390
|
if @commits_in_tag_cache.key?(commit[:sha])
|
|
391
|
+
# If we've already processed this sha in a previous run, just grab
|
|
392
|
+
# its parents from the cache
|
|
380
393
|
shas.merge(@commits_in_tag_cache[commit[:sha]])
|
|
394
|
+
elsif shas.include?(commit[:sha])
|
|
395
|
+
# If we've already processed this sha in the current run, stop there
|
|
396
|
+
# for the current commit because its parents have already been
|
|
397
|
+
# queued/processed. Jump to the next queued commit.
|
|
398
|
+
next
|
|
381
399
|
else
|
|
382
400
|
shas.add(commit[:sha])
|
|
383
401
|
commit[:parents].each do |p|
|
|
@@ -513,7 +531,7 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
|
|
|
513
531
|
env_var
|
|
514
532
|
end
|
|
515
533
|
|
|
516
|
-
# @return [String] helper to return
|
|
534
|
+
# @return [String] helper to return GitHub "user/project"
|
|
517
535
|
def user_project
|
|
518
536
|
"#{@options[:user]}/#{@options[:project]}"
|
|
519
537
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require "delegate"
|
|
4
|
-
|
|
4
|
+
require_relative "helper"
|
|
5
5
|
|
|
6
6
|
module GitHubChangelogGenerator
|
|
7
7
|
# This class wraps Options, and knows a list of known options. Others options
|
|
@@ -145,7 +145,17 @@ module GitHubChangelogGenerator
|
|
|
145
145
|
#
|
|
146
146
|
# @return [Hash] The GitHub `:token` key is censored in the output.
|
|
147
147
|
def censored_values
|
|
148
|
-
values.clone.tap { |opts| opts[:token] = opts[:token]
|
|
148
|
+
values.clone.tap { |opts| opts[:token] = censored_token(opts[:token]) }
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
def censored_token(opts_token)
|
|
152
|
+
if !opts_token.nil?
|
|
153
|
+
"Used token from options"
|
|
154
|
+
elsif ENV["CHANGELOG_GITHUB_TOKEN"]
|
|
155
|
+
"Used token from environment variable"
|
|
156
|
+
else
|
|
157
|
+
"No token used"
|
|
158
|
+
end
|
|
149
159
|
end
|
|
150
160
|
|
|
151
161
|
def unsupported_options
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
require_relative "helper"
|
|
4
|
+
require_relative "argv_parser"
|
|
5
|
+
require_relative "parser_file"
|
|
6
|
+
require_relative "file_parser_chooser"
|
|
7
7
|
|
|
8
8
|
module GitHubChangelogGenerator
|
|
9
9
|
class Parser
|
|
@@ -1,32 +1,8 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require "pathname"
|
|
4
|
-
|
|
5
3
|
module GitHubChangelogGenerator
|
|
6
4
|
ParserError = Class.new(StandardError)
|
|
7
5
|
|
|
8
|
-
class FileParserChooser
|
|
9
|
-
def initialize(options)
|
|
10
|
-
@options = options
|
|
11
|
-
@config_file = Pathname.new(options[:config_file])
|
|
12
|
-
end
|
|
13
|
-
|
|
14
|
-
def parse!(_argv)
|
|
15
|
-
return nil unless (path = resolve_path)
|
|
16
|
-
|
|
17
|
-
ParserFile.new(@options, File.open(path)).parse!
|
|
18
|
-
end
|
|
19
|
-
|
|
20
|
-
def resolve_path
|
|
21
|
-
return @config_file if @config_file.exist?
|
|
22
|
-
|
|
23
|
-
path = @config_file.expand_path
|
|
24
|
-
return path if File.exist?(path)
|
|
25
|
-
|
|
26
|
-
nil
|
|
27
|
-
end
|
|
28
|
-
end
|
|
29
|
-
|
|
30
6
|
# ParserFile is a configuration file reader which sets options in the
|
|
31
7
|
# given Hash.
|
|
32
8
|
#
|
|
@@ -58,10 +58,10 @@ module GitHubChangelogGenerator
|
|
|
58
58
|
|
|
59
59
|
log = generator.compound_changelog
|
|
60
60
|
|
|
61
|
-
output_filename =
|
|
61
|
+
output_filename = options[:output].to_s
|
|
62
62
|
File.open(output_filename, "w") { |file| file.write(log) }
|
|
63
63
|
puts "Done!"
|
|
64
|
-
puts "Generated log placed in #{
|
|
64
|
+
puts "Generated log placed in #{File.absolute_path(output_filename)}"
|
|
65
65
|
end
|
|
66
66
|
end
|
|
67
67
|
end
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
#!/usr/bin/env ruby
|
|
2
1
|
# frozen_string_literal: true
|
|
3
2
|
|
|
4
3
|
require "octokit"
|
|
@@ -7,16 +6,15 @@ require "logger"
|
|
|
7
6
|
require "active_support"
|
|
8
7
|
require "active_support/core_ext/object/blank"
|
|
9
8
|
require "json"
|
|
10
|
-
require "multi_json"
|
|
11
9
|
require "benchmark"
|
|
12
10
|
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
11
|
+
require_relative "github_changelog_generator/helper"
|
|
12
|
+
require_relative "github_changelog_generator/options"
|
|
13
|
+
require_relative "github_changelog_generator/parser"
|
|
14
|
+
require_relative "github_changelog_generator/parser_file"
|
|
15
|
+
require_relative "github_changelog_generator/generator/generator"
|
|
16
|
+
require_relative "github_changelog_generator/version"
|
|
17
|
+
require_relative "github_changelog_generator/reader"
|
|
20
18
|
|
|
21
19
|
# The main module, where placed all classes (now, at least)
|
|
22
20
|
module GitHubChangelogGenerator
|
|
@@ -24,24 +22,28 @@ module GitHubChangelogGenerator
|
|
|
24
22
|
class ChangelogGenerator
|
|
25
23
|
# Class, responsible for whole changelog generation cycle
|
|
26
24
|
# @return initialised instance of ChangelogGenerator
|
|
27
|
-
def initialize
|
|
28
|
-
@options = Parser.parse_options
|
|
29
|
-
@generator = Generator.new
|
|
25
|
+
def initialize(params = ARGV)
|
|
26
|
+
@options = Parser.parse_options(params)
|
|
27
|
+
@generator = Generator.new(@options)
|
|
30
28
|
end
|
|
31
29
|
|
|
32
30
|
# The entry point of this script to generate changelog
|
|
33
31
|
# @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags.
|
|
34
32
|
def run
|
|
35
|
-
log =
|
|
33
|
+
log = generator.compound_changelog
|
|
36
34
|
|
|
37
|
-
if
|
|
38
|
-
output_filename =
|
|
35
|
+
if options.write_to_file?
|
|
36
|
+
output_filename = options[:output].to_s
|
|
39
37
|
File.open(output_filename, "wb") { |file| file.write(log) }
|
|
40
38
|
puts "Done!"
|
|
41
|
-
puts "Generated log placed in #{
|
|
39
|
+
puts "Generated log placed in #{File.absolute_path(output_filename)}"
|
|
42
40
|
else
|
|
43
41
|
puts log
|
|
44
42
|
end
|
|
45
43
|
end
|
|
44
|
+
|
|
45
|
+
private
|
|
46
|
+
|
|
47
|
+
attr_reader :generator, :options
|
|
46
48
|
end
|
|
47
49
|
end
|
|
@@ -206,11 +206,11 @@
|
|
|
206
206
|
|
|
207
207
|
<p> --github-site URL</p>
|
|
208
208
|
|
|
209
|
-
<p> The Enterprise
|
|
209
|
+
<p> The Enterprise GitHub site on which your project is hosted.</p>
|
|
210
210
|
|
|
211
211
|
<p> --github-api URL</p>
|
|
212
212
|
|
|
213
|
-
<p> The enterprise endpoint to use for your
|
|
213
|
+
<p> The enterprise endpoint to use for your GitHub API.</p>
|
|
214
214
|
|
|
215
215
|
<p> --simple-list</p>
|
|
216
216
|
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
RSpec.describe GitHubChangelogGenerator::ChangelogGenerator do
|
|
4
|
+
describe "#run" do
|
|
5
|
+
let(:arguments) { [] }
|
|
6
|
+
let(:instance) { described_class.new(arguments) }
|
|
7
|
+
let(:output_path) { File.join(__dir__, "tmp", "test.output") }
|
|
8
|
+
|
|
9
|
+
after { FileUtils.rm_rf(output_path) }
|
|
10
|
+
|
|
11
|
+
let(:generator) { instance_double(::GitHubChangelogGenerator::Generator) }
|
|
12
|
+
|
|
13
|
+
before do
|
|
14
|
+
allow(instance).to receive(:generator) { generator }
|
|
15
|
+
allow(generator).to receive(:compound_changelog) { "content" }
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
context "when full path given as the --output argument" do
|
|
19
|
+
let(:arguments) { ["--output", output_path] }
|
|
20
|
+
it "puts the complete output path to STDOUT" do
|
|
21
|
+
expect { instance.run }.to output(Regexp.new(output_path)).to_stdout
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
context "when empty value given as the --output argument" do
|
|
26
|
+
let(:arguments) { ["--output", ""] }
|
|
27
|
+
it "puts the complete output path to STDOUT" do
|
|
28
|
+
expect { instance.run }.to output(/content/).to_stdout
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -120,7 +120,7 @@ module GitHubChangelogGenerator
|
|
|
120
120
|
|
|
121
121
|
let(:pull_requests) do
|
|
122
122
|
[
|
|
123
|
-
pr("no labels", [], "",
|
|
123
|
+
pr("no labels", [], "", "20", "login" => "user1"),
|
|
124
124
|
pr("breaking", ["breaking"], "", "23", "login" => "user5"),
|
|
125
125
|
pr("enhancement", ["enhancement"], "", "21", "login" => "user5"),
|
|
126
126
|
pr("bug", ["bug"], "", "22", "login" => "user5"),
|
|
@@ -129,7 +129,7 @@ module GitHubChangelogGenerator
|
|
|
129
129
|
pr("security", ["security"], "", "30", "login" => "user5"),
|
|
130
130
|
pr("all the labels", %w[breaking enhancement bug deprecated removed security], "", "24", "login" => "user5"),
|
|
131
131
|
pr("all the labels different order", %w[bug breaking enhancement security remove deprecated], "", "25", "login" => "user5"),
|
|
132
|
-
pr("some unmapped labels", %w[tests-fail bug], "",
|
|
132
|
+
pr("some unmapped labels", %w[tests-fail bug], "", "26", "login" => "user5"),
|
|
133
133
|
pr("no mapped labels", %w[docs maintenance], "", "27", "login" => "user5")
|
|
134
134
|
]
|
|
135
135
|
end
|
|
@@ -137,6 +137,67 @@ module GitHubChangelogGenerator
|
|
|
137
137
|
it { is_expected.to eq(expected_issues) }
|
|
138
138
|
end
|
|
139
139
|
end
|
|
140
|
+
|
|
141
|
+
describe "#find_issues_to_add" do
|
|
142
|
+
let(:issues) { [issue] }
|
|
143
|
+
let(:tag_name) { nil }
|
|
144
|
+
let(:filtered_tags) { [] }
|
|
145
|
+
before { generator.instance_variable_set(:@filtered_tags, filtered_tags) }
|
|
146
|
+
|
|
147
|
+
subject { generator.find_issues_to_add(issues, tag_name) }
|
|
148
|
+
|
|
149
|
+
context "issue without milestone" do
|
|
150
|
+
let(:issue) { { "labels" => [] } }
|
|
151
|
+
|
|
152
|
+
it { is_expected.to be_empty }
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
context "milestone title not in the filtered tags" do
|
|
156
|
+
let(:issue) { { "milestone" => { "title" => "a title" } } }
|
|
157
|
+
let(:filtered_tags) { [{ "name" => "another name" }] }
|
|
158
|
+
|
|
159
|
+
it { is_expected.to be_empty }
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
context "milestone title matches the tag name" do
|
|
163
|
+
let(:tag_name) { "tag name" }
|
|
164
|
+
let(:issue) { { "milestone" => { "title" => tag_name } } }
|
|
165
|
+
let(:filtered_tags) { [{ "name" => tag_name }] }
|
|
166
|
+
|
|
167
|
+
it { is_expected.to contain_exactly(issue) }
|
|
168
|
+
end
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
describe "#remove_issues_in_milestones" do
|
|
172
|
+
let(:issues) { [issue] }
|
|
173
|
+
|
|
174
|
+
context "issue without milestone" do
|
|
175
|
+
let(:issue) { { "labels" => [] } }
|
|
176
|
+
|
|
177
|
+
it "is not filtered" do
|
|
178
|
+
expect { generator.remove_issues_in_milestones(issues) }.to_not(change { issues })
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
context "remove issues of open milestones if option is set" do
|
|
183
|
+
let(:issue) { { "milestone" => { "state" => "open" } } }
|
|
184
|
+
let(:options) { { issues_of_open_milestones: false } }
|
|
185
|
+
|
|
186
|
+
it "is filtered" do
|
|
187
|
+
expect { generator.remove_issues_in_milestones(issues) }.to change { issues }.to([])
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
context "milestone in the tag list" do
|
|
192
|
+
let(:milestone_name) { "milestone name" }
|
|
193
|
+
let(:issue) { { "milestone" => { "title" => milestone_name } } }
|
|
194
|
+
|
|
195
|
+
it "is filtered" do
|
|
196
|
+
generator.instance_variable_set(:@filtered_tags, [{ "name" => milestone_name }])
|
|
197
|
+
expect { generator.remove_issues_in_milestones(issues) }.to change { issues }.to([])
|
|
198
|
+
end
|
|
199
|
+
end
|
|
200
|
+
end
|
|
140
201
|
end
|
|
141
202
|
end
|
|
142
203
|
end
|
|
@@ -44,4 +44,155 @@ RSpec.describe GitHubChangelogGenerator::Generator do
|
|
|
44
44
|
end
|
|
45
45
|
end
|
|
46
46
|
end
|
|
47
|
+
|
|
48
|
+
describe "#add_first_occurring_tag_to_prs" do
|
|
49
|
+
def sha(num)
|
|
50
|
+
base = num.to_s
|
|
51
|
+
pad_length = 40 - base.length
|
|
52
|
+
"#{'a' * pad_length}#{base}"
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
let(:release_branch_name) { "release" }
|
|
56
|
+
let(:generator) { described_class.new({ release_branch: release_branch_name }) }
|
|
57
|
+
let(:fake_fetcher) do
|
|
58
|
+
instance_double(GitHubChangelogGenerator::OctoFetcher,
|
|
59
|
+
fetch_tag_shas: nil,
|
|
60
|
+
fetch_comments_async: nil)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
before do
|
|
64
|
+
allow(fake_fetcher)
|
|
65
|
+
.to receive(:commits_in_branch).with(release_branch_name)
|
|
66
|
+
.and_return([sha(1), sha(2), sha(3), sha(4)])
|
|
67
|
+
allow(GitHubChangelogGenerator::OctoFetcher).to receive(:new).and_return(fake_fetcher)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
it "associates prs to the oldest tag containing the merge commit" do
|
|
71
|
+
prs = [{ "number" => "23", "events" => [{ "event" => "merged", "commit_id" => sha(2) }] }]
|
|
72
|
+
tags = [
|
|
73
|
+
{ "name" => "newer2.0", "shas_in_tag" => [sha(1), sha(2), sha(3)] },
|
|
74
|
+
{ "name" => "older1.0", "shas_in_tag" => [sha(1), sha(2)] }
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
78
|
+
|
|
79
|
+
aggregate_failures do
|
|
80
|
+
expect(prs_left).to be_empty
|
|
81
|
+
expect(prs.first["first_occurring_tag"]).to eq "older1.0"
|
|
82
|
+
|
|
83
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
84
|
+
expect(fake_fetcher).not_to have_received(:fetch_comments_async)
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
it "detects prs merged in the release branch" do
|
|
89
|
+
prs = [{ "number" => "23", "events" => [{ "event" => "merged", "commit_id" => sha(4) }] }]
|
|
90
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
91
|
+
|
|
92
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
93
|
+
|
|
94
|
+
aggregate_failures do
|
|
95
|
+
expect(prs_left).to be_empty
|
|
96
|
+
expect(prs.first["first_occurring_tag"]).to be_nil
|
|
97
|
+
|
|
98
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
99
|
+
expect(fake_fetcher).not_to have_received(:fetch_comments_async)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
it "detects closed prs marked as rebased in a tag" do
|
|
104
|
+
prs = [{ "number" => "23", "comments" => [{ "body" => "rebased commit: #{sha(2)}" }] }]
|
|
105
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
106
|
+
|
|
107
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
108
|
+
|
|
109
|
+
aggregate_failures do
|
|
110
|
+
expect(prs_left).to be_empty
|
|
111
|
+
expect(prs.first["first_occurring_tag"]).to eq "v1.0"
|
|
112
|
+
|
|
113
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
114
|
+
expect(fake_fetcher).to have_received(:fetch_comments_async)
|
|
115
|
+
end
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
it "detects closed prs marked as rebased in the release branch" do
|
|
119
|
+
prs = [{ "number" => "23", "comments" => [{ "body" => "rebased commit: #{sha(4)}" }] }]
|
|
120
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
121
|
+
|
|
122
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
123
|
+
|
|
124
|
+
aggregate_failures do
|
|
125
|
+
expect(prs_left).to be_empty
|
|
126
|
+
expect(prs.first["first_occurring_tag"]).to be_nil
|
|
127
|
+
|
|
128
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
129
|
+
expect(fake_fetcher).to have_received(:fetch_comments_async)
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
it "leaves prs merged in another branch" do
|
|
134
|
+
prs = [{ "number" => "23", "events" => [{ "event" => "merged", "commit_id" => sha(5) }] }]
|
|
135
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
136
|
+
|
|
137
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
138
|
+
|
|
139
|
+
aggregate_failures do
|
|
140
|
+
expect(prs_left).to eq prs
|
|
141
|
+
expect(prs.first["first_occurring_tag"]).to be_nil
|
|
142
|
+
|
|
143
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
144
|
+
expect(fake_fetcher).to have_received(:fetch_comments_async)
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
it "detects prs merged elsewhere and marked as rebased in a tag" do
|
|
149
|
+
prs = [{ "number" => "23",
|
|
150
|
+
"events" => [{ "event" => "merged", "commit_id" => sha(5) }],
|
|
151
|
+
"comments" => [{ "body" => "rebased commit: #{sha(2)}" }] }]
|
|
152
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
153
|
+
|
|
154
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
155
|
+
|
|
156
|
+
aggregate_failures do
|
|
157
|
+
expect(prs_left).to be_empty
|
|
158
|
+
expect(prs.first["first_occurring_tag"]).to eq "v1.0"
|
|
159
|
+
|
|
160
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
161
|
+
expect(fake_fetcher).to have_received(:fetch_comments_async)
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
it "detects prs merged elsewhere and marked as rebased in the release branch" do
|
|
166
|
+
prs = [{ "number" => "23",
|
|
167
|
+
"events" => [{ "event" => "merged", "commit_id" => sha(5) }],
|
|
168
|
+
"comments" => [{ "body" => "rebased commit: #{sha(4)}" }] }]
|
|
169
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
170
|
+
|
|
171
|
+
prs_left = generator.send(:add_first_occurring_tag_to_prs, tags, prs)
|
|
172
|
+
|
|
173
|
+
aggregate_failures do
|
|
174
|
+
expect(prs_left).to be_empty
|
|
175
|
+
expect(prs.first["first_occurring_tag"]).to be_nil
|
|
176
|
+
|
|
177
|
+
expect(fake_fetcher).to have_received(:fetch_tag_shas)
|
|
178
|
+
expect(fake_fetcher).to have_received(:fetch_comments_async)
|
|
179
|
+
end
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
it "raises an error for closed prs marked as rebased to an unknown commit" do
|
|
183
|
+
prs = [{ "number" => "23", "comments" => [{ "body" => "rebased commit: #{sha(5)}" }] }]
|
|
184
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
185
|
+
|
|
186
|
+
expect { generator.send(:add_first_occurring_tag_to_prs, tags, prs) }
|
|
187
|
+
.to raise_error StandardError, "PR 23 has a rebased SHA comment but that SHA was not found in the release branch or any tags"
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
it "raises an error for prs without merge event or rebase comment" do
|
|
191
|
+
prs = [{ "number" => "23" }]
|
|
192
|
+
tags = [{ "name" => "v1.0", "shas_in_tag" => [sha(1), sha(2)] }]
|
|
193
|
+
|
|
194
|
+
expect { generator.send(:add_first_occurring_tag_to_prs, tags, prs) }
|
|
195
|
+
.to raise_error StandardError, "No merge sha found for PR 23 via the GitHub API"
|
|
196
|
+
end
|
|
197
|
+
end
|
|
47
198
|
end
|
|
@@ -49,7 +49,7 @@ describe GitHubChangelogGenerator::Generator do
|
|
|
49
49
|
let(:generator) { described_class.new(default_options.merge(options)) }
|
|
50
50
|
|
|
51
51
|
before do
|
|
52
|
-
allow_any_instance_of(GitHubChangelogGenerator::OctoFetcher).to receive(:
|
|
52
|
+
allow_any_instance_of(GitHubChangelogGenerator::OctoFetcher).to receive(:fetch_all_tags).and_return(all_tags)
|
|
53
53
|
allow(generator).to receive(:fetch_tags_dates).with(all_tags)
|
|
54
54
|
allow(generator).to receive(:sort_tags_by_date).with(all_tags).and_return(sorted_tags)
|
|
55
55
|
generator.fetch_and_filter_tags
|
|
@@ -30,5 +30,14 @@ module GitHubChangelogGenerator
|
|
|
30
30
|
end
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
|
+
|
|
34
|
+
describe "#normalize_body" do
|
|
35
|
+
context "it should remove CR" do
|
|
36
|
+
let(:body) { "Some content from GitHub\r\n\r\nUser is describing something" }
|
|
37
|
+
it "returns a cleaned body" do
|
|
38
|
+
expect(section.send(:normalize_body, body)).to eq "Some content from GitHub\n\nUser is describing something"
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
33
42
|
end
|
|
34
43
|
end
|
|
@@ -72,12 +72,12 @@ describe GitHubChangelogGenerator::OctoFetcher do
|
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
|
|
75
|
-
describe "#
|
|
75
|
+
describe "#fetch_all_tags" do
|
|
76
76
|
context "when github_fetch_tags returns tags" do
|
|
77
77
|
it "returns tags" do
|
|
78
78
|
mock_tags = ["tag"]
|
|
79
79
|
allow(fetcher).to receive(:github_fetch_tags).and_return(mock_tags)
|
|
80
|
-
expect(fetcher.
|
|
80
|
+
expect(fetcher.fetch_all_tags).to eq(mock_tags)
|
|
81
81
|
end
|
|
82
82
|
end
|
|
83
83
|
end
|
|
@@ -547,21 +547,21 @@ describe GitHubChangelogGenerator::OctoFetcher do
|
|
|
547
547
|
expectations = [
|
|
548
548
|
%w[sha decfe840d1a1b86e0c28700de5362d3365a29555],
|
|
549
549
|
["url",
|
|
550
|
-
"https://api.github.com/repos/
|
|
550
|
+
"https://api.github.com/repos/github-changelog-generator/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555"],
|
|
551
551
|
# OLD API: "https://api.github.com/repos/skywinder/changelog_test/git/commits/decfe840d1a1b86e0c28700de5362d3365a29555"],
|
|
552
552
|
["html_url",
|
|
553
|
-
"https://github.com/
|
|
553
|
+
"https://github.com/github-changelog-generator/changelog_test/commit/decfe840d1a1b86e0c28700de5362d3365a29555"],
|
|
554
554
|
["author",
|
|
555
|
-
{ "login" => "skywinder", "id" => 3_356_474, "avatar_url" => "https://
|
|
555
|
+
{ "login" => "skywinder", "node_id" => "MDQ6VXNlcjMzNTY0NzQ=", "id" => 3_356_474, "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=4", "gravatar_id" => "", "url" => "https://api.github.com/users/skywinder", "html_url" => "https://github.com/skywinder", "followers_url" => "https://api.github.com/users/skywinder/followers", "following_url" => "https://api.github.com/users/skywinder/following{/other_user}", "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", "starred_url" => "https://api.github.com/users/skywinder/starred{/owner}{/repo}", "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", "organizations_url" => "https://api.github.com/users/skywinder/orgs", "repos_url" => "https://api.github.com/users/skywinder/repos", "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", "received_events_url" => "https://api.github.com/users/skywinder/received_events", "type" => "User", "site_admin" => false }],
|
|
556
556
|
["committer",
|
|
557
|
-
{ "login" => "skywinder", "id" => 3_356_474, "avatar_url" => "https://
|
|
557
|
+
{ "login" => "skywinder", "node_id" => "MDQ6VXNlcjMzNTY0NzQ=", "id" => 3_356_474, "avatar_url" => "https://avatars.githubusercontent.com/u/3356474?v=4", "gravatar_id" => "", "url" => "https://api.github.com/users/skywinder", "html_url" => "https://github.com/skywinder", "followers_url" => "https://api.github.com/users/skywinder/followers", "following_url" => "https://api.github.com/users/skywinder/following{/other_user}", "gists_url" => "https://api.github.com/users/skywinder/gists{/gist_id}", "starred_url" => "https://api.github.com/users/skywinder/starred{/owner}{/repo}", "subscriptions_url" => "https://api.github.com/users/skywinder/subscriptions", "organizations_url" => "https://api.github.com/users/skywinder/orgs", "repos_url" => "https://api.github.com/users/skywinder/repos", "events_url" => "https://api.github.com/users/skywinder/events{/privacy}", "received_events_url" => "https://api.github.com/users/skywinder/received_events", "type" => "User", "site_admin" => false }],
|
|
558
558
|
["parents",
|
|
559
559
|
[{ "sha" => "7ec095e5e3caceacedabf44d0b9b10da17c92e51",
|
|
560
560
|
"url" =>
|
|
561
|
-
"https://api.github.com/repos/
|
|
561
|
+
"https://api.github.com/repos/github-changelog-generator/changelog_test/commits/7ec095e5e3caceacedabf44d0b9b10da17c92e51",
|
|
562
562
|
# OLD API: "https://api.github.com/repos/skywinder/changelog_test/git/commits/7ec095e5e3caceacedabf44d0b9b10da17c92e51",
|
|
563
563
|
"html_url" =>
|
|
564
|
-
"https://github.com/
|
|
564
|
+
"https://github.com/github-changelog-generator/changelog_test/commit/7ec095e5e3caceacedabf44d0b9b10da17c92e51" }]]
|
|
565
565
|
]
|
|
566
566
|
|
|
567
567
|
expectations.each do |property, value|
|