lib/release_tools/version.rb (224 lines of code) (raw):
# frozen_string_literal: true
module ReleaseTools
class Version < String
VERSION_REGEX = %r{
\A(?<major>\d+)
\.(?<minor>\d+)
(\.(?<patch>\d+))?
(-(?<rc>rc(?<rc_number>\d*)))?
(-(?<internal>internal(?<internal_number>\d+)))?
(-\h+\.\h+)?
(-ee|\.ee\.\d+)?\z
}x
# GitLab releases monthly, so the minor version runs from 0 to 11
MAX_MINOR_VERSION = 11
def initialize(version_string)
super(version_string.strip)
if valid? && extract_from_version(:patch, fallback: nil).nil?
if rc?
super(to_rc(rc))
elsif internal?
super(to_internal(internal))
else
super(to_patch)
end
end
end
def ==(other)
if other.respond_to?(:to_ce)
to_ce.eql?(other.to_ce)
else
super
end
end
def <=>(other)
return nil unless other.is_a?(Version)
return 0 if self == other
if major > other.major ||
(major >= other.major && minor > other.minor) ||
(major >= other.major && minor >= other.minor && patch > other.patch) ||
(major >= other.major && minor >= other.minor && patch >= other.patch && other.rc?)
compare_rc_versions(other)
else
compare_internal_versions(other)
end
end
def ee?
end_with?('-ee')
end
def milestone_name
to_minor
end
def monthly?
patch.zero? && !rc? && !internal?
end
def patch?
patch.positive? && !internal?
end
def minor?
!patch? && minor.positive?
end
def major?
!patch? && !minor? && major.positive?
end
def major
@major ||= extract_from_version(:major).to_i
end
def minor
@minor ||= extract_from_version(:minor).to_i
end
def patch
@patch ||= extract_from_version(:patch).to_i
end
def rc
return unless rc?
@rc ||= extract_from_version(:rc_number).to_i
end
def internal
return unless internal?
@internal ||= extract_from_version(:internal_number).to_i
end
# rubocop:disable Naming/MemoizedInstanceVariableName
def rc?
return @is_rc if defined?(@is_rc)
@is_rc = extract_from_version(:rc, fallback: false)
end
def internal?
return @is_internal if defined?(@is_internal)
@is_internal = extract_from_version(:internal, fallback: false)
end
# rubocop:enable Naming/MemoizedInstanceVariableName
def version?
self =~ self.class::VERSION_REGEX
end
def release?
valid? && !rc? && !ee? && !internal?
end
def to_upcoming_pre_release
"#{next_milestone}.0-pre"
end
def next_milestone
ProductMilestone.after(self).title
end
def next_major
"#{major + 1}.0.0"
end
def next_minor
if minor >= MAX_MINOR_VERSION
"#{major + 1}.0.0" # Roll over to next major version
else
"#{major}.#{minor + 1}.0"
end
end
def next_internal
if internal
"#{major}.#{minor}.#{patch}-internal#{internal + 1}"
else
"#{previous_patch}-internal0"
end
end
def previous_minor
if minor.positive?
"#{major}.#{minor - 1}.0"
else
# When we are creating a new major release, we can not retrieve the
# previous stable branch from the current version. Instead we must
# find the last minor version of the previous major release.
version = Versions.last_version_for_major(major - 1)
unless version
raise "The last version before #{self} could not be found"
end
version
end
end
def previous_patch
return unless patch?
new_patch = self.class.new("#{major}.#{minor}.#{patch - 1}")
ee? ? new_patch.to_ee : new_patch
end
def next_patch
new_patch = self.class.new("#{major}.#{minor}.#{patch + 1}")
ee? ? new_patch.to_ee : new_patch
end
def stable_branch(ee: false)
to_minor.tr('.', '-').tap do |prefix|
if ee || ee?
prefix << '-stable-ee'
else
prefix << '-stable'
end
end
end
def tag(ee: false)
tag_for(self, ee: ee)
end
def previous_tag(ee: false)
return unless patch?
return if rc?
tag_for(previous_patch, ee: ee)
end
# Convert the current version to CE if it isn't already
def to_ce
return self unless ee?
self.class.new(to_s.gsub(/-ee$/, ''))
end
# Convert the current version to EE if it isn't already
def to_ee
return self if ee?
self.class.new("#{self}-ee")
end
def to_minor
"#{major}.#{minor}"
end
def to_omnibus(ee: false)
str = "#{to_patch}+"
str << "rc#{rc}." if rc?
str << "internal#{internal}." if internal?
str << (ee ? 'ee' : 'ce')
str << '.0'
end
def to_docker(ee: false)
to_omnibus(ee: ee).tr('+', '-')
end
def to_patch
"#{major}.#{minor}.#{patch}"
end
def to_normalized_version
if rc?
"#{to_patch}-rc#{rc}"
elsif internal?
"#{to_patch}-internal#{internal}"
else
to_patch
end
end
def to_rc(number = 1)
"#{to_patch}-rc#{number}".tap do |version|
version << '-ee' if ee?
end
end
def to_internal(number = 0)
"#{to_patch}-internal#{number}"
end
def slug
self.class.new(
downcase
.gsub(/[^a-z0-9]/, '-')[0..62]
.gsub(/(\A-+|-+\z)/, '')
)
end
def valid?
self.class::VERSION_REGEX.match?(self)
end
private
def tag_for(version, ee: false)
version = version.to_ee if ee
"v#{version}"
end
def extract_from_version(part, fallback: 0)
match_data = self.class::VERSION_REGEX.match(self)
if match_data && match_data.names.include?(part.to_s) && match_data[part]
String.new(match_data[part])
else
fallback
end
end
def compare_rc_versions(other)
rc? && other.rc? ? (rc - other.rc) : 1
end
def compare_internal_versions(other)
internal? && other.internal? ? (internal - other.internal) : -1
end
end
end