gfsm 0.1.2

Sign up to get free protection for your applications and to get access to all the features.
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: []