class Bundler::GemVersionPromoter - bundler: Ruby Standard Library Documentation

# class Bundler::GemVersionPromoter

This class contains all of the logic for determining the next version of a `Gem` to update to based on the requested level (patch, minor, major). Primarily designed to work with `Resolver` which will provide it the list of available dependency versions as found in its index, before returning it to to the resolution engine to select the best version.

### Attributes

level[R]
pre[RW]
strict[RW]

By default, strict is false, meaning every available version of a gem is returned from sort_versions. The order gives preference to the requested level (:patch, :minor, :major) but in complicated requirement cases some gems will by necessity be promoted past the requested level, or even reverted to older versions.

If strict is set to true, the results from `sort_versions` will be truncated, eliminating any version outside the current level scope. This can lead to unexpected outcomes or even VersionConflict exceptions that report a version of a gem not existing for versions that indeed do existing in the referenced source.

### Public Class Methods

new() click to toggle source

Creates a `GemVersionPromoter` instance.

@return [GemVersionPromoter]

    # File bundler/gem_version_promoter.rb, line 29
    def initialize
      @level = :major
      @strict = false
      @pre = false
    end

### Public Instance Methods

filter_versions(package, specs) click to toggle source

Given a `Resolver::Package` and an Array of Specifications of available versions for a gem, this method will truncate the Array if strict is true. That means filtering out downgrades from the version currently locked, and filtering out upgrades that go past the selected level (major, minor, or patch). @param package [Resolver::Package] The package being resolved. @param specs [Specification] An array of Specifications for the package. @return [Specification] A new instance of the Specification Array

    truncated.

    # File bundler/gem_version_promoter.rb, line 105
    def filter_versions(package, specs)
      return specs unless strict

      locked_version = package.locked_version
      return specs if locked_version.nil? || major?

      specs.select do |spec|
        gsv = spec.version

        must_match = minor? ? [0] : [0, 1]

        all_match = must_match.all? {|idx| gsv.segments[idx] == locked_version.segments[idx] }
        all_match && gsv >= locked_version
      end
    end

level=(value) click to toggle source

@param value [Symbol] One of three Symbols: :major, :minor or :patch.

    # File bundler/gem_version_promoter.rb, line 36
    def level=(value)
      v = case value
          when String, Symbol
            value.to_sym
      end

      raise ArgumentError, "Unexpected level #{v}. Must be :major, :minor or :patch" unless [:major, :minor, :patch].include?(v)
      @level = v
    end

major?() click to toggle source

@return [bool] Convenience method for testing value of level variable.

    # File bundler/gem_version_promoter.rb, line 82
    def major?
      level == :major
    end

minor?() click to toggle source

@return [bool] Convenience method for testing value of level variable.

    # File bundler/gem_version_promoter.rb, line 87
    def minor?
      level == :minor
    end

pre?() click to toggle source

@return [bool] Convenience method for testing value of pre variable.

    # File bundler/gem_version_promoter.rb, line 92
    def pre?
      pre == true
    end

sort_versions(package, specs) click to toggle source

Given a `Resolver::Package` and an Array of Specifications of available versions for a gem, this method will return the Array of Specifications sorted in an order to give preference to the current level (:major, :minor or :patch) when resolution is deciding what versions best resolve all dependencies in the bundle. @param package [Resolver::Package] The package being resolved. @param specs [Specification] An array of Specifications for the package. @return [Specification] A new instance of the Specification Array sorted.

    # File bundler/gem_version_promoter.rb, line 54
    def sort_versions(package, specs)
      locked_version = package.locked_version

      result = specs.sort do |a, b|
        unless package.prerelease_specified? || pre?
          a_pre = a.prerelease?
          b_pre = b.prerelease?

          next 1 if a_pre && !b_pre
          next -1 if b_pre && !a_pre
        end

        if major? || locked_version.nil?
          b <=> a
        elsif either_version_older_than_locked?(a, b, locked_version)
          b <=> a
        elsif segments_do_not_match?(a, b, :major)
          a <=> b
        elsif !minor? && segments_do_not_match?(a, b, :minor)
          a <=> b
        else
          b <=> a
        end
      end
      post_sort(result, package.unlock?, locked_version)
    end

### Private Instance Methods

either_version_older_than_locked?(a, b, locked_version) click to toggle source

    # File bundler/gem_version_promoter.rb, line 123
    def either_version_older_than_locked?(a, b, locked_version)
      a.version < locked_version || b.version < locked_version
    end

move_version_to_beginning(result, version) click to toggle source

    # File bundler/gem_version_promoter.rb, line 144
    def move_version_to_beginning(result, version)
      move, keep = result.partition {|s| s.version.to_s == version.to_s }
      move.concat(keep)
    end

post_sort(result, unlock, locked_version) click to toggle source

Specific version moves can’t always reliably be done during sorting as not all elements are compared against each other.

    # File bundler/gem_version_promoter.rb, line 134
    def post_sort(result, unlock, locked_version)
      # default :major behavior in Bundler does not do this
      return result if major?
      if unlock || locked_version.nil?
        result
      else
        move_version_to_beginning(result, locked_version)
      end
    end

segments_do_not_match?(a, b, level) click to toggle source

    # File bundler/gem_version_promoter.rb, line 127
    def segments_do_not_match?(a, b, level)
      index = [:major, :minor].index(level)
      a.segments[index] != b.segments[index]
    end