Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
# Changelog

## UNRELEASED

### Added

- Add support for Epoch semantic versioning
- `gem bump -v epoch` bumps `EXXX.Y.Z` to `<E+1>000.0.0`
- `gem bump -v epoch` bumps `E.X.Y.Z` to `<E+1>.0.0.0`
(PR: https://github.com/svenfuchs/gem-release/pull/114)

## v2.2.4 - 2024-12-28

### Changed
Expand Down
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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.
Expand All @@ -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
Expand Down
6 changes: 4 additions & 2 deletions lib/gem/release/cmds/bump.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand All @@ -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',
Expand Down
15 changes: 12 additions & 3 deletions lib/gem/release/files/version.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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 = /^(?<epoch>\d+)\./
SEMVER = /(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/
STAGE = /\.?(?<stage>.*)(?<stage_num>\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
Expand Down
2 changes: 1 addition & 1 deletion lib/gem/release/templates/version.rb
Original file line number Diff line number Diff line change
@@ -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" } %>
68 changes: 43 additions & 25 deletions lib/gem/release/version/number.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,22 @@ module Gem
module Release
module Version
class Number < Struct.new(:number, :target)
NUMBER = /^(\d+)\.?(\d+)?\.?(\d+)?(\-|\.)?(\w+)?\.?(\d+)?$/
EPOCH_NUMBER = /^(?<epoch>\d+)\.(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)(?<stage_delim>\-|\.)?(?<stage>\w+)?\.?(?<stage_num>\d+)?$/
NUMBER = /^(?<major>\d+)\.?(?<minor>\d+)?\.?(?<patch>\d+)?(?<stage_delim>\-|\.)?(?<stage>\w+)?\.?(?<stage_num>\d+)?$/
PRE_RELEASE = /^(\d+)\.(\d+)\.(\d+)\.?(.*)(\d+)$/

STAGES = %i(alpha beta pre rc)

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
Expand All @@ -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
Expand All @@ -53,20 +68,20 @@ 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)
targets.include?(target)
end

def to_release?
to?(:major, :minor, :patch)
to?(:epoch, :major, :minor, :patch)
end

def fresh_pre_release?
Expand All @@ -86,7 +101,7 @@ def same_stage?
end

def from_stage
parts[4]
parts[:stage]
end

def target
Expand All @@ -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
Expand Down
8 changes: 8 additions & 0 deletions spec/gem/release/cmds/bump_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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' }
Expand Down
5 changes: 5 additions & 0 deletions spec/gem/release/files/version_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Loading