module ThorSCMVersion class << self # Figures out whether the repository is managed by git. If not, use p4. # # @return [#kind_of? ScmVersion] def versioner if(File.directory?(".git")) return GitVersion else return P4Version end end end # author Josiah Kiehl <josiah@skirmisher.net> class ScmVersion include Comparable # Tags not matching this format will not show up in the tags list # # Examples: # 1.2.3 #=> valid # 1.2.3.4 #=> invalid # 1.2.3-alpha.1 #=> valid # 1.2.3-alpha #=> invalid VERSION_FORMAT = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)-?(?<prerelease>#{Prerelease::FORMAT})?(\+build\.)?(?<build>\d+)?$/ # Default file to write the current version to VERSION_FILENAME = 'VERSION' class << self # Retrieve all versions from the repository contained at path # # @param [String] path Path to the repository # @return [Array<ScmVersion>] def from_path(path = '.') retrieve_tags all_from_path(path).first || new(0,0,1) end # Create an ScmVersion object from a tag # # @param [String] tag # @return [ScmVersion] def from_tag(tag) matchdata = tag.match VERSION_FORMAT new(matchdata[:major], matchdata[:minor], matchdata[:patch], Prerelease.from_string(matchdata[:prerelease]), matchdata[:build]) end # In distributed SCMs, tags must be fetched from the server to # ensure that the latest tags are being used to calculate the # next version. def retrieve_tags # noop end end attr_accessor :major attr_accessor :minor attr_accessor :patch attr_accessor :prerelease attr_accessor :build def initialize(major = 0, minor = 0, patch = 0, prerelease = nil, build = 1) @major = major.to_i @minor = minor.to_i @patch = patch.to_i @prerelease = prerelease @build = build.nil? ? 1 : build.to_i end # Bumps the version in place # # @param [Symbol] type Type of bump to be performed # @param [String] prerelease_type Type of prerelease to bump to when doing a :prerelease bump # @return [ScmVersion] def bump!(type, prerelease_type = nil) case type.to_sym when :auto self.auto_bump when :major self.major += 1 when :minor self.minor += 1 when :patch self.patch += 1 when :prerelease if self.prerelease if prerelease_type.nil? || prerelease_type == self.prerelease.type self.prerelease += 1 else self.prerelease = Prerelease.new(prerelease_type) end else self.patch += 1 self.prerelease = Prerelease.new(prerelease_type) end when :build self.build += 1 else raise "Invalid release type: #{type}. Valid types are: major, minor, patch, or auto" end raise "Version: #{self.to_s} is less than or equal to the existing version." if self <= self.class.from_path reset_for type unless type == :auto self end # Reset levels lower than the type being reset for # # @param [Symbol] type Type under which all segments are to be reset def reset_for(type) matched = false [[:major, Proc.new { self.minor = 0 }], [:minor, Proc.new { self.patch = 0 }], [:patch, Proc.new { self.prerelease = nil }], [:prerelease, Proc.new { self.build = 1 }]].each do |matcher, reset_proc| next unless matched or type.to_sym == matcher matched = true reset_proc.call end self end # Write the version to the passed in file paths # # @param [Array<String>] files List of files to write def write_version(files = [ScmVersion::VERSION_FILENAME]) files.each do |ver_file| File.open(ver_file, 'w+') do |f| f.write self.to_s end end self end # Create the tag in the SCM corresponding to the version contained in self. # Abstract method. Must be implemented by subclasses. def tag raise NotImplementedError end # Perform a bump by reading recent commit messages in the SCM # Abstract method. Must be implemented by subclasses. def auto_bump(prerelease_type = nil) raise NotImplementedError end def to_s s = "#{major}.#{minor}.#{patch}" s << "-#{prerelease}" unless prerelease.nil? s << "+build.#{build}" unless build < 2 s end alias_method :version, :to_s def <=>(other) return unless other.is_a?(self.class) return 0 if self.version == other.version [:major, :minor, :patch, :prerelease, :build].each do |segment| next if self.send(segment) == other.send(segment) return 1 if self.send(segment) > other.send(segment) return -1 if self.send(segment) < other.send(segment) end return 0 end end end