gfsm 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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: []
|