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.
- checksums.yaml +5 -5
- data/LICENSE +1 -1
- data/README.md +126 -51
- data/bin/git-generate-changelog +1 -1
- data/lib/github_changelog_generator.rb +10 -6
- data/lib/github_changelog_generator/generator/entry.rb +218 -0
- data/lib/github_changelog_generator/generator/generator.rb +96 -119
- data/lib/github_changelog_generator/generator/generator_fetcher.rb +140 -21
- data/lib/github_changelog_generator/generator/generator_processor.rb +40 -10
- data/lib/github_changelog_generator/generator/generator_tags.rb +10 -12
- data/lib/github_changelog_generator/generator/section.rb +104 -0
- data/lib/github_changelog_generator/octo_fetcher.rb +113 -23
- data/lib/github_changelog_generator/options.rb +35 -4
- data/lib/github_changelog_generator/parser.rb +88 -49
- data/lib/github_changelog_generator/parser_file.rb +6 -2
- data/lib/github_changelog_generator/task.rb +2 -3
- data/lib/github_changelog_generator/version.rb +1 -1
- data/man/git-generate-changelog.1 +125 -51
- data/man/git-generate-changelog.1.html +145 -89
- data/man/git-generate-changelog.html +19 -7
- data/man/git-generate-changelog.md +141 -86
- data/spec/files/github-changelog-generator.md +114 -114
- data/spec/{install-gem-in-bundler.gemfile → install_gem_in_bundler.gemfile} +2 -0
- data/spec/spec_helper.rb +1 -5
- data/spec/unit/generator/entry_spec.rb +760 -0
- data/spec/unit/generator/generator_processor_spec.rb +9 -2
- data/spec/unit/generator/generator_tags_spec.rb +5 -21
- data/spec/unit/octo_fetcher_spec.rb +204 -197
- data/spec/unit/options_spec.rb +24 -0
- data/spec/unit/parse_file_spec.rb +2 -2
- data/spec/unit/reader_spec.rb +4 -4
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_commits/when_API_is_valid/returns_commits.json +1 -0
- 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.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_pull_requests/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_commit/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_date_of_tag/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_events_async/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/_github_fetch_tags/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_wrong_token_provided.json +1 -1
- data/spec/vcr/GitHubChangelogGenerator_OctoFetcher/_github_fetch_tags/when_wrong_token_provided/should_raise_Unauthorized_error.json +1 -1
- metadata +17 -17
- data/bin/ghclgen +0 -5
- data/lib/github_changelog_generator/generator/generator_generation.rb +0 -181
- data/spec/unit/generator/generator_generation_spec.rb +0 -73
@@ -1,20 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require "github_changelog_generator/octo_fetcher"
|
4
|
-
require "github_changelog_generator/generator/generator_generation"
|
5
4
|
require "github_changelog_generator/generator/generator_fetcher"
|
6
5
|
require "github_changelog_generator/generator/generator_processor"
|
7
6
|
require "github_changelog_generator/generator/generator_tags"
|
7
|
+
require "github_changelog_generator/generator/entry"
|
8
|
+
require "github_changelog_generator/generator/section"
|
8
9
|
|
9
10
|
module GitHubChangelogGenerator
|
10
11
|
# Default error for ChangelogGenerator
|
11
12
|
class ChangelogGeneratorError < StandardError
|
12
13
|
end
|
13
14
|
|
15
|
+
# This class is the high-level code for gathering issues and PRs for a github
|
16
|
+
# repository and generating a CHANGELOG.md file. A changelog is made up of a
|
17
|
+
# series of "entries" of all tagged releases, plus an extra entry for the
|
18
|
+
# unreleased changes. Entries are made up of various organizational
|
19
|
+
# "sections," and sections contain the github issues and PRs.
|
20
|
+
#
|
21
|
+
# So the changelog contains entries, entries contain sections, and sections
|
22
|
+
# contain issues and PRs.
|
23
|
+
#
|
24
|
+
# @see GitHubChangelogGenerator::Entry
|
25
|
+
# @see GitHubChangelogGenerator::Section
|
14
26
|
class Generator
|
15
|
-
attr_accessor :options, :filtered_tags, :
|
27
|
+
attr_accessor :options, :filtered_tags, :tag_section_mapping, :sorted_tags
|
16
28
|
|
17
|
-
# A Generator responsible for all logic, related with
|
29
|
+
# A Generator responsible for all logic, related with changelog generation from ready-to-parse issues
|
18
30
|
#
|
19
31
|
# Example:
|
20
32
|
# generator = GitHubChangelogGenerator::Generator.new
|
@@ -23,156 +35,121 @@ module GitHubChangelogGenerator
|
|
23
35
|
@options = options
|
24
36
|
@tag_times_hash = {}
|
25
37
|
@fetcher = GitHubChangelogGenerator::OctoFetcher.new(options)
|
38
|
+
@sections = []
|
26
39
|
end
|
27
40
|
|
28
|
-
|
29
|
-
|
41
|
+
# Main function to start changelog generation
|
42
|
+
#
|
43
|
+
# @return [String] Generated changelog file
|
44
|
+
def compound_changelog
|
45
|
+
@options.load_custom_ruby_files
|
46
|
+
fetch_and_filter_tags
|
47
|
+
fetch_issues_and_pr
|
30
48
|
|
31
|
-
|
49
|
+
log = ""
|
50
|
+
log += @options[:frontmatter] if @options[:frontmatter]
|
51
|
+
log += "#{options[:header]}\n\n"
|
32
52
|
|
33
|
-
|
53
|
+
log += if @options[:unreleased_only]
|
54
|
+
generate_entry_between_tags(@filtered_tags[0], nil)
|
55
|
+
else
|
56
|
+
generate_entries_for_all_tags
|
57
|
+
end
|
34
58
|
|
35
|
-
|
36
|
-
|
59
|
+
log += File.read(@options[:base]) if File.file?(@options[:base])
|
60
|
+
|
61
|
+
credit_line = "\n\n\\* *This Changelog was automatically generated by [github_changelog_generator](https://github.com/github-changelog-generator/github-changelog-generator)*"
|
62
|
+
log.gsub!(/#{credit_line}(\n)?/, "") # Remove old credit lines
|
63
|
+
log += "#{credit_line}\n"
|
64
|
+
|
65
|
+
@log = log
|
37
66
|
end
|
38
67
|
|
39
|
-
|
68
|
+
private
|
40
69
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# @param [String]
|
44
|
-
|
45
|
-
|
46
|
-
string = string.gsub('\\', '\\\\')
|
70
|
+
# Generate log only between 2 specified tags
|
71
|
+
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
72
|
+
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
73
|
+
def generate_entry_between_tags(older_tag, newer_tag)
|
74
|
+
filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
|
47
75
|
|
48
|
-
|
49
|
-
|
76
|
+
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
|
77
|
+
# do not generate empty unreleased section
|
78
|
+
return ""
|
50
79
|
end
|
51
80
|
|
52
|
-
string
|
53
|
-
end
|
54
|
-
|
55
|
-
# Generates log for section with header and body
|
56
|
-
#
|
57
|
-
# @param [Array] pull_requests List or PR's in new section
|
58
|
-
# @param [Array] issues List of issues in new section
|
59
|
-
# @param [String] newer_tag Name of the newer tag. Could be nil for `Unreleased` section
|
60
|
-
# @param [Hash, nil] older_tag Older tag, used for the links. Could be nil for last tag.
|
61
|
-
# @return [String] Ready and parsed section
|
62
|
-
def create_log_for_tag(pull_requests, issues, newer_tag, older_tag = nil)
|
63
81
|
newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
|
64
82
|
|
65
|
-
github_site = options[:github_site] || "https://github.com"
|
66
|
-
project_url = "#{github_site}/#{options[:user]}/#{options[:project]}"
|
67
|
-
|
68
83
|
# If the older tag is nil, go back in time from the latest tag and find
|
69
84
|
# the SHA for the first commit.
|
70
85
|
older_tag_name =
|
71
86
|
if older_tag.nil?
|
72
|
-
@fetcher.
|
87
|
+
@fetcher.oldest_commit["sha"]
|
73
88
|
else
|
74
89
|
older_tag["name"]
|
75
90
|
end
|
76
91
|
|
77
|
-
|
92
|
+
Entry.new(options).generate_entry_for_tag(filtered_pull_requests, filtered_issues, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name)
|
93
|
+
end
|
94
|
+
|
95
|
+
# Filters issues and pull requests based on, respectively, `actual_date`
|
96
|
+
# and `merged_at` timestamp fields. `actual_date` is the detected form of
|
97
|
+
# `closed_at` based on merge event SHA commit times.
|
98
|
+
#
|
99
|
+
# @return [Array] filtered issues and pull requests
|
100
|
+
def filter_issues_for_tags(newer_tag, older_tag)
|
101
|
+
filtered_pull_requests = filter_by_tag(@pull_requests, newer_tag)
|
102
|
+
filtered_issues = delete_by_time(@issues, "actual_date", older_tag, newer_tag)
|
103
|
+
|
104
|
+
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
78
105
|
|
79
|
-
if options[:
|
80
|
-
#
|
81
|
-
|
106
|
+
if options[:filter_issues_by_milestone]
|
107
|
+
# delete excess irrelevant issues (according milestones). Issue #22.
|
108
|
+
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
109
|
+
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
82
110
|
end
|
111
|
+
[filtered_issues, filtered_pull_requests]
|
112
|
+
end
|
113
|
+
|
114
|
+
# The full cycle of generation for whole project
|
115
|
+
# @return [String] All entries in the changelog
|
116
|
+
def generate_entries_for_all_tags
|
117
|
+
puts "Generating entry..." if options[:verbose]
|
118
|
+
|
119
|
+
entries = generate_unreleased_entry
|
83
120
|
|
84
|
-
|
85
|
-
|
86
|
-
|
121
|
+
@tag_section_mapping.each_pair do |_tag_section, left_right_tags|
|
122
|
+
older_tag, newer_tag = left_right_tags
|
123
|
+
entries += generate_entry_between_tags(older_tag, newer_tag)
|
87
124
|
end
|
88
125
|
|
89
|
-
|
126
|
+
entries
|
90
127
|
end
|
91
128
|
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
log = ""
|
101
|
-
log += generate_sub_section(sections[:breaking], options[:breaking_prefix])
|
102
|
-
log += generate_sub_section(sections[:enhancements], options[:enhancement_prefix])
|
103
|
-
log += generate_sub_section(sections[:bugs], options[:bug_prefix])
|
104
|
-
log += generate_sub_section(sections[:issues], options[:issue_prefix])
|
105
|
-
log
|
129
|
+
def generate_unreleased_entry
|
130
|
+
entry = ""
|
131
|
+
if options[:unreleased]
|
132
|
+
start_tag = @filtered_tags[0] || @sorted_tags.last
|
133
|
+
unreleased_entry = generate_entry_between_tags(start_tag, nil)
|
134
|
+
entry += unreleased_entry if unreleased_entry
|
135
|
+
end
|
136
|
+
entry
|
106
137
|
end
|
107
138
|
|
108
|
-
#
|
109
|
-
# (bugs, features, or just closed issues) by labels
|
139
|
+
# Fetches @pull_requests and @issues and filters them based on options.
|
110
140
|
#
|
111
|
-
# @
|
112
|
-
|
113
|
-
|
114
|
-
def parse_by_sections(issues, pull_requests)
|
115
|
-
sections = {
|
116
|
-
issues: [],
|
117
|
-
enhancements: [],
|
118
|
-
bugs: [],
|
119
|
-
breaking: []
|
120
|
-
}
|
121
|
-
|
122
|
-
issues.each do |dict|
|
123
|
-
added = false
|
124
|
-
|
125
|
-
dict["labels"].each do |label|
|
126
|
-
if options[:bug_labels].include?(label["name"])
|
127
|
-
sections[:bugs] << dict
|
128
|
-
added = true
|
129
|
-
elsif options[:enhancement_labels].include?(label["name"])
|
130
|
-
sections[:enhancements] << dict
|
131
|
-
added = true
|
132
|
-
elsif options[:breaking_labels].include?(label["name"])
|
133
|
-
sections[:breaking] << dict
|
134
|
-
added = true
|
135
|
-
end
|
136
|
-
|
137
|
-
break if added
|
138
|
-
end
|
141
|
+
# @return [Nil] No return.
|
142
|
+
def fetch_issues_and_pr
|
143
|
+
issues, pull_requests = @fetcher.fetch_closed_issues_and_pr
|
139
144
|
|
140
|
-
|
141
|
-
end
|
145
|
+
@pull_requests = options[:pulls] ? get_filtered_pull_requests(pull_requests) : []
|
142
146
|
|
143
|
-
|
144
|
-
end
|
147
|
+
@issues = options[:issues] ? get_filtered_issues(issues) : []
|
145
148
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
# @return [Hash] sections
|
151
|
-
def sort_pull_requests(pull_requests, sections)
|
152
|
-
added_pull_requests = []
|
153
|
-
pull_requests.each do |pr|
|
154
|
-
added = false
|
155
|
-
|
156
|
-
pr["labels"].each do |label|
|
157
|
-
if options[:bug_labels].include?(label["name"])
|
158
|
-
sections[:bugs] << pr
|
159
|
-
added_pull_requests << pr
|
160
|
-
added = true
|
161
|
-
elsif options[:enhancement_labels].include?(label["name"])
|
162
|
-
sections[:enhancements] << pr
|
163
|
-
added_pull_requests << pr
|
164
|
-
added = true
|
165
|
-
elsif options[:breaking_labels].include?(label["name"])
|
166
|
-
sections[:breaking] << pr
|
167
|
-
added_pull_requests << pr
|
168
|
-
added = true
|
169
|
-
end
|
170
|
-
|
171
|
-
break if added
|
172
|
-
end
|
173
|
-
end
|
174
|
-
added_pull_requests.each { |p| pull_requests.delete(p) }
|
175
|
-
sections
|
149
|
+
fetch_events_for_issues_and_pr
|
150
|
+
detect_actual_closed_dates(@issues + @pull_requests)
|
151
|
+
add_first_occurring_tag_to_prs(@sorted_tags, @pull_requests)
|
152
|
+
nil
|
176
153
|
end
|
177
154
|
end
|
178
155
|
end
|
@@ -7,9 +7,7 @@ module GitHubChangelogGenerator
|
|
7
7
|
# Fetch event for issues and pull requests
|
8
8
|
# @return [Array] array of fetched issues
|
9
9
|
def fetch_events_for_issues_and_pr
|
10
|
-
if options[:verbose]
|
11
|
-
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r"
|
12
|
-
end
|
10
|
+
print "Fetching events for issues and PR: 0/#{@issues.count + @pull_requests.count}\r" if options[:verbose]
|
13
11
|
|
14
12
|
# Async fetching events:
|
15
13
|
@fetcher.fetch_events_async(@issues + @pull_requests)
|
@@ -18,34 +16,141 @@ module GitHubChangelogGenerator
|
|
18
16
|
# Async fetching of all tags dates
|
19
17
|
def fetch_tags_dates(tags)
|
20
18
|
print "Fetching tag dates...\r" if options[:verbose]
|
21
|
-
# Async fetching tags:
|
22
|
-
threads = []
|
23
19
|
i = 0
|
24
|
-
all = tags.count
|
25
20
|
tags.each do |tag|
|
26
|
-
|
27
|
-
|
28
|
-
get_time_of_tag(tag)
|
29
|
-
print "Fetching tags dates: #{i + 1}/#{all}\r" if options[:verbose]
|
30
|
-
i += 1
|
31
|
-
end
|
21
|
+
get_time_of_tag(tag)
|
22
|
+
i += 1
|
32
23
|
end
|
33
|
-
|
34
|
-
puts "Fetching tags dates: #{i}" if options[:verbose]
|
24
|
+
puts "Fetching tags dates: #{i}/#{tags.count}" if options[:verbose]
|
35
25
|
end
|
36
26
|
|
37
27
|
# Find correct closed dates, if issues was closed by commits
|
38
28
|
def detect_actual_closed_dates(issues)
|
39
29
|
print "Fetching closed dates for issues...\r" if options[:verbose]
|
40
30
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
31
|
+
i = 0
|
32
|
+
issues.each do |issue|
|
33
|
+
find_closed_date_by_commit(issue)
|
34
|
+
i += 1
|
35
|
+
end
|
36
|
+
puts "Fetching closed dates for issues: #{i}/#{issues.count}" if options[:verbose]
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds a key "first_occurring_tag" to each PR with a value of the oldest
|
40
|
+
# tag that a PR's merge commit occurs in in the git history. This should
|
41
|
+
# indicate the release of each PR by git's history regardless of dates and
|
42
|
+
# divergent branches.
|
43
|
+
#
|
44
|
+
# @param [Array] tags The tags sorted by time, newest to oldest.
|
45
|
+
# @param [Array] prs The PRs to discover the tags of.
|
46
|
+
# @return [Nil] No return; PRs are updated in-place.
|
47
|
+
def add_first_occurring_tag_to_prs(tags, prs)
|
48
|
+
total = prs.count
|
49
|
+
|
50
|
+
prs_left = associate_tagged_prs(tags, prs, total)
|
51
|
+
prs_left = associate_release_branch_prs(prs_left, total)
|
52
|
+
prs_left = associate_rebase_comment_prs(tags, prs_left, total) if prs_left.any?
|
53
|
+
# PRs in prs_left will be untagged, not in release branch, and not
|
54
|
+
# rebased. They should not be included in the changelog as they probably
|
55
|
+
# have been merged to a branch other than the release branch.
|
56
|
+
@pull_requests -= prs_left
|
57
|
+
Helper.log.info "Associating PRs with tags: #{total}/#{total}"
|
58
|
+
end
|
59
|
+
|
60
|
+
# Associate merged PRs by the merge SHA contained in each tag. If the
|
61
|
+
# merge_commit_sha is not found in any tag's history, skip association.
|
62
|
+
#
|
63
|
+
# @param [Array] tags The tags sorted by time, newest to oldest.
|
64
|
+
# @param [Array] prs The PRs to associate.
|
65
|
+
# @return [Array] PRs without their merge_commit_sha in a tag.
|
66
|
+
def associate_tagged_prs(tags, prs, total)
|
67
|
+
@fetcher.fetch_tag_shas_async(tags)
|
68
|
+
|
69
|
+
i = 0
|
70
|
+
prs.reject do |pr|
|
71
|
+
found = false
|
72
|
+
# XXX Wish I could use merge_commit_sha, but gcg doesn't currently
|
73
|
+
# fetch that. See
|
74
|
+
# https://developer.github.com/v3/pulls/#get-a-single-pull-request vs.
|
75
|
+
# https://developer.github.com/v3/pulls/#list-pull-requests
|
76
|
+
if pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" })
|
77
|
+
# Iterate tags.reverse (oldest to newest) to find first tag of each PR.
|
78
|
+
if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(event["commit_id"]) })
|
79
|
+
pr["first_occurring_tag"] = oldest_tag["name"]
|
80
|
+
found = true
|
81
|
+
i += 1
|
82
|
+
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
|
83
|
+
end
|
84
|
+
else
|
85
|
+
# Either there were no events or no merged event. Github's api can be
|
86
|
+
# weird like that apparently. Check for a rebased comment before erroring.
|
87
|
+
no_events_pr = associate_rebase_comment_prs(tags, [pr], total)
|
88
|
+
raise StandardError, "No merge sha found for PR #{pr['number']} via the GitHub API" unless no_events_pr.empty?
|
89
|
+
|
90
|
+
found = true
|
91
|
+
i += 1
|
92
|
+
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
|
93
|
+
end
|
94
|
+
found
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
# Associate merged PRs by the HEAD of the release branch. If no
|
99
|
+
# --release-branch was specified, then the github default branch is used.
|
100
|
+
#
|
101
|
+
# @param [Array] prs_left PRs not associated with any tag.
|
102
|
+
# @param [Integer] total The total number of PRs to associate; used for verbose printing.
|
103
|
+
# @return [Array] PRs without their merge_commit_sha in the branch.
|
104
|
+
def associate_release_branch_prs(prs_left, total)
|
105
|
+
if prs_left.any?
|
106
|
+
i = total - prs_left.count
|
107
|
+
prs_left.reject do |pr|
|
108
|
+
found = false
|
109
|
+
if pr["events"] && (event = pr["events"].find { |e| e["event"] == "merged" }) && sha_in_release_branch(event["commit_id"])
|
110
|
+
found = true
|
111
|
+
i += 1
|
112
|
+
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
|
113
|
+
end
|
114
|
+
found
|
45
115
|
end
|
46
|
-
|
116
|
+
else
|
117
|
+
prs_left
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
# Associate merged PRs by the SHA detected in github comments of the form
|
122
|
+
# "rebased commit: <sha>". For use when the merge_commit_sha is not in the
|
123
|
+
# actual git history due to rebase.
|
124
|
+
#
|
125
|
+
# @param [Array] tags The tags sorted by time, newest to oldest.
|
126
|
+
# @param [Array] prs_left The PRs not yet associated with any tag or branch.
|
127
|
+
# @return [Array] PRs without rebase comments.
|
128
|
+
def associate_rebase_comment_prs(tags, prs_left, total)
|
129
|
+
i = total - prs_left.count
|
130
|
+
# Any remaining PRs were not found in the list of tags by their merge
|
131
|
+
# commit and not found in any specified release branch. Fallback to
|
132
|
+
# rebased commit comment.
|
133
|
+
@fetcher.fetch_comments_async(prs_left)
|
134
|
+
prs_left.reject do |pr|
|
135
|
+
found = false
|
136
|
+
if pr["comments"] && (rebased_comment = pr["comments"].reverse.find { |c| c["body"].match(%r{rebased commit: ([0-9a-f]{40})}i) })
|
137
|
+
rebased_sha = rebased_comment["body"].match(%r{rebased commit: ([0-9a-f]{40})}i)[1]
|
138
|
+
if (oldest_tag = tags.reverse.find { |tag| tag["shas_in_tag"].include?(rebased_sha) })
|
139
|
+
pr["first_occurring_tag"] = oldest_tag["name"]
|
140
|
+
found = true
|
141
|
+
i += 1
|
142
|
+
elsif sha_in_release_branch(rebased_sha)
|
143
|
+
found = true
|
144
|
+
i += 1
|
145
|
+
else
|
146
|
+
raise StandardError, "PR #{pr['number']} has a rebased SHA comment but that SHA was not found in the release branch or any tags"
|
147
|
+
end
|
148
|
+
print("Associating PRs with tags: #{i}/#{total}\r") if @options[:verbose]
|
149
|
+
else
|
150
|
+
puts "Warning: PR #{pr['number']} merge commit was not found in the release branch or tagged git history and no rebased SHA comment was found"
|
151
|
+
end
|
152
|
+
found
|
47
153
|
end
|
48
|
-
puts "Fetching closed dates for issues: Done!" if options[:verbose]
|
49
154
|
end
|
50
155
|
|
51
156
|
# Fill :actual_date parameter of specified issue by closed date of the commit, if it was closed by commit.
|
@@ -74,7 +179,7 @@ module GitHubChangelogGenerator
|
|
74
179
|
issue["actual_date"] = issue["closed_at"]
|
75
180
|
else
|
76
181
|
begin
|
77
|
-
commit = @fetcher.fetch_commit(event)
|
182
|
+
commit = @fetcher.fetch_commit(event["commit_id"])
|
78
183
|
issue["actual_date"] = commit["commit"]["author"]["date"]
|
79
184
|
|
80
185
|
# issue['actual_date'] = commit['author']['date']
|
@@ -84,5 +189,19 @@ module GitHubChangelogGenerator
|
|
84
189
|
end
|
85
190
|
end
|
86
191
|
end
|
192
|
+
|
193
|
+
private
|
194
|
+
|
195
|
+
# Detect if a sha occurs in the --release-branch. Uses the github repo
|
196
|
+
# default branch if not specified.
|
197
|
+
#
|
198
|
+
# @param [String] sha SHA to check.
|
199
|
+
# @return [Boolean] True if SHA is in the branch git history.
|
200
|
+
def sha_in_release_branch(sha)
|
201
|
+
branch = @options[:release_branch] || @fetcher.default_branch
|
202
|
+
commits_in_branch = @fetcher.fetch_compare(@fetcher.oldest_commit["sha"], branch)
|
203
|
+
shas_in_branch = commits_in_branch["commits"].collect { |commit| commit["sha"] }
|
204
|
+
shas_in_branch.include?(sha)
|
205
|
+
end
|
87
206
|
end
|
88
207
|
end
|