eb2b895a20
Added the type attribute to a CHANGELOG entry. When you create a new entry the software asks for the category of the change and sets the associated type in the file.
239 lines
5.3 KiB
Ruby
Executable file
239 lines
5.3 KiB
Ruby
Executable file
#!/usr/bin/env ruby
|
|
#
|
|
# Generate a changelog entry file in the correct location.
|
|
#
|
|
# Automatically stages the file and amends the previous commit if the `--amend`
|
|
# argument is used.
|
|
|
|
require 'optparse'
|
|
require 'yaml'
|
|
|
|
Options = Struct.new(
|
|
:amend,
|
|
:author,
|
|
:dry_run,
|
|
:force,
|
|
:merge_request,
|
|
:title,
|
|
:type
|
|
)
|
|
INVALID_TYPE = -1
|
|
|
|
class ChangelogOptionParser
|
|
Type = Struct.new(:name, :description)
|
|
TYPES = [
|
|
Type.new('added', 'New feature'),
|
|
Type.new('fixed', 'Bug fix'),
|
|
Type.new('changed', 'Feature change'),
|
|
Type.new('deprecated', 'New deprecation'),
|
|
Type.new('removed', 'Feature removal'),
|
|
Type.new('security', 'Security fix'),
|
|
Type.new('other', 'Other')
|
|
].freeze
|
|
TYPES_OFFSET = 1
|
|
|
|
class << self
|
|
def parse(argv)
|
|
options = Options.new
|
|
|
|
parser = OptionParser.new do |opts|
|
|
opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
|
|
|
|
# Note: We do not provide a shorthand for this in order to match the `git
|
|
# commit` interface
|
|
opts.on('--amend', 'Amend the previous commit') do |value|
|
|
options.amend = value
|
|
end
|
|
|
|
opts.on('-f', '--force', 'Overwrite an existing entry') do |value|
|
|
options.force = value
|
|
end
|
|
|
|
opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value|
|
|
options.merge_request = value
|
|
end
|
|
|
|
opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value|
|
|
options.dry_run = value
|
|
end
|
|
|
|
opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value|
|
|
options.author = git_user_name if value
|
|
end
|
|
|
|
opts.on('-t', '--type [string]', String, "The category of the change, valid options are: #{TYPES.map(&:name).join(', ')}") do |value|
|
|
options.type = parse_type(value)
|
|
end
|
|
|
|
opts.on('-h', '--help', 'Print help message') do
|
|
$stdout.puts opts
|
|
exit
|
|
end
|
|
end
|
|
|
|
parser.parse!(argv)
|
|
|
|
# Title is everything that remains, but let's clean it up a bit
|
|
options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
|
|
|
|
options
|
|
end
|
|
|
|
def read_type
|
|
read_type_message
|
|
|
|
type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
|
|
assert_valid_type!(type)
|
|
|
|
type.name
|
|
end
|
|
|
|
private
|
|
|
|
def parse_type(name)
|
|
type_found = TYPES.find do |type|
|
|
type.name == name
|
|
end
|
|
type_found ? type_found.name : INVALID_TYPE
|
|
end
|
|
|
|
def read_type_message
|
|
$stdout.puts "\n>> Please specify the index for the category of your change:"
|
|
TYPES.each_with_index do |type, index|
|
|
$stdout.puts "#{index + TYPES_OFFSET}. #{type.description}"
|
|
end
|
|
$stdout.print "\n?> "
|
|
end
|
|
|
|
def assert_valid_type!(type)
|
|
unless type
|
|
$stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
|
|
exit 1
|
|
end
|
|
end
|
|
|
|
def git_user_name
|
|
%x{git config user.name}.strip
|
|
end
|
|
end
|
|
end
|
|
|
|
class ChangelogEntry
|
|
attr_reader :options
|
|
|
|
def initialize(options)
|
|
@options = options
|
|
|
|
assert_feature_branch!
|
|
assert_title!
|
|
assert_new_file!
|
|
|
|
# Read type from $stdin unless is already set
|
|
options.type ||= ChangelogOptionParser.read_type
|
|
assert_valid_type!
|
|
|
|
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
|
|
$stdout.puts contents
|
|
|
|
unless options.dry_run
|
|
write
|
|
amend_commit if options.amend
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def contents
|
|
yaml_content = YAML.dump(
|
|
'title' => title,
|
|
'merge_request' => options.merge_request,
|
|
'author' => options.author,
|
|
'type' => options.type
|
|
)
|
|
remove_trailing_whitespace(yaml_content)
|
|
end
|
|
|
|
def write
|
|
File.write(file_path, contents)
|
|
end
|
|
|
|
def amend_commit
|
|
%x{git add #{file_path}}
|
|
exec("git commit --amend")
|
|
end
|
|
|
|
def fail_with(message)
|
|
$stderr.puts "\e[31merror\e[0m #{message}"
|
|
exit 1
|
|
end
|
|
|
|
def assert_feature_branch!
|
|
return unless branch_name == 'master'
|
|
|
|
fail_with "Create a branch first!"
|
|
end
|
|
|
|
def assert_new_file!
|
|
return unless File.exist?(file_path)
|
|
return if options.force
|
|
|
|
fail_with "#{file_path} already exists! Use `--force` to overwrite."
|
|
end
|
|
|
|
def assert_title!
|
|
return if options.title.length > 0 || options.amend
|
|
|
|
fail_with "Provide a title for the changelog entry or use `--amend`" \
|
|
" to use the title from the previous commit."
|
|
end
|
|
|
|
def assert_valid_type!
|
|
return unless options.type && options.type == INVALID_TYPE
|
|
|
|
fail_with 'Invalid category given!'
|
|
end
|
|
|
|
def title
|
|
if options.title.empty?
|
|
last_commit_subject
|
|
else
|
|
options.title
|
|
end
|
|
end
|
|
|
|
def last_commit_subject
|
|
%x{git log --format="%s" -1}.strip
|
|
end
|
|
|
|
def file_path
|
|
File.join(
|
|
unreleased_path,
|
|
branch_name.gsub(/[^\w-]/, '-') << '.yml'
|
|
)
|
|
end
|
|
|
|
def unreleased_path
|
|
File.join('changelogs', 'unreleased').tap do |path|
|
|
path << '-ee' if ee?
|
|
end
|
|
end
|
|
|
|
def ee?
|
|
@ee ||= File.exist?(File.expand_path('../CHANGELOG-EE.md', __dir__))
|
|
end
|
|
|
|
def branch_name
|
|
@branch_name ||= %x{git symbolic-ref --short HEAD}.strip
|
|
end
|
|
|
|
def remove_trailing_whitespace(yaml_content)
|
|
yaml_content.gsub(/ +$/, '')
|
|
end
|
|
end
|
|
|
|
if $0 == __FILE__
|
|
options = ChangelogOptionParser.parse(ARGV)
|
|
ChangelogEntry.new(options)
|
|
end
|
|
|
|
# vim: ft=ruby
|