# frozen_string_literal: truemoduleBundler# 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.classGemVersionPromoterattr_reader:levelattr_accessor:pre# 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.attr_accessor:strict# Creates a GemVersionPromoter instance.## @return [GemVersionPromoter]definitialize@level=:major@strict=false@pre=falseend# @param value [Symbol] One of three Symbols: :major, :minor or :patch.deflevel=(value)v=casevaluewhenString,Symbolvalue.to_symendraiseArgumentError,"Unexpected level #{v}. Must be :major, :minor or :patch"unless[:major,:minor,:patch].include?(v)@level=vend# 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.defsort_versions(package,specs)locked_version=package.locked_versionresult=specs.sortdo|a,b|unlesspackage.prerelease_specified?||pre?a_pre=a.prerelease?b_pre=b.prerelease?next1ifa_pre&&!b_prenext-1ifb_pre&&!a_preendifmajor?||locked_version.nil?b<=>aelsifeither_version_older_than_locked?(a,b,locked_version)b<=>aelsifsegments_do_not_match?(a,b,:major)a<=>belsif!minor?&&segments_do_not_match?(a,b,:minor)a<=>belseb<=>aendendpost_sort(result,package.unlock?,locked_version)end# @return [bool] Convenience method for testing value of level variable.defmajor?level==:majorend# @return [bool] Convenience method for testing value of level variable.defminor?level==:minorend# @return [bool] Convenience method for testing value of pre variable.defpre?pre==trueend# 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.deffilter_versions(package,specs)returnspecsunlessstrictlocked_version=package.locked_versionreturnspecsiflocked_version.nil?||major?specs.selectdo|spec|gsv=spec.versionmust_match=minor??[0]:[0,1]all_match=must_match.all?{|idx|gsv.segments[idx]==locked_version.segments[idx]}all_match&&gsv>=locked_versionendendprivatedefeither_version_older_than_locked?(a,b,locked_version)a.version<locked_version||b.version<locked_versionenddefsegments_do_not_match?(a,b,level)index=[:major,:minor].index(level)a.segments[index]!=b.segments[index]end# Specific version moves can't always reliably be done during sorting# as not all elements are compared against each other.defpost_sort(result,unlock,locked_version)# default :major behavior in Bundler does not do thisreturnresultifmajor?ifunlock||locked_version.nil?resultelsemove_version_to_beginning(result,locked_version)endenddefmove_version_to_beginning(result,version)move,keep=result.partition{|s|s.version.to_s==version.to_s}move.concat(keep)endendend