diff --git a/CHANGELOG.md b/CHANGELOG.md index 6870fd1..565f772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,14 @@ # Changelog +## UNRELEASED + +### Added + +- Add support for Epoch semantic versioning +- `gem bump -v epoch` bumps `EXXX.Y.Z` to `000.0.0` +- `gem bump -v epoch` bumps `E.X.Y.Z` to `.0.0.0` + (PR: https://github.com/svenfuchs/gem-release/pull/114) + ## v2.2.4 - 2024-12-28 ### Changed diff --git a/README.md b/README.md index 6cee3ae..3faa6af 100644 --- a/README.md +++ b/README.md @@ -209,7 +209,7 @@ gem_name - name of the gem (optional, will use the directory name, or all gemspe ### Options ``` --v, --version VERSION Target version: next [major|minor|patch|pre|release] or a given version number [x.x.x] +-v, --version VERSION Target version: next [epoch|major|minor|patch|pre|release] or a given version number [x.x.x] -c, --[no-]commit Create a commit after incrementing gem version (default: true) -m, --message MESSAGE Commit message template (default: Bump %{name} to %{version} %{skip_ci}) --skip-ci Add the [skip ci] tag to the commit message @@ -228,8 +228,8 @@ gem_name - name of the gem (optional, will use the directory name, or all gemspe ### Description Bumps the version number defined in lib/[gem_name]/version.rb to a -given, specific version number, or to the next major, minor, patch, or -pre-release level. +given, specific version number, or to the next epoch, major, minor, +patch, or pre-release level. Optionally it pushes to the origin repository. Also, optionally it invokes the `gem tag` and/or `gem release` command. @@ -249,6 +249,8 @@ minor # Bump to the next minor level (e.g. 0.0.1 to 0.1.0) patch # Bump to the next patch level (e.g. 0.0.1 to 0.0.2) pre|rc|etc # Bump to the next pre-release level (e.g. 0.0.1 to # 0.1.0.pre.1, 1.0.0.pre.1 to 1.0.0.pre.2) +epoch # Bump to the next epoch level +# (e.g. 4.0.0 to 1000.0.0 or 1.0.0.0 to 2.0.0.0) ``` When searching for the version file for a gem named `gem-name`: the diff --git a/lib/gem/release/cmds/bump.rb b/lib/gem/release/cmds/bump.rb index abbb34d..2fe5e2f 100644 --- a/lib/gem/release/cmds/bump.rb +++ b/lib/gem/release/cmds/bump.rb @@ -9,7 +9,7 @@ class Bump < Base description <<-str.split("\n").map(&:lstrip).join("\n") Bumps the version number defined in lib/[gem_name]/version.rb to a given, - specific version number, or to the next major, minor, patch, or pre-release + specific version number, or to the next epoch, major, minor, patch, or pre-release level. Optionally it pushes to the origin repository. Also, optionally it invokes the @@ -30,6 +30,8 @@ class Bump < Base patch # Bump to the next patch level (e.g. 0.0.1 to 0.0.2) pre|rc|etc # Bump to the next pre-release level (e.g. 0.0.1 to # 0.1.0.pre.1, 1.0.0.pre.1 to 1.0.0.pre.2) + epoch # Bump to the next epoch level + # (e.g. 4.0.0 to 1000.0.0 or 1.0.0.0 to 2.0.0.0) ``` When searching for the version file for a gem named `gem-name`: the following @@ -42,7 +44,7 @@ class Bump < Base arg :gem_name, 'name of the gem (optional, will use the directory name, or all gemspecs if --recurse is given)' DESCR = { - version: 'Target version: next [major|minor|patch|pre|release] or a given version number [x.x.x]', + version: 'Target version: next [epoch|major|minor|patch|pre|release] or a given version number [x.x.x]', branch: 'Check out a new branch for the target version (e.g. `v1.0.0`)', commit: 'Create a commit after incrementing gem version', message: 'Commit message template', diff --git a/lib/gem/release/files/version.rb b/lib/gem/release/files/version.rb index 42f933c..7a45242 100644 --- a/lib/gem/release/files/version.rb +++ b/lib/gem/release/files/version.rb @@ -5,10 +5,19 @@ module Release module Files class Version < Struct.new(:name, :version, :opts) VERSION = /(VERSION\s*=\s*(?:"|'))((?:(?!"|').)*)("|')/ - RELEASE = /^(\d+)\.(\d+)\.(\d+)(.*)$/ - PRE_RELEASE = /^(\d+)\.(\d+)\.(\d+)\.?(.*)(\d+)$/ - STAGES = [:major, :minor, :patch] + # Note from implementing Epoch versioning: + # The RELEASE+PRE_RELEASE+STAGES constants were already here but appear to be unused. + # As a first time contributor, I figured it would be safer to leave them in place. + # I have added EPOCH_RELEASE and EPOCH_PRE_RELEASE just to keep things consistent. + EPOCH_PREFIX = /^(?\d+)\./ + SEMVER = /(?\d+)\.(?\d+)\.(?\d+)/ + STAGE = /\.?(?.*)(?\d+)/ + RELEASE = /^#{SEMVER}(.*)$/ + PRE_RELEASE = /^#{SEMVER}$#{STAGE}/ + EPOCH_RELEASE = /#{EPOCH_PREFIX}#{SEMVER}(.*)$/ + EPOCH_PRE_RELEASE = /#{EPOCH_PREFIX}#{SEMVER}$#{STAGE}/ + STAGES = [:epoch, :major, :minor, :patch] def exists? !!path diff --git a/lib/gem/release/templates/version.rb b/lib/gem/release/templates/version.rb index fa70bb3..8a79b88 100644 --- a/lib/gem/release/templates/version.rb +++ b/lib/gem/release/templates/version.rb @@ -1 +1 @@ -<%= define(:module) { "VERSION = '0.0.1'" } %> +<%= define(:module) { "VERSION = '0.0.1'\n # VERSION = 0.0.0.1 # Epoch SemVer is also supported" } %> diff --git a/lib/gem/release/version/number.rb b/lib/gem/release/version/number.rb index b1b758d..bc8ae74 100644 --- a/lib/gem/release/version/number.rb +++ b/lib/gem/release/version/number.rb @@ -2,7 +2,8 @@ module Gem module Release module Version class Number < Struct.new(:number, :target) - NUMBER = /^(\d+)\.?(\d+)?\.?(\d+)?(\-|\.)?(\w+)?\.?(\d+)?$/ + EPOCH_NUMBER = /^(?\d+)\.(?\d+)\.(?\d+)\.(?\d+)(?\-|\.)?(?\w+)?\.?(?\d+)?$/ + NUMBER = /^(?\d+)\.?(?\d+)?\.?(?\d+)?(?\-|\.)?(?\w+)?\.?(?\d+)?$/ PRE_RELEASE = /^(\d+)\.(\d+)\.(\d+)\.?(.*)(\d+)$/ STAGES = %i(alpha beta pre rc) @@ -10,13 +11,13 @@ class Number < Struct.new(:number, :target) def bump return target if specific? validate_stage - parts = [[major, minor, patch].compact.join('.')] + parts = [[epoch, major, minor, patch].compact.join('.')] parts << [stage, num].join('.') if stage parts.join(stage_delim) end def pre? - !!parts[4] + !!parts[:stage] || !!parts[:stage_num] end private @@ -25,22 +26,36 @@ def specific? target =~ NUMBER || target =~ PRE_RELEASE end + def epoch + part = parts[:epoch] || return + part += 1 if to?(:epoch) + part + end + def major - part = parts[0] + part = parts[:major] + if to?(:epoch) + if parts[:epoch] + part = 0 + else + part -= part % 1000 + part += 1000 + end + end part += 1 if to?(:major) part end def minor - part = parts[1].to_i - part = 0 if to?(:major) + part = parts[:minor].to_i + part = 0 if to?(:epoch, :major) part += 1 if to?(:minor) || fresh_pre_release? part end def patch - part = parts[2].to_i - part = 0 if to?(:major, :minor) || fresh_pre_release? + part = parts[:patch].to_i + part = 0 if to?(:epoch, :major, :minor) || fresh_pre_release? part += 1 if to?(:patch) && from_release? part end @@ -53,12 +68,12 @@ def stage_delim # Use what's being used or default to dot (`.`) # dot is preferred due to rubygems issue # https://github.com/rubygems/rubygems/issues/592 - parts[3] || '.' + parts[:stage_delim] || '.' end def num return if to_release? - same_stage? ? parts[5].to_i + 1 : 1 + same_stage? ? parts[:stage_num] + 1 : 1 end def to?(*targets) @@ -66,7 +81,7 @@ def to?(*targets) end def to_release? - to?(:major, :minor, :patch) + to?(:epoch, :major, :minor, :patch) end def fresh_pre_release? @@ -86,7 +101,7 @@ def same_stage? end def from_stage - parts[4] + parts[:stage] end def target @@ -100,20 +115,23 @@ def validate_stage end def parts - @parts ||= matches.compact.map(&:to_i).tap do |parts| - parts[3] = matches[3] - parts[4] = matches[4].to_sym if matches[4] - end - end - - def matches - @matches ||= parse.to_a[1..-1] - end + @parts ||= { + epoch: (matches[:epoch]&.to_i if matches.names.include?('epoch')), + major: matches[:major].to_i, + minor: matches[:minor]&.to_i, + patch: matches[:patch]&.to_i, + stage_delim: matches[:stage_delim], + stage: matches[:stage]&.to_sym, + stage_num: matches[:stage_num]&.to_i, + }.compact + end - def parse - matches = number.match(NUMBER) - raise Abort, "Cannot parse version number #{number}" unless matches - matches + def matches + @matches = begin + @matches = number.match(EPOCH_NUMBER) || number.match(NUMBER) + raise Abort, "Cannot parse version number #{number}" unless @matches + @matches + end end end end diff --git a/spec/gem/release/cmds/bump_spec.rb b/spec/gem/release/cmds/bump_spec.rb index a06f561..0855eba 100644 --- a/spec/gem/release/cmds/bump_spec.rb +++ b/spec/gem/release/cmds/bump_spec.rb @@ -43,10 +43,13 @@ gemspec 'foo/foo', '1.0.0' version 'bar/lib/bar', '2.0.0' gemspec 'bar/bar', '2.0.0' + version 'epo/lib/epo', '3.0.0.0' + gemspec 'epo/epo', '3.0.0.0' run_cmd it { should output "Bumping foo from version 1.0.0 to 1.0.1" } it { should output "Bumping bar from version 2.0.0 to 2.0.1" } + it { should output "Bumping epo from version 3.0.0.0 to 3.0.0.1" } end describe 'given a gem name ending in *_rb' do @@ -68,6 +71,11 @@ it { should have_version 'foo/bar', '1.2.3' } end + describe 'epoch' do + let(:opts) { { version: :epoch } } + it { should have_version 'foo/bar', '1000.0.0' } + end + describe 'major' do let(:opts) { { version: :major } } it { should have_version 'foo/bar', '2.0.0' } diff --git a/spec/gem/release/files/version_spec.rb b/spec/gem/release/files/version_spec.rb index 3e139ce..ac4beae 100644 --- a/spec/gem/release/files/version_spec.rb +++ b/spec/gem/release/files/version_spec.rb @@ -15,5 +15,10 @@ let(:target) { :patch } it { expect(version.to).to eq '1.0.1' } end + + describe 'given :patch (default)' do + let(:target) { :epoch } + it { expect(version.to).to eq '1000.0.0' } + end end end diff --git a/spec/gem/release/version/number_spec.rb b/spec/gem/release/version/number_spec.rb index 083e03d..6dcbfcc 100644 --- a/spec/gem/release/version/number_spec.rb +++ b/spec/gem/release/version/number_spec.rb @@ -14,6 +14,11 @@ it { should eq '1.2.3.pre.17' } end + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -48,6 +53,11 @@ describe 'given 1.1.0' do let(:number) { '1.1.0' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -82,6 +92,11 @@ describe 'given 1.1.1' do let(:number) { '1.1.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -116,6 +131,11 @@ describe 'given 1.1.1.dev.1' do let(:number) { '1.1.1.dev.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -150,6 +170,11 @@ describe 'given 1.1.1-pre.1' do let(:number) { '1.1.1-pre.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -184,6 +209,11 @@ describe 'given 1.1.1-rc.1' do let(:number) { '1.1.1-rc.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -218,6 +248,11 @@ describe 'given 1.0.0-alpha.1' do let(:number) { '1.0.0-alpha.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :alpha' do let(:target) { :alpha } it { should eq '1.0.0-alpha.2' } @@ -242,6 +277,11 @@ describe 'given 1.1.1.pre.1' do let(:number) { '1.1.1.pre.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -276,6 +316,11 @@ describe 'given 1.1.1-rc.1' do let(:number) { '1.1.1-rc.1' } + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -307,6 +352,240 @@ end end + describe 'given epoch semver 1.0.0.0' do + let(:number) { '1.0.0.0' } + + describe 'given target: 1.2.3.9' do + let(:target) { '1.2.3.9' } + it { should eq '1.2.3.9' } + end + + describe 'given target: 1.0.0.0.pre.17' do + let(:target) { '1.0.0.0.pre.17' } + it { should eq '1.0.0.0.pre.17' } + end + + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '2.0.0.0' } + end + + describe 'given target: :major' do + let(:target) { :major } + it { should eq '1.1.0.0' } + end + + describe 'given target: :minor' do + let(:target) { :minor } + it { should eq '1.0.1.0' } + end + + describe 'given target: :patch' do + let(:target) { :patch } + it { should eq '1.0.0.1' } + end + + describe 'given target: nil (defaults to :patch)' do + let(:target) { nil } + it { should eq '1.0.0.1' } + end + + describe 'given target: :pre' do + let(:target) { :pre } + it { should eq '1.0.1.0.pre.1' } + end + + describe 'given target: :rc' do + let(:target) { :rc } + it { should eq '1.0.1.0.rc.1' } + end + end + + describe 'given epoch semver 1.2.0.0' do + let(:number) { '1.2.0.0' } + + describe 'given target: 1.2.3.9' do + let(:target) { '1.2.3.9' } + it { should eq '1.2.3.9' } + end + + describe 'given target: 1.2.0.0.pre.17' do + let(:target) { '1.2.0.0.pre.17' } + it { should eq '1.2.0.0.pre.17' } + end + + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '2.0.0.0' } + end + + describe 'given target: :major' do + let(:target) { :major } + it { should eq '1.3.0.0' } + end + + describe 'given target: :minor' do + let(:target) { :minor } + it { should eq '1.2.1.0' } + end + + describe 'given target: :patch' do + let(:target) { :patch } + it { should eq '1.2.0.1' } + end + + describe 'given target: nil (defaults to :patch)' do + let(:target) { nil } + it { should eq '1.2.0.1' } + end + + describe 'given target: :pre' do + let(:target) { :pre } + it { should eq '1.2.1.0.pre.1' } + end + + describe 'given target: :rc' do + let(:target) { :rc } + it { should eq '1.2.1.0.rc.1' } + end + end + + describe 'given epoch semver 1.2.3.0' do + let(:number) { '1.2.3.0' } + + describe 'given target: 1.2.3.9' do + let(:target) { '1.2.3.9' } + it { should eq '1.2.3.9' } + end + + describe 'given target: 1.2.3.0.pre.17' do + let(:target) { '1.2.3.0.pre.17' } + it { should eq '1.2.3.0.pre.17' } + end + + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '2.0.0.0' } + end + + describe 'given target: :major' do + let(:target) { :major } + it { should eq '1.3.0.0' } + end + + describe 'given target: :minor' do + let(:target) { :minor } + it { should eq '1.2.4.0' } + end + + describe 'given target: :patch' do + let(:target) { :patch } + it { should eq '1.2.3.1' } + end + + describe 'given target: nil (defaults to :patch)' do + let(:target) { nil } + it { should eq '1.2.3.1' } + end + + describe 'given target: :pre' do + let(:target) { :pre } + it { should eq '1.2.4.0.pre.1' } + end + + describe 'given target: :rc' do + let(:target) { :rc } + it { should eq '1.2.4.0.rc.1' } + end + end + + describe 'given epoch semver 1.2.3.4' do + let(:number) { '1.2.3.4' } + + describe 'given target: 1.2.3.9' do + let(:target) { '1.2.3.9' } + it { should eq '1.2.3.9' } + end + + describe 'given target: 1.2.3.4.pre.17' do + let(:target) { '1.2.3.4.pre.17' } + it { should eq '1.2.3.4.pre.17' } + end + + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '2.0.0.0' } + end + + describe 'given target: :major' do + let(:target) { :major } + it { should eq '1.3.0.0' } + end + + describe 'given target: :minor' do + let(:target) { :minor } + it { should eq '1.2.4.0' } + end + + describe 'given target: :patch' do + let(:target) { :patch } + it { should eq '1.2.3.5' } + end + + describe 'given target: nil (defaults to :patch)' do + let(:target) { nil } + it { should eq '1.2.3.5' } + end + + describe 'given target: :pre' do + let(:target) { :pre } + it { should eq '1.2.4.0.pre.1' } + end + + describe 'given target: :rc' do + let(:target) { :rc } + it { should eq '1.2.4.0.rc.1' } + end + end + + describe 'given epoch semver 1001.1.1' do + let(:number) { '1001.1.1' } + + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '2000.0.0' } + end + + describe 'given target: :major' do + let(:target) { :major } + it { should eq '1002.0.0' } + end + + describe 'given target: :minor' do + let(:target) { :minor } + it { should eq '1001.2.0' } + end + + describe 'given target: :patch' do + let(:target) { :patch } + it { should eq '1001.1.2' } + end + + describe 'given target: nil (defaults to :patch)' do + let(:target) { nil } + it { should eq '1001.1.2' } + end + + describe 'given target: :pre' do + let(:target) { :pre } + it { should eq '1001.2.0.pre.1' } + end + + describe 'given target: :rc' do + let(:target) { :rc } + it { should eq '1001.2.0.rc.1' } + end + end describe 'given 1.0' do let(:number) { '1.0' } @@ -316,6 +595,11 @@ it { should eq '1.2.3' } end + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' } @@ -355,6 +639,11 @@ it { should eq '1.2.3' } end + describe 'given target: :epoch' do + let(:target) { :epoch } + it { should eq '1000.0.0' } + end + describe 'given target: :major' do let(:target) { :major } it { should eq '2.0.0' }