github_changelog_generator 1.15.0.pre.beta → 1.16.1
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 +332 -285
- data/Rakefile +1 -1
- 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 +124 -125
- data/lib/github_changelog_generator/generator/generator_fetcher.rb +139 -23
- data/lib/github_changelog_generator/generator/generator_processor.rb +59 -27
- data/lib/github_changelog_generator/generator/generator_tags.rb +25 -21
- data/lib/github_changelog_generator/generator/section.rb +124 -0
- data/lib/github_changelog_generator/helper.rb +1 -1
- data/lib/github_changelog_generator/octo_fetcher.rb +233 -96
- data/lib/github_changelog_generator/options.rb +74 -2
- data/lib/github_changelog_generator/parser.rb +118 -74
- data/lib/github_changelog_generator/parser_file.rb +7 -3
- data/lib/github_changelog_generator/reader.rb +2 -2
- data/lib/github_changelog_generator/task.rb +4 -3
- data/lib/github_changelog_generator/version.rb +1 -1
- data/man/git-generate-changelog.1 +144 -45
- data/man/git-generate-changelog.1.html +157 -84
- data/man/git-generate-changelog.html +19 -7
- data/man/git-generate-changelog.md +151 -84
- 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 +2 -6
- data/spec/unit/generator/entry_spec.rb +766 -0
- data/spec/unit/generator/generator_processor_spec.rb +103 -41
- data/spec/unit/generator/generator_spec.rb +47 -0
- data/spec/unit/generator/generator_tags_spec.rb +51 -24
- data/spec/unit/generator/section_spec.rb +34 -0
- data/spec/unit/octo_fetcher_spec.rb +247 -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 +71 -38
- data/bin/ghclgen +0 -5
- data/lib/github_changelog_generator/generator/generator_generation.rb +0 -180
- data/spec/unit/generator/generator_generation_spec.rb +0 -73
data/Rakefile
CHANGED
@@ -13,7 +13,7 @@ RSpec::Core::RakeTask.new
|
|
13
13
|
|
14
14
|
desc "When releasing the gem, re-fetch latest cacert.pem from curl.haxx.se. Developer task."
|
15
15
|
task :update_ssl_ca_file do
|
16
|
-
`pushd lib/github_changelog_generator/ssl_certs && curl --remote-name --time-cond cacert.pem https://curl.
|
16
|
+
`pushd lib/github_changelog_generator/ssl_certs && curl --remote-name --time-cond cacert.pem https://curl.se/ca/cacert.pem && popd`
|
17
17
|
end
|
18
18
|
|
19
19
|
task default: %i[rubocop spec]
|
data/bin/git-generate-changelog
CHANGED
@@ -22,22 +22,26 @@ require "github_changelog_generator/reader"
|
|
22
22
|
module GitHubChangelogGenerator
|
23
23
|
# Main class and entry point for this script.
|
24
24
|
class ChangelogGenerator
|
25
|
-
# Class, responsible for whole
|
25
|
+
# Class, responsible for whole changelog generation cycle
|
26
26
|
# @return initialised instance of ChangelogGenerator
|
27
27
|
def initialize
|
28
28
|
@options = Parser.parse_options
|
29
29
|
@generator = Generator.new @options
|
30
30
|
end
|
31
31
|
|
32
|
-
# The entry point of this script to generate
|
32
|
+
# The entry point of this script to generate changelog
|
33
33
|
# @raise (ChangelogGeneratorError) Is thrown when one of specified tags was not found in list of tags.
|
34
34
|
def run
|
35
35
|
log = @generator.compound_changelog
|
36
36
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
37
|
+
if @options.write_to_file?
|
38
|
+
output_filename = @options[:output].to_s
|
39
|
+
File.open(output_filename, "wb") { |file| file.write(log) }
|
40
|
+
puts "Done!"
|
41
|
+
puts "Generated log placed in #{Dir.pwd}/#{output_filename}"
|
42
|
+
else
|
43
|
+
puts log
|
44
|
+
end
|
41
45
|
end
|
42
46
|
end
|
43
47
|
end
|
@@ -0,0 +1,218 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "github_changelog_generator/generator/section"
|
4
|
+
|
5
|
+
module GitHubChangelogGenerator
|
6
|
+
# This class generates the content for a single changelog entry. An entry is
|
7
|
+
# generally either for a specific tagged release or the collection of
|
8
|
+
# unreleased changes.
|
9
|
+
#
|
10
|
+
# An entry is comprised of header text followed by a series of sections
|
11
|
+
# relating to the entry.
|
12
|
+
#
|
13
|
+
# @see GitHubChangelogGenerator::Generator
|
14
|
+
# @see GitHubChangelogGenerator::Section
|
15
|
+
class Entry
|
16
|
+
attr_reader :content
|
17
|
+
|
18
|
+
def initialize(options = Options.new({}))
|
19
|
+
@content = ""
|
20
|
+
@options = Options.new(options)
|
21
|
+
end
|
22
|
+
|
23
|
+
# Generates log entry with header and body
|
24
|
+
#
|
25
|
+
# @param [Array] pull_requests List or PR's in new section
|
26
|
+
# @param [Array] issues List of issues in new section
|
27
|
+
# @param [String] newer_tag_name Name of the newer tag. Could be nil for `Unreleased` section.
|
28
|
+
# @param [String] newer_tag_link Name of the newer tag. Could be "HEAD" for `Unreleased` section.
|
29
|
+
# @param [Time] newer_tag_time Time of the newer tag
|
30
|
+
# @param [Hash, nil] older_tag_name Older tag, used for the links. Could be nil for last tag.
|
31
|
+
# @return [String] Ready and parsed section content.
|
32
|
+
def generate_entry_for_tag(pull_requests, issues, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name) # rubocop:disable Metrics/ParameterLists
|
33
|
+
github_site = @options[:github_site] || "https://github.com"
|
34
|
+
project_url = "#{github_site}/#{@options[:user]}/#{@options[:project]}"
|
35
|
+
|
36
|
+
create_sections
|
37
|
+
|
38
|
+
@content = generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
39
|
+
@content += generate_body(pull_requests, issues)
|
40
|
+
@content
|
41
|
+
end
|
42
|
+
|
43
|
+
def line_labels_for(issue)
|
44
|
+
labels = if @options[:issue_line_labels] == ["ALL"]
|
45
|
+
issue["labels"]
|
46
|
+
else
|
47
|
+
issue["labels"].select { |label| @options[:issue_line_labels].include?(label["name"]) }
|
48
|
+
end
|
49
|
+
labels.map { |label| " \[[#{label['name']}](#{label['url'].sub('api.github.com/repos', 'github.com')})\]" }.join("")
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
# Creates section objects for this entry.
|
55
|
+
# @return [Nil]
|
56
|
+
def create_sections
|
57
|
+
@sections = if @options.configure_sections?
|
58
|
+
parse_sections(@options[:configure_sections])
|
59
|
+
elsif @options.add_sections?
|
60
|
+
default_sections.concat parse_sections(@options[:add_sections])
|
61
|
+
else
|
62
|
+
default_sections
|
63
|
+
end
|
64
|
+
nil
|
65
|
+
end
|
66
|
+
|
67
|
+
# Turns the argument from the commandline of --configure-sections or
|
68
|
+
# --add-sections into an array of Section objects.
|
69
|
+
#
|
70
|
+
# @param [String, Hash] sections_desc Either string or hash describing sections
|
71
|
+
# @return [Array] Parsed section objects.
|
72
|
+
def parse_sections(sections_desc)
|
73
|
+
require "json"
|
74
|
+
|
75
|
+
sections_desc = sections_desc.to_json if sections_desc.class == Hash
|
76
|
+
|
77
|
+
begin
|
78
|
+
sections_json = JSON.parse(sections_desc)
|
79
|
+
rescue JSON::ParserError => e
|
80
|
+
raise "There was a problem parsing your JSON string for sections: #{e}"
|
81
|
+
end
|
82
|
+
|
83
|
+
sections_json.collect do |name, v|
|
84
|
+
Section.new(name: name.to_s, prefix: v["prefix"], labels: v["labels"], body_only: v["body_only"], options: @options)
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
# Generates header text for an entry.
|
89
|
+
#
|
90
|
+
# @param [String] newer_tag_name The name of a newer tag
|
91
|
+
# @param [String] newer_tag_link Used for URL generation. Could be same as #newer_tag_name or some specific value, like HEAD
|
92
|
+
# @param [Time] newer_tag_time Time when the newer tag was created
|
93
|
+
# @param [String] older_tag_name The name of an older tag; used for URLs.
|
94
|
+
# @param [String] project_url URL for the current project.
|
95
|
+
# @return [String] Header text content.
|
96
|
+
def generate_header(newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name, project_url)
|
97
|
+
header = ""
|
98
|
+
|
99
|
+
# Generate date string:
|
100
|
+
time_string = newer_tag_time.strftime(@options[:date_format])
|
101
|
+
|
102
|
+
# Generate tag name and link
|
103
|
+
release_url = if @options[:release_url]
|
104
|
+
format(@options[:release_url], newer_tag_link)
|
105
|
+
else
|
106
|
+
"#{project_url}/tree/#{newer_tag_link}"
|
107
|
+
end
|
108
|
+
header += if newer_tag_name.equal?(@options[:unreleased_label])
|
109
|
+
"## [#{newer_tag_name}](#{release_url})\n\n"
|
110
|
+
else
|
111
|
+
"## [#{newer_tag_name}](#{release_url}) (#{time_string})\n\n"
|
112
|
+
end
|
113
|
+
|
114
|
+
if @options[:compare_link] && older_tag_name
|
115
|
+
# Generate compare link
|
116
|
+
header += "[Full Changelog](#{project_url}/compare/#{older_tag_name}...#{newer_tag_link})\n\n"
|
117
|
+
end
|
118
|
+
|
119
|
+
header
|
120
|
+
end
|
121
|
+
|
122
|
+
# Generates complete body text for a tag (without a header)
|
123
|
+
#
|
124
|
+
# @param [Array] pull_requests
|
125
|
+
# @param [Array] issues
|
126
|
+
# @return [String] Content generated from sections of sorted issues & PRs.
|
127
|
+
def generate_body(pull_requests, issues)
|
128
|
+
sort_into_sections(pull_requests, issues)
|
129
|
+
@sections.map(&:generate_content).join
|
130
|
+
end
|
131
|
+
|
132
|
+
# Default sections to used when --configure-sections is not set.
|
133
|
+
#
|
134
|
+
# @return [Array] Section objects.
|
135
|
+
def default_sections
|
136
|
+
[
|
137
|
+
Section.new(name: "summary", prefix: @options[:summary_prefix], labels: @options[:summary_labels], options: @options, body_only: true),
|
138
|
+
Section.new(name: "breaking", prefix: @options[:breaking_prefix], labels: @options[:breaking_labels], options: @options),
|
139
|
+
Section.new(name: "enhancements", prefix: @options[:enhancement_prefix], labels: @options[:enhancement_labels], options: @options),
|
140
|
+
Section.new(name: "bugs", prefix: @options[:bug_prefix], labels: @options[:bug_labels], options: @options),
|
141
|
+
Section.new(name: "deprecated", prefix: @options[:deprecated_prefix], labels: @options[:deprecated_labels], options: @options),
|
142
|
+
Section.new(name: "removed", prefix: @options[:removed_prefix], labels: @options[:removed_labels], options: @options),
|
143
|
+
Section.new(name: "security", prefix: @options[:security_prefix], labels: @options[:security_labels], options: @options)
|
144
|
+
]
|
145
|
+
end
|
146
|
+
|
147
|
+
# Sorts issues and PRs into entry sections by labels and lack of labels.
|
148
|
+
#
|
149
|
+
# @param [Array] pull_requests
|
150
|
+
# @param [Array] issues
|
151
|
+
# @return [Nil]
|
152
|
+
def sort_into_sections(pull_requests, issues)
|
153
|
+
if @options[:issues]
|
154
|
+
unmapped_issues = sort_labeled_issues(issues)
|
155
|
+
add_unmapped_section(unmapped_issues)
|
156
|
+
end
|
157
|
+
if @options[:pulls]
|
158
|
+
unmapped_pull_requests = sort_labeled_issues(pull_requests)
|
159
|
+
add_unmapped_section(unmapped_pull_requests)
|
160
|
+
end
|
161
|
+
nil
|
162
|
+
end
|
163
|
+
|
164
|
+
# Iterates through sections and sorts labeled issues into them based on
|
165
|
+
# the label mapping. Returns any unmapped or unlabeled issues.
|
166
|
+
#
|
167
|
+
# @param [Array] issues Issues or pull requests.
|
168
|
+
# @return [Array] Issues that were not mapped into any sections.
|
169
|
+
def sort_labeled_issues(issues)
|
170
|
+
sorted_issues = []
|
171
|
+
issues.each do |issue|
|
172
|
+
label_names = issue["labels"].collect { |l| l["name"] }
|
173
|
+
|
174
|
+
# Add PRs in the order of the @sections array. This will either be the
|
175
|
+
# default sections followed by any --add-sections sections in
|
176
|
+
# user-defined order, or --configure-sections in user-defined order.
|
177
|
+
# Ignore the order of the issue labels from github which cannot be
|
178
|
+
# controled by the user.
|
179
|
+
@sections.each do |section|
|
180
|
+
unless (section.labels & label_names).empty?
|
181
|
+
section.issues << issue
|
182
|
+
sorted_issues << issue
|
183
|
+
break
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
issues - sorted_issues
|
188
|
+
end
|
189
|
+
|
190
|
+
# Creates a section for issues/PRs with no labels or no mapped labels.
|
191
|
+
#
|
192
|
+
# @param [Array] issues
|
193
|
+
# @return [Nil]
|
194
|
+
def add_unmapped_section(issues)
|
195
|
+
unless issues.empty?
|
196
|
+
# Distinguish between issues and pull requests
|
197
|
+
if issues.first.key?("pull_request")
|
198
|
+
name = "merged"
|
199
|
+
prefix = @options[:merge_prefix]
|
200
|
+
add_wo_labels = @options[:add_pr_wo_labels]
|
201
|
+
else
|
202
|
+
name = "issues"
|
203
|
+
prefix = @options[:issue_prefix]
|
204
|
+
add_wo_labels = @options[:add_issues_wo_labels]
|
205
|
+
end
|
206
|
+
add_issues = if add_wo_labels
|
207
|
+
issues
|
208
|
+
else
|
209
|
+
# Only add unmapped issues
|
210
|
+
issues.select { |issue| issue["labels"].any? }
|
211
|
+
end
|
212
|
+
merged = Section.new(name: name, prefix: prefix, labels: [], issues: add_issues, options: @options) unless add_issues.empty?
|
213
|
+
@sections << merged
|
214
|
+
end
|
215
|
+
nil
|
216
|
+
end
|
217
|
+
end
|
218
|
+
end
|
@@ -1,20 +1,38 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
3
|
+
require "github_changelog_generator/octo_fetcher"
|
4
|
+
require "github_changelog_generator/generator/generator_fetcher"
|
5
|
+
require "github_changelog_generator/generator/generator_processor"
|
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
|
-
|
29
|
+
CREDIT_LINE = <<~CREDIT
|
30
|
+
\\* *This Changelog was automatically generated \
|
31
|
+
by [github_changelog_generator]\
|
32
|
+
(https://github.com/github-changelog-generator/github-changelog-generator)*
|
33
|
+
CREDIT
|
34
|
+
|
35
|
+
# A Generator responsible for all logic, related with changelog generation from ready-to-parse issues
|
18
36
|
#
|
19
37
|
# Example:
|
20
38
|
# generator = GitHubChangelogGenerator::Generator.new
|
@@ -23,156 +41,137 @@ module GitHubChangelogGenerator
|
|
23
41
|
@options = options
|
24
42
|
@tag_times_hash = {}
|
25
43
|
@fetcher = GitHubChangelogGenerator::OctoFetcher.new(options)
|
44
|
+
@sections = []
|
26
45
|
end
|
27
46
|
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
47
|
+
# Main function to start changelog generation
|
48
|
+
#
|
49
|
+
# @return [String] Generated changelog file
|
50
|
+
def compound_changelog
|
51
|
+
@options.load_custom_ruby_files
|
52
|
+
|
53
|
+
Sync do
|
54
|
+
fetch_and_filter_tags
|
55
|
+
fetch_issues_and_pr
|
56
|
+
|
57
|
+
log = if @options[:unreleased_only]
|
58
|
+
generate_entry_between_tags(@filtered_tags[0], nil)
|
59
|
+
else
|
60
|
+
generate_entries_for_all_tags
|
61
|
+
end
|
62
|
+
log += File.read(@options[:base]) if File.file?(@options[:base])
|
63
|
+
log = remove_old_fixed_string(log)
|
64
|
+
log = insert_fixed_string(log)
|
65
|
+
@log = log
|
66
|
+
end
|
37
67
|
end
|
38
68
|
|
39
|
-
|
69
|
+
private
|
40
70
|
|
41
|
-
#
|
42
|
-
#
|
43
|
-
# @param [String]
|
44
|
-
|
45
|
-
|
46
|
-
string = string.gsub('\\', '\\\\')
|
71
|
+
# Generate log only between 2 specified tags
|
72
|
+
# @param [String] older_tag all issues before this tag date will be excluded. May be nil, if it's first tag
|
73
|
+
# @param [String] newer_tag all issue after this tag will be excluded. May be nil for unreleased section
|
74
|
+
def generate_entry_between_tags(older_tag, newer_tag)
|
75
|
+
filtered_issues, filtered_pull_requests = filter_issues_for_tags(newer_tag, older_tag)
|
47
76
|
|
48
|
-
|
49
|
-
|
77
|
+
if newer_tag.nil? && filtered_issues.empty? && filtered_pull_requests.empty?
|
78
|
+
# do not generate empty unreleased section
|
79
|
+
return ""
|
50
80
|
end
|
51
81
|
|
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
82
|
newer_tag_link, newer_tag_name, newer_tag_time = detect_link_tag_time(newer_tag)
|
64
83
|
|
65
|
-
github_site = options[:github_site] || "https://github.com"
|
66
|
-
project_url = "#{github_site}/#{options[:user]}/#{options[:project]}"
|
67
|
-
|
68
84
|
# If the older tag is nil, go back in time from the latest tag and find
|
69
85
|
# the SHA for the first commit.
|
70
86
|
older_tag_name =
|
71
87
|
if older_tag.nil?
|
72
|
-
@fetcher.
|
88
|
+
@fetcher.oldest_commit["sha"]
|
73
89
|
else
|
74
90
|
older_tag["name"]
|
75
91
|
end
|
76
92
|
|
77
|
-
|
93
|
+
Entry.new(options).generate_entry_for_tag(filtered_pull_requests, filtered_issues, newer_tag_name, newer_tag_link, newer_tag_time, older_tag_name)
|
94
|
+
end
|
95
|
+
|
96
|
+
# Filters issues and pull requests based on, respectively, `actual_date`
|
97
|
+
# and `merged_at` timestamp fields. `actual_date` is the detected form of
|
98
|
+
# `closed_at` based on merge event SHA commit times.
|
99
|
+
#
|
100
|
+
# @return [Array] filtered issues and pull requests
|
101
|
+
def filter_issues_for_tags(newer_tag, older_tag)
|
102
|
+
filtered_pull_requests = filter_by_tag(@pull_requests, newer_tag)
|
103
|
+
filtered_issues = delete_by_time(@issues, "actual_date", older_tag, newer_tag)
|
78
104
|
|
79
|
-
|
80
|
-
|
81
|
-
|
105
|
+
newer_tag_name = newer_tag.nil? ? nil : newer_tag["name"]
|
106
|
+
|
107
|
+
if options[:filter_issues_by_milestone]
|
108
|
+
# delete excess irrelevant issues (according milestones). Issue #22.
|
109
|
+
filtered_issues = filter_by_milestone(filtered_issues, newer_tag_name, @issues)
|
110
|
+
filtered_pull_requests = filter_by_milestone(filtered_pull_requests, newer_tag_name, @pull_requests)
|
82
111
|
end
|
112
|
+
[filtered_issues, filtered_pull_requests]
|
113
|
+
end
|
83
114
|
|
84
|
-
|
85
|
-
|
86
|
-
|
115
|
+
# The full cycle of generation for whole project
|
116
|
+
# @return [String] All entries in the changelog
|
117
|
+
def generate_entries_for_all_tags
|
118
|
+
puts "Generating entry..." if options[:verbose]
|
119
|
+
|
120
|
+
entries = generate_unreleased_entry
|
121
|
+
|
122
|
+
@tag_section_mapping.each_pair do |_tag_section, left_right_tags|
|
123
|
+
older_tag, newer_tag = left_right_tags
|
124
|
+
entries += generate_entry_between_tags(older_tag, newer_tag)
|
87
125
|
end
|
88
126
|
|
89
|
-
|
127
|
+
entries
|
90
128
|
end
|
91
129
|
|
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
|
130
|
+
def generate_unreleased_entry
|
131
|
+
entry = ""
|
132
|
+
if options[:unreleased]
|
133
|
+
start_tag = @filtered_tags[0] || @sorted_tags.last
|
134
|
+
unreleased_entry = generate_entry_between_tags(start_tag, nil)
|
135
|
+
entry += unreleased_entry if unreleased_entry
|
136
|
+
end
|
137
|
+
entry
|
106
138
|
end
|
107
139
|
|
108
|
-
#
|
109
|
-
# (bugs, features, or just closed issues) by labels
|
140
|
+
# Fetches @pull_requests and @issues and filters them based on options.
|
110
141
|
#
|
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
|
142
|
+
# @return [Nil] No return.
|
143
|
+
def fetch_issues_and_pr
|
144
|
+
issues, pull_requests = @fetcher.fetch_closed_issues_and_pr
|
139
145
|
|
140
|
-
|
141
|
-
end
|
146
|
+
@pull_requests = options[:pulls] ? get_filtered_pull_requests(pull_requests) : []
|
142
147
|
|
143
|
-
|
148
|
+
@issues = options[:issues] ? get_filtered_issues(issues) : []
|
149
|
+
|
150
|
+
fetch_events_for_issues_and_pr
|
151
|
+
detect_actual_closed_dates(@issues + @pull_requests)
|
152
|
+
add_first_occurring_tag_to_prs(@sorted_tags, @pull_requests)
|
153
|
+
nil
|
144
154
|
end
|
145
155
|
|
146
|
-
#
|
147
|
-
#
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
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
|
156
|
+
# Remove the previously assigned fixed message.
|
157
|
+
# @param log [String] Old lines are fixed
|
158
|
+
def remove_old_fixed_string(log)
|
159
|
+
log.gsub!(/#{Regexp.escape(@options[:frontmatter])}/, "") if @options[:frontmatter]
|
160
|
+
log.gsub!(/#{Regexp.escape(@options[:header])}\n{,2}/, "")
|
161
|
+
log.gsub!(/\n{,2}#{Regexp.escape(CREDIT_LINE)}/, "") # Remove old credit lines
|
162
|
+
log
|
163
|
+
end
|
164
|
+
|
165
|
+
# Add template messages to given string. Previously added
|
166
|
+
# messages of the same wording are removed.
|
167
|
+
# @param log [String]
|
168
|
+
def insert_fixed_string(log)
|
169
|
+
ins = ""
|
170
|
+
ins += @options[:frontmatter] if @options[:frontmatter]
|
171
|
+
ins += "#{@options[:header]}\n\n"
|
172
|
+
log.insert(0, ins)
|
173
|
+
log += "\n\n#{CREDIT_LINE}"
|
174
|
+
log
|
176
175
|
end
|
177
176
|
end
|
178
177
|
end
|