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.
Files changed (48) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +52 -16
  3. data/bin/github_changelog_generator +1 -1
  4. data/lib/github_changelog_generator/argv_parser.rb +2 -2
  5. data/lib/github_changelog_generator/file_parser_chooser.rb +27 -0
  6. data/lib/github_changelog_generator/generator/entry.rb +8 -6
  7. data/lib/github_changelog_generator/generator/generator.rb +15 -10
  8. data/lib/github_changelog_generator/generator/generator_fetcher.rb +22 -24
  9. data/lib/github_changelog_generator/generator/generator_processor.rb +15 -25
  10. data/lib/github_changelog_generator/generator/generator_tags.rb +24 -34
  11. data/lib/github_changelog_generator/generator/section.rb +9 -4
  12. data/lib/github_changelog_generator/octo_fetcher.rb +30 -12
  13. data/lib/github_changelog_generator/options.rb +12 -2
  14. data/lib/github_changelog_generator/parser.rb +4 -4
  15. data/lib/github_changelog_generator/parser_file.rb +0 -24
  16. data/lib/github_changelog_generator/task.rb +2 -2
  17. data/lib/github_changelog_generator/version.rb +1 -1
  18. data/lib/github_changelog_generator.rb +18 -16
  19. data/man/git-generate-changelog.html +2 -2
  20. data/spec/github_changelog_generator_spec.rb +32 -0
  21. data/spec/unit/generator/entry_spec.rb +2 -2
  22. data/spec/unit/generator/generator_processor_spec.rb +61 -0
  23. data/spec/unit/generator/generator_spec.rb +151 -0
  24. data/spec/unit/generator/generator_tags_spec.rb +1 -1
  25. data/spec/unit/generator/section_spec.rb +9 -0
  26. data/spec/unit/octo_fetcher_spec.rb +8 -8
  27. data/spec/unit/{parse_file_spec.rb → parser_file_spec.rb} +35 -15
  28. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits/when_API_is_valid/returns_commits.json +1 -1
  29. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits_before/when_API_is_valid/returns_commits.json +1 -1
  30. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issue_with_proper_key/values.json +1 -1
  31. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues.json +1 -1
  32. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues_with_labels.json +1 -1
  33. 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
  34. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_requests_with_labels.json +1 -1
  35. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid.json +1 -1
  36. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_correct_pull_request_keys.json +1 -1
  37. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_pull_requests.json +1 -1
  38. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid.json +1 -1
  39. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid/returns_commit.json +1 -1
  40. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid.json +1 -1
  41. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid/returns_date.json +1 -1
  42. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid.json +1 -1
  43. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid/populates_issues.json +1 -1
  44. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid.json +1 -1
  45. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags.json +1 -1
  46. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags_count.json +1 -1
  47. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid.json +1 -1
  48. 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 :async_http
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 get_all_tags
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
- i = 0
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
- print_in_same_line("Fetching events for issues and PR: #{i + 1}/#{issues.count}")
225
- i += 1
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: #{i}"
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 ||= client.branches(user_project).map { |branch| [branch[:name], branch] }.to_h
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 Github "user/project"
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
- require "github_changelog_generator/helper"
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].nil? ? "No token used" : "hidden value" }
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
- require "github_changelog_generator/helper"
5
- require "github_changelog_generator/argv_parser"
6
- require "github_changelog_generator/parser_file"
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 = (options[:output]).to_s
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 #{Dir.pwd}/#{output_filename}"
64
+ puts "Generated log placed in #{File.absolute_path(output_filename)}"
65
65
  end
66
66
  end
67
67
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module GitHubChangelogGenerator
4
- VERSION = "1.16.4"
4
+ VERSION = "1.18.0"
5
5
  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
- require "github_changelog_generator/helper"
14
- require "github_changelog_generator/options"
15
- require "github_changelog_generator/parser"
16
- require "github_changelog_generator/parser_file"
17
- require "github_changelog_generator/generator/generator"
18
- require "github_changelog_generator/version"
19
- require "github_changelog_generator/reader"
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 @options
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 = @generator.compound_changelog
33
+ log = generator.compound_changelog
36
34
 
37
- if @options.write_to_file?
38
- output_filename = @options[:output].to_s
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 #{Dir.pwd}/#{output_filename}"
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 Github site on which your project is hosted.</p>
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 Github API.</p>
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", [], "", "20", "login" => "user1"),
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], "", "26", "login" => "user5"),
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(:get_all_tags).and_return(all_tags)
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 "#get_all_tags" do
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.get_all_tags).to eq(mock_tags)
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/skywinder/changelog_test/commits/decfe840d1a1b86e0c28700de5362d3365a29555"],
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/skywinder/changelog_test/commit/decfe840d1a1b86e0c28700de5362d3365a29555"],
553
+ "https://github.com/github-changelog-generator/changelog_test/commit/decfe840d1a1b86e0c28700de5362d3365a29555"],
554
554
  ["author",
555
- { "login" => "skywinder", "id" => 3_356_474, "avatar_url" => "https://avatars2.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 }],
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://avatars2.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 }],
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/skywinder/changelog_test/commits/7ec095e5e3caceacedabf44d0b9b10da17c92e51",
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/skywinder/changelog_test/commit/7ec095e5e3caceacedabf44d0b9b10da17c92e51" }]]
564
+ "https://github.com/github-changelog-generator/changelog_test/commit/7ec095e5e3caceacedabf44d0b9b10da17c92e51" }]]
565
565
  ]
566
566
 
567
567
  expectations.each do |property, value|