gfsm 0.1.2
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.editorconfig +12 -0
- data/.gitignore +0 -0
- data/.gitlab-ci.yml +75 -0
- data/.tool-versions +1 -0
- data/.vscode/launch.json +15 -0
- data/Gemfile +7 -0
- data/Gemfile.lock +20 -0
- data/README.md +63 -0
- data/bin/gfsm +4 -0
- data/gfsm.gemspec +29 -0
- data/gfsmrc.yml +41 -0
- data/lib/commands/base_command.rb +18 -0
- data/lib/commands/changelog.rb +133 -0
- data/lib/commands/help.rb +32 -0
- data/lib/commands/version.rb +81 -0
- data/lib/data/change_type.rb +54 -0
- data/lib/data/commit.rb +30 -0
- data/lib/data/configuration.rb +48 -0
- data/lib/data/version.rb +33 -0
- data/lib/gfsm.rb +44 -0
- data/lib/tools/commits_extractor.rb +31 -0
- data/lib/tools/commits_subdivider.rb +25 -0
- data/lib/tools/current_version_loader.rb +27 -0
- data/lib/tools/output.rb +46 -0
- data/lib/tools/version_bumper.rb +31 -0
- metadata +116 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 4adb0619c155f4f5822638aaefbcf01f4c6d391afe7ec684b5f11c60ce87722e
|
4
|
+
data.tar.gz: 6c5d26fdec70a95caa05f4f2dc5c4849dda521a32a4d573e370a909c42c8b416
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 5a7ddfc1af4e5f4407b107bb53ad799d9c2392fe72f62ed8bfee06d1ef259dd78358a1fc80d7de2f32e52b4dc29573ee24d00e41d2a5f202e979bd0d5f524811
|
7
|
+
data.tar.gz: 8682eb74f946d40dea90cc361f2035ec8c7003f63f8ba94ba3c2e91a550ad73ae2a6cce15909d85cecd9da85fe1742244a220075bec3195bc93e88bde55d837d
|
data/.editorconfig
ADDED
data/.gitignore
ADDED
File without changes
|
data/.gitlab-ci.yml
ADDED
@@ -0,0 +1,75 @@
|
|
1
|
+
stages:
|
2
|
+
- version
|
3
|
+
- deploy
|
4
|
+
- release
|
5
|
+
|
6
|
+
workflow:
|
7
|
+
rules:
|
8
|
+
# For merge requests, create a pipeline.
|
9
|
+
- if: '$CI_MERGE_REQUEST_IID'
|
10
|
+
# For default branch, create a pipeline.
|
11
|
+
- if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'
|
12
|
+
# For tags, never create pipeline.
|
13
|
+
- if: '$CI_COMMIT_TAG'
|
14
|
+
when: never
|
15
|
+
|
16
|
+
Update version:
|
17
|
+
stage: version
|
18
|
+
image: ruby:2.7
|
19
|
+
before_script:
|
20
|
+
- gem install gfsm
|
21
|
+
script:
|
22
|
+
- gfsm version bump --force > .version
|
23
|
+
- gfsm changelog --force --output-file CHANGELOG.md
|
24
|
+
- echo "RELEASE_VERSION=$(<.version)" >> variables.env
|
25
|
+
artifacts:
|
26
|
+
reports:
|
27
|
+
dotenv: variables.env
|
28
|
+
paths:
|
29
|
+
- .version
|
30
|
+
- CHANGELOG.md
|
31
|
+
|
32
|
+
# Publish gem:
|
33
|
+
# - GEM_HOST_API_KEY: A valid RubyGems API key.
|
34
|
+
Publish gem:
|
35
|
+
stage: deploy
|
36
|
+
image: ruby:2.7
|
37
|
+
variables:
|
38
|
+
GEMSPEC_FILE: "gfsm.gemspec"
|
39
|
+
GEM_FILE: "gfsm-${RELEASE_VERSION}.gem"
|
40
|
+
before_script:
|
41
|
+
- |
|
42
|
+
rm -f ./*.gem
|
43
|
+
[ -f "${GEMSPEC_FILE}" ] || (echo "No ${GEMSPEC_FILE} file found!" && exit 1)
|
44
|
+
script:
|
45
|
+
- |
|
46
|
+
gem build "${GEMSPEC_FILE}"
|
47
|
+
[ -f "${GEM_FILE}" ] || (echo "No ${GEM_FILE} file found!" && exit 1)
|
48
|
+
gem push "${GEM_FILE}"
|
49
|
+
artifacts:
|
50
|
+
paths:
|
51
|
+
- "*.gem"
|
52
|
+
rules:
|
53
|
+
- if: '$GEM_HOST_API_KEY == null'
|
54
|
+
when: never
|
55
|
+
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
|
56
|
+
when: never
|
57
|
+
|
58
|
+
Create release:
|
59
|
+
stage: release
|
60
|
+
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
61
|
+
script:
|
62
|
+
- |
|
63
|
+
release-cli create --name "Release v$RELEASE_VERSION" --description CHANGELOG.md --tag-name "v$RELEASE_VERSION"
|
64
|
+
tags:
|
65
|
+
- unix
|
66
|
+
needs:
|
67
|
+
- job: "Update version"
|
68
|
+
artifacts: true
|
69
|
+
- job: "Publish gem"
|
70
|
+
artifacts: false
|
71
|
+
rules:
|
72
|
+
- if: '$GEM_HOST_API_KEY == null'
|
73
|
+
when: never
|
74
|
+
- if: '$CI_COMMIT_BRANCH != $CI_DEFAULT_BRANCH'
|
75
|
+
when: never
|
data/.tool-versions
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
ruby 2.7.5
|
data/.vscode/launch.json
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
{
|
2
|
+
// Use IntelliSense to learn about possible attributes.
|
3
|
+
// Hover to view descriptions of existing attributes.
|
4
|
+
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
5
|
+
"version": "0.2.0",
|
6
|
+
"configurations": [
|
7
|
+
{
|
8
|
+
"name": "Debug Local File",
|
9
|
+
"type": "Ruby",
|
10
|
+
"request": "launch",
|
11
|
+
"program": "${workspaceRoot}/lib/gfsm.rb",
|
12
|
+
"args": ["changelog"]
|
13
|
+
}
|
14
|
+
]
|
15
|
+
}
|
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
GEM
|
2
|
+
remote: https://rubygems.org/
|
3
|
+
specs:
|
4
|
+
git (1.11.0)
|
5
|
+
rchardet (~> 1.8)
|
6
|
+
rchardet (1.8.0)
|
7
|
+
sem_version (2.0.1)
|
8
|
+
yaml (0.2.0)
|
9
|
+
|
10
|
+
PLATFORMS
|
11
|
+
arm64-darwin-21
|
12
|
+
x86_64-linux
|
13
|
+
|
14
|
+
DEPENDENCIES
|
15
|
+
git
|
16
|
+
sem_version
|
17
|
+
yaml
|
18
|
+
|
19
|
+
BUNDLED WITH
|
20
|
+
2.3.17
|
data/README.md
ADDED
@@ -0,0 +1,63 @@
|
|
1
|
+
# GitLab Flavored Semantic Versionin
|
2
|
+
|
3
|
+
### Goal
|
4
|
+
|
5
|
+
This project tries to achieve automatic semantic versioning not basing the version bump
|
6
|
+
on the conventional commits standars, but rather on a `Changelog` value on the trailer
|
7
|
+
of commit messages.
|
8
|
+
|
9
|
+
This works well if a project follows a similar workflow to the one used at GitLab, where
|
10
|
+
each change must not be made on a development branch, never on the release branch and needs
|
11
|
+
to get merged through a merge request.
|
12
|
+
|
13
|
+
The beauty of this project is that you don't need to use the conventional commits specification,
|
14
|
+
so you can have commits more similar to
|
15
|
+
|
16
|
+
```
|
17
|
+
Added new feature A to address this requirement
|
18
|
+
```
|
19
|
+
|
20
|
+
instead of
|
21
|
+
|
22
|
+
```
|
23
|
+
feat(<type>): added new feature A to address this requirement
|
24
|
+
```
|
25
|
+
|
26
|
+
This translates to:
|
27
|
+
- _**no "wasted" characters for that message prefix**_
|
28
|
+
- more readable commit messages
|
29
|
+
- a nicer `Changelog`
|
30
|
+
```
|
31
|
+
New features:
|
32
|
+
- Added new feature A to address this requirement
|
33
|
+
- Added feature B
|
34
|
+
```
|
35
|
+
vs
|
36
|
+
```
|
37
|
+
New features:
|
38
|
+
- feat(<type>): added new feature A to address this requirement
|
39
|
+
- feat: added feature B
|
40
|
+
```
|
41
|
+
|
42
|
+
### Setup
|
43
|
+
|
44
|
+
The only tool you need to setup this project on your local environment is Ruby, which can be
|
45
|
+
downloaded using `asdf`. The Ruby version required is `2.7.5`.
|
46
|
+
|
47
|
+
Once Ruby is present on your system you can install the dependencies by running
|
48
|
+
|
49
|
+
```shell
|
50
|
+
bundle install
|
51
|
+
```
|
52
|
+
|
53
|
+
### Testing
|
54
|
+
|
55
|
+
At the moment there's no built-in testing platform, but I'm planning to use `rspec` to add unit tests.
|
56
|
+
|
57
|
+
### Using
|
58
|
+
|
59
|
+
There are two ways to run this tool locally at the moment:
|
60
|
+
|
61
|
+
```shell
|
62
|
+
./bin/gfsm [args]
|
63
|
+
```
|
data/bin/gfsm
ADDED
data/gfsm.gemspec
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
Gem::Specification.new do |spec|
|
2
|
+
spec.name = "gfsm"
|
3
|
+
spec.version = File.read(".version")
|
4
|
+
spec.authors = ["Zille Marco"]
|
5
|
+
|
6
|
+
spec.summary = %q{This gem adds support to bump the semantic version bump and generate changelog file based on the commits trailer.}
|
7
|
+
spec.description = %q{This gem adds support to bump the semantic version bump and generate changelog file based on the commits trailer.}
|
8
|
+
spec.homepage = "https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning"
|
9
|
+
spec.license = "MIT"
|
10
|
+
spec.required_ruby_version = Gem::Requirement.new(">= 2.7.0")
|
11
|
+
|
12
|
+
spec.metadata["allowed_push_host"] = "https://rubygems.org"
|
13
|
+
|
14
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
15
|
+
spec.metadata["source_code_uri"] = "https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning"
|
16
|
+
spec.metadata["changelog_uri"] = "https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning/-/releases"
|
17
|
+
|
18
|
+
# Specify which files should be added to the gem when it is released.
|
19
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
20
|
+
spec.files = Dir.chdir(File.expand_path("..", __FILE__)) do
|
21
|
+
`git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
|
22
|
+
end
|
23
|
+
spec.executables = ["gfsm"]
|
24
|
+
spec.require_paths = ["lib"]
|
25
|
+
|
26
|
+
spec.add_dependency "yaml"
|
27
|
+
spec.add_dependency "sem_version"
|
28
|
+
spec.add_dependency "git"
|
29
|
+
end
|
data/gfsmrc.yml
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
change_types:
|
2
|
+
new_features:
|
3
|
+
title: "New features"
|
4
|
+
matcher: added
|
5
|
+
bump: minor
|
6
|
+
priority: 50
|
7
|
+
fixes:
|
8
|
+
title: "Fixes"
|
9
|
+
matcher: fixed
|
10
|
+
bump: patch
|
11
|
+
priority: 30
|
12
|
+
feature_changes:
|
13
|
+
title: "Feature changes"
|
14
|
+
matcher: changed
|
15
|
+
bump: minor
|
16
|
+
priority: 48
|
17
|
+
deprecations:
|
18
|
+
title: "Deprecations"
|
19
|
+
matcher: deprecated
|
20
|
+
bump: minor
|
21
|
+
priority: 40
|
22
|
+
feature_removals:
|
23
|
+
title: "Feature removals"
|
24
|
+
matcher: removed
|
25
|
+
bump: minor
|
26
|
+
priority: 45
|
27
|
+
security_fixes:
|
28
|
+
title: "Security fixes"
|
29
|
+
matcher: security
|
30
|
+
bump: patch
|
31
|
+
priority: 38
|
32
|
+
performance_improvements:
|
33
|
+
title: "Performance improvements"
|
34
|
+
matcher: performance
|
35
|
+
bump: patch
|
36
|
+
priority: 35
|
37
|
+
other:
|
38
|
+
title: "Other"
|
39
|
+
matcher: other
|
40
|
+
bump: patch
|
41
|
+
priority: 0
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Commands
|
5
|
+
class BaseCommand
|
6
|
+
attr_reader :stdout, :stderr
|
7
|
+
|
8
|
+
def initialize(stdout: GFSM::Output, stderr: GFSM::Output)
|
9
|
+
@stdout = stdout
|
10
|
+
@stderr = stderr
|
11
|
+
end
|
12
|
+
|
13
|
+
def run(args = [])
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,133 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Commands
|
5
|
+
class Changelog < BaseCommand
|
6
|
+
|
7
|
+
NEXT_ENTRY_MARKER = "<!--- next entry here -->"
|
8
|
+
|
9
|
+
def self.help
|
10
|
+
<<~HELP
|
11
|
+
Usage: gfsm changelog [help] [--output-file [<path>]] [--no-incremental] [--force] [--prerelease] [--prerelease-name <prerelease_name>] [--configuration <configuration_file_path>] [--path <repository_path]
|
12
|
+
|
13
|
+
Arguments:
|
14
|
+
help # Prints this help
|
15
|
+
--output-file [<path>] # Path to the output changelog file. Defaults to 'CHANGELOG.md'.
|
16
|
+
# If not specified, the generate changelog content will be written to stdout
|
17
|
+
--no-incremental # When provided, the generated changelog won't look for an existing
|
18
|
+
# changelog file. When outputting to stdout the changelog will never be incremental
|
19
|
+
--force # When there are no commits with a changelog trailer
|
20
|
+
# the version won't get bumped. Use this flag to force
|
21
|
+
# the version bump (will increase the patch version)
|
22
|
+
--prerelease # Use this switch to also include a prerelease in the version.
|
23
|
+
# By default will add 'pre' and increment it like 'pre.1',
|
24
|
+
# 'pre.2' and so on
|
25
|
+
--prerelease-name <name> # Name of the prerelease that will get added when the
|
26
|
+
# switch is enabled
|
27
|
+
--configuration <path> # Path to the configuration YAML file
|
28
|
+
--path <path> # Path to the repository. By default will use the current directory
|
29
|
+
HELP
|
30
|
+
end
|
31
|
+
|
32
|
+
def run(args = [])
|
33
|
+
if !args.empty? && args[0] == "help"
|
34
|
+
GFSM::Output.puts(GFSM::Commands::Changelog.help)
|
35
|
+
else
|
36
|
+
no_incremental = args.include?("--no-incremental")
|
37
|
+
output_file_path = get_output_file_path(args)
|
38
|
+
changelog_section = compute_this_version_section(args)
|
39
|
+
|
40
|
+
if !output_file_path
|
41
|
+
GFSM::Output.puts <<~CHANGELOG
|
42
|
+
# Changelog
|
43
|
+
|
44
|
+
#{changelog_section}
|
45
|
+
CHANGELOG
|
46
|
+
else
|
47
|
+
if File.exists?(output_file_path) && !no_incremental
|
48
|
+
existing_content = File.read(output_file_path)
|
49
|
+
|
50
|
+
file_content = existing_content.gsub(/#{Regexp.quote(NEXT_ENTRY_MARKER)}/i, changelog_section)
|
51
|
+
|
52
|
+
File.open(output_file_path, 'w') { |file| file.write file_content }
|
53
|
+
else
|
54
|
+
File.open(output_file_path, 'w') do |file|
|
55
|
+
file.write <<~CHANGELOG
|
56
|
+
# Changelog
|
57
|
+
|
58
|
+
#{changelog_section}
|
59
|
+
CHANGELOG
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
true
|
66
|
+
end
|
67
|
+
|
68
|
+
private
|
69
|
+
|
70
|
+
def compute_this_version_section(args)
|
71
|
+
force = args.include?("--force")
|
72
|
+
prerelease = args.include?("--prerelease")
|
73
|
+
prerelease_name = get_prerelease_name(args)
|
74
|
+
repository_path = get_repository_path(args)
|
75
|
+
configuration_file_path = get_configuration_file_path(args)
|
76
|
+
|
77
|
+
version_bumper = GFSM::Tools::VersionBumper.new()
|
78
|
+
version = version_bumper.compute_version!(force, prerelease, prerelease_name, repository_path, configuration_file_path)
|
79
|
+
subdivisions = version_bumper.subdivisions
|
80
|
+
|
81
|
+
changelog_entries = ""
|
82
|
+
|
83
|
+
subdivisions.each do |change_type, commits|
|
84
|
+
changelog_entries += "\n#{change_type.to_changelog_entry}\n\n"
|
85
|
+
|
86
|
+
commits.each do |commit|
|
87
|
+
changelog_entries += "- #{commit.to_changelog_entry}\n"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
|
91
|
+
if !subdivisions || subdivisions.empty?
|
92
|
+
changelog_section = "#{NEXT_ENTRY_MARKER}\n\n## #{version}\n\n"
|
93
|
+
else
|
94
|
+
changelog_section = "#{NEXT_ENTRY_MARKER}\n\n## #{version}\n#{changelog_entries}"
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def extract_switch_value(args, switch, default_value)
|
99
|
+
switch_index = args.find_index(switch)
|
100
|
+
|
101
|
+
return default_value unless switch_index &&
|
102
|
+
(switch_index + 1) < args.length
|
103
|
+
|
104
|
+
args[switch_index + 1]
|
105
|
+
end
|
106
|
+
|
107
|
+
def extract_switch_value_if_present(args, switch, default_value)
|
108
|
+
switch_index = args.find_index(switch)
|
109
|
+
|
110
|
+
return nil unless switch_index
|
111
|
+
return default_value unless (switch_index + 1) < args.length
|
112
|
+
|
113
|
+
args[switch_index + 1]
|
114
|
+
end
|
115
|
+
|
116
|
+
def get_output_file_path(args)
|
117
|
+
extract_switch_value_if_present(args, "--output-file", "./CHANGELOG.md")
|
118
|
+
end
|
119
|
+
|
120
|
+
def get_configuration_file_path(args)
|
121
|
+
extract_switch_value(args, "--configuration", "./gfsmrc.yml")
|
122
|
+
end
|
123
|
+
|
124
|
+
def get_prerelease_name(args)
|
125
|
+
extract_switch_value(args, "--prerelease-name", "pre")
|
126
|
+
end
|
127
|
+
|
128
|
+
def get_repository_path(args)
|
129
|
+
extract_switch_value(args, "--path", ".")
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Commands
|
5
|
+
class Help < BaseCommand
|
6
|
+
def run(_ = [])
|
7
|
+
GFSM::Output.puts <<~HELP
|
8
|
+
# GitLab Flavored Semantic Versioning
|
9
|
+
|
10
|
+
Usage: gfsm <command> [<args>]
|
11
|
+
|
12
|
+
Available commands:
|
13
|
+
|
14
|
+
gfsm help # Print this help text
|
15
|
+
gfsm version # Command used to print the current version or bump it based on the configuration
|
16
|
+
gfsm changelog # Command used to generate a changelog based on the commits since the latest release
|
17
|
+
|
18
|
+
# Version command
|
19
|
+
|
20
|
+
#{GFSM::Commands::Version.help}
|
21
|
+
|
22
|
+
# Changelog command
|
23
|
+
|
24
|
+
#{GFSM::Commands::Changelog.help}
|
25
|
+
|
26
|
+
HELP
|
27
|
+
|
28
|
+
true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Commands
|
5
|
+
class Version < BaseCommand
|
6
|
+
|
7
|
+
def self.help
|
8
|
+
<<~HELP
|
9
|
+
Usage: gfsm version [help] [bump [--force] [--prerelease] [--prerelease-name <prerelease_name>] [--configuration <configuration_file_path>] [--path <repository_path]]
|
10
|
+
|
11
|
+
Arguments:
|
12
|
+
help # Prints this help
|
13
|
+
bump # Bump the current version based on the commits
|
14
|
+
# changelog trailer and the configuration file
|
15
|
+
--force # When there are no commits with a changelog trailer
|
16
|
+
# the version won't get bumped. Use this flag to force
|
17
|
+
# the version bump (will increase the patch version)
|
18
|
+
--prerelease # Use this switch to also include a prerelease in the version.
|
19
|
+
# By default will add 'pre' and increment it like 'pre.1',
|
20
|
+
# 'pre.2' and so on
|
21
|
+
--prerelease-name <name> # Name of the prerelease that will get added when the
|
22
|
+
# switch is enabled
|
23
|
+
--configuration <path> # Path to the configuration YAML file
|
24
|
+
--path <path> # Path to the repository. By default will use the current directory
|
25
|
+
HELP
|
26
|
+
end
|
27
|
+
|
28
|
+
def run(args = [])
|
29
|
+
if args.empty? || (args.length == 2 && args[0] != "bump")
|
30
|
+
repository_path = get_repository_path(args)
|
31
|
+
|
32
|
+
GFSM::Output.puts GFSM::Tools::CurrentVersionLoader.load_current_version(repository_path)
|
33
|
+
return true
|
34
|
+
end
|
35
|
+
|
36
|
+
case args.shift
|
37
|
+
when 'bump'
|
38
|
+
force = args.include?("--force")
|
39
|
+
prerelease = args.include?("--prerelease")
|
40
|
+
prerelease_name = get_prerelease_name(args)
|
41
|
+
repository_path = get_repository_path(args)
|
42
|
+
configuration_file_path = get_configuration_file_path(args)
|
43
|
+
|
44
|
+
version_bumper = GFSM::Tools::VersionBumper.new()
|
45
|
+
version = version_bumper.compute_version!(force, prerelease, prerelease_name, repository_path, configuration_file_path)
|
46
|
+
|
47
|
+
GFSM::Output.puts(version)
|
48
|
+
when 'help'
|
49
|
+
GFSM::Output.puts(GFSM::Commands::Version.help)
|
50
|
+
else
|
51
|
+
GFSM::Output.warn(GFSM::Commands::Version.help)
|
52
|
+
end
|
53
|
+
|
54
|
+
true
|
55
|
+
end
|
56
|
+
|
57
|
+
private
|
58
|
+
|
59
|
+
def extract_switch_value(args, switch, default_value)
|
60
|
+
switch_index = args.find_index(switch)
|
61
|
+
|
62
|
+
return default_value unless switch_index &&
|
63
|
+
(switch_index + 1) < args.length
|
64
|
+
|
65
|
+
args[switch_index + 1]
|
66
|
+
end
|
67
|
+
|
68
|
+
def get_configuration_file_path(args)
|
69
|
+
extract_switch_value(args, "--configuration", "./gfsmrc.yml")
|
70
|
+
end
|
71
|
+
|
72
|
+
def get_prerelease_name(args)
|
73
|
+
extract_switch_value(args, "--prerelease-name", "pre")
|
74
|
+
end
|
75
|
+
|
76
|
+
def get_repository_path(args)
|
77
|
+
extract_switch_value(args, "--path", ".")
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Data
|
5
|
+
class ChangeType
|
6
|
+
attr_reader :matcher, :title, :bump_type, :priority, :commits
|
7
|
+
|
8
|
+
def initialize(matcher, title, bump_type, priority)
|
9
|
+
@matcher = matcher
|
10
|
+
@title = title
|
11
|
+
@bump_type = bump_type
|
12
|
+
@priority = priority
|
13
|
+
@commits = []
|
14
|
+
end
|
15
|
+
|
16
|
+
def bump_major?
|
17
|
+
@bump_type == :major
|
18
|
+
end
|
19
|
+
|
20
|
+
def bump_minor?
|
21
|
+
@bump_type == :minor
|
22
|
+
end
|
23
|
+
|
24
|
+
def bump_patch?
|
25
|
+
@bump_type == :patch
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_s
|
29
|
+
@title
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_changelog_entry
|
33
|
+
"### #{@title}"
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.find_highest_bump(change_types)
|
37
|
+
highest_bump = :patch
|
38
|
+
|
39
|
+
change_types.each do |change_type|
|
40
|
+
if change_type.bump_major?
|
41
|
+
highest_bump = :major
|
42
|
+
|
43
|
+
# Early exit, it can't go higher than this!
|
44
|
+
break
|
45
|
+
elsif change_type.bump_minor?
|
46
|
+
highest_bump = :minor
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
highest_bump
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
data/lib/data/commit.rb
ADDED
@@ -0,0 +1,30 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'forwardable'
|
4
|
+
|
5
|
+
module GFSM
|
6
|
+
module Data
|
7
|
+
class Commit
|
8
|
+
extend Forwardable
|
9
|
+
|
10
|
+
attr_reader :category, :trailer_key, :subject
|
11
|
+
|
12
|
+
def initialize(commit, trailer_key, category)
|
13
|
+
@commit = commit
|
14
|
+
@trailer_key = trailer_key
|
15
|
+
@category = category
|
16
|
+
@subject = commit.message.lines.first.chomp
|
17
|
+
end
|
18
|
+
|
19
|
+
def short_sha(length = 7)
|
20
|
+
self.sha[0, length]
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_changelog_entry
|
24
|
+
"#{subject} (#{short_sha})"
|
25
|
+
end
|
26
|
+
|
27
|
+
delegate %i[message sha] => :@commit
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'yaml'
|
4
|
+
|
5
|
+
module GFSM
|
6
|
+
module Data
|
7
|
+
class Configuration
|
8
|
+
attr_reader :change_types
|
9
|
+
|
10
|
+
def initialize(config_path)
|
11
|
+
@change_types = []
|
12
|
+
|
13
|
+
configuration = YAML.load_file(config_path)
|
14
|
+
|
15
|
+
configuration.each do |key, value|
|
16
|
+
if key == "change_types"
|
17
|
+
self.load_change_types(value)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
@change_types.sort_by! { |change_type| change_type.priority }.reverse!
|
22
|
+
end
|
23
|
+
|
24
|
+
def get_change_type_from_category(category)
|
25
|
+
@change_types.find do |change_type|
|
26
|
+
category.match(change_type.matcher)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
private
|
31
|
+
|
32
|
+
def load_change_types(change_types_config)
|
33
|
+
change_types_config.each do |key, value|
|
34
|
+
if !value.include?("title") || !value.include?("matcher") || !value.include?("bump")
|
35
|
+
raise "Change type has invalid configuration"
|
36
|
+
end
|
37
|
+
|
38
|
+
@change_types << GFSM::Data::ChangeType.new(
|
39
|
+
/#{Regexp.quote(value["matcher"])}/i,
|
40
|
+
value["title"],
|
41
|
+
value["bump"].to_sym,
|
42
|
+
value.include?("priority") ? value["priority"] : -100
|
43
|
+
)
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
data/lib/data/version.rb
ADDED
@@ -0,0 +1,33 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'sem_version'
|
4
|
+
|
5
|
+
module GFSM
|
6
|
+
module Data
|
7
|
+
class Version < SemVersion
|
8
|
+
PRERELEASE_BUILD_VERSION = /^(?<prerelease_name>[a-zA-Z\-]+)(\.?)(?<prerelease_version>[0-9]*)$/i
|
9
|
+
|
10
|
+
def bump!(major = false, minor = false, patch = true, pre = false, prerelease_name = "pre")
|
11
|
+
if major
|
12
|
+
self.major = self.major + 1
|
13
|
+
self.minor = 0
|
14
|
+
self.patch = 0
|
15
|
+
elsif minor
|
16
|
+
self.minor = self.minor + 1
|
17
|
+
self.patch = 0
|
18
|
+
elsif patch
|
19
|
+
self.patch = self.patch + 1
|
20
|
+
end
|
21
|
+
|
22
|
+
if pre
|
23
|
+
preselease_match = self.pre.match(PRERELEASE_BUILD_VERSION) if self.pre
|
24
|
+
if preselease_match
|
25
|
+
self.pre = preselease_match[:prerelease_name] + "." + (preselease_match[:prerelease_version].to_i + 1).to_s
|
26
|
+
else
|
27
|
+
self.pre = prerelease_name
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
data/lib/gfsm.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Load the data
|
4
|
+
require 'data/change_type'
|
5
|
+
require 'data/commit'
|
6
|
+
require 'data/configuration'
|
7
|
+
require 'data/version'
|
8
|
+
|
9
|
+
# Load the tools
|
10
|
+
require 'tools/commits_extractor'
|
11
|
+
require 'tools/commits_subdivider'
|
12
|
+
require 'tools/current_version_loader'
|
13
|
+
require 'tools/version_bumper'
|
14
|
+
require 'tools/output'
|
15
|
+
|
16
|
+
# Load the commands
|
17
|
+
require 'commands/base_command'
|
18
|
+
require 'commands/changelog'
|
19
|
+
require 'commands/version'
|
20
|
+
require 'commands/help'
|
21
|
+
|
22
|
+
module GFSM
|
23
|
+
# This is the map between each command and its implementation class
|
24
|
+
COMMANDS = {
|
25
|
+
'version' => -> { GFSM::Commands::Version },
|
26
|
+
'changelog' => -> { GFSM::Commands::Changelog }
|
27
|
+
}.freeze
|
28
|
+
|
29
|
+
def self.main
|
30
|
+
subcommand = ARGV.shift
|
31
|
+
|
32
|
+
exit(COMMANDS[subcommand].call.new.run(ARGV)) if COMMANDS.key?(subcommand)
|
33
|
+
|
34
|
+
case subcommand
|
35
|
+
when /-{0,2}help/, '-h', nil
|
36
|
+
GFSM::Commands::Help.new.run(ARGV)
|
37
|
+
else
|
38
|
+
GFSM::Output.warn "#{subcommand} is not a GFSM command."
|
39
|
+
|
40
|
+
GFSM::Output.info "See 'gfsm help' for more detail."
|
41
|
+
false
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
|
5
|
+
module GFSM
|
6
|
+
module Tools
|
7
|
+
class CommitsExtractor
|
8
|
+
CHANGELOG_TRAILER_REGEX = /^(?<name>Changelog):\s*(?<category>.+)$/i
|
9
|
+
|
10
|
+
def self.extract_commits_with_changelog_trailer(repo_path = ".")
|
11
|
+
repo = Git.open(repo_path)
|
12
|
+
|
13
|
+
begin
|
14
|
+
last_tag_name = repo.describe(nil, { abbrev: 0 })
|
15
|
+
rescue
|
16
|
+
last_tag_name = nil
|
17
|
+
end
|
18
|
+
|
19
|
+
begin
|
20
|
+
commits = last_tag_name ? repo.log.between(last_tag_name, 'head') : repo.log
|
21
|
+
commits.each_with_object([]) do |commit, memo|
|
22
|
+
trailer = commit.message.match(CHANGELOG_TRAILER_REGEX)
|
23
|
+
memo << GFSM::Data::Commit.new(commit, trailer[:name], trailer[:category]) if trailer
|
24
|
+
end
|
25
|
+
rescue
|
26
|
+
[]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
|
5
|
+
module GFSM
|
6
|
+
module Tools
|
7
|
+
class CommitsSubdivider
|
8
|
+
def self.subdivide_commits(configuration, commits)
|
9
|
+
subdivisions = {}
|
10
|
+
|
11
|
+
commits.each do |commit|
|
12
|
+
change_type = configuration.get_change_type_from_category(commit.category)
|
13
|
+
|
14
|
+
if change_type
|
15
|
+
(subdivisions[change_type] ||= []) << commit
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
return subdivisions
|
20
|
+
end
|
21
|
+
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
require_relative '../data/version'
|
5
|
+
|
6
|
+
module GFSM
|
7
|
+
module Tools
|
8
|
+
class CurrentVersionLoader
|
9
|
+
def self.load_current_version(repo_path = ".")
|
10
|
+
repo = Git.open(repo_path)
|
11
|
+
begin
|
12
|
+
last_tag_name = repo.describe(nil, { abbrev: 0 })
|
13
|
+
rescue
|
14
|
+
last_tag_name = nil
|
15
|
+
end
|
16
|
+
|
17
|
+
return GFSM::Data::Version.new("0.0.0") unless last_tag_name
|
18
|
+
|
19
|
+
if last_tag_name.downcase.start_with?("v")
|
20
|
+
last_tag_name.slice!(0)
|
21
|
+
end
|
22
|
+
|
23
|
+
GFSM::Data::Version.new(last_tag_name)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/tools/output.rb
ADDED
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_stirng_literal: true
|
2
|
+
|
3
|
+
module GFSM
|
4
|
+
module Output
|
5
|
+
def self.included(klass)
|
6
|
+
klass.extend(Methods)
|
7
|
+
end
|
8
|
+
|
9
|
+
module Methods
|
10
|
+
def stdout_handle
|
11
|
+
$stdout.tap { |handle| handle.sync = true }
|
12
|
+
end
|
13
|
+
|
14
|
+
def stderr_handle
|
15
|
+
$stderr.tap { |handle| handle.sync = true }
|
16
|
+
end
|
17
|
+
|
18
|
+
def print(message = nil, stderr: false)
|
19
|
+
stderr ? stderr_handle.print(message) : stdout_handle.print(message)
|
20
|
+
end
|
21
|
+
|
22
|
+
def puts(message = nil, stderr: false)
|
23
|
+
stderr ? stderr_handle.puts(message) : stdout_handle.puts(message)
|
24
|
+
end
|
25
|
+
|
26
|
+
def info(message)
|
27
|
+
puts(message)
|
28
|
+
end
|
29
|
+
|
30
|
+
def warn(message)
|
31
|
+
puts("WARNING: #{message}", stderr: true)
|
32
|
+
end
|
33
|
+
|
34
|
+
def error(message)
|
35
|
+
puts("ERROR: #{message}", stderr: true)
|
36
|
+
end
|
37
|
+
|
38
|
+
def success(message)
|
39
|
+
puts(message)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
extend Methods
|
44
|
+
include Methods
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'git'
|
4
|
+
require_relative '../data/version'
|
5
|
+
|
6
|
+
module GFSM
|
7
|
+
module Tools
|
8
|
+
class VersionBumper
|
9
|
+
attr_reader :version, :subdivisions
|
10
|
+
|
11
|
+
def compute_version!(force, prerelease, prerelease_name, repository_path, configuration_file_path)
|
12
|
+
configuration = GFSM::Data::Configuration.new(configuration_file_path)
|
13
|
+
changelog_commits = GFSM::Tools::CommitsExtractor.extract_commits_with_changelog_trailer(repository_path)
|
14
|
+
|
15
|
+
@version = GFSM::Tools::CurrentVersionLoader.load_current_version(repository_path)
|
16
|
+
@subdivisions = GFSM::Tools::CommitsSubdivider.subdivide_commits(configuration, changelog_commits)
|
17
|
+
|
18
|
+
if !subdivisions || subdivisions.empty?
|
19
|
+
if force
|
20
|
+
@version.bump!(false, false, true, prerelease, prerelease_name)
|
21
|
+
end
|
22
|
+
else
|
23
|
+
highest_bump = GFSM::Data::ChangeType.find_highest_bump(subdivisions.keys)
|
24
|
+
@version.bump!(highest_bump == :major, highest_bump == :minor, highest_bump == :patch, prerelease, prerelease_name)
|
25
|
+
end
|
26
|
+
|
27
|
+
return @version
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
metadata
ADDED
@@ -0,0 +1,116 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: gfsm
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.1.2
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Zille Marco
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2022-07-04 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: yaml
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: '0'
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '0'
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: sem_version
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: git
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :runtime
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: This gem adds support to bump the semantic version bump and generate
|
56
|
+
changelog file based on the commits trailer.
|
57
|
+
email:
|
58
|
+
executables:
|
59
|
+
- gfsm
|
60
|
+
extensions: []
|
61
|
+
extra_rdoc_files: []
|
62
|
+
files:
|
63
|
+
- ".editorconfig"
|
64
|
+
- ".gitignore"
|
65
|
+
- ".gitlab-ci.yml"
|
66
|
+
- ".tool-versions"
|
67
|
+
- ".vscode/launch.json"
|
68
|
+
- Gemfile
|
69
|
+
- Gemfile.lock
|
70
|
+
- README.md
|
71
|
+
- bin/gfsm
|
72
|
+
- gfsm.gemspec
|
73
|
+
- gfsmrc.yml
|
74
|
+
- lib/commands/base_command.rb
|
75
|
+
- lib/commands/changelog.rb
|
76
|
+
- lib/commands/help.rb
|
77
|
+
- lib/commands/version.rb
|
78
|
+
- lib/data/change_type.rb
|
79
|
+
- lib/data/commit.rb
|
80
|
+
- lib/data/configuration.rb
|
81
|
+
- lib/data/version.rb
|
82
|
+
- lib/gfsm.rb
|
83
|
+
- lib/tools/commits_extractor.rb
|
84
|
+
- lib/tools/commits_subdivider.rb
|
85
|
+
- lib/tools/current_version_loader.rb
|
86
|
+
- lib/tools/output.rb
|
87
|
+
- lib/tools/version_bumper.rb
|
88
|
+
homepage: https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning
|
89
|
+
licenses:
|
90
|
+
- MIT
|
91
|
+
metadata:
|
92
|
+
allowed_push_host: https://rubygems.org
|
93
|
+
homepage_uri: https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning
|
94
|
+
source_code_uri: https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning
|
95
|
+
changelog_uri: https://gitlab.com/zillemarco/gitlab-flavored-semantic-versioning/-/releases
|
96
|
+
post_install_message:
|
97
|
+
rdoc_options: []
|
98
|
+
require_paths:
|
99
|
+
- lib
|
100
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
101
|
+
requirements:
|
102
|
+
- - ">="
|
103
|
+
- !ruby/object:Gem::Version
|
104
|
+
version: 2.7.0
|
105
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
106
|
+
requirements:
|
107
|
+
- - ">="
|
108
|
+
- !ruby/object:Gem::Version
|
109
|
+
version: '0'
|
110
|
+
requirements: []
|
111
|
+
rubygems_version: 3.3.17
|
112
|
+
signing_key:
|
113
|
+
specification_version: 4
|
114
|
+
summary: This gem adds support to bump the semantic version bump and generate changelog
|
115
|
+
file based on the commits trailer.
|
116
|
+
test_files: []
|