conventional_commits 0.3.0 → 0.4.0

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3f94b502d6513ea200421a55aa11f6b4256fba4f97ca7151377102628b6c697a
4
- data.tar.gz: d1e38a90cbbdafbd4225896a03a510511726587367184bfa2130e8327ce5e6c6
3
+ metadata.gz: 8ef389be49918b12a03941a89178003c8b6ba9962583516728948bb1c61d5557
4
+ data.tar.gz: e4e5c3dd45bb0e3c140b9519dcd76f432fc7812012402dde8a4a643da8318c9a
5
5
  SHA512:
6
- metadata.gz: 687b3c6cffc0a5df6df27d8b872bb518fd893fc168210d4c59aee70eca79cdcf696efaf643635de37010b04535b0183d4f546e6accbc5772e7ef7ca0b759437e
7
- data.tar.gz: a29267a2aae48205181b7e4553ada4577abc2927317a78bdbe44319460d3d5b582f81ba8224a695a299cdb922384486dbe5db6f106d2e1900016916d1815dc12
6
+ metadata.gz: d8435aa0ae03a4ab74880debbec1f7a925b39a972337a17432234f64907e97388b82d48692f58ac16f2d7566dd0f0d96e365ef2ef86940e5ea6372fd59eb5ad8
7
+ data.tar.gz: 5cc8f961cc6ce9296f0348fe6d200c53edbfb10996320065d3b3ae045125177204aa3a01a1be062b861c95561dc828e0c19236a1400bd0015f8d15f353df2971
data/README.md CHANGED
@@ -55,8 +55,29 @@ to create a new branch
55
55
  - `pattern` - is used to define the separators between in future it will be used to define the order as well
56
56
  - `type` - is used for mapping combination of short names for the type of request. Helps to avoid inconsistency.
57
57
 
58
+ ### Validating in CI
59
+ The hooks validate the current branch and `.git/COMMIT_EDITMSG`. In CI, pass them explicitly:
60
+
61
+ $ conventional_commits validate_branch --branch "$GITHUB_HEAD_REF"
62
+ $ conventional_commits validate_commit_msg --msg "$(git log -1 --format=%B "$SHA")"
63
+
64
+ With a detached `HEAD` and no `--branch`, `validate_branch` falls back to `$GITHUB_HEAD_REF`.
65
+
58
66
  ### Release
59
- Not implemented yet. It will contain tools to increment version number based on the rules
67
+ `next_version` prints the next version from the commits since the latest version tag
68
+ (`X.Y.Z` or `vX.Y.Z`), following `release.rules`:
69
+
70
+ $ conventional_commits next_version
71
+ 0.3.1
72
+
73
+ - Each commit's type is matched against the rules' types; aliases from `type` count as their
74
+ main type, so a `ref:` commit matches a rule listing `refactor`.
75
+ - A breaking change — `type!:` in the subject or a `BREAKING CHANGE:` footer — uses the rule
76
+ for `breaking`.
77
+ - The biggest bump of all the commits wins (`major` > `minor` > `patch`); `version: none`, or a
78
+ type without a rule, produces no release.
79
+ - It prints nothing when no commit calls for a release, and `--initial` (default `0.1.0`) when
80
+ there is no version tag yet.
60
81
 
61
82
 
62
83
  ## Development
@@ -17,4 +17,5 @@ require "helpers/file_extension"
17
17
  require "commit/commit_message_parser"
18
18
  require "commit/commit_message_validator"
19
19
  require "hooks/hooks_installer"
20
+ require "release/next_version_calculator"
20
21
  ConventionalCommits::BranchNameCLI.start(ARGV)
@@ -70,6 +70,18 @@ module ConventionalCommits
70
70
  raise Thor::Error, e.message
71
71
  end
72
72
 
73
+ desc "next_version", "prints the next release version from the commits since the latest version tag"
74
+ option :cfg_path, type: :string, required: false
75
+ option :initial, type: :string, required: false, desc: "Version to print when there is no version tag yet (default 0.1.0)"
76
+ def next_version
77
+ cfg_path = options["cfg_path"] || Configuration::DEFAULT_CONFIGURATION_PATH
78
+ version = ConventionalCommits::NextVersionCalculator.new
79
+ .next_version(cfg_path:, initial: options["initial"] || "0.1.0")
80
+ puts version if version
81
+ rescue StandardError => e
82
+ raise Thor::Error, e.message
83
+ end
84
+
73
85
  desc "install_hooks", "install all git hooks"
74
86
  def install_hooks
75
87
  installer = ConventionalCommits::Configuration::HooksInstaller.new
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ConventionalCommits
4
- VERSION = "0.3.0"
4
+ VERSION = "0.4.0"
5
5
  end
data/lib/git/git.rb CHANGED
@@ -10,6 +10,16 @@ module ConventionalCommits
10
10
  run_system_command "git symbolic-ref --short HEAD"
11
11
  end
12
12
 
13
+ def tags
14
+ run_system_command("git tag --list").split("\n").map(&:strip).reject(&:empty?)
15
+ end
16
+
17
+ # Full messages of the commits reachable from HEAD but not from `ref`, newest first.
18
+ def commit_messages_since(ref)
19
+ run_system_command("git log --format=%B%x00 #{ref}..HEAD")
20
+ .split("\x00").map(&:strip).reject(&:empty?)
21
+ end
22
+
13
23
  def run_system_command(_cmd)
