changelog_jira 1.12.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 (34) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +233 -0
  3. data/Rakefile +39 -0
  4. data/bin/git-generate-changelog +4 -0
  5. data/bin/github_changelog_generator +4 -0
  6. data/lib/CHANGELOG.md +58 -0
  7. data/lib/github_changelog_generator.rb +41 -0
  8. data/lib/github_changelog_generator/fetcher.rb +221 -0
  9. data/lib/github_changelog_generator/generator/generator.rb +143 -0
  10. data/lib/github_changelog_generator/generator/generator_fetcher.rb +83 -0
  11. data/lib/github_changelog_generator/generator/generator_generation.rb +190 -0
  12. data/lib/github_changelog_generator/generator/generator_processor.rb +193 -0
  13. data/lib/github_changelog_generator/generator/generator_tags.rb +184 -0
  14. data/lib/github_changelog_generator/helper.rb +37 -0
  15. data/lib/github_changelog_generator/parser.rb +285 -0
  16. data/lib/github_changelog_generator/parser_file.rb +103 -0
  17. data/lib/github_changelog_generator/reader.rb +84 -0
  18. data/lib/github_changelog_generator/task.rb +67 -0
  19. data/lib/github_changelog_generator/version.rb +3 -0
  20. data/man/git-generate-changelog.1 +252 -0
  21. data/man/git-generate-changelog.html +262 -0
  22. data/man/git-generate-changelog.md +179 -0
  23. data/spec/files/angular.js.md +9395 -0
  24. data/spec/files/bundler.md +1911 -0
  25. data/spec/files/github-changelog-generator.md +305 -0
  26. data/spec/install-gem-in-bundler.gemfile +3 -0
  27. data/spec/spec_helper.rb +55 -0
  28. data/spec/unit/fetcher_spec.rb +59 -0
  29. data/spec/unit/generator/generator_processor_spec.rb +28 -0
  30. data/spec/unit/generator/generator_tags_spec.rb +243 -0
  31. data/spec/unit/parse_file_spec.rb +73 -0
  32. data/spec/unit/parser_spec.rb +60 -0
  33. data/spec/unit/reader_spec.rb +113 -0
  34. metadata +190 -0
