Skip to content
Snippets Groups Projects
Commit 878a333f authored by DJ Mountney's avatar DJ Mountney
Browse files

Merge branch 'changelog-workflow' into 'master'

move to the commit based changelog workflow

See merge request gitlab-org/omnibus-gitlab!5150
parents 99a1373d 1c240457
No related branches found
No related tags found
3 merge requests!51Validate shift of Heptapod 0.25 to oldstable series,!40Heptapod 0.22 is the new stable,!36GitLab 13.11
Showing
with 56 additions and 421 deletions
---
title: Provide packages for openSUSE Leap 15.2
merge_request: 5134
author:
type: added
---
title: Backport BLOCKED_MODULE performance fix from Redis unstable
merge_request: 5125
author:
type: performance
---
title: Bump Container Registry to v3.2.1-gitlab
merge_request: 5111
author:
type: changed
---
title: Bump Container Registry to v3.2.0-gitlab
merge_request: 5097
author:
type: changed
---
title: Update GraphicsMagick to 1.3.36
merge_request: 5075
author: Takuya Noguchi
type: security
---
title: Render Gitaly pack_objects_cache settings
merge_request: 5120
author:
type: added
---
title: Create self-signed SSL key following gitlab.rb
merge_request: 5083
author: Kenneth Chu @kenneth
type: fixed
---
title: Upgrade OpenSSL to 1.1.1k
merge_request: 5140
author:
type: changed
---
title: Fix use_http2 & redirect_http GitLab Pages settings
merge_request: 5116
author: Ben Bodenmiller (@bbodenmiller)
type: fixed
---
title: Use git provided by Gitaly
merge_request: 5113
author:
type: changed
---
title: Gitaly default maintenance override
merge_request: 5089
author:
type: added
---
title: Add mail_room as a separate Gem dependency
merge_request: 5122
author:
type: changed
---
title: Add net-protocol BSD-2 license
merge_request: 5118
author:
type: added
---
title: Update Gitaly log permissions in Docker image
merge_request: 5117
author:
type: fixed
---
title: Disable statement timeout while running analyze for pg-upgrade
merge_request: 5121
author:
type: changed
#!/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
module ChangelogHelpers
Abort = Class.new(StandardError)
Done = Class.new(StandardError)
MAX_FILENAME_LENGTH = 140 # ecryptfs has a limit of 140 characters
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
class ChangelogOptionParser
extend ChangelogHelpers
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
raise Done.new
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
raise Abort, "Invalid category index, please select an index between 1 and #{TYPES.length}"
end
end
def git_user_name
capture_stdout(%w[git config user.name]).strip
end
end
end
class ChangelogEntry
include ChangelogHelpers
attr_reader :options
def initialize(options)
@options = options
end
def execute
assert_feature_branch!
assert_title! unless editor
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
if editor
system("#{editor} '#{file_path}'")
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 editor
ENV['EDITOR']
end
def amend_commit
fail_with "git add failed" unless system(*%W[git add #{file_path}])
Kernel.exec(*%w[git commit --amend])
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
capture_stdout(%w[git log --format=%s -1]).strip
end
def file_path
base_path = File.join(
unreleased_path,
branch_name.gsub(/[^\w-]/, '-'))
# Add padding for .yml extension
base_path[0..MAX_FILENAME_LENGTH - 5] + '.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 ||= capture_stdout(%w[git symbolic-ref --short HEAD]).strip
end
def remove_trailing_whitespace(yaml_content)
yaml_content.gsub(/ +$/, '')
end
end
if $0 == __FILE__
begin
options = ChangelogOptionParser.parse(ARGV)
ChangelogEntry.new(options).execute
rescue ChangelogHelpers::Abort => ex
$stderr.puts ex.message
exit 1
rescue ChangelogHelpers::Done
exit
end
end
# vim: ft=ruby
......@@ -2,6 +2,26 @@
require 'yaml'
def lint_commit(commit)
trailer = commit.message.match(/^Changelog:\s*(?<category>\w+)/)
return :missing if trailer.nil? || trailer[:category].nil?
category = trailer[:category]
return :valid if CATEGORIES.include?(category)
self.fail(
"Commit #{commit.sha} uses an invalid changelog category: #{category}"
)
:invalid
end
def presented_no_changelog_labels
NO_CHANGELOG_LABELS.map { |label| %(~"#{label}") }.join(', ')
end
NO_CHANGELOG_LABELS = [
'documentation',
'tooling',
......@@ -11,4 +31,10 @@
'meta'
].freeze
CATEGORIES = YAML
.load_file(File.expand_path('../../../.gitlab/changelog_config.yml', __dir__))
.fetch('categories')
.keys
.freeze
SEE_DOC = "See [the documentation](https://docs.gitlab.com/ee/development/changelog.html).".freeze
......@@ -14,5 +40,17 @@
SEE_DOC = "See [the documentation](https://docs.gitlab.com/ee/development/changelog.html).".freeze
CREATE_CHANGELOG_MESSAGE = <<~MSG.freeze
You can create one with:
CHANGELOG_MISSING = <<~MSG.freeze
**[CHANGELOG missing](https://docs.gitlab.com/ee/development/changelog.html).**
To ceate a changelog, annotate one or more commits with the `Changelog` Git
trailer. If you want to annotate the latest commit, you can do so using `git
commit --amend`. If you want to annotate older or multiple commits, you need to
do so using `git rebase -i`.
When adding the trailer, you can use the following values:
- #{CATEGORIES.join("\n- ")}
For example:
```
......@@ -17,5 +55,9 @@
```
scripts/changelog -m %<mr_iid>s "%<mr_title>s"
This is the subject of your commit.
This would be the body of your commit containing some extra details.
Changelog: added
```
......@@ -20,7 +62,8 @@
```
If your merge request doesn't warrant a CHANGELOG entry,
consider adding any of the %<labels>s labels.
If your merge request doesn't warrant a CHANGELOG entry, consider adding any of
the #{presented_no_changelog_labels} labels.
#{SEE_DOC}
MSG
......@@ -24,38 +67,4 @@
#{SEE_DOC}
MSG
def ee?
ENV['CI_PROJECT_NAME'] == 'gitlab-ee' || File.exist?('../../CHANGELOG-EE.md')
end
def ee_changelog?(changelog_path)
changelog_path =~ /unreleased-ee/
end
def ce_port_changelog?(changelog_path)
ee? && !ee_changelog?(changelog_path)
end
def check_changelog(path)
yaml = YAML.safe_load(File.read(path))
fail "`title` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["title"].nil?
fail "`type` should be set, in #{gitlab.html_link(path)}! #{SEE_DOC}" if yaml["type"].nil?
if yaml["merge_request"].nil?
message "Consider setting `merge_request` to #{gitlab.mr_json["iid"]} in #{gitlab.html_link(path)}. #{SEE_DOC}"
elsif yaml["merge_request"] != gitlab.mr_json["iid"] && !ce_port_changelog?(path)
fail "Merge request ID was not set to #{gitlab.mr_json["iid"]}! #{SEE_DOC}"
end
rescue Psych::SyntaxError, Psych::DisallowedClass, Psych::BadAlias
# YAML could not be parsed, fail the build.
fail "#{gitlab.html_link(path)} isn't valid YAML! #{SEE_DOC}"
rescue StandardError => e
warn "There was a problem trying to check the Changelog. Exception: #{e.name} - #{e.message}"
end
def presented_no_changelog_labels
NO_CHANGELOG_LABELS.map { |label| %(~"#{label}") }.join(', ')
end
changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
......@@ -61,11 +70,3 @@
changelog_needed = (gitlab.mr_labels & NO_CHANGELOG_LABELS).empty?
changelog_found = git.added_files.find { |path| path =~ %r{\A(ee/)?(changelogs/unreleased)(-ee)?/} }
mr_title = gitlab.mr_json["title"].gsub(/^WIP: */, '')
if git.modified_files.include?("CHANGELOG.md")
fail "**CHANGELOG.md was edited.** Please remove the additions and create a CHANGELOG entry.\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
end
if changelog_needed
......@@ -70,9 +71,11 @@
if changelog_needed
if changelog_found
check_changelog(changelog_found)
else
warn "**[CHANGELOG missing](https://docs.gitlab.com/ee/development/changelog.html).**\n\n" +
format(CREATE_CHANGELOG_MESSAGE, mr_iid: gitlab.mr_json["iid"], mr_title: mr_title, labels: presented_no_changelog_labels)
checked = 0
git.commits.each do |commit|
case lint_commit(commit)
when :valid, :invalid
checked += 1
end
end
......@@ -77,21 +80,4 @@
end
message(<<~MSG)
We are currently testing a new approach for generating changelogs. If one or
more of your commits warrant a changelog entry, please add the appropriate
`Changelog:` Git trailer to these commits. For example:
```
Add a new awesome feature
Here are the details about the feature being added.
Changelog: added
```
For more information, please take look at [this
issue](https://gitlab.com/gitlab-com/gl-infra/delivery/-/issues/1551).
Note: If you have created a changelog yaml file already, please do not delete it. In the first iteration of this approach, we are using both the yaml file and Git trailer.
MSG
warn(CHANGELOG_MISSING) if checked.zero?
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment