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