@@ -0,0 +1,184 @@
1
+ module GitHubChangelogGenerator
2
+ class Generator
3
+ # fetch, filter tags, fetch dates and sort them in time order
4
+ def fetch_and_filter_tags
5
+ @filtered_tags = get_filtered_tags(@fetcher.get_all_tags)
6
+ fetch_tags_dates
7
+ end
8
+
9
+ # Sort all tags by date
10
+ def sort_tags_by_date(tags)
11
+ puts "Sorting tags..." if @options[:verbose]
12
+ tags.sort_by! do |x|
13
+ get_time_of_tag(x)
14
+ end.reverse!
15
+ end
16
+
17
+ # Try to find tag date in local hash.
18
+ # Otherwise fFetch tag time and put it to local hash file.
19
+ # @param [Hash] tag_name name of the tag
20
+ # @return [Time] time of specified tag
21
+ def get_time_of_tag(tag_name)
22
+ raise ChangelogGeneratorError, "tag_name is nil".red if tag_name.nil?
23
+
24
+ name_of_tag = tag_name["name"]
25
+ time_for_name = @tag_times_hash[name_of_tag]
26
+ if !time_for_name.nil?
27
+ time_for_name
28
+ else
29
+ time_string = @fetcher.fetch_date_of_tag tag_name
30
+ @tag_times_hash[name_of_tag] = time_string
31
+ time_string
32
+ end
33
+ end
34
+
35
+ # Detect link, name and time for specified tag.
36
+ #
37
+ # @param [Hash] newer_tag newer tag. Can be nil, if it's Unreleased section.
38
+ # @return [Array] link, name and time of the tag
39
+ def detect_link_tag_time(newer_tag)
40
+ # if tag is nil - set current time
41
+ newer_tag_time = newer_tag.nil? ? Time.new : get_time_of_tag(newer_tag)
42
+
43
+ # if it's future release tag - set this value
44
+ if newer_tag.nil? && @options[:future_release]
45
+ newer_tag_name = @options[:future_release]
46
+ newer_tag_link = @options[:future_release]
47
+ else
48
+ # put unreleased label if there is no name for the tag
49
+ newer_tag_name = newer_tag.nil? ? @options[:unreleased_label] : newer_tag["name"]
50
+ newer_tag_link = newer_tag.nil? ? "HEAD" : newer_tag_name
51
+ end
52
+ [newer_tag_link, newer_tag_name, newer_tag_time]
53
+ end
54
+
55
+ # @return [Object] try to find newest tag using #Reader and :base option if specified otherwise returns nil
56
+ def detect_since_tag
57
+ @since_tag ||= @options.fetch(:since_tag) { version_of_first_item }
58
+ end
59
+
60
+ def version_of_first_item
61
+ return unless File.file?(@options[:base].to_s)
62
+
63
+ sections = GitHubChangelogGenerator::Reader.new.read(@options[:base])
64
+ sections.first["version"] if sections && sections.any?
65
+ end
66
+
67
+ # Return tags after filtering tags in lists provided by option: --between-tags & --exclude-tags
68
+ #
69
+ # @return [Array]
70
+ def get_filtered_tags(all_tags)
71
+ filtered_tags = filter_since_tag(all_tags)
72
+ filtered_tags = filter_between_tags(filtered_tags)
73
+ filter_excluded_tags(filtered_tags)
74
+ end
75
+
76
+ # @param [Array] all_tags all tags
77
+ # @return [Array] filtered tags according :since_tag option
78
+ def filter_since_tag(all_tags)
79
+ filtered_tags = all_tags
80
+ tag = detect_since_tag
81
+ if tag
82
+ if all_tags.map(&:name).include? tag
83
+ idx = all_tags.index { |t| t.name == tag }
84
+ filtered_tags = if idx > 0
85
+ all_tags[0..idx - 1]
86
+ else
87
+ []
88
+ end
89
+ else
90
+ Helper.log.warn "Warning: can't find tag #{tag}, specified with --since-tag option."
91
+ end
92
+ end
93
+ filtered_tags
94
+ end
95
+
96
+ # @param [Array] all_tags all tags
97
+ # @return [Array] filtered tags according :due_tag option
98
+ def filter_due_tag(all_tags)
99
+ filtered_tags = all_tags
100
+ tag = @options[:due_tag]
101
+ if tag
102
+ if (all_tags.count > 0) && (all_tags.map(&:name).include? tag)
103
+ idx = all_tags.index { |t| t.name == tag }
104
+ last_index = all_tags.count - 1
105
+ filtered_tags = if idx > 0 && idx < last_index
106
+ all_tags[idx + 1..last_index]
107
+ else
108
+ []
109
+ end
110
+ else
111
+ Helper.log.warn "Warning: can't find tag #{tag}, specified with --due-tag option."
112
+ end
113
+ end
114
+ filtered_tags
115
+ end
116
+
117
+ # @param [Array] all_tags all tags
118
+ # @return [Array] filtered tags according :between_tags option
119
+ def filter_between_tags(all_tags)
120
+ filtered_tags = all_tags
121
+ if @options[:between_tags]
122
+ @options[:between_tags].each do |tag|
123
+ unless all_tags.map(&:name).include? tag
124
+ Helper.log.warn "Warning: can't find tag #{tag}, specified with --between-tags option."
125
+ end
126
+ end
127
+ filtered_tags = all_tags.select { |tag| @options[:between_tags].include? tag.name }
128
+ end
129
+ filtered_tags
130
+ end
131
+
132
+ # @param [Array] all_tags all tags
133
+ # @return [Array] filtered tags according :exclude_tags or :exclude_tags_regex option
134
+ def filter_excluded_tags(all_tags)
135
+ if @options[:exclude_tags]
136
+ apply_exclude_tags(all_tags)
137
+ elsif @options[:exclude_tags_regex]
138
+ apply_exclude_tags_regex(all_tags)
139
+ else
140
+ all_tags
141
+ end
142
+ end
143
+
144
+ private
145
+
146
+ def apply_exclude_tags(all_tags)
147
+ if @options[:exclude_tags].is_a?(Regexp)
148
+ filter_tags_with_regex(all_tags, @options[:exclude_tags])
149
+ else
150
+ filter_exact_tags(all_tags)
151
+ end
152
+ end
153
+
154
+ def apply_exclude_tags_regex(all_tags)
155
+ filter_tags_with_regex(all_tags, Regexp.new(@options[:exclude_tags_regex]))
156
+ end
157
+
158
+ def filter_tags_with_regex(all_tags, regex)
159
+ warn_if_nonmatching_regex(all_tags)
160
+ all_tags.reject { |tag| regex =~ tag.name }
161
+ end
162
+
163
+ def filter_exact_tags(all_tags)
164
+ @options[:exclude_tags].each do |tag|
165
+ warn_if_tag_not_found(all_tags, tag)
166
+ end
167
+ all_tags.reject { |tag| @options[:exclude_tags].include? tag.name }
168
+ end
169
+
170
+ def warn_if_nonmatching_regex(all_tags)
171
+ unless all_tags.map(&:name).any? { |t| @options[:exclude_tags] =~ t }
172
+ Helper.log.warn "Warning: unable to reject any tag, using regex "\
173
+ "#{@options[:exclude_tags].inspect} in --exclude-tags "\
174
+ "option."
175
+ end
176
+ end
177
+
178
+ def warn_if_tag_not_found(all_tags, tag)
179
+ unless all_tags.map(&:name).include? tag
180
+ Helper.log.warn "Warning: can't find tag #{tag}, specified with --exclude-tags option."
181
+ end
182
+ end
183
+ end
184
+ end
@@ -0,0 +1,37 @@
1
+ require "logger"
2
+ module GitHubChangelogGenerator
3
+ module Helper
4
+ # @return true if the currently running program is a unit test
5
+ def self.test?
6
+ defined? SpecHelper
7
+ end
8
+
9
+ @log ||= if test?
10
+ Logger.new(nil) # don't show any logs when running tests
11
+ else
12
+ Logger.new(STDOUT)
13
+ end
14
+ @log.formatter = proc do |severity, _datetime, _progname, msg|
15
+ string = "#{msg}\n"
16
+
17
+ if severity == "DEBUG"
18
+ string = string.magenta
19
+ elsif severity == "INFO"
20
+ string = string.white
21
+ elsif severity == "WARN"
22
+ string = string.yellow
23
+ elsif severity == "ERROR"
24
+ string = string.red
25
+ elsif severity == "FATAL"
26
+ string = string.red.bold
27
+ end
28
+
29
+ string
30
+ end
31
+
32
+ # Logging happens using this method
33
+ class << self
34
+ attr_reader :log
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,285 @@
1
+ #!/usr/bin/env ruby
2
+ require "optparse"
3
+ require "pp"
4
+ require_relative "version"
5
+ require_relative "helper"
6
+ module GitHubChangelogGenerator
7
+ class Parser
8
+ # parse options with optparse
9
+ def self.parse_options
10
+ options = default_options
11
+
12
+ ParserFile.new(options).parse!
13
+
14
+ parser = setup_parser(options)
15
+ parser.parse!
16
+
17
+ user_and_project_from_git(options)
18
+
19
+ abort(parser.banner) unless options[:user] && options[:project]
20
+
21
+ print_options(options)
22
+
23
+ options
24
+ end
25
+
26
+ # @param [Hash] options to display
27
+ def self.print_options(options)
28
+ if options[:verbose]
29
+ Helper.log.info "Performing task with options:"
30
+ options_to_display = options.clone
31
+ options_to_display[:token] = options_to_display[:token].nil? ? nil : "hidden value"
32
+ pp options_to_display
33
+ puts ""
34
+ end
35
+ end
36
+
37
+ # setup parsing options
38
+ def self.setup_parser(options)
39
+ parser = OptionParser.new do |opts|
40
+ opts.banner = "Usage: github_changelog_generator [options]"
41
+ opts.on("-u", "--user [USER]", "Username of the owner of target GitHub repo") do |last|
42
+ options[:user] = last
43
+ end
44
+ opts.on("-p", "--project [PROJECT]", "Name of project on GitHub") do |last|
45
+ options[:project] = last
46
+ end
47
+ opts.on("-t", "--token [TOKEN]", "To make more than 50 requests per hour your GitHub token is required. You can generate it at: https://github.com/settings/tokens/new") do |last|
48
+ options[:token] = last
49
+ end
50
+ opts.on("-f", "--date-format [FORMAT]", "Date format. Default is %Y-%m-%d") do |last|
51
+ options[:date_format] = last
52
+ end
53
+ opts.on("-o", "--output [NAME]", "Output file. Default is CHANGELOG.md") do |last|
54
+ options[:output] = last
55
+ end
56
+ opts.on("-b", "--base [NAME]", "Optional base file to append generated changes to.") do |last|
57
+ options[:base] = last
58
+ end
59
+ opts.on("--bugs-label [LABEL]", "Setup custom label for bug-fixes section. Default is \"**Fixed bugs:**""") do |v|
60
+ options[:bug_prefix] = v
61
+ end
62
+ opts.on("--enhancement-label [LABEL]", "Setup custom label for enhancements section. Default is \"**Implemented enhancements:**\"") do |v|
63
+ options[:enhancement_prefix] = v
64
+ end
65
+ opts.on("--issues-label [LABEL]", "Setup custom label for closed-issues section. Default is \"**Closed issues:**\"") do |v|
66
+ options[:issue_prefix] = v
67
+ end
68
+ opts.on("--header-label [LABEL]", "Setup custom header label. Default is \"# Change Log\"") do |v|
69
+ options[:header] = v
70
+ end
71
+ opts.on("--front-matter [JSON]", "Add YAML front matter. Formatted as JSON because it's easier to add on the command line") do |v|
72
+ options[:frontmatter] = JSON.parse(v).to_yaml + "---\n"
73
+ end
74
+ opts.on("--pr-label [LABEL]", "Setup custom label for pull requests section. Default is \"**Merged pull requests:**\"") do |v|
75
+ options[:merge_prefix] = v
76
+ end
77
+ opts.on("--[no-]issues", "Include closed issues in changelog. Default is true") do |v|
78
+ options[:issues] = v
79
+ end
80
+ opts.on("--[no-]issues-wo-labels", "Include closed issues without labels in changelog. Default is true") do |v|
81
+ options[:add_issues_wo_labels] = v
82
+ end
83
+ opts.on("--[no-]pr-wo-labels", "Include pull requests without labels in changelog. Default is true") do |v|
84
+ options[:add_pr_wo_labels] = v
85
+ end
86
+ opts.on("--[no-]pull-requests", "Include pull-requests in changelog. Default is true") do |v|
87
+ options[:pulls] = v
88
+ end
89
+ opts.on("--[no-]filter-by-milestone", "Use milestone to detect when issue was resolved. Default is true") do |last|
90
+ options[:filter_issues_by_milestone] = last
91
+ end
92
+ opts.on("--[no-]author", "Add author of pull-request in the end. Default is true") do |author|
93
+ options[:author] = author
94
+ end
95
+ opts.on("--unreleased-only", "Generate log from unreleased closed issues only.") do |v|
96
+ options[:unreleased_only] = v
97
+ end
98
+ opts.on("--[no-]unreleased", "Add to log unreleased closed issues. Default is true") do |v|
99
+ options[:unreleased] = v
100
+ end
101
+ opts.on("--unreleased-label [label]", "Add to log unreleased closed issues. Default is true") do |v|
102
+ options[:unreleased_label] = v
103
+ end
104
+ opts.on("--[no-]compare-link", "Include compare link (Full Changelog) between older version and newer version. Default is true") do |v|
105
+ options[:compare_link] = v
106
+ end
107
+ opts.on("--include-labels x,y,z", Array, "Only issues with the specified labels will be included in the changelog.") do |list|
108
+ options[:include_labels] = list
109
+ end
110
+ opts.on("--exclude-labels x,y,z", Array, 'Issues with the specified labels will be always excluded from changelog. Default is \'duplicate,question,invalid,wontfix\'') do |list|
111
+ options[:exclude_labels] = list
112
+ end
113
+ opts.on("--bug-labels x,y,z", Array, 'Issues with the specified labels will be always added to "Fixed bugs" section. Default is \'bug,Bug\'') do |list|
114
+ options[:bug_labels] = list
115
+ end
116
+ opts.on("--enhancement-labels x,y,z", Array, 'Issues with the specified labels will be always added to "Implemented enhancements" section. Default is \'enhancement,Enhancement\'') do |list|
117
+ options[:enhancement_labels] = list
118
+ end
119
+ opts.on("--between-tags x,y,z", Array, "Change log will be filled only between specified tags") do |list|
120
+ options[:between_tags] = list
121
+ end
122
+ opts.on("--exclude-tags x,y,z", Array, "Change log will exclude specified tags") do |list|
123
+ options[:exclude_tags] = list
124
+ end
125
+ opts.on("--exclude-tags-regex [REGEX]", "Apply a regular expression on tag names so that they can be excluded, for example: --exclude-tags-regex \".*\+\d{1,}\" ") do |last|
126
+ options[:exclude_tags_regex] = last
127
+ end
128
+ opts.on("--since-tag x", "Change log will start after specified tag") do |v|
129
+ options[:since_tag] = v
130
+ end
131
+ opts.on("--due-tag x", "Change log will end before specified tag") do |v|
132
+ options[:due_tag] = v
133
+ end
134
+ opts.on("--max-issues [NUMBER]", Integer, "Max number of issues to fetch from GitHub. Default is unlimited") do |max|
135
+ options[:max_issues] = max
136
+ end
137
+ opts.on("--release-url [URL]", "The URL to point to for release links, in printf format (with the tag as variable).") do |url|
138
+ options[:release_url] = url
139
+ end
140
+ opts.on("--github-site [URL]", "The Enterprise Github site on which your project is hosted.") do |last|
141
+ options[:github_site] = last
142
+ end
143
+ opts.on("--github-api [URL]", "The enterprise endpoint to use for your Github API.") do |last|
144
+ options[:github_endpoint] = last
145
+ end
146
+ opts.on("--simple-list", "Create simple list from issues and pull requests. Default is false.") do |v|
147
+ options[:simple_list] = v
148
+ end
149
+ opts.on("--future-release [RELEASE-VERSION]", "Put the unreleased changes in the specified release number.") do |future_release|
150
+ options[:future_release] = future_release
151
+ end
152
+ opts.on("--release-branch [RELEASE-BRANCH]", "Limit pull requests to the release branch, such as master or release") do |release_branch|
153
+ options[:release_branch] = release_branch
154
+ end
155
+ opts.on("--[no-]verbose", "Run verbosely. Default is true") do |v|
156
+ options[:verbose] = v
157
+ end
158
+ opts.on("-v", "--version", "Print version number") do |_v|
159
+ puts "Version: #{GitHubChangelogGenerator::VERSION}"
160
+ exit
161
+ end
162
+ opts.on("-h", "--help", "Displays Help") do
163
+ puts opts
164
+ exit
165
+ end
166
+ end
167
+ parser
168
+ end
169
+
170
+ # just get default options
171
+ def self.default_options
172
+ {
173
+ tag1: nil,
174
+ tag2: nil,
175
+ date_format: "%Y-%m-%d",
176
+ output: "CHANGELOG.md",
177
+ base: "HISTORY.md",
178
+ issues: true,
179
+ add_issues_wo_labels: true,
180
+ add_pr_wo_labels: true,
181
+ pulls: true,
182
+ filter_issues_by_milestone: true,
183
+ author: true,
184
+ unreleased: true,
185
+ unreleased_label: "Unreleased",
186
+ compare_link: true,
187
+ enhancement_labels: %w(enhancement Enhancement),
188
+ bug_labels: %w(bug Bug),
189
+ exclude_labels: %w(duplicate question invalid wontfix Duplicate Question Invalid Wontfix),
190
+ max_issues: nil,
191
+ simple_list: false,
192
+ verbose: true,
193
+ header: "# Change Log",
194
+ merge_prefix: "**Merged pull requests:**",
195
+ issue_prefix: "**Closed issues:**",
196
+ bug_prefix: "**Fixed bugs:**",
197
+ enhancement_prefix: "**Implemented enhancements:**",
198
+ git_remote: "origin"
199
+ }
200
+ end
201
+
202
+ def self.user_and_project_from_git(options)
203
+ if options[:user].nil? || options[:project].nil?
204
+ detect_user_and_project(options, ARGV[0], ARGV[1])
205
+ end
206
+ end
207
+
208
+ # Detects user and project from git
209
+ def self.detect_user_and_project(options, arg0 = nil, arg1 = nil)
210
+ options[:user], options[:project] = user_project_from_option(arg0, arg1, options[:github_site])
211
+ return if options[:user] && options[:project]
212
+
213
+ if ENV["RUBYLIB"] =~ /ruby-debug-ide/
214
+ options[:user] = "skywinder"
215
+ options[:project] = "changelog_test"
216
+ else
217
+ remote = `git config --get remote.#{options[:git_remote]}.url`
218
+ options[:user], options[:project] = user_project_from_remote(remote)
219
+ end
220
+ end
221
+
222
+ # Try to find user and project name from git remote output
223
+ #
224
+ # @param [String] output of git remote command
225
+ # @return [Array] user and project
226
+ def self.user_project_from_option(arg0, arg1, github_site)
227
+ user = nil
228
+ project = nil
229
+ github_site ||= "github.com"
230
+ if arg0 && !arg1
231
+ # this match should parse strings such "https://github.com/skywinder/Github-Changelog-Generator" or "skywinder/Github-Changelog-Generator" to user and name
232
+ puts arg0
233
+ match = /(?:.+#{Regexp.escape(github_site)}\/)?(.+)\/(.+)/.match(arg0)
234
+
235
+ begin
236
+ param = match[2].nil?
237
+ rescue
238
+ puts "Can't detect user and name from first parameter: '#{arg0}' -> exit'"
239
+ return
240
+ end
241
+ if param
242
+ return
243
+ else
244
+ user = match[1]
245
+ project = match[2]
246
+ end
247
+ end
248
+ [user, project]
249
+ end
250
+
251
+ # Try to find user and project name from git remote output
252
+ #
253
+ # @param [String] output of git remote command
254
+ # @return [Array] user and project
255
+ def self.user_project_from_remote(remote)
256
+ # try to find repo in format:
257
+ # origin git@github.com:skywinder/Github-Changelog-Generator.git (fetch)
258
+ # git@github.com:skywinder/Github-Changelog-Generator.git
259
+ regex1 = /.*(?:[:\/])((?:-|\w|\.)*)\/((?:-|\w|\.)*)(?:\.git).*/
260
+
261
+ # try to find repo in format:
262
+ # origin https://github.com/skywinder/ChangelogMerger (fetch)
263
+ # https://github.com/skywinder/ChangelogMerger
264
+ regex2 = /.*\/((?:-|\w|\.)*)\/((?:-|\w|\.)*).*/
265
+
266
+ remote_structures = [regex1, regex2]
267
+
268
+ user = nil
269
+ project = nil
270
+ remote_structures.each do |regex|
271
+ matches = Regexp.new(regex).match(remote)
272
+
273
+ if matches && matches[1] && matches[2]
274
+ puts "Detected user:#{matches[1]}, project:#{matches[2]}"
275
+ user = matches[1]
276
+ project = matches[2]
277
+ end
278
+
279
+ break unless matches.nil?
280
+ end
281
+
282
+ [user, project]
283
+ end
284
+ end
285
+ end