14
24
  Open3.popen3(_cmd) { |_stdin, stdout, _stderr, _wait_thr| stdout.read }
15
25
  end
@@ -2,12 +2,13 @@
2
2
 
3
3
  module ConventionalCommits
4
4
  module Configuration
5
- # Configuration for branch name policies
5
+ # A release rule: which commit types produce which version bump
6
6
  class ReleaseRule
7
7
  attr_reader :types, :version
8
8
 
9
+ # Accepts both `types: [..]` and the singular `type: ..` used for the breaking rule.
9
10
  def initialize(options = {})
10
- @types = options["types"] || []
11
+ @types = Array(options["types"] || options["type"])
11
12
  @version = options["version"] || "none"
12
13
  end
13
14
  end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ConventionalCommits
4
+ # Works out the next release version from the commits since the latest version tag, using the
5
+ # `release.rules` of the configuration: each commit's type (aliases included) maps to a bump,
6
+ # and the biggest bump wins. A breaking change (`type!:` or a `BREAKING CHANGE:` footer) uses
7
+ # the rule for the `breaking` type.
8
+ class NextVersionCalculator
9
+ VERSION_TAG = /\Av?\d+\.\d+\.\d+\z/
10
+ SUBJECT = /\A(?<type>[A-Za-z]+)(?:\([^)]*\))?(?<breaking>!)?:/
11
+ BREAKING_FOOTER = /^BREAKING[ -]CHANGE:/
12
+ BREAKING_TYPE = "breaking"
13
+ BUMPS = %w[none patch minor major].freeze
14
+
15
+ attr_reader :git, :reader
16
+
17
+ def initialize(git: Git.new, reader: Configuration::MainConfigurationReader.new)
18
+ @git = git
19
+ @reader = reader
20
+ end
21
+
22
+ # The next version as "X.Y.Z", `initial` when there is no version tag yet, or nil when no
23
+ # commit since the latest tag calls for a release.
24
+ def next_version(cfg_path: Configuration::DEFAULT_CONFIGURATION_PATH, initial: "0.1.0")
25
+ tag = latest_version_tag
26
+ return initial if tag.nil?
27
+
28
+ config = reader.get_configuration(path: cfg_path)
29
+ bumps = git.commit_messages_since(tag).map { |message| bump_for(message, config) }
30
+ bumped(tag, bumps.max_by { |bump| BUMPS.index(bump) } || "none")
31
+ end
32
+
33
+ private
34
+
35
+ def latest_version_tag
36
+ git.tags.grep(VERSION_TAG).max_by { |tag| Gem::Version.new(tag.delete_prefix("v")) }
37
+ end
38
+
39
+ def bump_for(message, config)
40
+ subject = SUBJECT.match(message.lines.first.to_s.strip)
41
+ return "none" if subject.nil?
42
+
43
+ if subject[:breaking] || message.match?(BREAKING_FOOTER)
44
+ rule_bump(BREAKING_TYPE, config) || type_bump(subject[:type], config)
45
+ else
46
+ type_bump(subject[:type], config)
47
+ end
48
+ end
49
+
50
+ def type_bump(type, config)
51
+ rule_bump(main_type(type.downcase, config), config) || "none"
52
+ end
53
+
54
+ def rule_bump(type, config)
55
+ rule = config.release.rules.find do |candidate|
56
+ candidate.types.map { |rule_type| main_type(rule_type.to_s, config) }.include?(type)
57
+ end
58
+ rule && validated(rule.version)
59
+ end
60
+
61
+ # Rule types and commit types may be aliases ("feature", "ref"); both sides compare as the
62
+ # configured main type. `breaking` and unknown types stay as they are.
63
+ def main_type(type, config)
64
+ main = config.type.main_type(type)
65
+ main.to_s.empty? ? type : main
66
+ end
67
+
68
+ def validated(bump)
69
+ raise GenericError, "Unknown release version '#{bump}', expected one of #{BUMPS}" unless BUMPS.include?(bump)
70
+
71
+ bump
72
+ end
73
+
74
+ def bumped(tag, bump)
75
+ return nil if bump == "none"
76
+
77
+ major, minor, patch = tag.delete_prefix("v").split(".").map(&:to_i)
78
+ case bump
79
+ when "major" then "#{major + 1}.0.0"
80
+ when "minor" then "#{major}.#{minor + 1}.0"
81
+ else "#{major}.#{minor}.#{patch + 1}"
82
+ end
83
+ end
84
+ end
85
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: conventional_commits
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Swift-Gurus
@@ -129,6 +129,7 @@ files:
129
129
  - lib/models/release_configuration.rb
130
130
  - lib/models/release_rule.rb
131
131
  - lib/models/type_configuration.rb
132
+ - lib/release/next_version_calculator.rb
132
133
  - sig/conventional_commit/branch_name_generator.rbs
133
134
  - sig/conventional_commits.rbs
134
135
  - sig/conventional_commits/branch_name_cli.rbs