debian-mirror-gitlab/bin/changelog

277 lines
6.2 KiB
Text
Raw Normal View History

2017-08-17 22:00:37 +05:30
#!/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,
2018-03-17 18:26:18 +05:30
:title,
:type
2017-08-17 22:00:37 +05:30
)
2018-03-17 18:26:18 +05:30
INVALID_TYPE = -1
2017-08-17 22:00:37 +05:30
2018-11-08 19:23:39 +05:30
module ChangelogHelpers
Abort = Class.new(StandardError)
Done = Class.new(StandardError)
2019-10-12 21:52:04 +05:30
MAX_FILENAME_LENGTH = 99 # GNU tar has a 99 character limit
2018-11-18 11:00:15 +05:30
2018-11-08 19:23:39 +05:30
def capture_stdout(cmd)
output = IO.popen(cmd, &:read)
fail_with "command failed: #{cmd.join(' ')}" unless $?.success?
output
end
def fail_with(message)
raise Abort, "\e[31merror\e[0m #{message}"
end
end
2017-08-17 22:00:37 +05:30
class ChangelogOptionParser
2018-11-08 19:23:39 +05:30
extend ChangelogHelpers
2018-03-17 18:26:18 +05:30
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('performance', 'Performance improvement'),
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
2018-11-08 19:23:39 +05:30
raise Done.new
2018-03-17 18:26:18 +05:30
end
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
parser.parse!(argv)
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
# Title is everything that remains, but let's clean it up a bit
options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
options
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
def read_type
read_type_message
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
assert_valid_type!(type)
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
type.name
end
private
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
def parse_type(name)
type_found = TYPES.find do |type|
type.name == name
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
type_found ? type_found.name : INVALID_TYPE
2017-08-17 22:00:37 +05:30
end
2018-03-17 18:26:18 +05:30
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
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
def assert_valid_type!(type)
unless type
2018-11-08 19:23:39 +05:30
raise Abort, "Invalid category index, please select an index between 1 and #{TYPES.length}"
2018-03-17 18:26:18 +05:30
end
end
2017-08-17 22:00:37 +05:30
2018-03-17 18:26:18 +05:30
def git_user_name
2018-11-08 19:23:39 +05:30
capture_stdout(%w[git config user.name]).strip
2018-03-17 18:26:18 +05:30
end
2017-08-17 22:00:37 +05:30
end
end
class ChangelogEntry
2018-11-08 19:23:39 +05:30
include ChangelogHelpers
2017-08-17 22:00:37 +05:30
attr_reader :options
def initialize(options)
@options = options
2018-11-18 11:00:15 +05:30
end
2017-08-17 22:00:37 +05:30
2018-11-18 11:00:15 +05:30
def execute
2017-08-17 22:00:37 +05:30
assert_feature_branch!
2019-03-02 22:35:43 +05:30
assert_title! unless editor
2018-03-17 18:26:18 +05:30
assert_new_file!
# Read type from $stdin unless is already set
options.type ||= ChangelogOptionParser.read_type
assert_valid_type!
2017-08-17 22:00:37 +05:30
$stdout.puts "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents
unless options.dry_run
write
amend_commit if options.amend
end
2019-03-02 22:35:43 +05:30
if editor
system("#{editor} '#{file_path}'")
end
2017-08-17 22:00:37 +05:30
end
private
def contents
yaml_content = YAML.dump(
'title' => title,
'merge_request' => options.merge_request,
2018-03-17 18:26:18 +05:30
'author' => options.author,
'type' => options.type
2017-08-17 22:00:37 +05:30
)
remove_trailing_whitespace(yaml_content)
end
def write
File.write(file_path, contents)
end
2019-03-02 22:35:43 +05:30
def editor
ENV['EDITOR']
end
2017-08-17 22:00:37 +05:30
def amend_commit
2018-11-08 19:23:39 +05:30
fail_with "git add failed" unless system(*%W[git add #{file_path}])
2017-08-17 22:00:37 +05:30
2018-11-08 19:23:39 +05:30
Kernel.exec(*%w[git commit --amend])
2017-08-17 22:00:37 +05:30
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
2018-03-17 18:26:18 +05:30
def assert_valid_type!
return unless options.type && options.type == INVALID_TYPE
fail_with 'Invalid category given!'
end
2017-08-17 22:00:37 +05:30
def title
if options.title.empty?
last_commit_subject
else
options.title
end
end
def last_commit_subject
2018-11-08 19:23:39 +05:30
capture_stdout(%w[git log --format=%s -1]).strip
2017-08-17 22:00:37 +05:30
end
def file_path
2018-11-18 11:00:15 +05:30
base_path = File.join(
2017-08-17 22:00:37 +05:30
unreleased_path,
2018-11-18 11:00:15 +05:30
branch_name.gsub(/[^\w-]/, '-'))
# Add padding for .yml extension
base_path[0..MAX_FILENAME_LENGTH - 5] + '.yml'
2017-08-17 22:00:37 +05:30
end
def unreleased_path
2018-03-27 19:54:05 +05:30
path = File.join('changelogs', 'unreleased')
path = File.join('ee', path) if ee?
path
2017-08-17 22:00:37 +05:30
end
def ee?
@ee ||= File.exist?(File.expand_path('../CHANGELOG-EE.md', __dir__))
end
def branch_name
2018-11-08 19:23:39 +05:30
@branch_name ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
2017-08-17 22:00:37 +05:30
end
def remove_trailing_whitespace(yaml_content)
yaml_content.gsub(/ +$/, '')
end
end
if $0 == __FILE__
2018-11-08 19:23:39 +05:30
begin
options = ChangelogOptionParser.parse(ARGV)
2018-11-18 11:00:15 +05:30
ChangelogEntry.new(options).execute
2018-11-08 19:23:39 +05:30
rescue ChangelogHelpers::Abort => ex
$stderr.puts ex.message
exit 1
rescue ChangelogHelpers::Done
exit
end
2017-08-17 22:00:37 +05:30
end
# vim: ft=ruby