Commit 3a18588e authored by Jacob Vosmaer's avatar Jacob Vosmaer Committed by Nick Thomas

Merge branch 'changelog-generator' into 'master'

Add automatic changelog generation

See merge request gitlab-org/gitlab-workhorse!484
parent 45c84794
# Changelog for gitlab-workhorse
Formerly known as 'gitlab-git-http-server'.
v 8.30.2
- Limit memory footprint of a command that generates ZIP artifacts metadata
......
......@@ -81,15 +81,19 @@ clean-workhorse:
$(call message,$@)
rm -f $(EXE_ALL)
.PHONY: check-version
check-version:
@test -n "$(VERSION)" || (echo "VERSION not set." ; exit 1)
.PHONY: tag
tag:
tag: check-version
$(call message,$@)
sh _support/tag.sh
sh _support/tag.sh "$(VERSION)"
.PHONY: signed_tag
signed_tag:
signed_tag: check-version
$(call message,$@)
TAG_OPTS=-s sh _support/tag.sh
TAG_OPTS=-s sh _support/tag.sh "$(VERSION)"
.PHONY: clean-build
clean-build:
......
......@@ -22,13 +22,9 @@ maintainers. The release process is:
- pick a release branch. For x.y.0, use `master`. For all other
versions (x.y.1, x.y.2 etc.) , use `x-y-stable`. Also see [below](#versioning)
- create a merge request to update CHANGELOG and VERSION on the
release branch
- You can use `git log --first-parent <last-version>..master` to see the list of changes
- merge the merge request
- run `make tag` or `make signed_tag` on the release branch. This will
make a tag matching the VERSION file.
- push the tag to gitlab.com
- run `make tag VERSION=x.y.z"` or `make signed_tag VERSION=x.y.z` on the release branch. This will
compile the changelog, bump the VERSION file, and make a tag matching it.
- push the branch and the tag to gitlab.com
## Versioning
......
#!/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.
#
# Stolen from gitlab-org/gitaly, lifted from gitlab-org/gitlab-ce
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('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
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
path = File.join('changelogs', 'unreleased')
path = File.join('ee', path) if ee?
path
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
#!/usr/bin/env ruby
# Generates the changelog from the yaml entries in changelogs/unreleased
#
# Lifted form gitlab-org/gitaly
require 'yaml'
require 'fileutils'
class ChangelogEntry
attr_reader :title, :merge_request, :type, :author
def initialize(file_path)
yaml = YAML.safe_load(File.read(file_path))
@title = yaml['title']
@merge_request = yaml['merge_request']
@type = yaml['type']
@author = yaml['author']
end
def to_s
str = ""
str << "- #{title}\n"
str << " https://gitlab.com/gitlab-org/gitlab-workhorse/-/merge_requests/#{merge_request}\n"
str << " Contributed by #{author}\n" if author
str
end
end
ROOT_DIR = File.expand_path('../..', __FILE__)
UNRELEASED_ENTRIES = File.join(ROOT_DIR, 'changelogs', 'unreleased')
CHANGELOG_FILE = File.join(ROOT_DIR, 'CHANGELOG')
def main(version)
entries = []
Dir["#{UNRELEASED_ENTRIES}/*.yml"].each do |yml|
entries << ChangelogEntry.new(yml)
FileUtils.rm(yml)
end
sections = []
types = entries.map(&:type).uniq.sort
types.each do |type|
text = ''
text << "### #{type.capitalize}\n"
entries.each do |e|
next unless e.type == type
text << e.to_s
end
sections << text
end
sections << '- No changes.' if sections.empty?
new_version_entry = ["## v#{version}\n\n", sections.join("\n"), "\n"].join
current_changelog = File.read(CHANGELOG_FILE).lines
header = current_changelog.shift(2)
new_changelog = [header, new_version_entry, current_changelog.join]
File.write(CHANGELOG_FILE, new_changelog.join)
end
unless ARGV.count == 1
warn "Usage: #{$0} VERSION"
warn "Specify version as x.y.z"
abort
end
main(ARGV.first)
set -e
main() {
get_version
version=$1
set_version
changelog
git commit VERSION -m "Update VERSION to $version"
tag_name="v${version}"
git tag $TAG_OPTS -m "Version ${version}" -a ${tag_name}
git show ${tag_name}
......@@ -12,13 +18,28 @@ main() {
EOF
}
get_version() {
v=$(sed 1q VERSION)
if ! echo "${v}" | grep -q '^[0-9]\+\.[0-9]\+\.[0-9]\+$' ; then
echo "Invalid VERSION: ${v}"
set_version() {
if ! echo "${version}" | grep -q '^[0-9]\+\.[0-9]\+\.[0-9]\+$' ; then
echo "Invalid VERSION: ${version}"
exit 1
fi
if git tag --list | grep -q "^v${version}$" ; then
echo "Tag already exists for ${version}"
exit 1
fi
version="${v}"
echo "$version" > VERSION
}
changelog() {
_support/generate_changelog "$version"
git commit CHANGELOG changelogs/unreleased --file - <<EOF
Update CHANGELOG for ${version}
[ci skip]
EOF
}
main
\ No newline at end of file
main "$@"
---
title: Add automatic changelog generation
merge_request: 484
author:
type: other
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment