github_changelog_generator 1.15.0.pre.rc → 1.15.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 (57) hide show
  1. checksums.yaml +5 -5
  2. data/LICENSE +1 -1
  3. data/README.md +126 -51
  4. data/bin/git-generate-changelog +1 -1
  5. data/lib/github_changelog_generator.rb +10 -6
  6. data/lib/github_changelog_generator/generator/entry.rb +218 -0
  7. data/lib/github_changelog_generator/generator/generator.rb +96 -119
  8. data/lib/github_changelog_generator/generator/generator_fetcher.rb +140 -21
  9. data/lib/github_changelog_generator/generator/generator_processor.rb +40 -10
  10. data/lib/github_changelog_generator/generator/generator_tags.rb +10 -12
  11. data/lib/github_changelog_generator/generator/section.rb +104 -0
  12. data/lib/github_changelog_generator/octo_fetcher.rb +113 -23
  13. data/lib/github_changelog_generator/options.rb +35 -4
  14. data/lib/github_changelog_generator/parser.rb +88 -49
  15. data/lib/github_changelog_generator/parser_file.rb +6 -2
  16. data/lib/github_changelog_generator/task.rb +2 -3
  17. data/lib/github_changelog_generator/version.rb +1 -1
  18. data/man/git-generate-changelog.1 +125 -51
  19. data/man/git-generate-changelog.1.html +145 -89
  20. data/man/git-generate-changelog.html +19 -7
  21. data/man/git-generate-changelog.md +141 -86
  22. data/spec/files/github-changelog-generator.md +114 -114
  23. data/spec/{install-gem-in-bundler.gemfile → install_gem_in_bundler.gemfile} +2 -0
  24. data/spec/spec_helper.rb +1 -5
  25. data/spec/unit/generator/entry_spec.rb +760 -0
  26. data/spec/unit/generator/generator_processor_spec.rb +9 -2
  27. data/spec/unit/generator/generator_tags_spec.rb +5 -21
  28. data/spec/unit/octo_fetcher_spec.rb +204 -197
  29. data/spec/unit/options_spec.rb +24 -0
  30. data/spec/unit/parse_file_spec.rb +2 -2
  31. data/spec/unit/reader_spec.rb +4 -4
  32. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits/when_API_is_valid/returns_commits.json +1 -0
  33. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits_before/when_API_is_valid/returns_commits.json +1 -1
  34. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid.json +1 -1
  35. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issue_with_proper_key/values.json +1 -1
  36. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues.json +1 -1
  37. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_issues_with_labels.json +1 -1
  38. 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
  39. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_issues_and_pr/when_API_call_is_valid/returns_pull_requests_with_labels.json +1 -1
  40. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid.json +1 -1
  41. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_correct_pull_request_keys.json +1 -1
  42. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_closed_pull_requests/when_API_call_is_valid/returns_pull_requests.json +1 -1
  43. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid.json +1 -1
  44. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_commit/when_API_call_is_valid/returns_commit.json +1 -1
  45. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid.json +1 -1
  46. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_date_of_tag/when_API_call_is_valid/returns_date.json +1 -1
  47. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid.json +1 -1
  48. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_fetch_events_async/when_API_call_is_valid/populates_issues.json +1 -1
  49. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid.json +1 -1
  50. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags.json +1 -1
  51. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_API_call_is_valid/should_return_tags_count.json +1 -1
  52. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_wrong_token_provided.json +1 -1
  53. data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_wrong_token_provided/should_raise_Unauthorized_error.json +1 -1
  54. metadata +17 -17
  55. data/bin/ghclgen +0 -5
  56. data/lib/github_changelog_generator/generator/generator_generation.rb +0 -181
  57. data/spec/unit/generator/generator_generation_spec.rb +0 -73
@@ -2,7 +2,7 @@
2
2
 
3
3
  module GitHubChangelogGenerator
4
4
  class Generator
5
- # delete all labels with labels from options[:exclude_labels] array
5
+ # delete all issues with labels from options[:exclude_labels] array
6
6
  # @param [Array] issues
7
7
  # @return [Array] filtered array
8
8
  def exclude_issues_by_labels(issues)
@@ -14,6 +14,19 @@ module GitHubChangelogGenerator
14
14
  end
15
15
  end
16
16
 
17
+ # Only include issues without labels if options[:add_issues_wo_labels]
18
+ # @param [Array] issues
19
+ # @return [Array] filtered array
20
+ def exclude_issues_without_labels(issues)
21
+ return issues if issues.empty?
22
+ return issues if issues.first.key?("pull_request") && options[:add_pr_wo_labels]
23
+ return issues if !issues.first.key?("pull_request") && options[:add_issues_wo_labels]
24
+
25
+ issues.reject do |issue|
26
+ issue["labels"].empty?
27
+ end
28
+ end
29
+
17
30
  # @return [Array] filtered issues accourding milestone
18
31
  def filter_by_milestone(filtered_issues, tag_name, all_issues)
19
32
  remove_issues_in_milestones(filtered_issues)
@@ -63,11 +76,22 @@ module GitHubChangelogGenerator
63
76
  end
64
77
  end
65
78
 
79
+ # Method filter issues, that belong only specified tag range
80
+ #
81
+ # @param [Array] issues issues to filter
82
+ # @param [Hash, Nil] newer_tag Tag to find PRs of. May be nil for unreleased section
83
+ # @return [Array] filtered issues
84
+ def filter_by_tag(issues, newer_tag = nil)
85
+ issues.select do |issue|
86
+ issue["first_occurring_tag"] == (newer_tag.nil? ? nil : newer_tag["name"])
87
+ end
88
+ end
89
+
66
90
  # Method filter issues, that belong only specified tag range
67
91
  # @param [Array] issues issues to filter
68
92
  # @param [Symbol] hash_key key of date value default is :actual_date
69
- # @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
70
- # @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
93
+ # @param [Hash, Nil] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
94
+ # @param [Hash, Nil] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
71
95
  # @return [Array] filtered issues
72
96
  def delete_by_time(issues, hash_key = "actual_date", older_tag = nil, newer_tag = nil)
73
97
  # in case if not tags specified - return unchanged array
@@ -97,9 +121,11 @@ module GitHubChangelogGenerator
97
121
 
98
122
  def ensure_older_tag(older_tag, newer_tag)
99
123
  return older_tag if older_tag
124
+
100
125
  idx = sorted_tags.index { |t| t["name"] == newer_tag["name"] }
101
126
  # skip if we are already at the oldest element
102
127
  return if idx == sorted_tags.size - 1
128
+
103
129
  sorted_tags[idx - 1]
104
130
  end
105
131
 
@@ -112,11 +138,11 @@ module GitHubChangelogGenerator
112
138
  tag_in_range_new
113
139
  end
114
140
 
115
- def tag_newer_old_tag?(older_tag_time, t)
141
+ def tag_newer_old_tag?(older_tag_time, time)
116
142
  tag_in_range_old = if older_tag_time.nil?
117
143
  true
118
144
  else
119
- t > older_tag_time
145
+ time > older_tag_time
120
146
  end
121
147
  tag_in_range_old
122
148
  end
@@ -130,33 +156,37 @@ module GitHubChangelogGenerator
130
156
  filtered_issues
131
157
  end
132
158
 
133
- # @return [Array] issues without labels or empty array if add_issues_wo_labels is false
159
+ # @param [Array] issues Issues & PRs to filter when without labels
160
+ # @return [Array] Issues & PRs without labels or empty array if
161
+ # add_issues_wo_labels or add_pr_wo_labels are false
134
162
  def filter_wo_labels(issues)
135
- if options[:add_issues_wo_labels]
163
+ if (!issues.empty? && issues.first.key?("pull_requests") && options[:add_pr_wo_labels]) || options[:add_issues_wo_labels]
136
164
  issues
137
165
  else
138
166
  issues.select { |issue| issue["labels"].map { |l| l["name"] }.any? }
139
167
  end
140
168
  end
141
169
 
170
+ # @todo Document this
142
171
  def filter_by_include_labels(issues)
143
172
  if options[:include_labels].nil?
144
173
  issues
145
174
  else
146
175
  issues.select do |issue|
147
176
  labels = issue["labels"].map { |l| l["name"] } & options[:include_labels]
148
- labels.any?
177
+ labels.any? || issue["labels"].empty?
149
178
  end
150
179
  end
151
180
  end
152
181
 
153
182
  # General filtered function
154
183
  #
155
- # @param [Array] all_issues
184
+ # @param [Array] all_issues PRs or issues
156
185
  # @return [Array] filtered issues
157
186
  def filter_array_by_labels(all_issues)
158
187
  filtered_issues = include_issues_by_labels(all_issues)
159
- exclude_issues_by_labels(filtered_issues)
188
+ filtered_issues = exclude_issues_by_labels(filtered_issues)
189
+ exclude_issues_without_labels(filtered_issues)
160
190
  end
161
191
 
162
192
  # Filter issues according labels
@@ -4,8 +4,8 @@ module GitHubChangelogGenerator
4
4
  class Generator
5
5
  # fetch, filter tags, fetch dates and sort them in time order
6
6
  def fetch_and_filter_tags
7
- detect_since_tag
8
- detect_due_tag
7
+ since_tag
8
+ due_tag
9
9
 
10
10
  all_tags = @fetcher.get_all_tags
11
11
  fetch_tags_dates(all_tags) # Creates a Hash @tag_times_hash
@@ -35,7 +35,7 @@ module GitHubChangelogGenerator
35
35
  tag = section_tags[i]
36
36
 
37
37
  # Don't create section header for the "since" tag
38
- next if @since_tag && tag["name"] == @since_tag
38
+ next if since_tag && tag["name"] == since_tag
39
39
 
40
40
  # Don't create a section header for the first tag in between_tags
41
41
  next if options[:between_tags] && tag == section_tags.last
@@ -98,11 +98,11 @@ module GitHubChangelogGenerator
98
98
  end
99
99
 
100
100
  # @return [Object] try to find newest tag using #Reader and :base option if specified otherwise returns nil
101
- def detect_since_tag
101
+ def since_tag
102
102
  @since_tag ||= options.fetch(:since_tag) { version_of_first_item }
103
103
  end
104
104
 
105
- def detect_due_tag
105
+ def due_tag
106
106
  @due_tag ||= options.fetch(:due_tag, nil)
107
107
  end
108
108
 
@@ -125,7 +125,7 @@ module GitHubChangelogGenerator
125
125
  # @return [Array] filtered tags according :since_tag option
126
126
  def filter_since_tag(all_tags)
127
127
  filtered_tags = all_tags
128
- tag = detect_since_tag
128
+ tag = since_tag
129
129
  if tag
130
130
  if all_tags.map { |t| t["name"] }.include? tag
131
131
  idx = all_tags.index { |t| t["name"] == tag }
@@ -135,7 +135,7 @@ module GitHubChangelogGenerator
135
135
  []
136
136
  end
137
137
  else
138
- Helper.log.warn "Warning: can't find tag #{tag}, specified with --since-tag option."
138
+ raise ChangelogGeneratorError, "Error: can't find tag #{tag}, specified with --since-tag option."
139
139
  end
140
140
  end
141
141
  filtered_tags
@@ -145,7 +145,7 @@ module GitHubChangelogGenerator
145
145
  # @return [Array] filtered tags according :due_tag option
146
146
  def filter_due_tag(all_tags)
147
147
  filtered_tags = all_tags
148
- tag = detect_due_tag
148
+ tag = due_tag
149
149
  if tag
150
150
  if all_tags.any? && all_tags.map { |t| t["name"] }.include?(tag)
151
151
  idx = all_tags.index { |t| t["name"] == tag }
@@ -155,7 +155,7 @@ module GitHubChangelogGenerator
155
155
  []
156
156
  end
157
157
  else
158
- Helper.log.warn "Warning: can't find tag #{tag}, specified with --due-tag option."
158
+ raise ChangelogGeneratorError, "Error: can't find tag #{tag}, specified with --due-tag option."
159
159
  end
160
160
  end
161
161
  filtered_tags
@@ -208,9 +208,7 @@ module GitHubChangelogGenerator
208
208
  end
209
209
 
210
210
  def warn_if_tag_not_found(all_tags, tag)
211
- unless all_tags.map { |t| t["name"] }.include?(tag)
212
- Helper.log.warn "Warning: can't find tag #{tag}, specified with --exclude-tags option."
213
- end
211
+ Helper.log.warn("Warning: can't find tag #{tag}, specified with --exclude-tags option.") unless all_tags.map { |t| t["name"] }.include?(tag)
214
212
  end
215
213
  end
216
214
  end
