# 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.

### Constants

DEBUG

### Attributes

level[R]
locked_specs[R]
prerelease_specified[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 by 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.

unlock_gems[R]

### Public Class Methods

new(locked_specs = SpecSet.new([]), unlock_gems = []) click to toggle source

Given a list of `locked_specs` and a list of gems to unlock creates a `GemVersionPromoter` instance.

@param `locked_specs` [SpecSet] All current locked specs. Unlike `Definition`

    where this list is empty if all gems are being updated, this should
    always be populated for all gems so this class can properly function.

@param `unlock_gems` [String] List of gem names being unlocked. If empty,

    all gems will be considered unlocked.

@return [GemVersionPromoter]

    # File lib/bundler/gem_version_promoter.rb, line 38
    def initialize(locked_specs = SpecSet.new([]), unlock_gems = [])
      @level = :major
      @strict = false
      @locked_specs = locked_specs
      @unlock_gems = unlock_gems
      @sort_versions = {}
      @prerelease_specified = {}
    end

### Public Instance Methods

level=(value) click to toggle source

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

    # File lib/bundler/gem_version_promoter.rb, line 48
    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 lib/bundler/gem_version_promoter.rb, line 92
    def major?
      level == :major
    end

minor?() click to toggle source

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

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

sort_versions(dep, spec_groups) click to toggle source

Given a `Dependency` and an `Array` of SpecGroups of available versions for a gem, this method will return the `Array` of SpecGroups sorted (and possibly truncated if strict is true) 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 dep [Dependency] The `Dependency` of the gem. @param spec_groups [SpecGroup] An array of SpecGroups for the same gem

    named in the @dep param.

@return [SpecGroup] A new instance of the SpecGroup `Array` sorted and

    possibly filtered.

    # File lib/bundler/gem_version_promoter.rb, line 68
    def sort_versions(dep, spec_groups)
      before_result = "before sort_versions: #{debug_format_result(dep, spec_groups).inspect}" if DEBUG

      @sort_versions[dep] ||= begin
        gem_name = dep.name

        # An Array per version returned, different entries for different platforms.
        # We only need the version here so it's ok to hard code this to the first instance.
        locked_spec = locked_specs[gem_name].first

        if strict
          filter_dep_specs(spec_groups, locked_spec)
        else
          sort_dep_specs(spec_groups, locked_spec)
        end.tap do |specs|
          if DEBUG
            STDERR.puts before_result
            STDERR.puts " after sort_versions: #{debug_format_result(dep, specs).inspect}"
          end
        end
      end
    end

### Private Instance Methods

debug_format_result(dep, spec_groups) click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 183
    def debug_format_result(dep, spec_groups)
      a = [dep.to_s,
           spec_groups.map {|sg| [sg.version, sg.dependencies_for_activated_platforms.map {|dp| [dp.name, dp.requirement.to_s] }] }]
      last_map = a.last.map {|sg_data| [sg_data.first.version, sg_data.last.map {|aa| aa.join(" ") }] }
      [a.first, last_map, level, strict ? :strict : :not_strict]
    end

either_version_older_than_locked() click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 153
    def either_version_older_than_locked
      @a_ver < @locked_version || @b_ver < @locked_version
    end

filter_dep_specs(spec_groups, locked_spec) click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 103
    def filter_dep_specs(spec_groups, locked_spec)
      res = spec_groups.select do |spec_group|
        if locked_spec && !major?
          gsv = spec_group.version
          lsv = locked_spec.version

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

          matches = must_match.map {|idx| gsv.segments[idx] == lsv.segments[idx] }
          (matches.uniq == [true]) ? (gsv >= lsv) : false
        else
          true
        end
      end

      sort_dep_specs(res, locked_spec)
    end

move_version_to_end(result, version) click to toggle source

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

post_sort(result) 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 lib/bundler/gem_version_promoter.rb, line 168
    def post_sort(result)
      # default :major behavior in Bundler does not do this
      return result if major?
      if unlocking_gem?
        result
      else
        move_version_to_end(result, @locked_version)
      end
    end

segments_do_not_match(level) click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 157
    def segments_do_not_match(level)
      index = [:major, :minor].index(level)
      @a_ver.segments[index] != @b_ver.segments[index]
    end

sort_dep_specs(spec_groups, locked_spec) click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 121
    def sort_dep_specs(spec_groups, locked_spec)
      return spec_groups unless locked_spec
      @gem_name = locked_spec.name
      @locked_version = locked_spec.version

      result = spec_groups.sort do |a, b|
        @a_ver = a.version
        @b_ver = b.version

        unless @prerelease_specified[@gem_name]
          a_pre = @a_ver.prerelease?
          b_pre = @b_ver.prerelease?

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

        if major?
          @a_ver <=> @b_ver
        elsif either_version_older_than_locked
          @a_ver <=> @b_ver
        elsif segments_do_not_match(:major)
          @b_ver <=> @a_ver
        elsif !minor? && segments_do_not_match(:minor)
          @b_ver <=> @a_ver
        else
          @a_ver <=> @b_ver
        end
      end
      post_sort(result)
    end

unlocking_gem?() click to toggle source

    # File lib/bundler/gem_version_promoter.rb, line 162
    def unlocking_gem?
      unlock_gems.empty? || unlock_gems.include?(@gem_name)
    end