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 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
@@ -0,0 +1,12 @@
1
+ # EditorConfig is awesome: https://EditorConfig.org
2
+
3
+ # top-most EditorConfig file
4
+ root = true
5
+
6
+ [*]
7
+ indent_style = space
8
+ indent_size = 2
9
+ end_of_line = lf
10
+ charset = utf-8
11
+ trim_trailing_whitespace = false
12
+ insert_final_newline = false
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
@@ -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
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ gem "yaml"
6
+ gem "sem_version"
7
+ gem "git"
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
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require 'gfsm'
4
+ GFSM.main
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
@@ -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
@@ -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
@@ -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: []