Commit 65ad11b4 authored by Robert Speicher's avatar Robert Speicher

Merge branch '21949-add-type-to-changelog' into 'master'

Let's start labeling our CHANGELOG entries

Closes #21949

See merge request !11579
parents 130c369a eb2b895a
...@@ -14,54 +14,107 @@ Options = Struct.new( ...@@ -14,54 +14,107 @@ Options = Struct.new(
:dry_run, :dry_run,
:force, :force,
:merge_request, :merge_request,
:title :title,
:type
) )
INVALID_TYPE = -1
class ChangelogOptionParser class ChangelogOptionParser
def self.parse(argv) Type = Struct.new(:name, :description)
options = Options.new 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 = OptionParser.new do |opts| parser.parse!(argv)
opts.banner = "Usage: #{__FILE__} [options] [title]\n\n"
# Note: We do not provide a shorthand for this in order to match the `git # Title is everything that remains, but let's clean it up a bit
# commit` interface options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '')
opts.on('--amend', 'Amend the previous commit') do |value|
options.amend = value
end
opts.on('-f', '--force', 'Overwrite an existing entry') do |value| options
options.force = value end
end
opts.on('-m', '--merge-request [integer]', Integer, 'Merge Request ID') do |value| def read_type
options.merge_request = value read_type_message
end
opts.on('-n', '--dry-run', "Don't actually write anything, just print") do |value| type = TYPES[$stdin.getc.to_i - TYPES_OFFSET]
options.dry_run = value assert_valid_type!(type)
end
opts.on('-u', '--git-username', 'Use Git user.name configuration as the author') do |value| type.name
options.author = git_user_name if value end
end
private
opts.on('-h', '--help', 'Print help message') do def parse_type(name)
$stdout.puts opts type_found = TYPES.find do |type|
exit type.name == name
end end
type_found ? type_found.name : INVALID_TYPE
end end
parser.parse!(argv) def read_type_message
$stdout.puts "\n>> Please specify the index for the category of your change:"
# Title is everything that remains, but let's clean it up a bit TYPES.each_with_index do |type, index|
options.title = argv.join(' ').strip.squeeze(' ').tr("\r\n", '') $stdout.puts "#{index + TYPES_OFFSET}. #{type.description}"
end
$stdout.print "\n?> "
end
options def assert_valid_type!(type)
end unless type
$stderr.puts "Invalid category index, please select an index between 1 and #{TYPES.length}"
exit 1
end
end
def self.git_user_name def git_user_name
%x{git config user.name}.strip %x{git config user.name}.strip
end
end end
end end
...@@ -72,8 +125,12 @@ class ChangelogEntry ...@@ -72,8 +125,12 @@ class ChangelogEntry
@options = options @options = options
assert_feature_branch! assert_feature_branch!
assert_new_file!
assert_title! 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 "\e[32mcreate\e[0m #{file_path}"
$stdout.puts contents $stdout.puts contents
...@@ -90,7 +147,8 @@ class ChangelogEntry ...@@ -90,7 +147,8 @@ class ChangelogEntry
yaml_content = YAML.dump( yaml_content = YAML.dump(
'title' => title, 'title' => title,
'merge_request' => options.merge_request, 'merge_request' => options.merge_request,
'author' => options.author 'author' => options.author,
'type' => options.type
) )
remove_trailing_whitespace(yaml_content) remove_trailing_whitespace(yaml_content)
end end
...@@ -129,6 +187,12 @@ class ChangelogEntry ...@@ -129,6 +187,12 @@ class ChangelogEntry
" to use the title from the previous commit." " to use the title from the previous commit."
end end
def assert_valid_type!
return unless options.type && options.type == INVALID_TYPE
fail_with 'Invalid category given!'
end
def title def title
if options.title.empty? if options.title.empty?
last_commit_subject last_commit_subject
......
---
title: Added type to CHANGELOG entries
merge_request:
author: Jacopo Beschi @jacopo-beschi
...@@ -15,11 +15,14 @@ following format: ...@@ -15,11 +15,14 @@ following format:
title: "Going through change[log]s" title: "Going through change[log]s"
merge_request: 1972 merge_request: 1972
author: Ozzy Osbourne author: Ozzy Osbourne
type: added
``` ```
The `merge_request` value is a reference to a merge request that adds this The `merge_request` value is a reference to a merge request that adds this
entry, and the `author` key is used to give attribution to community entry, and the `author` key is used to give attribution to community
contributors. **Both are optional**. contributors. **Both are optional**.
The `type` field maps the category of the change,
valid options are: added, fixed, changed, deprecated, removed, security, other. **Type field is mandatory**.
Community contributors and core team members are encouraged to add their name to Community contributors and core team members are encouraged to add their name to
the `author` field. GitLab team members **should not**. the `author` field. GitLab team members **should not**.
...@@ -94,6 +97,19 @@ Its simplest usage is to provide the value for `title`: ...@@ -94,6 +97,19 @@ Its simplest usage is to provide the value for `title`:
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' $ bin/changelog 'Hey DZ, I added a feature to GitLab!'
``` ```
At this point the script would ask you to select the category of the change (mapped to the `type` field in the entry):
```text
>> Please specify the category of your change:
1. New feature
2. Bug fix
3. Feature change
4. New deprecation
5. Feature removal
6. Security fix
7. Other
```
The entry filename is based on the name of the current Git branch. If you run The entry filename is based on the name of the current Git branch. If you run
the command above on a branch called `feature/hey-dz`, it will generate a the command above on a branch called `feature/hey-dz`, it will generate a
`changelogs/unreleased/feature-hey-dz.yml` file. `changelogs/unreleased/feature-hey-dz.yml` file.
...@@ -106,26 +122,29 @@ create changelogs/unreleased/my-feature.yml ...@@ -106,26 +122,29 @@ create changelogs/unreleased/my-feature.yml
title: Hey DZ, I added a feature to GitLab! title: Hey DZ, I added a feature to GitLab!
merge_request: merge_request:
author: author:
type:
``` ```
If you're working on the GitLab EE repository, the entry will be added to If you're working on the GitLab EE repository, the entry will be added to
`changelogs/unreleased-ee/` instead. `changelogs/unreleased-ee/` instead.
#### Arguments #### Arguments
| Argument | Shorthand | Purpose | | Argument | Shorthand | Purpose |
| ----------------- | --------- | --------------------------------------------- | | ----------------- | --------- | ---------------------------------------------------------------------------------------------------------- |
| [`--amend`] | | Amend the previous commit | | [`--amend`] | | Amend the previous commit |
| [`--force`] | `-f` | Overwrite an existing entry | | [`--force`] | `-f` | Overwrite an existing entry |
| [`--merge-request`] | `-m` | Set merge request ID | | [`--merge-request`] | `-m` | Set merge request ID |
| [`--dry-run`] | `-n` | Don't actually write anything, just print | | [`--dry-run`] | `-n` | Don't actually write anything, just print |
| [`--git-username`] | `-u` | Use Git user.name configuration as the author | | [`--git-username`] | `-u` | Use Git user.name configuration as the author |
| [`--help`] | `-h` | Print help message | | [`--type`] | `-t` | The category of the change, valid options are: added, fixed, changed, deprecated, removed, security, other |
| [`--help`] | `-h` | Print help message |
[`--amend`]: #-amend [`--amend`]: #-amend
[`--force`]: #-force-or-f [`--force`]: #-force-or-f
[`--merge-request`]: #-merge-request-or-m [`--merge-request`]: #-merge-request-or-m
[`--dry-run`]: #-dry-run-or-n [`--dry-run`]: #-dry-run-or-n
[`--git-username`]: #-git-username-or-u [`--git-username`]: #-git-username-or-u
[`--type`]: #-type-or-t
[`--help`]: #-help [`--help`]: #-help
##### `--amend` ##### `--amend`
...@@ -147,6 +166,7 @@ create changelogs/unreleased/feature-hey-dz.yml ...@@ -147,6 +166,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab title: Added an awesome new feature to GitLab
merge_request: merge_request:
author: author:
type:
``` ```
##### `--force` or `-f` ##### `--force` or `-f`
...@@ -164,6 +184,7 @@ create changelogs/unreleased/feature-hey-dz.yml ...@@ -164,6 +184,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab! title: Hey DZ, I added a feature to GitLab!
merge_request: 1983 merge_request: 1983
author: author:
type:
``` ```
##### `--merge-request` or `-m` ##### `--merge-request` or `-m`
...@@ -178,6 +199,7 @@ create changelogs/unreleased/feature-hey-dz.yml ...@@ -178,6 +199,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab! title: Hey DZ, I added a feature to GitLab!
merge_request: 1983 merge_request: 1983
author: author:
type:
``` ```
##### `--dry-run` or `-n` ##### `--dry-run` or `-n`
...@@ -192,6 +214,7 @@ create changelogs/unreleased/feature-hey-dz.yml ...@@ -192,6 +214,7 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Added an awesome new feature to GitLab title: Added an awesome new feature to GitLab
merge_request: merge_request:
author: author:
type:
$ ls changelogs/unreleased/ $ ls changelogs/unreleased/
``` ```
...@@ -211,6 +234,21 @@ create changelogs/unreleased/feature-hey-dz.yml ...@@ -211,6 +234,21 @@ create changelogs/unreleased/feature-hey-dz.yml
title: Hey DZ, I added a feature to GitLab! title: Hey DZ, I added a feature to GitLab!
merge_request: merge_request:
author: Jane Doe author: Jane Doe
type:
```
##### `--type` or `-t`
Use the **`--type`** or **`-t`** argument to provide the `type` value:
```text
$ bin/changelog 'Hey DZ, I added a feature to GitLab!' -t added
create changelogs/unreleased/feature-hey-dz.yml
---
title: Hey DZ, I added a feature to GitLab!
merge_request:
author:
type: added
``` ```
### History and Reasoning ### History and Reasoning
......
...@@ -4,56 +4,90 @@ load File.expand_path('../../bin/changelog', __dir__) ...@@ -4,56 +4,90 @@ load File.expand_path('../../bin/changelog', __dir__)
describe 'bin/changelog' do describe 'bin/changelog' do
describe ChangelogOptionParser do describe ChangelogOptionParser do
it 'parses --ammend' do describe '.parse' do
options = described_class.parse(%w[foo bar --amend]) it 'parses --amend' do
options = described_class.parse(%w[foo bar --amend])
expect(options.amend).to eq true expect(options.amend).to eq true
end end
it 'parses --force and -f' do it 'parses --force and -f' do
%w[--force -f].each do |flag| %w[--force -f].each do |flag|
options = described_class.parse(%W[foo #{flag} bar]) options = described_class.parse(%W[foo #{flag} bar])
expect(options.force).to eq true expect(options.force).to eq true
end
end end
end
it 'parses --merge-request and -m' do it 'parses --merge-request and -m' do
%w[--merge-request -m].each do |flag| %w[--merge-request -m].each do |flag|
options = described_class.parse(%W[foo #{flag} 1234 bar]) options = described_class.parse(%W[foo #{flag} 1234 bar])
expect(options.merge_request).to eq 1234 expect(options.merge_request).to eq 1234
end
end end
end
it 'parses --dry-run and -n' do it 'parses --dry-run and -n' do
%w[--dry-run -n].each do |flag| %w[--dry-run -n].each do |flag|
options = described_class.parse(%W[foo #{flag} bar]) options = described_class.parse(%W[foo #{flag} bar])
expect(options.dry_run).to eq true expect(options.dry_run).to eq true
end
end end
end
it 'parses --git-username and -u' do it 'parses --git-username and -u' do
allow(described_class).to receive(:git_user_name).and_return('Jane Doe') allow(described_class).to receive(:git_user_name).and_return('Jane Doe')
%w[--git-username -u].each do |flag| %w[--git-username -u].each do |flag|
options = described_class.parse(%W[foo #{flag} bar]) options = described_class.parse(%W[foo #{flag} bar])
expect(options.author).to eq 'Jane Doe' expect(options.author).to eq 'Jane Doe'
end
end
it 'parses --type and -t' do
%w[--type -t].each do |flag|
options = described_class.parse(%W[foo #{flag} security])
expect(options.type).to eq 'security'
end
end end
end
it 'parses -h' do it 'parses -h' do
expect do expect do
expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout expect { described_class.parse(%w[foo -h bar]) }.to output.to_stdout
end.to raise_error(SystemExit) end.to raise_error(SystemExit)
end
it 'assigns title' do
options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend])
expect(options.title).to eq 'foo bar baz'
end
end end
it 'assigns title' do describe '.read_type' do
options = described_class.parse(%W[foo -m 1 bar\n -u baz\r\n --amend]) let(:type) { '1' }
expect(options.title).to eq 'foo bar baz' it 'reads type from $stdin' do
expect($stdin).to receive(:getc).and_return(type)
expect do
expect(described_class.read_type).to eq('added')
end.to output.to_stdout
end
context 'invalid type given' do
let(:type) { '99' }
it 'shows error message and exits the program' do
allow($stdin).to receive(:getc).and_return(type)
expect do
expect do
expect{ described_class.read_type }.to raise_error(SystemExit)
end.to output("Invalid category index, please select an index between 1 and 7\n").to_stderr
end.to output.to_stdout
end
end
end end
end end
end end
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