@@ -0,0 +1,104 @@
1
+ # frozen_string_literal: true
2
+
3
+ module GitHubChangelogGenerator
4
+ # This class generates the content for a single section of a changelog entry.
5
+ # It turns the tagged issues and PRs into a well-formatted list of changes to
6
+ # be later incorporated into a changelog entry.
7
+ #
8
+ # @see GitHubChangelogGenerator::Entry
9
+ class Section
10
+ attr_accessor :name, :prefix, :issues, :labels, :body_only
11
+
12
+ def initialize(opts = {})
13
+ @name = opts[:name]
14
+ @prefix = opts[:prefix]
15
+ @labels = opts[:labels] || []
16
+ @issues = opts[:issues] || []
17
+ @options = opts[:options] || Options.new({})
18
+ @body_only = opts[:body_only] || false
19
+ end
20
+
21
+ # Returns the content of a section.
22
+ #
23
+ # @return [String] Generate section content
24
+ def generate_content
25
+ content = ""
26
+
27
+ if @issues.any?
28
+ content += "#{@prefix}\n\n" unless @options[:simple_list] || @prefix.blank?
29
+ @issues.each do |issue|
30
+ merge_string = get_string_for_issue(issue)
31
+ content += "- " unless @body_only
32
+ content += "#{merge_string}\n"
33
+ end
34
+ content += "\n"
35
+ end
36
+ content
37
+ end
38
+
39
+ private
40
+
41
+ # Parse issue and generate single line formatted issue line.
42
+ #
43
+ # Example output:
44
+ # - Add coveralls integration [\#223](https://github.com/github-changelog-generator/github-changelog-generator/pull/223) (@github-changelog-generator)
45
+ #
46
+ # @param [Hash] issue Fetched issue from GitHub
47
+ # @return [String] Markdown-formatted single issue
48
+ def get_string_for_issue(issue)
49
+ encapsulated_title = encapsulate_string issue["title"]
50
+
51
+ title_with_number = "#{encapsulated_title} [\\##{issue['number']}](#{issue['html_url']})"
52
+ title_with_number = "#{title_with_number}#{line_labels_for(issue)}" if @options[:issue_line_labels].present?
53
+ line = issue_line_with_user(title_with_number, issue)
54
+ issue_line_with_body(line, issue)
55
+ end
56
+
57
+ def issue_line_with_body(line, issue)
58
+ return issue["body"] if @body_only && issue["body"].present?
59
+ return line if !@options[:issue_line_body] || issue["body"].blank?
60
+
61
+ # get issue body till first line break
62
+ body_paragraph = body_till_first_break(issue["body"])
63
+ # remove spaces from begining and end of the string
64
+ body_paragraph.rstrip!
65
+ # encapsulate to md
66
+ encapsulated_body = "\s\s\n" + encapsulate_string(body_paragraph)
67
+
68
+ "**#{line}** #{encapsulated_body}"
69
+ end
70
+
71
+ def body_till_first_break(body)
72
+ body.split(/\n/).first
73
+ end
74
+
75
+ def issue_line_with_user(line, issue)
76
+ return line if !@options[:author] || issue["pull_request"].nil?
77
+
78
+ user = issue["user"]
79
+ return "#{line} ({Null user})" unless user
80
+
81
+ if @options[:usernames_as_github_logins]
82
+ "#{line} (@#{user['login']})"
83
+ else
84
+ "#{line} ([#{user['login']}](#{user['html_url']}))"
85
+ end
86
+ end
87
+
88
+ ENCAPSULATED_CHARACTERS = %w(< > * _ \( \) [ ] #)
89
+
90
+ # Encapsulate characters to make Markdown look as expected.
91
+ #
92
+ # @param [String] string
93
+ # @return [String] encapsulated input string
94
+ def encapsulate_string(string)
95
+ string = string.gsub('\\', '\\\\')
96
+
97
+ ENCAPSULATED_CHARACTERS.each do |char|
98
+ string = string.gsub(char, "\\#{char}")
99
+ end
100
+
101
+ string
102
+ end
103
+ end
104
+ end
@@ -10,10 +10,10 @@ module GitHubChangelogGenerator
10
10
  # fetcher = GitHubChangelogGenerator::OctoFetcher.new(options)
11
11
  class OctoFetcher
12
12
  PER_PAGE_NUMBER = 100
13
- MAX_THREAD_NUMBER = 25
13
+ MAX_THREAD_NUMBER = 10
14
14
  MAX_FORBIDDEN_RETRIES = 100
15
15
  CHANGELOG_GITHUB_TOKEN = "CHANGELOG_GITHUB_TOKEN"
16
- GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, change log may be " \
16
+ GH_RATE_LIMIT_EXCEEDED_MSG = "Warning: Can't finish operation: GitHub API rate limit exceeded, changelog may be " \
17
17
  "missing some issues. You can limit the number of issues fetched using the `--max-issues NUM` argument."
18
18
  NO_TOKEN_PROVIDED = "Warning: No token provided (-t option) and variable $CHANGELOG_GITHUB_TOKEN was not found. " \
19
19
  "This script can make only 50 requests to GitHub API per hour without token!"
@@ -33,6 +33,8 @@ module GitHubChangelogGenerator
33
33
  @http_cache = @options[:http_cache]
34
34
  @cache_file = nil
35
35
  @cache_log = nil
36
+ @commits = []
37
+ @compares = {}
36
38
  prepare_cache
37
39
  configure_octokit_ssl
38
40
  @client = Octokit::Client.new(github_options)
@@ -40,6 +42,7 @@ module GitHubChangelogGenerator
40
42
 
41
43
  def prepare_cache
42
44
  return unless @http_cache
45
+
43
46
  @cache_file = @options.fetch(:cache_file) { File.join(Dir.tmpdir, "github-changelog-http-cache") }
44
47
  @cache_log = @options.fetch(:cache_log) { File.join(Dir.tmpdir, "github-changelog-logger.log") }
45
48
  init_cache
@@ -55,7 +58,7 @@ module GitHubChangelogGenerator
55
58
  end
56
59
 
57
60
  def configure_octokit_ssl
58
- ca_file = @options[:ssl_ca_file] || ENV["SSL_CA_FILE"] || File.expand_path("../ssl_certs/cacert.pem", __FILE__)
61
+ ca_file = @options[:ssl_ca_file] || ENV["SSL_CA_FILE"] || File.expand_path("ssl_certs/cacert.pem", __dir__)
59
62
  Octokit.connection_options = { ssl: { ca_file: ca_file } }
60
63
  end
61
64
 
@@ -138,7 +141,7 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
138
141
  def fetch_closed_issues_and_pr
139
142
  print "Fetching closed issues...\r" if @options[:verbose]
140
143
  issues = []
141
- page_i = 0
144
+ page_i = 0
142
145
  count_pages = calculate_pages(@client, "issues", closed_pr_options)
143
146
 
144
147
  iterate_pages(@client, "issues", closed_pr_options) do |new_issues|
@@ -162,10 +165,6 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
162
165
  pull_requests = []
163
166
  options = { state: "closed" }
164
167
 
165
- unless @options[:release_branch].nil?
166
- options[:base] = @options[:release_branch]
167
- end
168
-
169
168
  page_i = 0
170
169
  count_pages = calculate_pages(@client, "pull_requests", options)
171
170
 
@@ -211,38 +210,129 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
211
210
  Helper.log.info "Fetching events for issues and PR: #{i}"
212
211
  end
213
212
 
213
+ # Fetch comments for PRs and add them to "comments"
214
+ #
215
+ # @param [Array] prs The array of PRs.
216
+ # @return [Void] No return; PRs are updated in-place.
217
+ def fetch_comments_async(prs)
218
+ threads = []
219
+
220
+ prs.each_slice(MAX_THREAD_NUMBER) do |prs_slice|
221
+ prs_slice.each do |pr|
222
+ threads << Thread.new do
223
+ pr["comments"] = []
224
+ iterate_pages(@client, "issue_comments", pr["number"]) do |new_comment|
225
+ pr["comments"].concat(new_comment)
226
+ end
227
+ pr["comments"] = pr["comments"].map { |comment| stringify_keys_deep(comment.to_hash) }
228
+ end
229
+ end
230
+ threads.each(&:join)
231
+ threads = []
232
+ end
233
+ nil
234
+ end
235
+
214
236
  # Fetch tag time from repo
215
237
  #
216
238
  # @param [Hash] tag GitHub data item about a Tag
217
239
  #
218
240
  # @return [Time] time of specified tag
219
241
  def fetch_date_of_tag(tag)
220
- commit_data = check_github_response { @client.commit(user_project, tag["commit"]["sha"]) }
242
+ commit_data = fetch_commit(tag["commit"]["sha"])
221
243
  commit_data = stringify_keys_deep(commit_data.to_hash)
222
244
 
223
245
  commit_data["commit"]["committer"]["date"]
224
246
  end
225
247
 
248
+ # Fetch and cache comparison between two github refs
249
+ #
250
+ # @param [String] older The older sha/tag/branch.
251
+ # @param [String] newer The newer sha/tag/branch.
252
+ # @return [Hash] Github api response for comparison.
253
+ def fetch_compare(older, newer)
254
+ unless @compares["#{older}...#{newer}"]
255
+ compare_data = check_github_response { @client.compare(user_project, older, newer || "HEAD") }
256
+ raise StandardError, "Sha #{older} and sha #{newer} are not related; please file a github-changelog-generator issues and describe how to replicate this issue." if compare_data["status"] == "diverged"
257
+
258
+ @compares["#{older}...#{newer}"] = stringify_keys_deep(compare_data.to_hash)
259
+ end
260
+ @compares["#{older}...#{newer}"]
261
+ end
262
+
226
263
  # Fetch commit for specified event
227
264
  #
265
+ # @param [String] commit_id the SHA of a commit to fetch
228
266
  # @return [Hash]
229
- def fetch_commit(event)
230
- check_github_response do
231
- commit = @client.commit(user_project, event["commit_id"])
232
- commit = stringify_keys_deep(commit.to_hash)
233
- commit
267
+ def fetch_commit(commit_id)
268
+ found = commits.find do |commit|
269
+ commit["sha"] == commit_id
270
+ end
271
+ if found
272
+ stringify_keys_deep(found.to_hash)
273
+ else
274
+ # cache miss; don't add to @commits because unsure of order.
275
+ check_github_response do
276
+ commit = @client.commit(user_project, commit_id)
277
+ commit = stringify_keys_deep(commit.to_hash)
278
+ commit
279
+ end
234
280
  end
235
281
  end
236
282
 
237
- # Fetch all commits before certain point
283
+ # Fetch all commits
238
284
  #
239
- # @return [String]
240
- def commits_before(start_time)
241
- commits = []
242
- iterate_pages(@client, "commits_before", start_time.to_datetime.to_s) do |new_commits|
243
- commits.concat(new_commits)
285
+ # @return [Array] Commits in a repo.
286
+ def commits
287
+ if @commits.empty?
288
+ iterate_pages(@client, "commits") do |new_commits|
289
+ @commits.concat(new_commits)
290
+ end
244
291
  end
245
- commits
292
+ @commits
293
+ end
294
+
295
+ # Return the oldest commit in a repo
296
+ #
297
+ # @return [Hash] Oldest commit in the github git history.
298
+ def oldest_commit
299
+ commits.last
300
+ end
301
+
302
+ # @return [String] Default branch of the repo
303
+ def default_branch
304
+ @default_branch ||= @client.repository(user_project)[:default_branch]
305
+ end
306
+
307
+ # Fetch all SHAs occurring in or before a given tag and add them to
308
+ # "shas_in_tag"
309
+ #
310
+ # @param [Array] tags The array of tags.
311
+ # @return [Nil] No return; tags are updated in-place.
312
+ def fetch_tag_shas_async(tags)
313
+ i = 0
314
+ threads = []
315
+ print_in_same_line("Fetching SHAs for tags: #{i}/#{tags.count}\r") if @options[:verbose]
316
+
317
+ tags.each_slice(MAX_THREAD_NUMBER) do |tags_slice|
318
+ tags_slice.each do |tag|
319
+ threads << Thread.new do
320
+ # Use oldest commit because comparing two arbitrary tags may be diverged
321
+ commits_in_tag = fetch_compare(oldest_commit["sha"], tag["name"])
322
+ tag["shas_in_tag"] = commits_in_tag["commits"].collect { |commit| commit["sha"] }
323
+ print_in_same_line("Fetching SHAs for tags: #{i + 1}/#{tags.count}") if @options[:verbose]
324
+ i += 1
325
+ end
326
+ end
327
+ threads.each(&:join)
328
+ threads = []
329
+ end
330
+
331
+ # to clear line from prev print
332
+ print_empty_line
333
+
334
+ Helper.log.info "Fetching SHAs for tags: #{i}"
335
+ nil
246
336
  end
247
337
 
248
338
  private
@@ -316,8 +406,8 @@ Make sure, that you push tags to remote repo via 'git push --tags'"
316
406
  end
317
407
 
318
408
  # Presents the exception, and the aborts with the message.
319
- def fail_with_message(e, message)
320
- Helper.log.error("#{e.class}: #{e.message}")
409
+ def fail_with_message(error, message)
410
+ Helper.log.error("#{error.class}: #{error.message}")
321
411
  sys_abort(message)
322
412
  end
323
413