github_changelog_generator 1.15.0.pre.rc → 1.15.0

Sign up to get free protection for your applications and to get access to all the features.
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