VYPR
High severityNVD Advisory· Published Apr 29, 2021· Updated Aug 4, 2024

CVE-2020-36327

CVE-2020-36327

Description

Bundler 1.16.0 through 2.2.9 and 2.2.11 through 2.2.16 sometimes chooses a dependency source based on the highest gem version number, which means that a rogue gem found at a public source may be chosen, even if the intended choice was a private gem that is a dependency of another private gem that is explicitly depended on by the application. NOTE: it is not correct to use CVE-2021-24105 for every "Dependency Confusion" issue in every product.

AI Insight

LLM-synthesized narrative grounded in this CVE's description and references.

Bundler 1.16.0 through 2.2.9 and 2.2.11 through 2.2.16 allows dependency confusion by choosing source based on highest version for transitive dependencies.

Vulnerability

Bundler versions 1.16.0 through 2.2.9 and 2.2.11 through 2.2.16 contain a vulnerability in dependency resolution that allows dependency confusion attacks [1]. When a private gem is declared with an explicit source, its transitive dependencies (implicit gems) may be resolved by falling back to public sources if not found in the parent's source, and the highest version number is chosen regardless of source [1]. This affects applications with implicit private dependencies.

Exploitation

An attacker must publish a gem on a public source (e.g., rubygems.org) with the same name as a private transitive dependency and a version higher than any private version. No authentication on the victim side is required beyond normal gem installation [1]. The victim's Bundler, during bundle install, resolves the transitive dependency by selecting the highest version from any source, thus pulling the attacker's gem.

Impact

Successful exploitation allows the attacker to execute arbitrary code in the context of the application when the malicious gem is loaded. This can lead to information disclosure, remote code execution, or further compromise of the development or production environment [1].

Mitigation

The vulnerability is fixed in Bundler version 2.2.18, released in May 2021 [1] [2]. The fix modifies source priority logic to prevent fallback for implicit dependencies [3]. Users should upgrade to 2.2.18 or later. As a workaround, explicitly declare all transitive private gems with source scoping, but upgrade is recommended. No known exploitation as part of KEV.

AI Insight generated on May 21, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.

Affected packages

Versions sourced from the GitHub Security Advisory.

PackageAffected versionsPatched versions
bundlerRubyGems
>= 1.16.0, < 2.2.102.2.10
bundlerRubyGems
>= 2.2.11, < 2.2.182.2.18

Affected products

53

Patches

2
cc7c33372189

Merge pull request #4377 from rubygems/release/bundler_2.2.10_rubygems_3.2.10

https://github.com/rubygems/rubygemsDavid RodríguezFeb 15, 2021via osv
38 files changed · +649 430
  • bundler/CHANGELOG.md+15 0 modified
    @@ -1,3 +1,18 @@
    +# 2.2.10 (February 15, 2021)
    +
    +## Security fixes:
    +
    +  - Fix source priority for transitive dependencies and split lockfile rubygems source sections [#3655](https://github.com/rubygems/rubygems/pull/3655)
    +
    +## Bug fixes:
    +
    +  - Fix adding platforms to lockfile sometimes conflicting on ruby requirements [#4371](https://github.com/rubygems/rubygems/pull/4371)
    +  - Fix bundler sometimes choosing ruby variants over java ones [#4367](https://github.com/rubygems/rubygems/pull/4367)
    +
    +## Documentation:
    +
    +  - Update man pages to reflect to new default for bundle install jobs [#4188](https://github.com/rubygems/rubygems/pull/4188)
    +
     # 2.2.9 (February 8, 2021)
     
     ## Enhancements:
    
  • bundler/lib/bundler/definition.rb+49 25 modified
    @@ -106,6 +106,19 @@ def initialize(lockfile, dependencies, sources, unlock, ruby_version = nil, opti
             @locked_platforms = []
           end
     
    +      @locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
    +      @disable_multisource = !Bundler.frozen_bundle? || @locked_gem_sources.none? {|s| s.remotes.size > 1 }
    +
    +      unless @disable_multisource
    +        msg = "Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. " \
    +          "You should regenerate your lockfile in a non frozen environment."
    +
    +        Bundler::SharedHelpers.major_deprecation 2, msg
    +
    +        @sources.allow_multisource!
    +        @locked_gem_sources.each(&:allow_multisource!)
    +      end
    +
           @unlock[:gems] ||= []
           @unlock[:sources] ||= []
           @unlock[:ruby] ||= if @ruby_version && locked_ruby_version_object
    @@ -145,6 +158,14 @@ def gem_version_promoter
           end
         end
     
    +    def disable_multisource?
    +      @disable_multisource
    +    end
    +
    +    def allow_multisource!
    +      @disable_multisource = false
    +    end
    +
         def resolve_with_cache!
           raise "Specs already loaded" if @specs
           sources.cached!
    @@ -264,7 +285,7 @@ def resolve
               # Run a resolve against the locally available gems
               Bundler.ui.debug("Found changes from the lockfile, re-resolving dependencies because #{change_reason}")
               expanded_dependencies = expand_dependencies(dependencies + metadata_dependencies, @remote)
    -          Resolver.resolve(expanded_dependencies, index, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
    +          Resolver.resolve(expanded_dependencies, source_requirements, last_resolve, gem_version_promoter, additional_base_requirements_for_resolve, platforms)
             end
           end
         end
    @@ -530,6 +551,9 @@ def find_indexed_specs(current_spec)
         attr_reader :sources
         private :sources
     
    +    attr_reader :locked_gem_sources
    +    private :locked_gem_sources
    +
         def nothing_changed?
           !@source_changes && !@dependency_changes && !@new_platform && !@path_changes && !@local_changes && !@locked_specs_incomplete_for_platform
         end
    @@ -654,21 +678,20 @@ def converge_path_sources_to_gemspec_sources
         end
     
         def converge_rubygems_sources
    -      return false if Bundler.feature_flag.disable_multisource?
    +      return false if disable_multisource?
     
    -      changes = false
    +      return false if locked_gem_sources.empty?
     
    -      # Get the RubyGems sources from the Gemfile.lock
    -      locked_gem_sources = @locked_sources.select {|s| s.is_a?(Source::Rubygems) }
           # Get the RubyGems remotes from the Gemfile
           actual_remotes = sources.rubygems_remotes
    +      return false if actual_remotes.empty?
    +
    +      changes = false
     
           # If there is a RubyGems source in both
    -      if !locked_gem_sources.empty? && !actual_remotes.empty?
    -        locked_gem_sources.each do |locked_gem|
    -          # Merge the remotes from the Gemfile into the Gemfile.lock
    -          changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
    -        end
    +      locked_gem_sources.each do |locked_gem|
    +        # Merge the remotes from the Gemfile into the Gemfile.lock
    +        changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
           end
     
           changes
    @@ -893,30 +916,18 @@ def source_requirements
           # Record the specs available in each gem's source, so that those
           # specs will be available later when the resolver knows where to
           # look for that gemspec (or its dependencies)
    -      default = sources.default_source
    -      source_requirements = { :default => default }
    -      default = nil unless Bundler.feature_flag.disable_multisource?
    -      dependencies.each do |dep|
    -        next unless source = dep.source || default
    -        source_requirements[dep.name] = source
    -      end
    +      source_requirements = { :default => sources.default_source }.merge(dependency_source_requirements)
           metadata_dependencies.each do |dep|
             source_requirements[dep.name] = sources.metadata_source
           end
    +      source_requirements[:global] = index unless disable_multisource?
           source_requirements[:default_bundler] = source_requirements["bundler"] || source_requirements[:default]
           source_requirements["bundler"] = sources.metadata_source # needs to come last to override
           source_requirements
         end
     
         def pinned_spec_names(skip = nil)
    -      pinned_names = []
    -      default = Bundler.feature_flag.disable_multisource? && sources.default_source
    -      @dependencies.each do |dep|
    -        next unless dep_source = dep.source || default
    -        next if dep_source == skip
    -        pinned_names << dep.name
    -      end
    -      pinned_names
    +      dependency_source_requirements.reject {|_, source| source == skip }.keys
         end
     
         def requested_groups
    @@ -973,5 +984,18 @@ def equivalent_rubygems_remotes?(source)
     
           Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
         end
    +
    +    def dependency_source_requirements
    +      @dependency_source_requirements ||= begin
    +        source_requirements = {}
    +        default = disable_multisource? && sources.default_source
    +        dependencies.each do |dep|
    +          dep_source = dep.source || default
    +          next unless dep_source
    +          source_requirements[dep.name] = dep_source
    +        end
    +        source_requirements
    +      end
    +    end
       end
     end
    
  • bundler/lib/bundler/dsl.rb+38 25 modified
    @@ -24,6 +24,9 @@ def self.evaluate(gemfile, lockfile, unlock)
         def initialize
           @source               = nil
           @sources              = SourceList.new
    +
    +      @global_rubygems_sources = []
    +
           @git_sources          = {}
           @dependencies         = []
           @groups               = []
    @@ -45,6 +48,7 @@ def eval_gemfile(gemfile, contents = nil)
           @gemfiles << expanded_gemfile_path
           contents ||= Bundler.read_file(@gemfile.to_s)
           instance_eval(contents.dup.tap{|x| x.untaint if RUBY_VERSION < "2.7" }, gemfile.to_s, 1)
    +      check_primary_source_safety
         rescue Exception => e # rubocop:disable Lint/RescueException
           message = "There was an error " \
             "#{e.is_a?(GemfileEvalError) ? "evaluating" : "parsing"} " \
    @@ -164,8 +168,7 @@ def source(source, *args, &blk)
           elsif block_given?
             with_source(@sources.add_rubygems_source("remotes" => source), &blk)
           else
    -        check_primary_source_safety(@sources)
    -        @sources.global_rubygems_source = source
    +        @global_rubygems_sources << source
           end
         end
     
    @@ -183,24 +186,14 @@ def git_source(name, &block)
         end
     
         def path(path, options = {}, &blk)
    -      unless block_given?
    -        msg = "You can no longer specify a path source by itself. Instead, \n" \
    -              "either use the :path option on a gem, or specify the gems that \n" \
    -              "bundler should find in the path source by passing a block to \n" \
    -              "the path method, like: \n\n" \
    -              "    path 'dir/containing/rails' do\n" \
    -              "      gem 'rails'\n" \
    -              "    end\n\n"
    -
    -        raise DeprecatedError, msg if Bundler.feature_flag.disable_multisource?
    -        SharedHelpers.major_deprecation(2, msg.strip)
    -      end
    -
           source_options = normalize_hash(options).merge(
             "path" => Pathname.new(path),
             "root_path" => gemfile_root,
             "gemspec" => gemspecs.find {|g| g.name == options["name"] }
           )
    +
    +      source_options["global"] = true unless block_given?
    +
           source = @sources.add_path_source(source_options)
           with_source(source, &blk)
         end
    @@ -279,6 +272,11 @@ def method_missing(name, *args)
           raise GemfileError, "Undefined local variable or method `#{name}' for Gemfile"
         end
     
    +    def check_primary_source_safety
    +      check_path_source_safety
    +      check_rubygems_source_safety
    +    end
    +
         private
     
         def add_git_sources
    @@ -440,25 +438,40 @@ def normalize_source(source)
           end
         end
     
    -    def check_primary_source_safety(source_list)
    -      return if source_list.rubygems_primary_remotes.empty? && source_list.global_rubygems_source.nil?
    +    def check_path_source_safety
    +      return if @sources.global_path_source.nil?
    +
    +      msg = "You can no longer specify a path source by itself. Instead, \n" \
    +              "either use the :path option on a gem, or specify the gems that \n" \
    +              "bundler should find in the path source by passing a block to \n" \
    +              "the path method, like: \n\n" \
    +              "    path 'dir/containing/rails' do\n" \
    +              "      gem 'rails'\n" \
    +              "    end\n\n"
     
    -      if Bundler.feature_flag.disable_multisource?
    +      SharedHelpers.major_deprecation(2, msg.strip)
    +    end
    +
    +    def check_rubygems_source_safety
    +      if @global_rubygems_sources.size <= 1
    +        @sources.global_rubygems_source = @global_rubygems_sources.first
    +        return
    +      end
    +
    +      @global_rubygems_sources.each do |source|
    +        @sources.add_rubygems_remote(source)
    +      end
    +
    +      if Bundler.feature_flag.bundler_3_mode?
             msg = "This Gemfile contains multiple primary sources. " \
               "Each source after the first must include a block to indicate which gems " \
               "should come from that source"
    -        unless Bundler.feature_flag.bundler_2_mode?
    -          msg += ". To downgrade this error to a warning, run " \
    -            "`bundle config unset disable_multisource`"
    -        end
             raise GemfileEvalError, msg
           else
             Bundler::SharedHelpers.major_deprecation 2, "Your Gemfile contains multiple primary sources. " \
               "Using `source` more than once without a block is a security risk, and " \
               "may result in installing unexpected gems. To resolve this warning, use " \
    -          "a block to indicate which gems should come from the secondary source. " \
    -          "To upgrade this warning to an error, run `bundle config set --local " \
    -          "disable_multisource true`."
    +          "a block to indicate which gems should come from the secondary source."
           end
         end
     
    
  • bundler/lib/bundler/feature_flag.rb+0 1 modified
    @@ -32,7 +32,6 @@ def self.settings_method(name, key, &default)
         settings_flag(:cache_all) { bundler_3_mode? }
         settings_flag(:default_install_uses_path) { bundler_3_mode? }
         settings_flag(:deployment_means_frozen) { bundler_3_mode? }
    -    settings_flag(:disable_multisource) { bundler_3_mode? }
         settings_flag(:forget_cli_options) { bundler_3_mode? }
         settings_flag(:global_gem_cache) { bundler_3_mode? }
         settings_flag(:only_update_to_newer_versions) { bundler_3_mode? }
    
  • bundler/lib/bundler/inline.rb+1 0 modified
    @@ -50,6 +50,7 @@ def Bundler.root
         Bundler::Plugin.gemfile_install(&gemfile) if Bundler.feature_flag.plugins?
         builder = Bundler::Dsl.new
         builder.instance_eval(&gemfile)
    +    builder.check_primary_source_safety
     
         Bundler.settings.temporary(:frozen => false) do
           definition = builder.to_definition(nil, true)
    
  • bundler/lib/bundler/lockfile_parser.rb+12 8 modified
    @@ -64,8 +64,6 @@ def initialize(lockfile)
           @state        = nil
           @specs        = {}
     
    -      @rubygems_aggregate = Source::Rubygems.new
    -
           if lockfile.match(/<<<<<<<|=======|>>>>>>>|\|\|\|\|\|\|\|/)
             raise LockfileError, "Your #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)} contains merge conflicts.\n" \
               "Run `git checkout HEAD -- #{Bundler.default_lockfile.relative_path_from(SharedHelpers.pwd)}` first to get a clean lock."
    @@ -89,7 +87,6 @@ def initialize(lockfile)
               send("parse_#{@state}", line)
             end
           end
    -      @sources << @rubygems_aggregate unless Bundler.feature_flag.disable_multisource?
           @specs = @specs.values.sort_by(&:identifier)
           warn_for_outdated_bundler_version
         rescue ArgumentError => e
    @@ -134,16 +131,19 @@ def parse_source(line)
                 @sources << @current_source
               end
             when GEM
    -          if Bundler.feature_flag.disable_multisource?
    +          source_remotes = Array(@opts["remote"])
    +
    +          if source_remotes.size == 1
                 @opts["remotes"] = @opts.delete("remote")
                 @current_source = TYPES[@type].from_lock(@opts)
    -            @sources << @current_source
               else
    -            Array(@opts["remote"]).each do |url|
    -              @rubygems_aggregate.add_remote(url)
    +            source_remotes.each do |url|
    +              rubygems_aggregate.add_remote(url)
                 end
    -            @current_source = @rubygems_aggregate
    +            @current_source = rubygems_aggregate
               end
    +
    +          @sources << @current_source
             when PLUGIN
               @current_source = Plugin.source_from_lock(@opts)
               @sources << @current_source
    @@ -245,5 +245,9 @@ def parse_bundled_with(line)
         def parse_ruby(line)
           @ruby_version = line.strip
         end
    +
    +    def rubygems_aggregate
    +      @rubygems_aggregate ||= Source::Rubygems.new
    +    end
       end
     end
    
  • bundler/lib/bundler/man/bundle-config.1+4 10 modified
    @@ -56,9 +56,6 @@ Executing \fBbundle config unset \-\-local <name> <value>\fR will delete the con
     .P
     Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
     .
    -.P
    -Executing \fBbundle config set \-\-local disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config unset disable_multisource\fR downgrades this error to a warning\.
    -.
     .SH "REMEMBERING OPTIONS"
     Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\.
     .
    @@ -184,9 +181,6 @@ The following is a list of all configuration keys and their purpose\. You can le
     \fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
     .
     .IP "\(bu" 4
    -\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config unset disable_multisource\fR to unset\.
    -.
    -.IP "\(bu" 4
     \fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
     .
     .IP "\(bu" 4
    @@ -211,10 +205,10 @@ The following is a list of all configuration keys and their purpose\. You can le
     \fBignore_messages\fR (\fBBUNDLE_IGNORE_MESSAGES\fR): When set, no post install messages will be printed\. To silence a single gem, use dot notation like \fBignore_messages\.httparty true\fR\.
     .
     .IP "\(bu" 4
    -\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR) Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
    +\fBinit_gems_rb\fR (\fBBUNDLE_INIT_GEMS_RB\fR): Generate a \fBgems\.rb\fR instead of a \fBGemfile\fR when running \fBbundle init\fR\.
     .
     .IP "\(bu" 4
    -\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1\.
    +\fBjobs\fR (\fBBUNDLE_JOBS\fR): The number of gems Bundler can install in parallel\. Defaults to 1 on Windows, and to the the number of processors on other platforms\.
     .
     .IP "\(bu" 4
     \fBno_install\fR (\fBBUNDLE_NO_INSTALL\fR): Whether \fBbundle package\fR should skip installing gems\.
    @@ -241,7 +235,7 @@ The following is a list of all configuration keys and their purpose\. You can le
     \fBprefer_patch\fR (BUNDLE_PREFER_PATCH): Prefer updating only to next patch version during updates\. Makes \fBbundle update\fR calls equivalent to \fBbundler update \-\-patch\fR\.
     .
     .IP "\(bu" 4
    -\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR) Print only version number from \fBbundler \-\-version\fR\.
    +\fBprint_only_version_number\fR (\fBBUNDLE_PRINT_ONLY_VERSION_NUMBER\fR): Print only version number from \fBbundler \-\-version\fR\.
     .
     .IP "\(bu" 4
     \fBredirect\fR (\fBBUNDLE_REDIRECT\fR): The number of redirects allowed for network requests\. Defaults to \fB5\fR\.
    @@ -283,7 +277,7 @@ The following is a list of all configuration keys and their purpose\. You can le
     \fBunlock_source_unlocks_spec\fR (\fBBUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC\fR): Whether running \fBbundle update \-\-source NAME\fR unlocks a gem with the given name\. Defaults to \fBtrue\fR\.
     .
     .IP "\(bu" 4
    -\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR) Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
    +\fBupdate_requires_all_flag\fR (\fBBUNDLE_UPDATE_REQUIRES_ALL_FLAG\fR): Require passing \fB\-\-all\fR to \fBbundle update\fR when everything should be updated, and disallow passing no options to \fBbundle update\fR\.
     .
     .IP "\(bu" 4
     \fBuser_agent\fR (\fBBUNDLE_USER_AGENT\fR): The custom user agent fragment Bundler includes in API requests\.
    
  • bundler/lib/bundler/man/bundle-config.1.ronn+8 15 modified
    @@ -47,10 +47,6 @@ configuration only from the local application.
     Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
     cause it to ignore all configuration.
     
    -Executing `bundle config set --local disable_multisource true` upgrades the warning about
    -the Gemfile containing multiple primary sources to an error. Executing `bundle
    -config unset disable_multisource` downgrades this error to a warning.
    -
     ## REMEMBERING OPTIONS
     
     Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or
    @@ -178,10 +174,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
     * `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
        Allow Bundler to use a local git override without checking if the revision
        present in the lockfile is present in the repository.
    -* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`):
    -   When set, Gemfiles containing multiple sources will produce errors
    -   instead of warnings.
    -   Use `bundle config unset disable_multisource` to unset.
     * `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
        Stop Bundler from accessing gems installed to RubyGems' normal location.
     * `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
    @@ -206,13 +198,14 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
     * `global_gem_cache` (`BUNDLE_GLOBAL_GEM_CACHE`):
        Whether Bundler should cache all gems globally, rather than locally to the
        installing Ruby installation.
    -* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`): When set, no post install
    -   messages will be printed. To silence a single gem, use dot notation like
    -   `ignore_messages.httparty true`.
    -* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`)
    +* `ignore_messages` (`BUNDLE_IGNORE_MESSAGES`):
    +   When set, no post install messages will be printed. To silence a single gem,
    +   use dot notation like `ignore_messages.httparty true`.
    +* `init_gems_rb` (`BUNDLE_INIT_GEMS_RB`):
        Generate a `gems.rb` instead of a `Gemfile` when running `bundle init`.
     * `jobs` (`BUNDLE_JOBS`):
    -   The number of gems Bundler can install in parallel. Defaults to 1.
    +   The number of gems Bundler can install in parallel. Defaults to 1 on Windows,
    +   and to the the number of processors on other platforms.
     * `no_install` (`BUNDLE_NO_INSTALL`):
        Whether `bundle package` should skip installing gems.
     * `no_prune` (`BUNDLE_NO_PRUNE`):
    @@ -233,7 +226,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
        Enable Bundler's experimental plugin system.
     * `prefer_patch` (BUNDLE_PREFER_PATCH):
        Prefer updating only to next patch version during updates. Makes `bundle update` calls equivalent to `bundler update --patch`.
    -* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`)
    +* `print_only_version_number` (`BUNDLE_PRINT_ONLY_VERSION_NUMBER`):
        Print only version number from `bundler --version`.
     * `redirect` (`BUNDLE_REDIRECT`):
        The number of redirects allowed for network requests. Defaults to `5`.
    @@ -269,7 +262,7 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
     * `unlock_source_unlocks_spec` (`BUNDLE_UNLOCK_SOURCE_UNLOCKS_SPEC`):
        Whether running `bundle update --source NAME` unlocks a gem with the given
        name. Defaults to `true`.
    -* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`)
    +* `update_requires_all_flag` (`BUNDLE_UPDATE_REQUIRES_ALL_FLAG`):
        Require passing `--all` to `bundle update` when everything should be updated,
        and disallow passing no options to `bundle update`.
     * `user_agent` (`BUNDLE_USER_AGENT`):
    
  • bundler/lib/bundler/plugin/installer.rb+8 9 modified
    @@ -16,15 +16,13 @@ def install(names, options)
     
             version = options[:version] || [">= 0"]
     
    -        Bundler.settings.temporary(:disable_multisource => false) do
    -          if options[:git]
    -            install_git(names, version, options)
    -          elsif options[:local_git]
    -            install_local_git(names, version, options)
    -          else
    -            sources = options[:source] || Bundler.rubygems.sources
    -            install_rubygems(names, version, sources)
    -          end
    +        if options[:git]
    +          install_git(names, version, options)
    +        elsif options[:local_git]
    +          install_local_git(names, version, options)
    +        else
    +          sources = options[:source] || Bundler.rubygems.sources
    +          install_rubygems(names, version, sources)
             end
           end
     
    @@ -84,6 +82,7 @@ def install_all_sources(names, version, git_source_options, rubygems_source)
             deps = names.map {|name| Dependency.new name, version }
     
             definition = Definition.new(nil, deps, source_list, true)
    +        definition.allow_multisource!
             install_definition(definition)
           end
     
    
  • bundler/lib/bundler/plugin.rb+1 0 modified
    @@ -105,6 +105,7 @@ def gemfile_install(gemfile = nil, &inline)
             else
               builder.eval_gemfile(gemfile)
             end
    +        builder.check_primary_source_safety
             definition = builder.to_definition(nil, true)
     
             return if definition.dependencies.empty?
    
  • bundler/lib/bundler/resolver.rb+31 35 modified
    @@ -17,15 +17,14 @@ class Resolver
         # ==== Returns
         # <GemBundle>,nil:: If the list of dependencies can be resolved, a
         #   collection of gemspecs is returned. Otherwise, nil is returned.
    -    def self.resolve(requirements, index, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
    +    def self.resolve(requirements, source_requirements = {}, base = [], gem_version_promoter = GemVersionPromoter.new, additional_base_requirements = [], platforms = nil)
           base = SpecSet.new(base) unless base.is_a?(SpecSet)
    -      resolver = new(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
    +      resolver = new(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
           result = resolver.start(requirements)
           SpecSet.new(result)
         end
     
    -    def initialize(index, source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
    -      @index = index
    +    def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
           @source_requirements = source_requirements
           @base = base
           @resolver = Molinillo::Resolver.new(self, self)
    @@ -36,14 +35,14 @@ def initialize(index, source_requirements, base, gem_version_promoter, additiona
             @base_dg.add_vertex(ls.name, DepProxy.get_proxy(dep, ls.platform), true)
           end
           additional_base_requirements.each {|d| @base_dg.add_vertex(d.name, d) }
    -      @platforms = platforms
    +      @platforms = platforms.reject {|p| p != Gem::Platform::RUBY && (platforms - [p]).any? {|pl| generic(pl) == p } }
           @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY]
           @gem_version_promoter = gem_version_promoter
           @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major?
    -      @lockfile_uses_separate_rubygems_sources = Bundler.feature_flag.disable_multisource?
    +      @no_aggregate_global_source = @source_requirements[:global].nil?
     
           @variant_specific_names = []
    -      @generic_names = []
    +      @generic_names = ["Ruby\0", "RubyGems\0"]
         end
     
         def start(requirements)
    @@ -125,8 +124,7 @@ def search_for(dependency_proxy)
           dependency = dependency_proxy.dep
           name = dependency.name
           search_result = @search_for[dependency_proxy] ||= begin
    -        index = index_for(dependency)
    -        results = index.search(dependency, @base[name])
    +        results = results_for(dependency, @base[name])
     
             if vertex = @base_dg.vertex_named(name)
               locked_requirement = vertex.payload.requirement
    @@ -195,23 +193,26 @@ def search_for(dependency_proxy)
           search_result
         end
     
    -    def index_for(dependency)
    +    def index_for(dependency, base)
           source = @source_requirements[dependency.name]
           if source
             source.specs
    -      elsif @lockfile_uses_separate_rubygems_sources
    -        Index.build do |idx|
    -          if dependency.all_sources
    -            dependency.all_sources.each {|s| idx.add_source(s.specs) if s }
    -          else
    -            idx.add_source @source_requirements[:default].specs
    -          end
    +      elsif @no_aggregate_global_source
    +        dependency.all_sources.find(-> { Index.new }) do |s|
    +          idx = s.specs
    +          results = idx.search(dependency, base)
    +          next if results.empty? || results == base
    +          return idx
             end
           else
    -        @index
    +        @source_requirements[:global]
           end
         end
     
    +    def results_for(dependency, base)
    +      index_for(dependency, base).search(dependency, base)
    +    end
    +
         def name_for(dependency)
           dependency.name
         end
    @@ -238,11 +239,13 @@ def dependencies_equal?(dependencies, other_dependencies)
     
         def relevant_sources_for_vertex(vertex)
           if vertex.root?
    -        [@source_requirements[vertex.name]]
    -      elsif @lockfile_uses_separate_rubygems_sources
    +        [@source_requirements[vertex.name]].compact
    +      elsif @no_aggregate_global_source
             vertex.recursive_predecessors.map do |v|
               @source_requirements[v.name]
    -        end << @source_requirements[:default]
    +        end.compact << @source_requirements[:default]
    +      else
    +        []
           end
         end
     
    @@ -283,7 +286,7 @@ def amount_constrained(dependency)
             if (base = @base[dependency.name]) && !base.empty?
               dependency.requirement.satisfied_by?(base.first.version) ? 0 : 1
             else
    -          all = index_for(dependency).search(dependency.name).size
    +          all = index_for(dependency, base).search(dependency.name).size
     
               if all <= 1
                 all - 1_000_000
    @@ -326,7 +329,7 @@ def verify_gemfile_dependencies_are_found!(requirements)
                 "The source does not contain any versions of '#{name}'"
               end
             else
    -          message = "Could not find gem '#{requirement}' in any of the gem sources " \
    +          message = "Could not find gem '#{SharedHelpers.pretty_dependency(requirement)}' in any of the gem sources " \
                 "listed in your Gemfile#{cache_message}."
             end
             raise GemNotFound, message
    @@ -411,14 +414,8 @@ def version_conflict_message(e)
     
                 relevant_sources = if conflict.requirement.source
                   [conflict.requirement.source]
    -            elsif conflict.requirement.all_sources
    -              conflict.requirement.all_sources
    -            elsif @lockfile_uses_separate_rubygems_sources
    -              # every conflict should have an explicit group of sources when we
    -              # enforce strict pinning
    -              raise "no source set for #{conflict}"
                 else
    -              []
    +              conflict.requirement.all_sources
                 end.compact.map(&:to_s).uniq.sort
     
                 metadata_requirement = name.end_with?("\0")
    @@ -455,7 +452,8 @@ def version_conflict_message(e)
         def validate_resolved_specs!(resolved_specs)
           resolved_specs.each do |v|
             name = v.name
    -        next unless sources = relevant_sources_for_vertex(v)
    +        sources = relevant_sources_for_vertex(v)
    +        next unless sources.any?
             sources.compact!
             if default_index = sources.index(@source_requirements[:default])
               sources.delete_at(default_index)
    @@ -464,14 +462,12 @@ def validate_resolved_specs!(resolved_specs)
             sources.uniq!
             next if sources.size <= 1
     
    -        multisource_disabled = Bundler.feature_flag.disable_multisource?
    -
             msg = ["The gem '#{name}' was found in multiple relevant sources."]
             msg.concat sources.map {|s| "  * #{s}" }.sort
    -        msg << "You #{multisource_disabled ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
    +        msg << "You #{@no_aggregate_global_source ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
             msg = msg.join("\n")
     
    -        raise SecurityError, msg if multisource_disabled
    +        raise SecurityError, msg if @no_aggregate_global_source
             Bundler.ui.warn "Warning: #{msg}"
           end
         end
    
  • bundler/lib/bundler/settings.rb+0 1 modified
    @@ -20,7 +20,6 @@ class Settings
           disable_exec_load
           disable_local_branch_check
           disable_local_revision_check
    -      disable_multisource
           disable_shared_gems
           disable_version_check
           force_ruby_platform
    
  • bundler/lib/bundler/source_list.rb+32 21 modified
    @@ -5,24 +5,41 @@ class SourceList
         attr_reader :path_sources,
           :git_sources,
           :plugin_sources,
    -      :global_rubygems_source,
    -      :metadata_source
    +      :global_path_source,
    +      :metadata_source,
    +      :disable_multisource
    +
    +    def global_rubygems_source
    +      @global_rubygems_source ||= rubygems_aggregate_class.new
    +    end
     
         def initialize
           @path_sources           = []
           @git_sources            = []
           @plugin_sources         = []
           @global_rubygems_source = nil
    -      @rubygems_aggregate     = rubygems_aggregate_class.new
    +      @global_path_source     = nil
           @rubygems_sources       = []
           @metadata_source        = Source::Metadata.new
    +      @disable_multisource    = true
    +    end
    +
    +    def disable_multisource?
    +      @disable_multisource
    +    end
    +
    +    def allow_multisource!
    +      rubygems_sources.map(&:allow_multisource!)
    +      @disable_multisource = false
         end
     
         def add_path_source(options = {})
           if options["gemspec"]
             add_source_to_list Source::Gemspec.new(options), path_sources
           else
    -        add_source_to_list Source::Path.new(options), path_sources
    +        path_source = add_source_to_list Source::Path.new(options), path_sources
    +        @global_path_source ||= path_source if options["global"]
    +        path_source
           end
         end
     
    @@ -41,24 +58,20 @@ def add_plugin_source(source, options = {})
         end
     
         def global_rubygems_source=(uri)
    -      if Bundler.feature_flag.disable_multisource?
    -        @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
    -      end
    -      add_rubygems_remote(uri)
    +      @global_rubygems_source ||= rubygems_aggregate_class.new("remotes" => uri)
         end
     
         def add_rubygems_remote(uri)
    -      return if Bundler.feature_flag.disable_multisource?
    -      @rubygems_aggregate.add_remote(uri)
    -      @rubygems_aggregate
    +      global_rubygems_source.add_remote(uri)
    +      global_rubygems_source
         end
     
         def default_source
    -      global_rubygems_source || @rubygems_aggregate
    +      global_path_source || global_rubygems_source
         end
     
         def rubygems_sources
    -      @rubygems_sources + [default_source]
    +      @rubygems_sources + [global_rubygems_source]
         end
     
         def rubygems_remotes
    @@ -75,7 +88,7 @@ def get(source)
     
         def lock_sources
           lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
    -      if Bundler.feature_flag.disable_multisource?
    +      if disable_multisource?
             lock_sources + rubygems_sources.sort_by(&:to_s)
           else
             lock_sources << combine_rubygems_sources
    @@ -92,9 +105,9 @@ def replace_sources!(replacement_sources)
             end
           end
     
    -      replacement_rubygems = !Bundler.feature_flag.disable_multisource? &&
    +      replacement_rubygems = !disable_multisource? &&
             replacement_sources.detect {|s| s.is_a?(Source::Rubygems) }
    -      @rubygems_aggregate = replacement_rubygems if replacement_rubygems
    +      @global_rubygems_source = replacement_rubygems if replacement_rubygems
     
           return true if !equal_sources?(lock_sources, replacement_sources) && !equivalent_sources?(lock_sources, replacement_sources)
           return true if replacement_rubygems && rubygems_remotes.sort_by(&:to_s) != replacement_rubygems.remotes.sort_by(&:to_s)
    @@ -110,10 +123,6 @@ def remote!
           all_sources.each(&:remote!)
         end
     
    -    def rubygems_primary_remotes
    -      @rubygems_aggregate.remotes
    -    end
    -
         private
     
         def rubygems_aggregate_class
    @@ -136,7 +145,9 @@ def source_list_for(source)
         end
     
         def combine_rubygems_sources
    -      Source::Rubygems.new("remotes" => rubygems_remotes)
    +      aggregate_source = Source::Rubygems.new("remotes" => rubygems_remotes)
    +      aggregate_source.allow_multisource! unless disable_multisource?
    +      aggregate_source
         end
     
         def warn_on_git_protocol(source)
    
  • bundler/lib/bundler/source/rubygems.rb+10 1 modified
    @@ -21,6 +21,7 @@ def initialize(options = {})
             @allow_remote = false
             @allow_cached = false
             @caches = [cache_path, *Bundler.rubygems.gem_cache]
    +        @disable_multisource = true
     
             Array(options["remotes"] || []).reverse_each {|r| add_remote(r) }
           end
    @@ -49,8 +50,16 @@ def include?(o)
             o.is_a?(Rubygems) && (o.credless_remotes - credless_remotes).empty?
           end
     
    +      def disable_multisource?
    +        @disable_multisource
    +      end
    +
    +      def allow_multisource!
    +        @disable_multisource = false
    +      end
    +
           def can_lock?(spec)
    -        return super if Bundler.feature_flag.disable_multisource?
    +        return super if disable_multisource?
             spec.source.is_a?(Rubygems)
           end
     
    
  • bundler/lib/bundler/version.rb+1 1 modified
    @@ -1,7 +1,7 @@
     # frozen_string_literal: false
     
     module Bundler
    -  VERSION = "2.2.9".freeze
    +  VERSION = "2.2.10".freeze
     
       def self.bundler_major_version
         @bundler_major_version ||= VERSION.split(".").first.to_i
    
  • bundler/spec/bundler/dsl_spec.rb+1 14 modified
    @@ -25,7 +25,7 @@
           expect { subject.git_source(:example) }.to raise_error(Bundler::InvalidOption)
         end
     
    -    context "default hosts", :bundler => "2" do
    +    context "default hosts", :bundler => "< 3" do
           it "converts :github to URI using https" do
             subject.gem("sparks", :github => "indirect/sparks")
             github_uri = "https://github.com/indirect/sparks.git"
    @@ -195,19 +195,6 @@
         #   gem 'spree_api'
         #   gem 'spree_backend'
         # end
    -    describe "#github", :bundler => "< 3" do
    -      it "from github" do
    -        spree_gems = %w[spree_core spree_api spree_backend]
    -        subject.github "spree" do
    -          spree_gems.each {|spree_gem| subject.send :gem, spree_gem }
    -        end
    -
    -        subject.dependencies.each do |d|
    -          expect(d.source.uri).to eq("https://github.com/spree/spree.git")
    -        end
    -      end
    -    end
    -
         describe "#github" do
           it "from github" do
             spree_gems = %w[spree_core spree_api spree_backend]
    
  • bundler/spec/bundler/plugin_spec.rb+1 0 modified
    @@ -112,6 +112,7 @@
         before do
           allow(Plugin::DSL).to receive(:new) { builder }
           allow(builder).to receive(:eval_gemfile).with(gemfile)
    +      allow(builder).to receive(:check_primary_source_safety)
           allow(builder).to receive(:to_definition) { definition }
           allow(builder).to receive(:inferred_plugins) { [] }
         end
    
  • bundler/spec/bundler/source_list_spec.rb+1 20 modified
    @@ -372,26 +372,7 @@
           source_list.add_git_source("uri" => "git://first-git.org/path.git")
         end
     
    -    it "combines the rubygems sources into a single instance, removing duplicate remotes from the end", :bundler => "< 3" do
    -      expect(source_list.lock_sources).to eq [
    -        Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
    -        Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
    -        Bundler::Source::Git.new("uri" => "git://third-git.org/path.git"),
    -        ASourcePlugin.new("uri" => "https://second-plugin.org/random"),
    -        ASourcePlugin.new("uri" => "https://third-bar.org/foo"),
    -        Bundler::Source::Path.new("path" => "/first/path/to/gem"),
    -        Bundler::Source::Path.new("path" => "/second/path/to/gem"),
    -        Bundler::Source::Path.new("path" => "/third/path/to/gem"),
    -        Bundler::Source::Rubygems.new("remotes" => [
    -          "https://duplicate-rubygems.org",
    -          "https://first-rubygems.org",
    -          "https://second-rubygems.org",
    -          "https://third-rubygems.org",
    -        ]),
    -      ]
    -    end
    -
    -    it "returns all sources, without combining rubygems sources", :bundler => "3" do
    +    it "returns all sources, without combining rubygems sources" do
           expect(source_list.lock_sources).to eq [
             Bundler::Source::Git.new("uri" => "git://first-git.org/path.git"),
             Bundler::Source::Git.new("uri" => "git://second-git.org/path.git"),
    
  • bundler/spec/commands/exec_spec.rb+2 1 modified
    @@ -762,7 +762,8 @@ def bin_path(a,b,c)
           let(:exit_code) { Bundler::GemNotFound.new.status_code }
           let(:expected) { "" }
           let(:expected_err) { <<-EOS.strip }
    -\e[31mCould not find gem 'rack (= 2)' in any of the gem sources listed in your Gemfile.\e[0m
    +\e[31mCould not find gem 'rack (= 2)' in locally installed gems.
    +The source contains the following versions of 'rack': 0.9.1, 1.0.0\e[0m
     \e[33mRun `bundle install` to install missing gems.\e[0m
           EOS
     
    
  • bundler/spec/commands/lock_spec.rb+45 0 modified
    @@ -491,6 +491,51 @@ def read_lockfile(file = "Gemfile.lock")
         end
       end
     
    +  it "does not conflict on ruby requirements when adding new platforms" do
    +    next_minor = Gem.ruby_version.segments[0..1].map.with_index {|s, i| i == 1 ? s + 1 : s }.join(".")
    +
    +    build_repo4 do
    +      build_gem "raygun-apm", "1.0.78" do |s|
    +        s.platform = "x86_64-linux"
    +        s.required_ruby_version = "< #{next_minor}.dev"
    +      end
    +
    +      build_gem "raygun-apm", "1.0.78" do |s|
    +        s.platform = "universal-darwin"
    +        s.required_ruby_version = "< #{next_minor}.dev"
    +      end
    +
    +      build_gem "raygun-apm", "1.0.78" do |s|
    +        s.platform = "x64-mingw32"
    +        s.required_ruby_version = "< #{next_minor}.dev"
    +      end
    +    end
    +
    +    gemfile <<-G
    +      source "https://localgemserver.test"
    +
    +      gem "raygun-apm"
    +    G
    +
    +    lockfile <<-L
    +      GEM
    +        remote: https://localgemserver.test/
    +        specs:
    +          raygun-apm (1.0.78-universal-darwin)
    +
    +      PLATFORMS
    +        x86_64-darwin-19
    +
    +      DEPENDENCIES
    +        raygun-apm
    +
    +      BUNDLED WITH
    +         #{Bundler::VERSION}
    +    L
    +
    +    bundle "lock --add-platform x86_64-linux", :artifice => :compact_index, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
    +  end
    +
       context "when an update is available" do
         let(:repo) { gem_repo2 }
     
    
  • bundler/spec/commands/post_bundle_message_spec.rb+1 10 modified
    @@ -113,16 +113,7 @@
             bundle "config set force_ruby_platform true"
           end
     
    -      it "should report a helpful error message", :bundler => "< 3" do
    -        install_gemfile <<-G, :raise_on_error => false
    -          source "#{file_uri_for(gem_repo1)}"
    -          gem "rack"
    -          gem "not-a-gem", :group => :development
    -        G
    -        expect(err).to include("Could not find gem 'not-a-gem' in any of the gem sources listed in your Gemfile.")
    -      end
    -
    -      it "should report a helpful error message", :bundler => "3" do
    +      it "should report a helpful error message" do
             install_gemfile <<-G, :raise_on_error => false
               source "#{file_uri_for(gem_repo1)}"
               gem "rack"
    
  • bundler/spec/install/gemfile/gemspec_spec.rb+7 8 modified
    @@ -422,14 +422,13 @@
               end
             end
     
    -        %w[ruby jruby].each do |platform|
    -          simulate_platform(platform) do
    -            install_gemfile <<-G
    -              source "#{file_uri_for(gem_repo2)}"
    -              gemspec
    -            G
    -          end
    -        end
    +        gemfile <<-G
    +          source "#{file_uri_for(gem_repo2)}"
    +          gemspec
    +        G
    +
    +        simulate_platform("ruby") { bundle "install" }
    +        simulate_platform("jruby") { bundle "install" }
           end
     
           context "on ruby" do
    
  • bundler/spec/install/gemfile/platform_spec.rb+6 19 modified
    @@ -88,14 +88,15 @@
         simulate_new_machine
     
         simulate_platform "ruby"
    -    install_gemfile <<-G
    -      source "#{file_uri_for(gem_repo1)}"
    -
    -      gem "nokogiri"
    -    G
    +    bundle "install"
     
         expect(the_bundle).to include_gems "nokogiri 1.4.2"
         expect(the_bundle).not_to include_gems "weakling"
    +
    +    simulate_platform "java"
    +    bundle "install"
    +
    +    expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
       end
     
       it "does not keep unneeded platforms for gems that are used" do
    @@ -241,20 +242,6 @@
         end
       end
     
    -  it "works the other way with gems that have different dependencies" do
    -    simulate_platform "ruby"
    -    install_gemfile <<-G
    -      source "#{file_uri_for(gem_repo1)}"
    -
    -      gem "nokogiri"
    -    G
    -
    -    simulate_platform "java"
    -    bundle "install"
    -
    -    expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA", "weakling 0.0.3"
    -  end
    -
       it "works with gems with platform-specific dependency having different requirements order" do
         simulate_platform x64_mac
     
    
  • bundler/spec/install/gemfile/sources_spec.rb+177 94 modified
    @@ -27,7 +27,7 @@
             G
           end
     
    -      it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "2" do
    +      it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do
             bundle :install
     
             expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
    @@ -54,7 +54,7 @@
             G
           end
     
    -      it "warns about ambiguous gems, but installs anyway", :bundler => "2" do
    +      it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do
             bundle :install
             expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
             expect(err).to include("Installed from: #{file_uri_for(gem_repo1)}")
    @@ -96,7 +96,7 @@
     
           it "installs the gems without any warning" do
             bundle :install
    -        expect(out).not_to include("Warning")
    +        expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("rack-obama 1.0.0")
             expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1")
           end
    @@ -128,16 +128,15 @@
               end
             end
     
    -        gemfile <<-G
    +        install_gemfile <<-G
               source "#{file_uri_for(gem_repo3)}"
               gem "rack-obama" # should come from repo3!
               gem "rack", :source => "#{file_uri_for(gem_repo1)}"
             G
           end
     
           it "installs the gems without any warning" do
    -        bundle :install
    -        expect(out).not_to include("Warning")
    +        expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
           end
         end
    @@ -173,8 +172,8 @@
     
               it "installs from the same source without any warning" do
                 bundle :install
    -            expect(out).not_to include("Warning")
    -            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
    +            expect(err).not_to include("Warning")
    +            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
               end
             end
     
    @@ -188,27 +187,38 @@
                 end
               end
     
    -          context "when disable_multisource is set" do
    -            before do
    -              bundle "config set disable_multisource true"
    -            end
    +          it "installs from the same source without any warning" do
    +            bundle :install
     
    -            it "installs from the same source without any warning" do
    -              bundle :install
    +            expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    +            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
     
    -              expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    -              expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    -              expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
    +            # In https://github.com/bundler/bundler/issues/3585 this failed
    +            # when there is already a lock file, and the gems are missing, so try again
    +            system_gems []
    +            bundle :install
     
    -              # when there is already a lock file, and the gems are missing, so try again
    -              system_gems []
    -              bundle :install
    +            expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    +            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
    +          end
    +        end
     
    -              expect(out).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    -              expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    -              expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
    +        context "and in another source with a higher version" do
    +          before do
    +            # need this to be broken to check for correct source ordering
    +            build_repo gem_repo2 do
    +              build_gem "rack", "1.0.1" do |s|
    +                s.write "lib/rack.rb", "RACK = 'FAIL'"
    +              end
                 end
               end
    +
    +          it "installs from the same source without any warning" do
    +            bundle :install
    +
    +            expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    +            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
    +          end
             end
           end
     
    @@ -222,7 +232,7 @@
     
             context "and not in any other sources" do
               before do
    -            gemfile <<-G
    +            install_gemfile <<-G
                   source "#{file_uri_for(gem_repo2)}"
                   source "#{file_uri_for(gem_repo3)}" do
                     gem "depends_on_rack"
    @@ -231,8 +241,7 @@
               end
     
               it "installs from the other source without any warning" do
    -            bundle :install
    -            expect(out).not_to include("Warning")
    +            expect(err).not_to include("Warning")
                 expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
               end
             end
    @@ -248,7 +257,7 @@
                 G
               end
     
    -          it "installs from the other source and warns about ambiguous gems", :bundler => "2" do
    +          it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do
                 bundle :install
                 expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
                 expect(err).to include("Installed from: #{file_uri_for(gem_repo2)}")
    @@ -280,7 +289,7 @@
                 G
               end
     
    -          it "installs the dependency from the pinned source without warning", :bundler => "2" do
    +          it "installs the dependency from the pinned source without warning", :bundler => "< 3" do
                 bundle :install
     
                 expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
    @@ -305,80 +314,74 @@
         end
     
         context "when a top-level gem has an indirect dependency" do
    -      context "when disable_multisource is set" do
    -        before do
    -          bundle "config set disable_multisource true"
    -        end
    -
    -        before do
    -          build_repo gem_repo2 do
    -            build_gem "depends_on_rack", "1.0.1" do |s|
    -              s.add_dependency "rack"
    -            end
    -          end
    -
    -          build_repo gem_repo3 do
    -            build_gem "unrelated_gem", "1.0.0"
    +      before do
    +        build_repo gem_repo2 do
    +          build_gem "depends_on_rack", "1.0.1" do |s|
    +            s.add_dependency "rack"
               end
    +        end
     
    -          gemfile <<-G
    -            source "#{file_uri_for(gem_repo2)}"
    +        build_repo gem_repo3 do
    +          build_gem "unrelated_gem", "1.0.0"
    +        end
     
    -            gem "depends_on_rack"
    +        gemfile <<-G
    +          source "#{file_uri_for(gem_repo2)}"
     
    -            source "#{file_uri_for(gem_repo3)}" do
    -              gem "unrelated_gem"
    -            end
    -          G
    -        end
    +          gem "depends_on_rack"
     
    -        context "and the dependency is only in the top-level source" do
    -          before do
    -            update_repo gem_repo2 do
    -              build_gem "rack", "1.0.0"
    -            end
    +          source "#{file_uri_for(gem_repo3)}" do
    +            gem "unrelated_gem"
               end
    +        G
    +      end
     
    -          it "installs all gems without warning" do
    -            bundle :install
    -            expect(err).not_to include("Warning")
    -            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +      context "and the dependency is only in the top-level source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0"
               end
             end
     
    -        context "and the dependency is only in a pinned source" do
    -          before do
    -            update_repo gem_repo3 do
    -              build_gem "rack", "1.0.0" do |s|
    -                s.write "lib/rack.rb", "RACK = 'FAIL'"
    -              end
    +        it "installs all gems without warning" do
    +          bundle :install
    +          expect(err).not_to include("Warning")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +        end
    +      end
    +
    +      context "and the dependency is only in a pinned source" do
    +        before do
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0" do |s|
    +              s.write "lib/rack.rb", "RACK = 'FAIL'"
                 end
               end
    +        end
     
    -          it "does not find the dependency" do
    -            bundle :install, :raise_on_error => false
    -            expect(err).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources")
    -          end
    +        it "does not find the dependency" do
    +          bundle :install, :raise_on_error => false
    +          expect(err).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources")
             end
    +      end
     
    -        context "and the dependency is in both the top-level and a pinned source" do
    -          before do
    -            update_repo gem_repo2 do
    -              build_gem "rack", "1.0.0"
    -            end
    +      context "and the dependency is in both the top-level and a pinned source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0"
    +          end
     
    -            update_repo gem_repo3 do
    -              build_gem "rack", "1.0.0" do |s|
    -                s.write "lib/rack.rb", "RACK = 'FAIL'"
    -              end
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0" do |s|
    +              s.write "lib/rack.rb", "RACK = 'FAIL'"
                 end
               end
    +        end
     
    -          it "installs the dependency from the top-level source without warning" do
    -            bundle :install
    -            expect(err).not_to include("Warning")
    -            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    -          end
    +        it "installs the dependency from the top-level source without warning" do
    +          bundle :install
    +          expect(err).not_to include("Warning")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
             end
           end
         end
    @@ -389,15 +392,14 @@
               build_gem "not_in_repo1", "1.0.0"
             end
     
    -        gemfile <<-G
    +        install_gemfile <<-G, :raise_on_error => false
               source "#{file_uri_for(gem_repo3)}"
               gem "not_in_repo1", :source => "#{file_uri_for(gem_repo1)}"
             G
           end
     
           it "does not install the gem" do
    -        bundle :install, :raise_on_error => false
    -        expect(err).to include("Could not find gem 'not_in_repo1'")
    +        expect(err).to include("Could not find gem 'not_in_repo1")
           end
         end
     
    @@ -433,6 +435,92 @@
           end
         end
     
    +    context "with a lockfile with aggregated rubygems sources" do
    +      let(:aggregate_gem_section_lockfile) do
    +        <<~L
    +          GEM
    +            remote: #{file_uri_for(gem_repo1)}/
    +            remote: #{file_uri_for(gem_repo3)}/
    +            specs:
    +              rack (0.9.1)
    +
    +          PLATFORMS
    +            #{specific_local_platform}
    +
    +          DEPENDENCIES
    +            rack!
    +
    +          BUNDLED WITH
    +             #{Bundler::VERSION}
    +        L
    +      end
    +
    +      let(:split_gem_section_lockfile) do
    +        <<~L
    +          GEM
    +            remote: #{file_uri_for(gem_repo1)}/
    +            specs:
    +
    +          GEM
    +            remote: #{file_uri_for(gem_repo3)}/
    +            specs:
    +              rack (0.9.1)
    +
    +          PLATFORMS
    +            #{specific_local_platform}
    +
    +          DEPENDENCIES
    +            rack!
    +
    +          BUNDLED WITH
    +             #{Bundler::VERSION}
    +        L
    +      end
    +
    +      before do
    +        build_repo gem_repo3 do
    +          build_gem "rack", "0.9.1"
    +        end
    +
    +        gemfile <<-G
    +          source "#{file_uri_for(gem_repo1)}"
    +          source "#{file_uri_for(gem_repo3)}" do
    +            gem 'rack'
    +          end
    +        G
    +
    +        lockfile aggregate_gem_section_lockfile
    +      end
    +
    +      it "installs a lockfile with separate rubygems source sections if not in frozen mode" do
    +        bundle "install"
    +
    +        expect(lockfile).to eq(split_gem_section_lockfile)
    +
    +        expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3")
    +      end
    +
    +      it "installs the existing lockfile but prints a warning if in frozen mode", :bundler => "< 3" do
    +        bundle "config set --local deployment true"
    +
    +        bundle "install"
    +
    +        expect(lockfile).to eq(aggregate_gem_section_lockfile)
    +
    +        expect(the_bundle).to include_gems("rack 0.9.1", :source => "remote3")
    +      end
    +
    +      it "refuses to install the existing lockfile and prints an error if in frozen mode", :bundler => "3" do
    +        bundle "config set --local deployment true"
    +
    +        bundle "install", :raise_on_error =>false
    +
    +        expect(lockfile).to eq(aggregate_gem_section_lockfile)
    +        expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +        expect(out).to be_empty
    +      end
    +    end
    +
         context "with a path gem in the same Gemfile" do
           before do
             build_lib "foo"
    @@ -457,14 +545,13 @@
         before do
           system_gems "rack-0.9.1"
     
    -      gemfile <<-G
    +      install_gemfile <<-G
             source "#{file_uri_for(gem_repo1)}"
             gem "rack" # shoud come from repo1!
           G
         end
     
         it "installs the gems without any warning" do
    -      bundle :install
           expect(err).not_to include("Warning")
           expect(the_bundle).to include_gems("rack 1.0.0")
         end
    @@ -615,16 +702,12 @@
           G
         end
     
    -    it "keeps the old version", :bundler => "2" do
    -      expect(the_bundle).to include_gems("rack 1.0.0")
    -    end
    -
    -    it "installs the higher version in the new repo", :bundler => "3" do
    +    it "installs the higher version in the new repo" do
           expect(the_bundle).to include_gems("rack 1.2")
         end
       end
     
    -  context "when a gem is available from multiple ambiguous sources", :bundler => "3" do
    +  context "when a gem is available from multiple ambiguous sources" do
         it "raises, suggesting a source block" do
           build_repo4 do
             build_gem "depends_on_rack" do |s|
    
  • bundler/spec/install/gems/flex_spec.rb+1 31 modified
    @@ -245,37 +245,7 @@
       end
     
       describe "when adding a new source" do
    -    it "updates the lockfile", :bundler => "< 3" do
    -      build_repo2
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo1)}"
    -        gem "rack"
    -      G
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo1)}"
    -        source "#{file_uri_for(gem_repo2)}"
    -        gem "rack"
    -      G
    -
    -      lockfile_should_be <<-L
    -      GEM
    -        remote: #{file_uri_for(gem_repo1)}/
    -        remote: #{file_uri_for(gem_repo2)}/
    -        specs:
    -          rack (1.0.0)
    -
    -      PLATFORMS
    -        #{lockfile_platforms}
    -
    -      DEPENDENCIES
    -        rack
    -
    -      BUNDLED WITH
    -         #{Bundler::VERSION}
    -      L
    -    end
    -
    -    it "updates the lockfile", :bundler => "3" do
    +    it "updates the lockfile" do
           build_repo2
           install_gemfile <<-G
             source "#{file_uri_for(gem_repo1)}"
    
  • bundler/spec/lock/lockfile_spec.rb+1 34 modified
    @@ -318,40 +318,7 @@
         G
       end
     
    -  it "generates a lockfile without credentials for a configured source", :bundler => "< 3" do
    -    bundle "config set http://localgemserver.test/ user:pass"
    -
    -    install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
    -      source "http://localgemserver.test/" do
    -
    -      end
    -
    -      source "http://user:pass@othergemserver.test/" do
    -        gem "rack-obama", ">= 1.0"
    -      end
    -    G
    -
    -    lockfile_should_be <<-G
    -      GEM
    -        remote: http://localgemserver.test/
    -        remote: http://user:pass@othergemserver.test/
    -        specs:
    -          rack (1.0.0)
    -          rack-obama (1.0)
    -            rack
    -
    -      PLATFORMS
    -        #{lockfile_platforms}
    -
    -      DEPENDENCIES
    -        rack-obama (>= 1.0)!
    -
    -      BUNDLED WITH
    -         #{Bundler::VERSION}
    -    G
    -  end
    -
    -  it "generates a lockfile without credentials for a configured source", :bundler => "3" do
    +  it "generates a lockfile without credentials for a configured source" do
         bundle "config set http://localgemserver.test/ user:pass"
     
         install_gemfile(<<-G, :artifice => "endpoint_strict_basic_authentication", :quiet => true)
    
  • bundler/spec/other/major_deprecation_spec.rb+72 32 modified
    @@ -17,7 +17,7 @@
             bundle "exec ruby -e #{source.dump}"
           end
     
    -      it "is deprecated in favor of .unbundled_env", :bundler => "2" do
    +      it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do
             expect(deprecations).to include \
               "`Bundler.clean_env` has been deprecated in favor of `Bundler.unbundled_env`. " \
               "If you instead want the environment before bundler was originally loaded, use `Bundler.original_env` " \
    @@ -33,7 +33,7 @@
             bundle "exec ruby -e #{source.dump}"
           end
     
    -      it "is deprecated in favor of .unbundled_env", :bundler => "2" do
    +      it "is deprecated in favor of .unbundled_env", :bundler => "< 3" do
             expect(deprecations).to include(
               "`Bundler.with_clean_env` has been deprecated in favor of `Bundler.with_unbundled_env`. " \
               "If you instead want the environment before bundler was originally loaded, use `Bundler.with_original_env` " \
    @@ -50,7 +50,7 @@
             bundle "exec ruby -e #{source.dump}"
           end
     
    -      it "is deprecated in favor of .unbundled_system", :bundler => "2" do
    +      it "is deprecated in favor of .unbundled_system", :bundler => "< 3" do
             expect(deprecations).to include(
               "`Bundler.clean_system` has been deprecated in favor of `Bundler.unbundled_system`. " \
               "If you instead want to run the command in the environment before bundler was originally loaded, use `Bundler.original_system` " \
    @@ -67,7 +67,7 @@
             bundle "exec ruby -e #{source.dump}"
           end
     
    -      it "is deprecated in favor of .unbundled_exec", :bundler => "2" do
    +      it "is deprecated in favor of .unbundled_exec", :bundler => "< 3" do
             expect(deprecations).to include(
               "`Bundler.clean_exec` has been deprecated in favor of `Bundler.unbundled_exec`. " \
               "If you instead want to exec to a command in the environment before bundler was originally loaded, use `Bundler.original_exec` " \
    @@ -84,7 +84,7 @@
             bundle "exec ruby -e #{source.dump}"
           end
     
    -      it "is deprecated in favor of .load", :bundler => "2" do
    +      it "is deprecated in favor of .load", :bundler => "< 3" do
             expect(deprecations).to include "Bundler.environment has been removed in favor of Bundler.load (called at -e:1)"
           end
     
    @@ -109,7 +109,7 @@
           bundle "check --path vendor/bundle", :raise_on_error => false
         end
     
    -    it "should print a deprecation warning", :bundler => "2" do
    +    it "should print a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include(
             "The `--path` flag is deprecated because it relies on being " \
             "remembered across bundler invocations, which bundler will no " \
    @@ -118,7 +118,7 @@
           )
         end
     
    -    pending "should fail with a helpful error", :bundler => "3"
    +    pending "fails with a helpful error", :bundler => "3"
       end
     
       context "bundle check --path=" do
    @@ -131,7 +131,7 @@
           bundle "check --path=vendor/bundle", :raise_on_error => false
         end
     
    -    it "should print a deprecation warning", :bundler => "2" do
    +    it "should print a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include(
             "The `--path` flag is deprecated because it relies on being " \
             "remembered across bundler invocations, which bundler will no " \
    @@ -140,7 +140,7 @@
           )
         end
     
    -    pending "should fail with a helpful error", :bundler => "3"
    +    pending "fails with a helpful error", :bundler => "3"
       end
     
       context "bundle cache --all" do
    @@ -153,7 +153,7 @@
           bundle "cache --all", :raise_on_error => false
         end
     
    -    it "should print a deprecation warning", :bundler => "2" do
    +    it "should print a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include(
             "The `--all` flag is deprecated because it relies on being " \
             "remembered across bundler invocations, which bundler will no " \
    @@ -162,7 +162,7 @@
           )
         end
     
    -    pending "should fail with a helpful error", :bundler => "3"
    +    pending "fails with a helpful error", :bundler => "3"
       end
     
       describe "bundle config" do
    @@ -292,7 +292,7 @@
           G
         end
     
    -    it "should output a deprecation warning", :bundler => "2" do
    +    it "should output a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include("The --binstubs option will be removed in favor of `bundle binstubs --all`")
         end
     
    @@ -356,7 +356,7 @@
               bundle "install #{flag_name} #{value}"
             end
     
    -        it "should print a deprecation warning", :bundler => "2" do
    +        it "should print a deprecation warning", :bundler => "< 3" do
               expect(deprecations).to include(
                 "The `#{flag_name}` flag is deprecated because it relies on " \
                 "being remembered across bundler invocations, which bundler " \
    @@ -365,7 +365,7 @@
               )
             end
     
    -        pending "should fail with a helpful error", :bundler => "3"
    +        pending "fails with a helpful error", :bundler => "3"
           end
         end
       end
    @@ -378,18 +378,58 @@
           G
         end
     
    -    it "shows a deprecation", :bundler => "2" do
    +    it "shows a deprecation", :bundler => "< 3" do
           expect(deprecations).to include(
             "Your Gemfile contains multiple primary sources. " \
             "Using `source` more than once without a block is a security risk, and " \
             "may result in installing unexpected gems. To resolve this warning, use " \
    -        "a block to indicate which gems should come from the secondary source. " \
    -        "To upgrade this warning to an error, run `bundle config set --local " \
    -        "disable_multisource true`."
    +        "a block to indicate which gems should come from the secondary source."
           )
         end
     
    -    pending "should fail with a helpful error", :bundler => "3"
    +    pending "fails with a helpful error", :bundler => "3"
    +  end
    +
    +  context "bundle install with a lockfile with a single rubygems section with multiple remotes in frozen mode" do
    +    before do
    +      build_repo gem_repo3 do
    +        build_gem "rack", "0.9.1"
    +      end
    +
    +      gemfile <<-G
    +        source "#{file_uri_for(gem_repo1)}"
    +        source "#{file_uri_for(gem_repo3)}" do
    +          gem 'rack'
    +        end
    +      G
    +
    +      lockfile <<~L
    +        GEM
    +          remote: #{file_uri_for(gem_repo1)}/
    +          remote: #{file_uri_for(gem_repo3)}/
    +          specs:
    +            rack (0.9.1)
    +
    +        PLATFORMS
    +          ruby
    +
    +        DEPENDENCIES
    +          rack!
    +
    +        BUNDLED WITH
    +           #{Bundler::VERSION}
    +      L
    +
    +      bundle "config set --local deployment true"
    +    end
    +
    +    it "shows a deprecation", :bundler => "< 3" do
    +      bundle "install"
    +
    +      expect(deprecations).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure. You should regenerate your lockfile in a non frozen environment.")
    +    end
    +
    +    pending "fails with a helpful error", :bundler => "3"
       end
     
       context "when Bundler.setup is run in a ruby script" do
    @@ -422,14 +462,14 @@
           RUBY
         end
     
    -    it "should print a capistrano deprecation warning", :bundler => "2" do
    +    it "should print a capistrano deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include("Bundler no longer integrates " \
                                  "with Capistrano, but Capistrano provides " \
                                  "its own integration with Bundler via the " \
                                  "capistrano-bundler gem. Use it instead.")
         end
     
    -    pending "should fail with a helpful error", :bundler => "3"
    +    pending "fails with a helpful error", :bundler => "3"
       end
     
       describe Bundler::Dsl do
    @@ -439,7 +479,7 @@
         end
     
         context "with github gems" do
    -      it "does not warn about removal", :bundler => "2" do
    +      it "does not warn about removal", :bundler => "< 3" do
             expect(Bundler.ui).not_to receive(:warn)
             subject.gem("sparks", :github => "indirect/sparks")
             github_uri = "https://github.com/indirect/sparks.git"
    @@ -461,7 +501,7 @@
         end
     
         context "with bitbucket gems" do
    -      it "does not warn about removal", :bundler => "2" do
    +      it "does not warn about removal", :bundler => "< 3" do
             expect(Bundler.ui).not_to receive(:warn)
             subject.gem("not-really-a-gem", :bitbucket => "mcorp/flatlab-rails")
           end
    @@ -483,7 +523,7 @@
         end
     
         context "with gist gems" do
    -      it "does not warn about removal", :bundler => "2" do
    +      it "does not warn about removal", :bundler => "< 3" do
             expect(Bundler.ui).not_to receive(:warn)
             subject.gem("not-really-a-gem", :gist => "1234")
           end
    @@ -514,7 +554,7 @@
             bundle :show
           end
     
    -      it "prints a deprecation warning recommending `bundle list`", :bundler => "2" do
    +      it "prints a deprecation warning recommending `bundle list`", :bundler => "< 3" do
             expect(deprecations).to include("use `bundle list` instead of `bundle show`")
           end
     
    @@ -526,7 +566,7 @@
             bundle "show --outdated"
           end
     
    -      it "prints a deprecation warning informing about its removal", :bundler => "2" do
    +      it "prints a deprecation warning informing about its removal", :bundler => "< 3" do
             expect(deprecations).to include("the `--outdated` flag to `bundle show` was undocumented and will be removed without replacement")
           end
     
    @@ -538,7 +578,7 @@
             bundle "show --verbose"
           end
     
    -      it "prints a deprecation warning informing about its removal", :bundler => "2" do
    +      it "prints a deprecation warning informing about its removal", :bundler => "< 3" do
             expect(deprecations).to include("the `--verbose` flag to `bundle show` was undocumented and will be removed without replacement")
           end
     
    @@ -550,7 +590,7 @@
             bundle "show rack"
           end
     
    -      it "prints a deprecation warning recommending `bundle info`", :bundler => "2" do
    +      it "prints a deprecation warning recommending `bundle info`", :bundler => "< 3" do
             expect(deprecations).to include("use `bundle info rack` instead of `bundle show rack`")
           end
     
    @@ -562,7 +602,7 @@
             bundle "show --paths"
           end
     
    -      it "prints a deprecation warning recommending `bundle list`", :bundler => "2" do
    +      it "prints a deprecation warning recommending `bundle list`", :bundler => "< 3" do
             expect(deprecations).to include("use `bundle list` instead of `bundle show --paths`")
           end
     
    @@ -574,7 +614,7 @@
             bundle "show rack --paths"
           end
     
    -      it "prints deprecation warning recommending `bundle info`", :bundler => "2" do
    +      it "prints deprecation warning recommending `bundle info`", :bundler => "< 3" do
             expect(deprecations).to include("use `bundle info rack --path` instead of `bundle show rack --paths`")
           end
     
    @@ -587,7 +627,7 @@
           bundle "console", :raise_on_error => false
         end
     
    -    it "prints a deprecation warning", :bundler => "2" do
    +    it "prints a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include \
             "bundle console will be replaced by `bin/console` generated by `bundle gem <name>`"
         end
    @@ -603,7 +643,7 @@
           bundle "viz"
         end
     
    -    it "prints a deprecation warning", :bundler => "2" do
    +    it "prints a deprecation warning", :bundler => "< 3" do
           expect(deprecations).to include "The `viz` command has been moved to the `bundle-viz` gem, see https://github.com/bundler/bundler-viz"
         end
     
    
  • bundler/spec/resolver/platform_spec.rb+17 0 modified
    @@ -28,6 +28,23 @@
         end
       end
     
    +  it "resolves multiplatform gems with redundant platforms correctly" do
    +    @index = build_index do
    +      gem "zookeeper", "1.4.11"
    +      gem "zookeeper", "1.4.11", "java" do
    +        dep "slyphon-log4j", "= 1.2.15"
    +        dep "slyphon-zookeeper_jar", "= 3.3.5"
    +      end
    +      gem "slyphon-log4j", "1.2.15"
    +      gem "slyphon-zookeeper_jar", "3.3.5", "java"
    +    end
    +
    +    dep "zookeeper"
    +    platforms "java", "ruby", "universal-java-11"
    +
    +    should_resolve_as %w[zookeeper-1.4.11 zookeeper-1.4.11-java slyphon-log4j-1.2.15 slyphon-zookeeper_jar-3.3.5-java]
    +  end
    +
       it "takes the latest ruby gem, even if an older platform specific version is available" do
         @index = build_index do
           gem "foo", "1.0.0"
    
  • bundler/spec/runtime/platform_spec.rb+83 0 modified
    @@ -57,6 +57,89 @@
         expect(the_bundle).to include_gems "nokogiri 1.4.2"
       end
     
    +  it "will keep both platforms when both ruby and a specific ruby platform are locked and the bundle is unlocked" do
    +    build_repo4 do
    +      build_gem "nokogiri", "1.11.1" do |s|
    +        s.add_dependency "mini_portile2", "~> 2.5.0"
    +        s.add_dependency "racc", "~> 1.4"
    +      end
    +
    +      build_gem "nokogiri", "1.11.1" do |s|
    +        s.platform = Bundler.local_platform
    +        s.add_dependency "racc", "~> 1.4"
    +      end
    +
    +      build_gem "mini_portile2", "2.5.0"
    +      build_gem "racc", "1.5.2"
    +    end
    +
    +    good_lockfile = <<~L
    +      GEM
    +        remote: #{file_uri_for(gem_repo4)}/
    +        specs:
    +          mini_portile2 (2.5.0)
    +          nokogiri (1.11.1)
    +            mini_portile2 (~> 2.5.0)
    +            racc (~> 1.4)
    +          nokogiri (1.11.1-#{Bundler.local_platform})
    +            racc (~> 1.4)
    +          racc (1.5.2)
    +
    +      PLATFORMS
    +        ruby
    +        #{Bundler.local_platform}
    +
    +      DEPENDENCIES
    +        nokogiri (~> 1.11)
    +
    +      BUNDLED WITH
    +         #{Bundler::VERSION}
    +    L
    +
    +    gemfile <<-G
    +      source "#{file_uri_for(gem_repo4)}"
    +      gem "nokogiri", "~> 1.11"
    +    G
    +
    +    lockfile good_lockfile
    +
    +    bundle "update nokogiri"
    +
    +    expect(lockfile).to eq(good_lockfile)
    +  end
    +
    +  it "will use the java platform if both generic java and generic ruby platforms are locked", :jruby do
    +    gemfile <<-G
    +      source "#{file_uri_for(gem_repo1)}"
    +      gem "nokogiri"
    +    G
    +
    +    lockfile <<-G
    +      GEM
    +        remote: #{file_uri_for(gem_repo1)}/
    +        specs:
    +          nokogiri (1.4.2)
    +          nokogiri (1.4.2-java)
    +            weakling (>= 0.0.3)
    +          weakling (0.0.3)
    +
    +      PLATFORMS
    +        java
    +        ruby
    +
    +      DEPENDENCIES
    +        nokogiri
    +
    +      BUNDLED WITH
    +        2.1.4
    +    G
    +
    +    bundle "install"
    +
    +    expect(out).to include("Fetching nokogiri 1.4.2 (java)")
    +    expect(the_bundle).to include_gems "nokogiri 1.4.2 JAVA"
    +  end
    +
       it "will add the resolve for the current platform" do
         lockfile <<-G
           GEM
    
  • bundler/spec/support/indexes.rb+1 1 modified
    @@ -30,7 +30,7 @@ def resolve(args = [])
           args[1] ||= Bundler::GemVersionPromoter.new # gem_version_promoter
           args[2] ||= [] # additional_base_requirements
           args[3] ||= @platforms # platforms
    -      Bundler::Resolver.resolve(deps, @index, source_requirements, *args)
    +      Bundler::Resolver.resolve(deps, source_requirements, *args)
         end
     
         def should_resolve_as(specs)
    
  • CHANGELOG.md+9 0 modified
    @@ -1,3 +1,12 @@
    +# 3.2.10 / 2021-02-15
    +
    +## Documentation:
    +
    +* Add a `gem push` example to `gem help`. Pull request #4373 by
    +  deivid-rodriguez
    +* Improve documentation for `required_ruby_version`. Pull request #4343 by
    +  AlexWayfer
    +
     # 3.2.9 / 2021-02-08
     
     ## Bug fixes:
    
  • dev_gems.rb.lock+1 1 modified
    @@ -112,4 +112,4 @@ DEPENDENCIES
       webrick (~> 1.6)
     
     BUNDLED WITH
    -   2.2.9
    +   2.2.10
    
  • .github/workflows/ruby-core.yml+5 3 modified
    @@ -46,15 +46,17 @@ jobs:
           - uses: actions/checkout@v2
             with:
               path: rubygems/rubygems
    -      - name: Test RubyGems
    +      - name: Sync tools
             run: |
               ruby tool/sync_default_gems.rb rubygems
    -          make -j2 -s test-all TESTS="rubygems --no-retry"
    +          ruby tool/sync_default_gems.rb bundler
    +        working-directory: ruby/ruby
    +      - name: Test RubyGems
    +        run: make -j2 -s test-all TESTS="rubygems --no-retry"
             working-directory: ruby/ruby
             if: matrix.target == 'Rubygems'
           - name: Test Bundler
             run: |
    -          ruby tool/sync_default_gems.rb bundler
               git checkout lib/bundler/bundler.gemspec
               git add .
               make test-bundler-parallel
    
  • lib/rubygems/command.rb+1 0 modified
    @@ -634,6 +634,7 @@ def wrap(text, width) # :doc:
         gem install rake
         gem list --local
         gem build package.gemspec
    +    gem push package-0.0.1.gem
         gem help install
     
       Further help:
    
  • lib/rubygems.rb+1 1 modified
    @@ -8,7 +8,7 @@
     require 'rbconfig'
     
     module Gem
    -  VERSION = "3.2.9".freeze
    +  VERSION = "3.2.10".freeze
     end
     
     # Must be first since it unloads the prelude from 1.9.2
    
  • lib/rubygems/specification.rb+3 0 modified
    @@ -666,6 +666,9 @@ def rdoc_options
       #
       #  # Only prereleases or final releases after 2.6.0.preview2
       #  spec.required_ruby_version = '> 2.6.0.preview2'
    +  #
    +  #  # This gem will work with 2.3.0 or greater, including major version 3, but lesser than 4.0.0
    +  #  spec.required_ruby_version = '>= 2.3', '< 4'
     
       def required_ruby_version=(req)
         @required_ruby_version = Gem::Requirement.create req
    
  • rubygems-update.gemspec+1 1 modified
    @@ -2,7 +2,7 @@
     
     Gem::Specification.new do |s|
       s.name = "rubygems-update"
    -  s.version = "3.2.9"
    +  s.version = "3.2.10"
       s.authors = ["Jim Weirich", "Chad Fowler", "Eric Hodel", "Luis Lavena", "Aaron Patterson", "Samuel Giddins", "André Arko", "Evan Phoenix", "Hiroshi SHIBATA"]
       s.email = ["", "", "drbrain@segment7.net", "luislavena@gmail.com", "aaron@tenderlovemaking.com", "segiddins@segiddins.me", "andre@arko.net", "evan@phx.io", "hsbt@ruby-lang.org"]
     
    
  • test/rubygems/test_gem.rb+2 8 modified
    @@ -1958,15 +1958,9 @@ def test_use_gemdeps_missing_gem
           io.write 'gem "a"'
         end
     
    -    platform = Bundler::GemHelpers.generic_local_platform
    -    if platform == Gem::Platform::RUBY
    -      platform = ''
    -    else
    -      platform = " #{platform}"
    -    end
    -
         expected = <<-EXPECTED
    -Could not find gem 'a#{platform}' in any of the gem sources listed in your Gemfile.
    +Could not find gem 'a' in locally installed gems.
    +The source does not contain any versions of 'a'
     You may need to `gem install -g` to install missing gems
     
         EXPECTED
    
078bf682ac40

Merge pull request #4609 from rubygems/final_source_priority_fixes

https://github.com/rubygems/rubygemsDavid RodríguezMay 25, 2021via ghsa
52 files changed · +772 680
  • bundler/lib/bundler/cli/outdated.rb+7 10 modified
    @@ -146,17 +146,14 @@ def nothing_outdated_message
         end
     
         def retrieve_active_spec(definition, current_spec)
    -      if strict
    -        active_spec = definition.find_resolved_spec(current_spec)
    -      else
    -        active_specs = definition.find_indexed_specs(current_spec)
    -        if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
    -          active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
    -        end
    -        active_spec = active_specs.last
    -      end
    +      active_spec = definition.resolve.find_by_name_and_platform(current_spec.name, current_spec.platform)
    +      return active_spec if strict
     
    -      active_spec
    +      active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
    +      if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
    +        active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
    +      end
    +      active_specs.last
         end
     
         def print_gems(gems_list)
    
  • bundler/lib/bundler/definition.rb+14 75 modified
    @@ -224,7 +224,6 @@ def missing_specs?
           Bundler.ui.debug "The definition is missing #{missing.map(&:full_name)}"
           true
         rescue BundlerError => e
    -      @index = nil
           @resolve = nil
           @specs = nil
           @gem_version_promoter = nil
    @@ -287,50 +286,6 @@ def resolve
           end
         end
     
    -    def index
    -      @index ||= Index.build do |idx|
    -        dependency_names = @dependencies.map(&:name)
    -
    -        sources.all_sources.each do |source|
    -          source.dependency_names = dependency_names - pinned_spec_names(source)
    -          idx.add_source source.specs
    -          dependency_names.concat(source.unmet_deps).uniq!
    -        end
    -
    -        double_check_for_index(idx, dependency_names)
    -      end
    -    end
    -
    -    # Suppose the gem Foo depends on the gem Bar.  Foo exists in Source A.  Bar has some versions that exist in both
    -    # sources A and B.  At this point, the API request will have found all the versions of Bar in source A,
    -    # but will not have found any versions of Bar from source B, which is a problem if the requested version
    -    # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
    -    # each spec we found, we add all possible versions from all sources to the index.
    -    def double_check_for_index(idx, dependency_names)
    -      pinned_names = pinned_spec_names
    -      loop do
    -        idxcount = idx.size
    -
    -        names = :names # do this so we only have to traverse to get dependency_names from the index once
    -        unmet_dependency_names = lambda do
    -          return names unless names == :names
    -          new_names = sources.all_sources.map(&:dependency_names_to_double_check)
    -          return names = nil if new_names.compact!
    -          names = new_names.flatten(1).concat(dependency_names)
    -          names.uniq!
    -          names -= pinned_names
    -          names
    -        end
    -
    -        sources.all_sources.each do |source|
    -          source.double_check_for(unmet_dependency_names)
    -        end
    -
    -        break if idxcount == idx.size
    -      end
    -    end
    -    private :double_check_for_index
    -
         def has_rubygems_remotes?
           sources.rubygems_sources.any? {|s| s.remotes.any? }
         end
    @@ -539,14 +494,6 @@ def most_specific_locked_platform
           end
         end
     
    -    def find_resolved_spec(current_spec)
    -      specs.find_by_name_and_platform(current_spec.name, current_spec.platform)
    -    end
    -
    -    def find_indexed_specs(current_spec)
    -      index[current_spec.name].select {|spec| spec.match_platform(current_spec.platform) }.sort_by(&:version)
    -    end
    -
         attr_reader :sources
         private :sources
     
    @@ -563,6 +510,10 @@ def unlocking?
     
         private
     
    +    def precompute_source_requirements_for_indirect_dependencies?
    +      sources.non_global_rubygems_sources.all?(&:dependency_api_available?) && sources.no_aggregate_global_source?
    +    end
    +
         def current_ruby_platform_locked?
           return false unless generic_local_platform == Gem::Platform::RUBY
     
    @@ -688,9 +639,9 @@ def converge_rubygems_sources
           changes = false
     
           # If there is a RubyGems source in both
    -      locked_gem_sources.each do |locked_gem|
    +      locked_gem_sources.each do |locked_gem_source|
             # Merge the remotes from the Gemfile into the Gemfile.lock
    -        changes |= locked_gem.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
    +        changes |= locked_gem_source.replace_remotes(actual_remotes, Bundler.settings[:allow_deployment_source_credential_changes])
           end
     
           changes
    @@ -909,26 +860,22 @@ def expand_dependency_with_platforms(dep, platforms)
         end
     
         def source_requirements
    -      # Load all specs from remote sources
    -      index
    -
           # Record the specs available in each gem's source, so that those
           # specs will be available later when the resolver knows where to
           # look for that gemspec (or its dependencies)
    -      source_requirements = { :default => sources.default_source }.merge(dependency_source_requirements)
    +      source_requirements = if precompute_source_requirements_for_indirect_dependencies?
    +        { :default => sources.default_source }.merge(source_map.all_requirements)
    +      else
    +        { :default => Source::RubygemsAggregate.new(sources, source_map) }.merge(source_map.direct_requirements)
    +      end
           metadata_dependencies.each do |dep|
             source_requirements[dep.name] = sources.metadata_source
           end
    -      source_requirements[:global] = index unless Bundler.feature_flag.disable_multisource?
    -      source_requirements[:default_bundler] = source_requirements["bundler"] || source_requirements[:default]
    +      source_requirements[:default_bundler] = source_requirements["bundler"] || sources.default_source
           source_requirements["bundler"] = sources.metadata_source # needs to come last to override
           source_requirements
         end
     
    -    def pinned_spec_names(skip = nil)
    -      dependency_source_requirements.reject {|_, source| source == skip }.keys
    -    end
    -
         def requested_groups
           groups - Bundler.settings[:without] - @optional_groups + Bundler.settings[:with]
         end
    @@ -984,16 +931,8 @@ def equivalent_rubygems_remotes?(source)
           Bundler.settings[:allow_deployment_source_credential_changes] && source.equivalent_remotes?(sources.rubygems_remotes)
         end
     
    -    def dependency_source_requirements
    -      @dependency_source_requirements ||= begin
    -        source_requirements = {}
    -        default = sources.default_source
    -        dependencies.each do |dep|
    -          dep_source = dep.source || default
    -          source_requirements[dep.name] = dep_source
    -        end
    -        source_requirements
    -      end
    +    def source_map
    +      @source_map ||= SourceMap.new(sources, dependencies)
         end
       end
     end
    
  • bundler/lib/bundler/feature_flag.rb+0 1 modified
    @@ -31,7 +31,6 @@ def self.settings_method(name, key, &default)
         settings_flag(:auto_clean_without_path) { bundler_3_mode? }
         settings_flag(:cache_all) { bundler_3_mode? }
         settings_flag(:default_install_uses_path) { bundler_3_mode? }
    -    settings_flag(:disable_multisource) { bundler_3_mode? }
         settings_flag(:forget_cli_options) { bundler_3_mode? }
         settings_flag(:global_gem_cache) { bundler_3_mode? }
         settings_flag(:path_relative_to_cwd) { bundler_3_mode? }
    
  • bundler/lib/bundler/index.rb+1 2 modified
    @@ -122,10 +122,9 @@ def spec_names
           names
         end
     
    -    # returns a list of the dependencies
         def unmet_dependency_names
           dependency_names.select do |name|
    -        name != "bundler" && search(name).empty?
    +        search(name).empty?
           end
         end
     
    
  • bundler/lib/bundler/man/bundle.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE" "1" "April 2021" "" ""
    +.TH "BUNDLE" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\fR \- Ruby Dependency Management
    
  • bundler/lib/bundler/man/bundle-add.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-ADD" "1" "April 2021" "" ""
    +.TH "BUNDLE\-ADD" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
    
  • bundler/lib/bundler/man/bundle-binstubs.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-BINSTUBS" "1" "April 2021" "" ""
    +.TH "BUNDLE\-BINSTUBS" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-binstubs\fR \- Install the binstubs of the listed gems
    
  • bundler/lib/bundler/man/bundle-cache.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-CACHE" "1" "April 2021" "" ""
    +.TH "BUNDLE\-CACHE" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-cache\fR \- Package your needed \fB\.gem\fR files into your application
    
  • bundler/lib/bundler/man/bundle-check.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-CHECK" "1" "April 2021" "" ""
    +.TH "BUNDLE\-CHECK" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-check\fR \- Verifies if dependencies are satisfied by installed gems
    
  • bundler/lib/bundler/man/bundle-clean.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-CLEAN" "1" "April 2021" "" ""
    +.TH "BUNDLE\-CLEAN" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-clean\fR \- Cleans up unused gems in your bundler directory
    
  • bundler/lib/bundler/man/bundle-config.1+1 7 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-CONFIG" "1" "April 2021" "" ""
    +.TH "BUNDLE\-CONFIG" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-config\fR \- Set bundler configuration options
    @@ -56,9 +56,6 @@ Executing \fBbundle config unset \-\-local <name> <value>\fR will delete the con
     .P
     Executing bundle with the \fBBUNDLE_IGNORE_CONFIG\fR environment variable set will cause it to ignore all configuration\.
     .
    -.P
    -Executing \fBbundle config set \-\-local disable_multisource true\fR upgrades the warning about the Gemfile containing multiple primary sources to an error\. Executing \fBbundle config unset disable_multisource\fR downgrades this error to a warning\.
    -.
     .SH "REMEMBERING OPTIONS"
     Flags passed to \fBbundle install\fR or the Bundler runtime, such as \fB\-\-path foo\fR or \fB\-\-without production\fR, are remembered between commands and saved to your local application\'s configuration (normally, \fB\./\.bundle/config\fR)\.
     .
    @@ -184,9 +181,6 @@ The following is a list of all configuration keys and their purpose\. You can le
     \fBdisable_local_revision_check\fR (\fBBUNDLE_DISABLE_LOCAL_REVISION_CHECK\fR): Allow Bundler to use a local git override without checking if the revision present in the lockfile is present in the repository\.
     .
     .IP "\(bu" 4
    -\fBdisable_multisource\fR (\fBBUNDLE_DISABLE_MULTISOURCE\fR): When set, Gemfiles containing multiple sources will produce errors instead of warnings\. Use \fBbundle config unset disable_multisource\fR to unset\.
    -.
    -.IP "\(bu" 4
     \fBdisable_shared_gems\fR (\fBBUNDLE_DISABLE_SHARED_GEMS\fR): Stop Bundler from accessing gems installed to RubyGems\' normal location\.
     .
     .IP "\(bu" 4
    
  • bundler/lib/bundler/man/bundle-config.1.ronn+0 8 modified
    @@ -47,10 +47,6 @@ configuration only from the local application.
     Executing bundle with the `BUNDLE_IGNORE_CONFIG` environment variable set will
     cause it to ignore all configuration.
     
    -Executing `bundle config set --local disable_multisource true` upgrades the warning about
    -the Gemfile containing multiple primary sources to an error. Executing `bundle
    -config unset disable_multisource` downgrades this error to a warning.
    -
     ## REMEMBERING OPTIONS
     
     Flags passed to `bundle install` or the Bundler runtime, such as `--path foo` or
    @@ -178,10 +174,6 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
     * `disable_local_revision_check` (`BUNDLE_DISABLE_LOCAL_REVISION_CHECK`):
        Allow Bundler to use a local git override without checking if the revision
        present in the lockfile is present in the repository.
    -* `disable_multisource` (`BUNDLE_DISABLE_MULTISOURCE`):
    -   When set, Gemfiles containing multiple sources will produce errors
    -   instead of warnings.
    -   Use `bundle config unset disable_multisource` to unset.
     * `disable_shared_gems` (`BUNDLE_DISABLE_SHARED_GEMS`):
        Stop Bundler from accessing gems installed to RubyGems' normal location.
     * `disable_version_check` (`BUNDLE_DISABLE_VERSION_CHECK`):
    
  • bundler/lib/bundler/man/bundle-doctor.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-DOCTOR" "1" "April 2021" "" ""
    +.TH "BUNDLE\-DOCTOR" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-doctor\fR \- Checks the bundle for common problems
    
  • bundler/lib/bundler/man/bundle-exec.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-EXEC" "1" "April 2021" "" ""
    +.TH "BUNDLE\-EXEC" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-exec\fR \- Execute a command in the context of the bundle
    
  • bundler/lib/bundler/man/bundle-gem.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-GEM" "1" "April 2021" "" ""
    +.TH "BUNDLE\-GEM" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-gem\fR \- Generate a project skeleton for creating a rubygem
    
  • bundler/lib/bundler/man/bundle-info.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-INFO" "1" "April 2021" "" ""
    +.TH "BUNDLE\-INFO" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-info\fR \- Show information for the given gem in your bundle
    
  • bundler/lib/bundler/man/bundle-init.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-INIT" "1" "April 2021" "" ""
    +.TH "BUNDLE\-INIT" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-init\fR \- Generates a Gemfile into the current working directory
    
  • bundler/lib/bundler/man/bundle-inject.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-INJECT" "1" "April 2021" "" ""
    +.TH "BUNDLE\-INJECT" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-inject\fR \- Add named gem(s) with version requirements to Gemfile
    
  • bundler/lib/bundler/man/bundle-install.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-INSTALL" "1" "April 2021" "" ""
    +.TH "BUNDLE\-INSTALL" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-install\fR \- Install the dependencies specified in your Gemfile
    
  • bundler/lib/bundler/man/bundle-list.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-LIST" "1" "April 2021" "" ""
    +.TH "BUNDLE\-LIST" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-list\fR \- List all the gems in the bundle
    
  • bundler/lib/bundler/man/bundle-lock.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-LOCK" "1" "April 2021" "" ""
    +.TH "BUNDLE\-LOCK" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing
    
  • bundler/lib/bundler/man/bundle-open.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-OPEN" "1" "April 2021" "" ""
    +.TH "BUNDLE\-OPEN" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-open\fR \- Opens the source directory for a gem in your bundle
    
  • bundler/lib/bundler/man/bundle-outdated.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-OUTDATED" "1" "April 2021" "" ""
    +.TH "BUNDLE\-OUTDATED" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-outdated\fR \- List installed gems with newer versions available
    
  • bundler/lib/bundler/man/bundle-platform.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-PLATFORM" "1" "April 2021" "" ""
    +.TH "BUNDLE\-PLATFORM" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-platform\fR \- Displays platform compatibility information
    
  • bundler/lib/bundler/man/bundle-pristine.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-PRISTINE" "1" "April 2021" "" ""
    +.TH "BUNDLE\-PRISTINE" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-pristine\fR \- Restores installed gems to their pristine condition
    
  • bundler/lib/bundler/man/bundle-remove.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-REMOVE" "1" "April 2021" "" ""
    +.TH "BUNDLE\-REMOVE" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-remove\fR \- Removes gems from the Gemfile
    
  • bundler/lib/bundler/man/bundle-show.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-SHOW" "1" "April 2021" "" ""
    +.TH "BUNDLE\-SHOW" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-show\fR \- Shows all the gems in your bundle, or the path to a gem
    
  • bundler/lib/bundler/man/bundle-update.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-UPDATE" "1" "April 2021" "" ""
    +.TH "BUNDLE\-UPDATE" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-update\fR \- Update your gems to the latest available versions
    
  • bundler/lib/bundler/man/bundle-viz.1+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "BUNDLE\-VIZ" "1" "April 2021" "" ""
    +.TH "BUNDLE\-VIZ" "1" "May 2021" "" ""
     .
     .SH "NAME"
     \fBbundle\-viz\fR \- Generates a visual dependency graph for your Gemfile
    
  • bundler/lib/bundler/man/gemfile.5+1 1 modified
    @@ -1,7 +1,7 @@
     .\" generated with Ronn/v0.7.3
     .\" http://github.com/rtomayko/ronn/tree/0.7.3
     .
    -.TH "GEMFILE" "5" "April 2021" "" ""
    +.TH "GEMFILE" "5" "May 2021" "" ""
     .
     .SH "NAME"
     \fBGemfile\fR \- A format for describing gem dependencies for Ruby programs
    
  • bundler/lib/bundler/plugin/api/source.rb+14 0 modified
    @@ -244,6 +244,20 @@ def unmet_deps
               specs.unmet_dependency_names
             end
     
    +        # Used by definition.
    +        #
    +        # Note: Do not override if you don't know what you are doing.
    +        def spec_names
    +          specs.spec_names
    +        end
    +
    +        # Used by definition.
    +        #
    +        # Note: Do not override if you don't know what you are doing.
    +        def add_dependency_names(names)
    +          @dependencies |= Array(names)
    +        end
    +
             # Note: Do not override if you don't know what you are doing.
             def can_lock?(spec)
               spec.source == self
    
  • bundler/lib/bundler.rb+1 0 modified
    @@ -69,6 +69,7 @@ module Bundler
       autoload :SharedHelpers,          File.expand_path("bundler/shared_helpers", __dir__)
       autoload :Source,                 File.expand_path("bundler/source", __dir__)
       autoload :SourceList,             File.expand_path("bundler/source_list", __dir__)
    +  autoload :SourceMap,              File.expand_path("bundler/source_map", __dir__)
       autoload :SpecSet,                File.expand_path("bundler/spec_set", __dir__)
       autoload :StubSpecification,      File.expand_path("bundler/stub_specification", __dir__)
       autoload :UI,                     File.expand_path("bundler/ui", __dir__)
    
  • bundler/lib/bundler/resolver.rb+10 64 modified
    @@ -26,12 +26,6 @@ def self.resolve(requirements, source_requirements = {}, base = [], gem_version_
     
         def initialize(source_requirements, base, gem_version_promoter, additional_base_requirements, platforms)
           @source_requirements = source_requirements
    -
    -      @index_requirements = source_requirements.each_with_object({}) do |source_requirement, index_requirements|
    -        name, source = source_requirement
    -        index_requirements[name] = name == :global ? source : source.specs
    -      end
    -
           @base = base
           @resolver = Molinillo::Resolver.new(self, self)
           @search_for = {}
    @@ -45,7 +39,6 @@ def initialize(source_requirements, base, gem_version_promoter, additional_base_
           @resolving_only_for_ruby = platforms == [Gem::Platform::RUBY]
           @gem_version_promoter = gem_version_promoter
           @use_gvp = Bundler.feature_flag.use_gem_version_promoter_for_major_updates? || !@gem_version_promoter.major?
    -      @no_aggregate_global_source = @source_requirements[:global].nil?
         end
     
         def start(requirements)
    @@ -55,7 +48,6 @@ def start(requirements)
           verify_gemfile_dependencies_are_found!(requirements)
           dg = @resolver.resolve(requirements, @base_dg)
           dg.
    -        tap {|resolved| validate_resolved_specs!(resolved) }.
             map(&:payload).
             reject {|sg| sg.name.end_with?("\0") }.
             map(&:to_specs).
    @@ -171,16 +163,11 @@ def search_for(dependency_proxy)
         end
     
         def index_for(dependency)
    -      source = @index_requirements[dependency.name]
    -      if source
    -        source
    -      elsif @no_aggregate_global_source
    -        Index.build do |idx|
    -          dependency.all_sources.each {|s| idx.add_source(s.specs) }
    -        end
    -      else
    -        @index_requirements[:global]
    -      end
    +      source_for(dependency.name).specs
    +    end
    +
    +    def source_for(name)
    +      @source_requirements[name] || @source_requirements[:default]
         end
     
         def results_for(dependency, base)
    @@ -211,23 +198,10 @@ def dependencies_equal?(dependencies, other_dependencies)
           dependencies.map(&:dep) == other_dependencies.map(&:dep)
         end
     
    -    def relevant_sources_for_vertex(vertex)
    -      if vertex.root?
    -        [@source_requirements[vertex.name]]
    -      elsif @no_aggregate_global_source
    -        vertex.recursive_predecessors.map do |v|
    -          @source_requirements[v.name]
    -        end.compact << @source_requirements[:default]
    -      else
    -        []
    -      end
    -    end
    -
         def sort_dependencies(dependencies, activated, conflicts)
           dependencies.sort_by do |dependency|
             name = name_for(dependency)
             vertex = activated.vertex_named(name)
    -        dependency.all_sources = relevant_sources_for_vertex(vertex)
             [
               @base_dg.vertex_named(name) ? 0 : 1,
               vertex.payload ? 0 : 1,
    @@ -369,7 +343,7 @@ def version_conflict_message(e)
                 if other_bundler_required
                   o << "\n\n"
     
    -              candidate_specs = @index_requirements[:default_bundler].search(conflict_dependency)
    +              candidate_specs = source_for(:default_bundler).specs.search(conflict_dependency)
                   if candidate_specs.any?
                     target_version = candidate_specs.last.version
                     new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ")
    @@ -386,11 +360,7 @@ def version_conflict_message(e)
               elsif !conflict.existing
                 o << "\n"
     
    -            relevant_sources = if conflict.requirement.source
    -              [conflict.requirement.source]
    -            else
    -              conflict.requirement.all_sources
    -            end.compact.map(&:to_s).uniq.sort
    +            relevant_source = conflict.requirement.source || source_for(name)
     
                 metadata_requirement = name.end_with?("\0")
     
    @@ -403,12 +373,10 @@ def version_conflict_message(e)
                 end
                 o << " "
     
    -            o << if relevant_sources.empty?
    -              "in any of the sources.\n"
    -            elsif metadata_requirement
    -              "is not available in #{relevant_sources.join(" or ")}"
    +            o << if metadata_requirement
    +              "is not available in #{relevant_source}"
                 else
    -              "in any of the relevant sources:\n  #{relevant_sources * "\n  "}\n"
    +              "in #{relevant_source}.\n"
                 end
               end
             end,
    @@ -422,27 +390,5 @@ def version_conflict_message(e)
             end
           )
         end
    -
    -    def validate_resolved_specs!(resolved_specs)
    -      resolved_specs.each do |v|
    -        name = v.name
    -        sources = relevant_sources_for_vertex(v)
    -        next unless sources.any?
    -        if default_index = sources.index(@source_requirements[:default])
    -          sources.delete_at(default_index)
    -        end
    -        sources.reject! {|s| s.specs.search(name).empty? }
    -        sources.uniq!
    -        next if sources.size <= 1
    -
    -        msg = ["The gem '#{name}' was found in multiple relevant sources."]
    -        msg.concat sources.map {|s| "  * #{s}" }.sort
    -        msg << "You #{@no_aggregate_global_source ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
    -        msg = msg.join("\n")
    -
    -        raise SecurityError, msg if @no_aggregate_global_source
    -        Bundler.ui.warn "Warning: #{msg}"
    -      end
    -    end
       end
     end
    
  • bundler/lib/bundler/rubygems_ext.rb+2 2 modified
    @@ -105,7 +105,7 @@ def dependencies_to_gemfile(dependencies, group = nil)
       end
     
       class Dependency
    -    attr_accessor :source, :groups, :all_sources
    +    attr_accessor :source, :groups
     
         alias_method :eql?, :==
     
    @@ -116,7 +116,7 @@ def encode_with(coder)
         end
     
         def to_yaml_properties
    -      instance_variables.reject {|p| ["@source", "@groups", "@all_sources"].include?(p.to_s) }
    +      instance_variables.reject {|p| ["@source", "@groups"].include?(p.to_s) }
         end
     
         def to_lock
    
  • bundler/lib/bundler/settings.rb+0 1 modified
    @@ -20,7 +20,6 @@ class Settings
           disable_exec_load
           disable_local_branch_check
           disable_local_revision_check
    -      disable_multisource
           disable_shared_gems
           disable_version_check
           force_ruby_platform
    
  • bundler/lib/bundler/source_list.rb+29 10 modified
    @@ -21,15 +21,19 @@ def initialize
           @rubygems_sources       = []
           @metadata_source        = Source::Metadata.new
     
    -      @disable_multisource = true
    +      @merged_gem_lockfile_sections = false
         end
     
    -    def disable_multisource?
    -      @disable_multisource
    +    def merged_gem_lockfile_sections?
    +      @merged_gem_lockfile_sections
         end
     
         def merged_gem_lockfile_sections!
    -      @disable_multisource = false
    +      @merged_gem_lockfile_sections = true
    +    end
    +
    +    def no_aggregate_global_source?
    +      global_rubygems_source.remotes.size <= 1
         end
     
         def add_path_source(options = {})
    @@ -70,7 +74,11 @@ def default_source
         end
     
         def rubygems_sources
    -      @rubygems_sources + [global_rubygems_source]
    +      non_global_rubygems_sources + [global_rubygems_source]
    +    end
    +
    +    def non_global_rubygems_sources
    +      @rubygems_sources
         end
     
         def rubygems_remotes
    @@ -81,16 +89,27 @@ def all_sources
           path_sources + git_sources + plugin_sources + rubygems_sources + [metadata_source]
         end
     
    +    def non_default_explicit_sources
    +      all_sources - [default_source, metadata_source]
    +    end
    +
         def get(source)
           source_list_for(source).find {|s| equal_source?(source, s) || equivalent_source?(source, s) }
         end
     
         def lock_sources
    -      lock_sources = (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
    -      if disable_multisource?
    -        lock_sources + rubygems_sources.sort_by(&:to_s).uniq
    +      lock_other_sources + lock_rubygems_sources
    +    end
    +
    +    def lock_other_sources
    +      (path_sources + git_sources + plugin_sources).sort_by(&:to_s)
    +    end
    +
    +    def lock_rubygems_sources
    +      if merged_gem_lockfile_sections?
    +        [combine_rubygems_sources]
           else
    -        lock_sources << combine_rubygems_sources
    +        rubygems_sources.sort_by(&:to_s).uniq
           end
         end
     
    @@ -104,7 +123,7 @@ def replace_sources!(replacement_sources)
             end
           end
     
    -      replacement_rubygems = !disable_multisource? &&
    +      replacement_rubygems = merged_gem_lockfile_sections? &&
             replacement_sources.detect {|s| s.is_a?(Source::Rubygems) }
           @global_rubygems_source = replacement_rubygems if replacement_rubygems
     
    
  • bundler/lib/bundler/source_map.rb+58 0 added
    @@ -0,0 +1,58 @@
    +# frozen_string_literal: true
    +
    +module Bundler
    +  class SourceMap
    +    attr_reader :sources, :dependencies
    +
    +    def initialize(sources, dependencies)
    +      @sources = sources
    +      @dependencies = dependencies
    +    end
    +
    +    def pinned_spec_names(skip = nil)
    +      direct_requirements.reject {|_, source| source == skip }.keys
    +    end
    +
    +    def all_requirements
    +      requirements = direct_requirements.dup
    +
    +      unmet_deps = sources.non_default_explicit_sources.map do |source|
    +        (source.spec_names - pinned_spec_names).each do |indirect_dependency_name|
    +          previous_source = requirements[indirect_dependency_name]
    +          if previous_source.nil?
    +            requirements[indirect_dependency_name] = source
    +          else
    +            no_ambiguous_sources = Bundler.feature_flag.bundler_3_mode?
    +
    +            msg = ["The gem '#{indirect_dependency_name}' was found in multiple relevant sources."]
    +            msg.concat [previous_source, source].map {|s| "  * #{s}" }.sort
    +            msg << "You #{no_ambiguous_sources ? :must : :should} add this gem to the source block for the source you wish it to be installed from."
    +            msg = msg.join("\n")
    +
    +            raise SecurityError, msg if no_ambiguous_sources
    +            Bundler.ui.warn "Warning: #{msg}"
    +          end
    +        end
    +
    +        source.unmet_deps
    +      end
    +
    +      sources.default_source.add_dependency_names(unmet_deps.flatten - requirements.keys)
    +
    +      requirements
    +    end
    +
    +    def direct_requirements
    +      @direct_requirements ||= begin
    +        requirements = {}
    +        default = sources.default_source
    +        dependencies.each do |dep|
    +          dep_source = dep.source || default
    +          dep_source.add_dependency_names(dep.name)
    +          requirements[dep.name] = dep_source
    +        end
    +        requirements
    +      end
    +    end
    +  end
    +end
    
  • bundler/lib/bundler/source.rb+9 0 modified
    @@ -7,6 +7,7 @@ class Source
         autoload :Metadata, File.expand_path("source/metadata", __dir__)
         autoload :Path,     File.expand_path("source/path", __dir__)
         autoload :Rubygems, File.expand_path("source/rubygems", __dir__)
    +    autoload :RubygemsAggregate, File.expand_path("source/rubygems_aggregate", __dir__)
     
         attr_accessor :dependency_names
     
    @@ -39,6 +40,10 @@ def cached!; end
     
         def remote!; end
     
    +    def add_dependency_names(names)
    +      @dependency_names = Array(dependency_names) | Array(names)
    +    end
    +
         # it's possible that gems from one source depend on gems from some
         # other source, so now we download gemspecs and iterate over those
         # dependencies, looking for gems we don't have info on yet.
    @@ -48,6 +53,10 @@ def dependency_names_to_double_check
           specs.dependency_names
         end
     
    +    def spec_names
    +      specs.spec_names
    +    end
    +
         def include?(other)
           other == self
         end
    
  • bundler/lib/bundler/source/rubygems_aggregate.rb+64 0 added
    @@ -0,0 +1,64 @@
    +# frozen_string_literal: true
    +
    +module Bundler
    +  class Source
    +    class RubygemsAggregate
    +      attr_reader :source_map, :sources
    +
    +      def initialize(sources, source_map)
    +        @sources = sources
    +        @source_map = source_map
    +
    +        @index = build_index
    +      end
    +
    +      def specs
    +        @index
    +      end
    +
    +      def to_s
    +        "any of the sources"
    +      end
    +
    +      private
    +
    +      def build_index
    +        Index.build do |idx|
    +          dependency_names = source_map.pinned_spec_names
    +
    +          sources.all_sources.each do |source|
    +            source.dependency_names = dependency_names - source_map.pinned_spec_names(source)
    +            idx.add_source source.specs
    +            dependency_names.concat(source.unmet_deps).uniq!
    +          end
    +
    +          double_check_for_index(idx, dependency_names)
    +        end
    +      end
    +
    +      # Suppose the gem Foo depends on the gem Bar.  Foo exists in Source A.  Bar has some versions that exist in both
    +      # sources A and B.  At this point, the API request will have found all the versions of Bar in source A,
    +      # but will not have found any versions of Bar from source B, which is a problem if the requested version
    +      # of Foo specifically depends on a version of Bar that is only found in source B. This ensures that for
    +      # each spec we found, we add all possible versions from all sources to the index.
    +      def double_check_for_index(idx, dependency_names)
    +        pinned_names = source_map.pinned_spec_names
    +
    +        names = :names # do this so we only have to traverse to get dependency_names from the index once
    +        unmet_dependency_names = lambda do
    +          return names unless names == :names
    +          new_names = sources.all_sources.map(&:dependency_names_to_double_check)
    +          return names = nil if new_names.compact!
    +          names = new_names.flatten(1).concat(dependency_names)
    +          names.uniq!
    +          names -= pinned_names
    +          names
    +        end
    +
    +        sources.all_sources.each do |source|
    +          source.double_check_for(unmet_dependency_names)
    +        end
    +      end
    +    end
    +  end
    +end
    
  • bundler/lib/bundler/source/rubygems.rb+15 4 modified
    @@ -259,8 +259,16 @@ def replace_remotes(other_remotes, allow_equivalent = false)
             !equivalent
           end
     
    +      def spec_names
    +        if @allow_remote && dependency_api_available?
    +          remote_specs.spec_names
    +        else
    +          []
    +        end
    +      end
    +
           def unmet_deps
    -        if @allow_remote && api_fetchers.any?
    +        if @allow_remote && dependency_api_available?
               remote_specs.unmet_dependency_names
             else
               []
    @@ -276,7 +284,7 @@ def fetchers
     
           def double_check_for(unmet_dependency_names)
             return unless @allow_remote
    -        return unless api_fetchers.any?
    +        return unless dependency_api_available?
     
             unmet_dependency_names = unmet_dependency_names.call
             unless unmet_dependency_names.nil?
    @@ -298,17 +306,20 @@ def dependency_names_to_double_check
             remote_specs.each do |spec|
               case spec
               when EndpointSpecification, Gem::Specification, StubSpecification, LazySpecification
    -            names.concat(spec.runtime_dependencies)
    +            names.concat(spec.runtime_dependencies.map(&:name))
               when RemoteSpecification # from the full index
                 return nil
               else
                 raise "unhandled spec type (#{spec.inspect})"
               end
             end
    -        names.map!(&:name) if names
             names
           end
     
    +      def dependency_api_available?
    +        api_fetchers.any?
    +      end
    +
           protected
     
           def credless_remotes
    
  • bundler/spec/bundler/definition_spec.rb+0 27 modified
    @@ -278,33 +278,6 @@
         end
       end
     
    -  describe "find_resolved_spec" do
    -    it "with no platform set in SpecSet" do
    -      ss = Bundler::SpecSet.new([build_stub_spec("a", "1.0"), build_stub_spec("b", "1.0")])
    -      dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
    -      dfn.instance_variable_set("@specs", ss)
    -      found = dfn.find_resolved_spec(build_spec("a", "0.9", "ruby").first)
    -      expect(found.name).to eq "a"
    -      expect(found.version.to_s).to eq "1.0"
    -    end
    -  end
    -
    -  describe "find_indexed_specs" do
    -    it "with no platform set in indexed specs" do
    -      index = Bundler::Index.new
    -      %w[1.0.0 1.0.1 1.1.0].each {|v| index << build_stub_spec("foo", v) }
    -
    -      dfn = Bundler::Definition.new(nil, [], mock_source_list, true)
    -      dfn.instance_variable_set("@index", index)
    -      found = dfn.find_indexed_specs(build_spec("foo", "0.9", "ruby").first)
    -      expect(found.length).to eq 3
    -    end
    -  end
    -
    -  def build_stub_spec(name, version)
    -    Bundler::StubSpecification.new(name, version, nil, nil)
    -  end
    -
       def mock_source_list
         Class.new do
           def all_sources
    
  • bundler/spec/commands/lock_spec.rb+1 1 modified
    @@ -533,7 +533,7 @@ def read_lockfile(file = "Gemfile.lock")
              #{Bundler::VERSION}
         L
     
    -    bundle "lock --add-platform x86_64-linux", :artifice => :compact_index, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
    +    bundle "lock --add-platform x86_64-linux", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo4.to_s }
       end
     
       context "when an update is available" do
    
  • bundler/spec/commands/outdated_spec.rb+2 2 modified
    @@ -205,8 +205,8 @@
         end
     
         it "works" do
    -      bundle :install, :artifice => :compact_index
    -      bundle :outdated, :artifice => :compact_index, :raise_on_error => false
    +      bundle :install, :artifice => "compact_index"
    +      bundle :outdated, :artifice => "compact_index", :raise_on_error => false
     
           expected_output = <<~TABLE
             Gem  Current  Latest  Requested  Groups
    
  • bundler/spec/commands/update_spec.rb+2 2 modified
    @@ -649,8 +649,8 @@
         end
     
         it "works" do
    -      bundle :install, :artifice => :compact_index
    -      bundle "update oj", :artifice => :compact_index
    +      bundle :install, :artifice => "compact_index"
    +      bundle "update oj", :artifice => "compact_index"
     
           expect(out).to include("Bundle updated!")
           expect(the_bundle).to include_gems "oj 3.11.5"
    
  • bundler/spec/install/gemfile/sources_spec.rb+502 378 modified
    @@ -20,23 +20,23 @@
     
           before do
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo3)}"
    -          source "#{file_uri_for(gem_repo1)}"
    +          source "https://gem.repo3"
    +          source "https://gem.repo1"
               gem "rack-obama"
               gem "rack"
             G
           end
     
           it "warns about ambiguous gems, but installs anyway, prioritizing sources last to first", :bundler => "< 3" do
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
     
             expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
    -        expect(err).to include("Installed from: #{file_uri_for(gem_repo1)}")
    +        expect(err).to include("Installed from: https://gem.repo1")
             expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
           end
     
           it "fails", :bundler => "3" do
    -        bundle :instal, :raise_on_error => false
    +        bundle :instal, :artifice => "compact_index", :raise_on_error => false
             expect(err).to include("Each source after the first must include a block")
             expect(exitstatus).to eq(4)
           end
    @@ -47,22 +47,22 @@
     
           before do
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo3)}"
    -          source "#{file_uri_for(gem_repo1)}"
    +          source "https://gem.repo3"
    +          source "https://gem.repo1"
               gem "rack-obama"
               gem "rack", "1.0.0" # force it to install the working version in repo1
             G
           end
     
           it "warns about ambiguous gems, but installs anyway", :bundler => "< 3" do
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
             expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
    -        expect(err).to include("Installed from: #{file_uri_for(gem_repo1)}")
    +        expect(err).to include("Installed from: https://gem.repo1")
             expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0", :source => "remote1")
           end
     
           it "fails", :bundler => "3" do
    -        bundle :install, :raise_on_error => false
    +        bundle :install, :artifice => "compact_index", :raise_on_error => false
             expect(err).to include("Each source after the first must include a block")
             expect(exitstatus).to eq(4)
           end
    @@ -85,8 +85,8 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo3)}"
    -          source "#{file_uri_for(gem_repo1)}" do
    +          source "https://gem.repo3"
    +          source "https://gem.repo1" do
                 gem "thin" # comes first to test name sorting
                 gem "rack"
               end
    @@ -95,20 +95,20 @@
           end
     
           it "installs the gems without any warning" do
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
             expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("rack-obama 1.0.0")
             expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote1")
           end
     
           it "can cache and deploy" do
    -        bundle :cache
    +        bundle :cache, :artifice => "compact_index"
     
             expect(bundled_app("vendor/cache/rack-1.0.0.gem")).to exist
             expect(bundled_app("vendor/cache/rack-obama-1.0.gem")).to exist
     
             bundle "config set --local deployment true"
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
     
             expect(the_bundle).to include_gems("rack-obama 1.0.0", "rack 1.0.0")
           end
    @@ -128,10 +128,10 @@
               end
             end
     
    -        install_gemfile <<-G
    -          source "#{file_uri_for(gem_repo3)}"
    +        install_gemfile <<-G, :artifice => "compact_index"
    +          source "https://gem.repo3"
               gem "rack-obama" # should come from repo3!
    -          gem "rack", :source => "#{file_uri_for(gem_repo1)}"
    +          gem "rack", :source => "https://gem.repo1"
             G
           end
     
    @@ -155,8 +155,8 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo2)}"
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo2"
    +          source "https://gem.repo3" do
                 gem "depends_on_rack"
               end
             G
    @@ -168,7 +168,7 @@
             end
     
             it "installs from the same source without any warning" do
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
               expect(err).not_to include("Warning")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
             end
    @@ -185,15 +185,15 @@
             end
     
             it "installs from the same source without any warning" do
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
     
               expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
     
               # In https://github.com/bundler/bundler/issues/3585 this failed
               # when there is already a lock file, and the gems are missing, so try again
               system_gems []
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
     
               expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
    @@ -218,9 +218,9 @@
     
           context "and not in any other sources" do
             before do
    -          install_gemfile <<-G
    -            source "#{file_uri_for(gem_repo2)}"
    -            source "#{file_uri_for(gem_repo3)}" do
    +          install_gemfile <<-G, :artifice => "compact_index"
    +            source "https://gem.repo2"
    +            source "https://gem.repo3" do
                   gem "depends_on_rack"
                 end
               G
    @@ -235,23 +235,23 @@
           context "and in yet another source" do
             before do
               gemfile <<-G
    -            source "#{file_uri_for(gem_repo1)}"
    -            source "#{file_uri_for(gem_repo2)}"
    -            source "#{file_uri_for(gem_repo3)}" do
    +            source "https://gem.repo1"
    +            source "https://gem.repo2"
    +            source "https://gem.repo3" do
                   gem "depends_on_rack"
                 end
               G
             end
     
             it "installs from the other source and warns about ambiguous gems", :bundler => "< 3" do
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
               expect(err).to include("Warning: the gem 'rack' was found in multiple sources.")
    -          expect(err).to include("Installed from: #{file_uri_for(gem_repo2)}")
    +          expect(err).to include("Installed from: https://gem.repo2")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
             end
     
             it "fails", :bundler => "3" do
    -          bundle :install, :raise_on_error => false
    +          bundle :install, :artifice => "compact_index", :raise_on_error => false
               expect(err).to include("Each source after the first must include a block")
               expect(exitstatus).to eq(4)
             end
    @@ -267,31 +267,31 @@
               end
     
               gemfile <<-G
    -            source "#{file_uri_for(gem_repo3)}" # contains depends_on_rack
    -            source "#{file_uri_for(gem_repo2)}" # contains broken rack
    +            source "https://gem.repo3" # contains depends_on_rack
    +            source "https://gem.repo2" # contains broken rack
     
                 gem "depends_on_rack" # installed from gem_repo3
    -            gem "rack", :source => "#{file_uri_for(gem_repo1)}"
    +            gem "rack", :source => "https://gem.repo1"
               G
             end
     
             it "installs the dependency from the pinned source without warning", :bundler => "< 3" do
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
     
               expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
     
               # In https://github.com/rubygems/bundler/issues/3585 this failed
               # when there is already a lock file, and the gems are missing, so try again
               system_gems []
    -          bundle :install
    +          bundle :install, :artifice => "compact_index"
     
               expect(err).not_to include("Warning: the gem 'rack' was found in multiple sources.")
               expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0")
             end
     
             it "fails", :bundler => "3" do
    -          bundle :install, :raise_on_error => false
    +          bundle :install, :artifice => "compact_index", :raise_on_error => false
               expect(err).to include("Each source after the first must include a block")
               expect(exitstatus).to eq(4)
             end
    @@ -308,360 +308,457 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo2)}"
    +          source "https://gem.repo2"
     
               gem "private_gem_1"
     
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo3" do
                 gem "private_gem_2"
               end
             G
           end
     
           it "fails" do
    -        bundle :install, :raise_on_error => false
    -        expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository #{file_uri_for(gem_repo2)}/ or installed locally.")
    +        bundle :install, :artifice => "compact_index", :raise_on_error => false
    +        expect(err).to include("Could not find gem 'private_gem_1' in rubygems repository https://gem.repo2/ or installed locally.")
             expect(err).to include("The source does not contain any versions of 'private_gem_1'")
           end
         end
     
    -    context "when a top-level gem has an indirect dependency" do
    -      context "when disable_multisource is set" do
    -        before do
    -          bundle "config set disable_multisource true"
    +    context "when an indirect dependency can't be found in the aggregate rubygems source", :bundler => "< 3" do
    +      before do
    +        build_repo2
    +
    +        build_repo gem_repo3 do
    +          build_gem "depends_on_missing", "1.0.1" do |s|
    +            s.add_dependency "missing"
    +          end
             end
     
    -        before do
    -          build_repo gem_repo2 do
    -            build_gem "depends_on_rack", "1.0.1" do |s|
    -              s.add_dependency "rack"
    -            end
    +        gemfile <<-G
    +          source "https://gem.repo2"
    +
    +          source "https://gem.repo3"
    +
    +          gem "depends_on_missing"
    +        G
    +      end
    +
    +      it "fails" do
    +        bundle :install, :artifice => "compact_index", :raise_on_error => false
    +        expect(err).to include("Could not find gem 'missing', which is required by gem 'depends_on_missing', in any of the sources.")
    +      end
    +    end
    +
    +    context "when a top-level gem has an indirect dependency" do
    +      before do
    +        build_repo gem_repo2 do
    +          build_gem "depends_on_rack", "1.0.1" do |s|
    +            s.add_dependency "rack"
               end
    +        end
    +
    +        build_repo gem_repo3 do
    +          build_gem "unrelated_gem", "1.0.0"
    +        end
    +
    +        gemfile <<-G
    +          source "https://gem.repo2"
    +
    +          gem "depends_on_rack"
     
    -          build_repo gem_repo3 do
    -            build_gem "unrelated_gem", "1.0.0"
    +          source "https://gem.repo3" do
    +            gem "unrelated_gem"
               end
    +        G
    +      end
     
    -          gemfile <<-G
    -            source "#{file_uri_for(gem_repo2)}"
    +      context "and the dependency is only in the top-level source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0"
    +          end
    +        end
     
    -            gem "depends_on_rack"
    +        it "installs the dependency from the top-level source without warning" do
    +          bundle :install, :artifice => "compact_index"
    +          expect(err).not_to include("Warning")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2")
    +          expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3")
    +        end
    +      end
     
    -            source "#{file_uri_for(gem_repo3)}" do
    -              gem "unrelated_gem"
    +      context "and the dependency is only in a pinned source" do
    +        before do
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0" do |s|
    +              s.write "lib/rack.rb", "RACK = 'FAIL'"
                 end
    -          G
    +          end
             end
     
    -        context "and the dependency is only in the top-level source" do
    -          before do
    -            update_repo gem_repo2 do
    -              build_gem "rack", "1.0.0"
    -            end
    +        it "does not find the dependency" do
    +          bundle :install, :artifice => "compact_index", :raise_on_error => false
    +          expect(err).to include(
    +            "Could not find gem 'rack', which is required by gem 'depends_on_rack', in rubygems repository https://gem.repo2/ or installed locally."
    +          )
    +        end
    +      end
    +
    +      context "and the dependency is in both the top-level and a pinned source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0"
               end
     
    -          it "installs all gems without warning" do
    -            bundle :install
    -            expect(err).not_to include("Warning")
    -            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0" do |s|
    +              s.write "lib/rack.rb", "RACK = 'FAIL'"
    +            end
               end
             end
     
    -        context "and the dependency is only in a pinned source" do
    -          before do
    -            update_repo gem_repo3 do
    -              build_gem "rack", "1.0.0" do |s|
    -                s.write "lib/rack.rb", "RACK = 'FAIL'"
    -              end
    -            end
    +        it "installs the dependency from the top-level source without warning" do
    +          bundle :install, :artifice => "compact_index"
    +          expect(err).not_to include("Warning")
    +          expect(run("require 'rack'; puts RACK")).to eq("1.0.0")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +          expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote2")
    +          expect(the_bundle).to include_gems("unrelated_gem 1.0.0", :source => "remote3")
    +        end
    +      end
    +    end
    +
    +    context "when a scoped gem has a deeply nested indirect dependency" do
    +      before do
    +        build_repo gem_repo3 do
    +          build_gem "depends_on_depends_on_rack", "1.0.1" do |s|
    +            s.add_dependency "depends_on_rack"
               end
     
    -          it "does not find the dependency" do
    -            bundle :install, :raise_on_error => false
    -            expect(err).to include("Could not find gem 'rack', which is required by gem 'depends_on_rack', in any of the relevant sources")
    +          build_gem "depends_on_rack", "1.0.1" do |s|
    +            s.add_dependency "rack"
               end
             end
     
    -        context "and the dependency is in both the top-level and a pinned source" do
    -          before do
    -            update_repo gem_repo2 do
    -              build_gem "rack", "1.0.0"
    -            end
    +        gemfile <<-G
    +          source "https://gem.repo2"
     
    -            update_repo gem_repo3 do
    -              build_gem "rack", "1.0.0" do |s|
    -                s.write "lib/rack.rb", "RACK = 'FAIL'"
    -              end
    -            end
    +          source "https://gem.repo3" do
    +            gem "depends_on_depends_on_rack"
               end
    +        G
    +      end
     
    -          it "installs the dependency from the top-level source without warning" do
    -            bundle :install
    -            expect(err).not_to include("Warning")
    -            expect(the_bundle).to include_gems("depends_on_rack 1.0.1", "rack 1.0.0", "unrelated_gem 1.0.0")
    +      context "and the dependency is only in the top-level source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0"
               end
             end
    +
    +        it "installs the dependency from the top-level source" do
    +          bundle :install, :artifice => "compact_index"
    +          expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0")
    +          expect(the_bundle).to include_gems("rack 1.0.0", :source => "remote2")
    +          expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", :source => "remote3")
    +        end
           end
     
    -      context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do
    +      context "and the dependency is only in a pinned source" do
             before do
    -          build_repo gem_repo2 do
    -            build_gem "activesupport", "6.0.3.4" do |s|
    -              s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
    -              s.add_dependency "i18n", ">= 0.7", "< 2"
    -              s.add_dependency "minitest", "~> 5.1"
    -              s.add_dependency "tzinfo", "~> 1.1"
    -              s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2"
    -            end
    +          build_repo2
     
    -            build_gem "activesupport", "6.1.2.1" do |s|
    -              s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
    -              s.add_dependency "i18n", ">= 1.6", "< 2"
    -              s.add_dependency "minitest", ">= 5.1"
    -              s.add_dependency "tzinfo", "~> 2.0"
    -              s.add_dependency "zeitwerk", "~> 2.3"
    -            end
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0"
    +          end
    +        end
     
    -            build_gem "concurrent-ruby", "1.1.8"
    -            build_gem "concurrent-ruby", "1.1.9"
    -            build_gem "connection_pool", "2.2.3"
    +        it "installs the dependency from the pinned source" do
    +          bundle :install, :artifice => "compact_index"
    +          expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
    +        end
    +      end
     
    -            build_gem "i18n", "1.8.9" do |s|
    -              s.add_dependency "concurrent-ruby", "~> 1.0"
    +      context "and the dependency is in both the top-level and a pinned source" do
    +        before do
    +          update_repo gem_repo2 do
    +            build_gem "rack", "1.0.0" do |s|
    +              s.write "lib/rack.rb", "RACK = 'FAIL'"
                 end
    +          end
     
    -            build_gem "minitest", "5.14.3"
    -            build_gem "rack", "2.2.3"
    -            build_gem "redis", "4.2.5"
    +          update_repo gem_repo3 do
    +            build_gem "rack", "1.0.0"
    +          end
    +        end
     
    -            build_gem "sidekiq", "6.1.3" do |s|
    -              s.add_dependency "connection_pool", ">= 2.2.2"
    -              s.add_dependency "rack", "~> 2.0"
    -              s.add_dependency "redis", ">= 4.2.0"
    -            end
    +        it "installs the dependency from the pinned source without warning" do
    +          bundle :install, :artifice => "compact_index"
    +          expect(the_bundle).to include_gems("depends_on_depends_on_rack 1.0.1", "depends_on_rack 1.0.1", "rack 1.0.0", :source => "remote3")
    +        end
    +      end
    +    end
     
    -            build_gem "thread_safe", "0.3.6"
    +    context "when the lockfile has aggregated rubygems sources and newer versions of dependencies are available" do
    +      before do
    +        build_repo gem_repo2 do
    +          build_gem "activesupport", "6.0.3.4" do |s|
    +            s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
    +            s.add_dependency "i18n", ">= 0.7", "< 2"
    +            s.add_dependency "minitest", "~> 5.1"
    +            s.add_dependency "tzinfo", "~> 1.1"
    +            s.add_dependency "zeitwerk", "~> 2.2", ">= 2.2.2"
    +          end
     
    -            build_gem "tzinfo", "1.2.9" do |s|
    -              s.add_dependency "thread_safe", "~> 0.1"
    -            end
    +          build_gem "activesupport", "6.1.2.1" do |s|
    +            s.add_dependency "concurrent-ruby", "~> 1.0", ">= 1.0.2"
    +            s.add_dependency "i18n", ">= 1.6", "< 2"
    +            s.add_dependency "minitest", ">= 5.1"
    +            s.add_dependency "tzinfo", "~> 2.0"
    +            s.add_dependency "zeitwerk", "~> 2.3"
    +          end
     
    -            build_gem "tzinfo", "2.0.4" do |s|
    -              s.add_dependency "concurrent-ruby", "~> 1.0"
    -            end
    +          build_gem "concurrent-ruby", "1.1.8"
    +          build_gem "concurrent-ruby", "1.1.9"
    +          build_gem "connection_pool", "2.2.3"
     
    -            build_gem "zeitwerk", "2.4.2"
    +          build_gem "i18n", "1.8.9" do |s|
    +            s.add_dependency "concurrent-ruby", "~> 1.0"
               end
     
    -          build_repo gem_repo3 do
    -            build_gem "sidekiq-pro", "5.2.1" do |s|
    -              s.add_dependency "connection_pool", ">= 2.2.3"
    -              s.add_dependency "sidekiq", ">= 6.1.0"
    -            end
    +          build_gem "minitest", "5.14.3"
    +          build_gem "rack", "2.2.3"
    +          build_gem "redis", "4.2.5"
    +
    +          build_gem "sidekiq", "6.1.3" do |s|
    +            s.add_dependency "connection_pool", ">= 2.2.2"
    +            s.add_dependency "rack", "~> 2.0"
    +            s.add_dependency "redis", ">= 4.2.0"
               end
     
    -          gemfile <<-G
    -            # frozen_string_literal: true
    +          build_gem "thread_safe", "0.3.6"
     
    -            source "#{file_uri_for(gem_repo2)}"
    +          build_gem "tzinfo", "1.2.9" do |s|
    +            s.add_dependency "thread_safe", "~> 0.1"
    +          end
     
    -            gem "activesupport"
    +          build_gem "tzinfo", "2.0.4" do |s|
    +            s.add_dependency "concurrent-ruby", "~> 1.0"
    +          end
     
    -            source "#{file_uri_for(gem_repo3)}" do
    -              gem "sidekiq-pro"
    -            end
    -          G
    +          build_gem "zeitwerk", "2.4.2"
    +        end
     
    -          lockfile <<~L
    -            GEM
    -              remote: #{file_uri_for(gem_repo2)}/
    -              remote: #{file_uri_for(gem_repo3)}/
    -              specs:
    -                activesupport (6.0.3.4)
    -                  concurrent-ruby (~> 1.0, >= 1.0.2)
    -                  i18n (>= 0.7, < 2)
    -                  minitest (~> 5.1)
    -                  tzinfo (~> 1.1)
    -                  zeitwerk (~> 2.2, >= 2.2.2)
    -                concurrent-ruby (1.1.8)
    -                connection_pool (2.2.3)
    -                i18n (1.8.9)
    -                  concurrent-ruby (~> 1.0)
    -                minitest (5.14.3)
    -                rack (2.2.3)
    -                redis (4.2.5)
    -                sidekiq (6.1.3)
    -                  connection_pool (>= 2.2.2)
    -                  rack (~> 2.0)
    -                  redis (>= 4.2.0)
    -                sidekiq-pro (5.2.1)
    -                  connection_pool (>= 2.2.3)
    -                  sidekiq (>= 6.1.0)
    -                thread_safe (0.3.6)
    -                tzinfo (1.2.9)
    -                  thread_safe (~> 0.1)
    -                zeitwerk (2.4.2)
    -
    -            PLATFORMS
    -              #{specific_local_platform}
    -
    -            DEPENDENCIES
    -              activesupport
    -              sidekiq-pro!
    -
    -            BUNDLED WITH
    -               #{Bundler::VERSION}
    -          L
    +        build_repo gem_repo3 do
    +          build_gem "sidekiq-pro", "5.2.1" do |s|
    +            s.add_dependency "connection_pool", ">= 2.2.3"
    +            s.add_dependency "sidekiq", ">= 6.1.0"
    +          end
             end
     
    -        it "does not install newer versions or generate lockfile changes when running bundle install, and warns", :bundler => "< 3" do
    -          initial_lockfile = lockfile
    +        gemfile <<-G
    +          # frozen_string_literal: true
     
    -          bundle :install
    +          source "https://gem.repo2"
     
    -          expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +          gem "activesupport"
     
    -          expect(the_bundle).to include_gems("activesupport 6.0.3.4")
    -          expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
    -          expect(the_bundle).to include_gems("tzinfo 1.2.9")
    -          expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
    -          expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
    -          expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
    +          source "https://gem.repo3" do
    +            gem "sidekiq-pro"
    +          end
    +        G
     
    -          expect(lockfile).to eq(initial_lockfile)
    -        end
    +        lockfile <<~L
    +          GEM
    +            remote: https://gem.repo2/
    +            remote: https://gem.repo3/
    +            specs:
    +              activesupport (6.0.3.4)
    +                concurrent-ruby (~> 1.0, >= 1.0.2)
    +                i18n (>= 0.7, < 2)
    +                minitest (~> 5.1)
    +                tzinfo (~> 1.1)
    +                zeitwerk (~> 2.2, >= 2.2.2)
    +              concurrent-ruby (1.1.8)
    +              connection_pool (2.2.3)
    +              i18n (1.8.9)
    +                concurrent-ruby (~> 1.0)
    +              minitest (5.14.3)
    +              rack (2.2.3)
    +              redis (4.2.5)
    +              sidekiq (6.1.3)
    +                connection_pool (>= 2.2.2)
    +                rack (~> 2.0)
    +                redis (>= 4.2.0)
    +              sidekiq-pro (5.2.1)
    +                connection_pool (>= 2.2.3)
    +                sidekiq (>= 6.1.0)
    +              thread_safe (0.3.6)
    +              tzinfo (1.2.9)
    +                thread_safe (~> 0.1)
    +              zeitwerk (2.4.2)
     
    -        it "fails when running bundle install", :bundler => "3" do
    -          initial_lockfile = lockfile
    +          PLATFORMS
    +            #{specific_local_platform}
     
    -          bundle :install, :raise_on_error => false
    +          DEPENDENCIES
    +            activesupport
    +            sidekiq-pro!
     
    -          expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +          BUNDLED WITH
    +             #{Bundler::VERSION}
    +        L
    +      end
     
    -          expect(lockfile).to eq(initial_lockfile)
    -        end
    +      it "does not install newer versions or generate lockfile changes when running bundle install, and warns", :bundler => "< 3" do
    +        initial_lockfile = lockfile
     
    -        it "splits sections and upgrades gems when running bundle update, and doesn't warn" do
    -          bundle "update --all"
    -          expect(err).to be_empty
    -
    -          expect(the_bundle).not_to include_gems("activesupport 6.0.3.4")
    -          expect(the_bundle).to include_gems("activesupport 6.1.2.1")
    -          expect(the_bundle).not_to include_gems("tzinfo 1.2.9")
    -          expect(the_bundle).to include_gems("tzinfo 2.0.4")
    -          expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
    -          expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
    -
    -          expect(lockfile).to eq <<~L
    -            GEM
    -              remote: #{file_uri_for(gem_repo2)}/
    -              specs:
    -                activesupport (6.1.2.1)
    -                  concurrent-ruby (~> 1.0, >= 1.0.2)
    -                  i18n (>= 1.6, < 2)
    -                  minitest (>= 5.1)
    -                  tzinfo (~> 2.0)
    -                  zeitwerk (~> 2.3)
    -                concurrent-ruby (1.1.9)
    -                connection_pool (2.2.3)
    -                i18n (1.8.9)
    -                  concurrent-ruby (~> 1.0)
    -                minitest (5.14.3)
    -                rack (2.2.3)
    -                redis (4.2.5)
    -                sidekiq (6.1.3)
    -                  connection_pool (>= 2.2.2)
    -                  rack (~> 2.0)
    -                  redis (>= 4.2.0)
    -                tzinfo (2.0.4)
    -                  concurrent-ruby (~> 1.0)
    -                zeitwerk (2.4.2)
    -
    -            GEM
    -              remote: #{file_uri_for(gem_repo3)}/
    -              specs:
    -                sidekiq-pro (5.2.1)
    -                  connection_pool (>= 2.2.3)
    -                  sidekiq (>= 6.1.0)
    -
    -            PLATFORMS
    -              #{specific_local_platform}
    -
    -            DEPENDENCIES
    -              activesupport
    -              sidekiq-pro!
    -
    -            BUNDLED WITH
    -               #{Bundler::VERSION}
    -          L
    -        end
    +        bundle :install, :artifice => "compact_index"
     
    -        it "it keeps the current lockfile format and upgrades the requested gem when running bundle update with an argument, and warns", :bundler => "< 3" do
    -          bundle "update concurrent-ruby"
    -          expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    -
    -          expect(the_bundle).to include_gems("activesupport 6.0.3.4")
    -          expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
    -          expect(the_bundle).to include_gems("tzinfo 1.2.9")
    -          expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
    -          expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
    -          expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
    -
    -          expect(lockfile).to eq <<~L
    -            GEM
    -              remote: #{file_uri_for(gem_repo2)}/
    -              remote: #{file_uri_for(gem_repo3)}/
    -              specs:
    -                activesupport (6.0.3.4)
    -                  concurrent-ruby (~> 1.0, >= 1.0.2)
    -                  i18n (>= 0.7, < 2)
    -                  minitest (~> 5.1)
    -                  tzinfo (~> 1.1)
    -                  zeitwerk (~> 2.2, >= 2.2.2)
    -                concurrent-ruby (1.1.9)
    -                connection_pool (2.2.3)
    -                i18n (1.8.9)
    -                  concurrent-ruby (~> 1.0)
    -                minitest (5.14.3)
    -                rack (2.2.3)
    -                redis (4.2.5)
    -                sidekiq (6.1.3)
    -                  connection_pool (>= 2.2.2)
    -                  rack (~> 2.0)
    -                  redis (>= 4.2.0)
    -                sidekiq-pro (5.2.1)
    -                  connection_pool (>= 2.2.3)
    -                  sidekiq (>= 6.1.0)
    -                thread_safe (0.3.6)
    -                tzinfo (1.2.9)
    -                  thread_safe (~> 0.1)
    -                zeitwerk (2.4.2)
    -
    -            PLATFORMS
    -              #{specific_local_platform}
    -
    -            DEPENDENCIES
    -              activesupport
    -              sidekiq-pro!
    -
    -            BUNDLED WITH
    -               #{Bundler::VERSION}
    -          L
    -        end
    +        expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +
    +        expect(the_bundle).to include_gems("activesupport 6.0.3.4")
    +        expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
    +        expect(the_bundle).to include_gems("tzinfo 1.2.9")
    +        expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
    +        expect(the_bundle).to include_gems("concurrent-ruby 1.1.8")
    +        expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.9")
     
    -        it "fails when running bundle update with an argument", :bundler => "3" do
    -          initial_lockfile = lockfile
    +        expect(lockfile).to eq(initial_lockfile)
    +      end
     
    -          bundle "update concurrent-ruby", :raise_on_error => false
    +      it "fails when running bundle install", :bundler => "3" do
    +        initial_lockfile = lockfile
     
    -          expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +        bundle :install, :artifice => "compact_index", :raise_on_error => false
     
    -          expect(lockfile).to eq(initial_lockfile)
    -        end
    +        expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +
    +        expect(lockfile).to eq(initial_lockfile)
    +      end
    +
    +      it "splits sections and upgrades gems when running bundle update, and doesn't warn" do
    +        bundle "update --all", :artifice => "compact_index"
    +        expect(err).to be_empty
    +
    +        expect(the_bundle).not_to include_gems("activesupport 6.0.3.4")
    +        expect(the_bundle).to include_gems("activesupport 6.1.2.1")
    +        expect(the_bundle).not_to include_gems("tzinfo 1.2.9")
    +        expect(the_bundle).to include_gems("tzinfo 2.0.4")
    +        expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
    +        expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
    +
    +        expect(lockfile).to eq <<~L
    +          GEM
    +            remote: https://gem.repo2/
    +            specs:
    +              activesupport (6.1.2.1)
    +                concurrent-ruby (~> 1.0, >= 1.0.2)
    +                i18n (>= 1.6, < 2)
    +                minitest (>= 5.1)
    +                tzinfo (~> 2.0)
    +                zeitwerk (~> 2.3)
    +              concurrent-ruby (1.1.9)
    +              connection_pool (2.2.3)
    +              i18n (1.8.9)
    +                concurrent-ruby (~> 1.0)
    +              minitest (5.14.3)
    +              rack (2.2.3)
    +              redis (4.2.5)
    +              sidekiq (6.1.3)
    +                connection_pool (>= 2.2.2)
    +                rack (~> 2.0)
    +                redis (>= 4.2.0)
    +              tzinfo (2.0.4)
    +                concurrent-ruby (~> 1.0)
    +              zeitwerk (2.4.2)
    +
    +          GEM
    +            remote: https://gem.repo3/
    +            specs:
    +              sidekiq-pro (5.2.1)
    +                connection_pool (>= 2.2.3)
    +                sidekiq (>= 6.1.0)
    +
    +          PLATFORMS
    +            #{specific_local_platform}
    +
    +          DEPENDENCIES
    +            activesupport
    +            sidekiq-pro!
    +
    +          BUNDLED WITH
    +             #{Bundler::VERSION}
    +        L
    +      end
    +
    +      it "it keeps the current lockfile format and upgrades the requested gem when running bundle update with an argument, and warns", :bundler => "< 3" do
    +        bundle "update concurrent-ruby", :artifice => "compact_index"
    +        expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +
    +        expect(the_bundle).to include_gems("activesupport 6.0.3.4")
    +        expect(the_bundle).not_to include_gems("activesupport 6.1.2.1")
    +        expect(the_bundle).to include_gems("tzinfo 1.2.9")
    +        expect(the_bundle).not_to include_gems("tzinfo 2.0.4")
    +        expect(the_bundle).to include_gems("concurrent-ruby 1.1.9")
    +        expect(the_bundle).not_to include_gems("concurrent-ruby 1.1.8")
    +
    +        expect(lockfile).to eq <<~L
    +          GEM
    +            remote: https://gem.repo2/
    +            remote: https://gem.repo3/
    +            specs:
    +              activesupport (6.0.3.4)
    +                concurrent-ruby (~> 1.0, >= 1.0.2)
    +                i18n (>= 0.7, < 2)
    +                minitest (~> 5.1)
    +                tzinfo (~> 1.1)
    +                zeitwerk (~> 2.2, >= 2.2.2)
    +              concurrent-ruby (1.1.9)
    +              connection_pool (2.2.3)
    +              i18n (1.8.9)
    +                concurrent-ruby (~> 1.0)
    +              minitest (5.14.3)
    +              rack (2.2.3)
    +              redis (4.2.5)
    +              sidekiq (6.1.3)
    +                connection_pool (>= 2.2.2)
    +                rack (~> 2.0)
    +                redis (>= 4.2.0)
    +              sidekiq-pro (5.2.1)
    +                connection_pool (>= 2.2.3)
    +                sidekiq (>= 6.1.0)
    +              thread_safe (0.3.6)
    +              tzinfo (1.2.9)
    +                thread_safe (~> 0.1)
    +              zeitwerk (2.4.2)
    +
    +          PLATFORMS
    +            #{specific_local_platform}
    +
    +          DEPENDENCIES
    +            activesupport
    +            sidekiq-pro!
    +
    +          BUNDLED WITH
    +             #{Bundler::VERSION}
    +        L
    +      end
    +
    +      it "fails when running bundle update with an argument", :bundler => "3" do
    +        initial_lockfile = lockfile
    +
    +        bundle "update concurrent-ruby", :artifice => "compact_index", :raise_on_error => false
    +
    +        expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    +
    +        expect(lockfile).to eq(initial_lockfile)
           end
         end
     
    -    context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved", :bundler => "< 3" do
    +    context "when a top-level gem has an indirect dependency present in the default source, but with a different version from the one resolved" do
           before do
             build_lib "activesupport", "7.0.0.alpha", :path => lib_path("rails/activesupport")
             build_lib "rails", "7.0.0.alpha", :path => lib_path("rails") do |s|
    @@ -677,7 +774,7 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo2)}"
    +          source "https://gem.repo2"
     
               gemspec :path => "#{lib_path("rails")}"
     
    @@ -686,7 +783,7 @@
           end
     
           it "installs all gems without warning" do
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
             expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", "rails 7.0.0.alpha")
             expect(the_bundle).to include_gems("activesupport 7.0.0.alpha", :source => "path@#{lib_path("rails/activesupport")}")
    @@ -711,9 +808,9 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo2)}"
    +          source "https://gem.repo2"
     
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo3" do
                 gem "handsoap"
               end
     
    @@ -724,14 +821,14 @@
           it "installs from the default source without any warnings or errors and generates a proper lockfile" do
             expected_lockfile = <<~L
               GEM
    -            remote: #{file_uri_for(gem_repo2)}/
    +            remote: https://gem.repo2/
                 specs:
                   nokogiri (1.11.1)
                     racca (~> 1.4)
                   racca (1.5.2)
     
               GEM
    -            remote: #{file_uri_for(gem_repo3)}/
    +            remote: https://gem.repo3/
                 specs:
                   handsoap (0.2.5.5)
                     nokogiri (>= 1.2.3)
    @@ -747,7 +844,7 @@
                  #{Bundler::VERSION}
             L
     
    -        bundle "install --verbose"
    +        bundle "install --verbose", :artifice => "compact_index"
             expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2")
             expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3")
    @@ -756,7 +853,7 @@
     
             # Even if the gems are already installed
             FileUtils.rm bundled_app_lock
    -        bundle "install --verbose"
    +        bundle "install --verbose", :artifice => "compact_index"
             expect(err).not_to include("Warning")
             expect(the_bundle).to include_gems("handsoap 0.2.5.5", "nokogiri 1.11.1", "racca 1.5.2")
             expect(the_bundle).to include_gems("handsoap 0.2.5.5", :source => "remote3")
    @@ -771,9 +868,9 @@
               build_gem "not_in_repo1", "1.0.0"
             end
     
    -        install_gemfile <<-G, :raise_on_error => false
    -          source "#{file_uri_for(gem_repo3)}"
    -          gem "not_in_repo1", :source => "#{file_uri_for(gem_repo1)}"
    +        install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
    +          source "https://gem.repo3"
    +          gem "not_in_repo1", :source => "https://gem.repo1"
             G
           end
     
    @@ -788,11 +885,11 @@
     
             lockfile <<-L
               GEM
    -            remote: #{file_uri_for(gem_repo1)}
    +            remote: https://gem.repo1
                 specs:
     
               GEM
    -            remote: #{file_uri_for(gem_repo3)}
    +            remote: https://gem.repo3
                 specs:
                   rack (0.9.1)
     
    @@ -804,8 +901,8 @@
             L
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo1)}"
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo1"
    +          source "https://gem.repo3" do
                 gem 'rack'
               end
             G
    @@ -821,8 +918,8 @@
           let(:aggregate_gem_section_lockfile) do
             <<~L
               GEM
    -            remote: #{file_uri_for(gem_repo1)}/
    -            remote: #{file_uri_for(gem_repo3)}/
    +            remote: https://gem.repo1/
    +            remote: https://gem.repo3/
                 specs:
                   rack (0.9.1)
     
    @@ -840,11 +937,11 @@
           let(:split_gem_section_lockfile) do
             <<~L
               GEM
    -            remote: #{file_uri_for(gem_repo1)}/
    +            remote: https://gem.repo1/
                 specs:
     
               GEM
    -            remote: #{file_uri_for(gem_repo3)}/
    +            remote: https://gem.repo3/
                 specs:
                   rack (0.9.1)
     
    @@ -865,8 +962,8 @@
             end
     
             gemfile <<-G
    -          source "#{file_uri_for(gem_repo1)}"
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo1"
    +          source "https://gem.repo3" do
                 gem 'rack'
               end
             G
    @@ -877,7 +974,7 @@
           it "installs the existing lockfile but prints a warning", :bundler => "< 3" do
             bundle "config set --local deployment true"
     
    -        bundle "install"
    +        bundle "install", :artifice => "compact_index"
     
             expect(lockfile).to eq(aggregate_gem_section_lockfile)
             expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    @@ -887,7 +984,7 @@
           it "refuses to install the existing lockfile and prints an error", :bundler => "3" do
             bundle "config set --local deployment true"
     
    -        bundle "install", :raise_on_error =>false
    +        bundle "install", :artifice => "compact_index", :raise_on_error =>false
     
             expect(lockfile).to eq(aggregate_gem_section_lockfile)
             expect(err).to include("Your lockfile contains a single rubygems source section with multiple remotes, which is insecure.")
    @@ -900,13 +997,13 @@
             build_lib "foo"
     
             gemfile <<-G
    -          gem "rack", :source => "#{file_uri_for(gem_repo1)}"
    +          gem "rack", :source => "https://gem.repo1"
               gem "foo", :path => "#{lib_path("foo-1.0")}"
             G
           end
     
           it "does not unlock the non-path gem after install" do
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
     
             bundle %(exec ruby -e 'puts "OK"')
     
    @@ -919,8 +1016,8 @@
         before do
           system_gems "rack-0.9.1"
     
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo1)}"
    +      install_gemfile <<-G, :artifice => "compact_index"
    +        source "https://gem.repo1"
             gem "rack" # should come from repo1!
           G
         end
    @@ -941,14 +1038,14 @@
     
           # Installing this gemfile...
           gemfile <<-G
    -        source '#{file_uri_for(gem_repo1)}'
    +        source 'https://gem.repo1'
             gem 'rack'
    -        gem 'foo', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}'
    -        gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}'
    +        gem 'foo', '~> 0.1', :source => 'https://gem.repo4'
    +        gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
           G
     
           bundle "config set --local path ../gems/system"
    -      bundle :install
    +      bundle :install, :artifice => "compact_index"
     
           # And then we add some new versions...
           update_repo4 do
    @@ -959,11 +1056,11 @@
     
         it "allows them to be unlocked separately" do
           # And install this gemfile, updating only foo.
    -      install_gemfile <<-G
    -        source '#{file_uri_for(gem_repo1)}'
    +      install_gemfile <<-G, :artifice => "compact_index"
    +        source 'https://gem.repo1'
             gem 'rack'
    -        gem 'foo', '~> 0.2', :source => '#{file_uri_for(gem_repo4)}'
    -        gem 'bar', '~> 0.1', :source => '#{file_uri_for(gem_repo4)}'
    +        gem 'foo', '~> 0.2', :source => 'https://gem.repo4'
    +        gem 'bar', '~> 0.1', :source => 'https://gem.repo4'
           G
     
           # It should update foo to 0.2, but not the (locked) bar 0.1
    @@ -983,11 +1080,11 @@
             build_git "git1"
             build_git "git2"
     
    -        install_gemfile <<-G
    -          source "#{file_uri_for(gem_repo1)}"
    +        install_gemfile <<-G, :artifice => "compact_index"
    +          source "https://gem.repo1"
               gem "rails"
     
    -          source "#{file_uri_for(gem_repo3)}" do
    +          source "https://gem.repo3" do
                 gem "rack"
               end
     
    @@ -999,7 +1096,7 @@
           end
     
           it "does not re-resolve" do
    -        bundle :install, :verbose => true
    +        bundle :install, :artifice => "compact_index", :verbose => true
             expect(out).to include("using resolution from the lockfile")
             expect(out).not_to include("re-resolving dependencies")
           end
    @@ -1008,27 +1105,24 @@
     
       context "when a gem is installed to system gems" do
         before do
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo1)}"
    +      install_gemfile <<-G, :artifice => "compact_index"
    +        source "https://gem.repo1"
             gem "rack"
           G
         end
     
         context "and the gemfile changes" do
           it "is still able to find that gem from remote sources" do
    -        source_uri = file_uri_for(gem_repo1)
    -        second_uri = file_uri_for(gem_repo4)
    -
             build_repo4 do
               build_gem "rack", "2.0.1.1.forked"
               build_gem "thor", "0.19.1.1.forked"
             end
     
             # When this gemfile is installed...
    -        install_gemfile <<-G
    -          source "#{source_uri}"
    +        install_gemfile <<-G, :artifice => "compact_index"
    +          source "https://gem.repo1"
     
    -          source "#{second_uri}" do
    +          source "https://gem.repo4" do
                 gem "rack", "2.0.1.1.forked"
                 gem "thor"
               end
    @@ -1037,25 +1131,25 @@
     
             # Then we change the Gemfile by adding a version to thor
             gemfile <<-G
    -          source "#{source_uri}"
    +          source "https://gem.repo1"
     
    -          source "#{second_uri}" do
    +          source "https://gem.repo4" do
                 gem "rack", "2.0.1.1.forked"
                 gem "thor", "0.19.1.1.forked"
               end
               gem "rack-obama"
             G
     
             # But we should still be able to find rack 2.0.1.1.forked and install it
    -        bundle :install
    +        bundle :install, :artifice => "compact_index"
           end
         end
       end
     
       describe "source changed to one containing a higher version of a dependency" do
         before do
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo1)}"
    +      install_gemfile <<-G, :artifice => "compact_index"
    +        source "https://gem.repo1"
     
             gem "rack"
           G
    @@ -1072,8 +1166,8 @@
             s.add_dependency "bar", "=1.0.0"
           end
     
    -      install_gemfile <<-G
    -        source "#{file_uri_for(gem_repo2)}"
    +      install_gemfile <<-G, :artifice => "compact_index"
    +        source "https://gem.repo2"
             gem "rack"
             gemspec :path => "#{tmp.join("gemspec_test")}"
           G
    @@ -1093,23 +1187,52 @@
           build_gem "example", "1.0.2"
         end
     
    -    install_gemfile <<-G
    -      source "#{file_uri_for(gem_repo4)}"
    +    install_gemfile <<-G, :artifice => "compact_index"
    +      source "https://gem.repo4"
     
    -      gem "example", :source => "#{file_uri_for(gem_repo2)}"
    +      gem "example", :source => "https://gem.repo2"
         G
     
         bundle "info example"
         expect(out).to include("example (0.1.0)")
     
         system_gems "example-1.0.2", :path => default_bundle_path, :gem_repo => gem_repo4
     
    -    bundle "update example --verbose"
    +    bundle "update example --verbose", :artifice => "compact_index"
         expect(out).not_to include("Using example 1.0.2")
         expect(out).to include("Using example 0.1.0")
       end
     
    -  context "when a gem is available from multiple ambiguous sources", :bundler => "3" do
    +  context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "< 3" do
    +    it "succeeds but warns, suggesting a source block" do
    +      build_repo4 do
    +        build_gem "depends_on_rack" do |s|
    +          s.add_dependency "rack"
    +        end
    +        build_gem "rack"
    +      end
    +
    +      install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
    +        source "https://gem.repo4" do
    +          gem "depends_on_rack"
    +        end
    +
    +        source "https://gem.repo1" do
    +          gem "thin"
    +        end
    +      G
    +      expect(err).to eq strip_whitespace(<<-EOS).strip
    +        Warning: The gem 'rack' was found in multiple relevant sources.
    +          * rubygems repository https://gem.repo1/ or installed locally
    +          * rubygems repository https://gem.repo4/ or installed locally
    +        You should add this gem to the source block for the source you wish it to be installed from.
    +      EOS
    +      expect(last_command).to be_success
    +      expect(the_bundle).to be_locked
    +    end
    +  end
    +
    +  context "when an indirect dependency is available from multiple ambiguous sources", :bundler => "3" do
         it "raises, suggesting a source block" do
           build_repo4 do
             build_gem "depends_on_rack" do |s|
    @@ -1118,18 +1241,19 @@
             build_gem "rack"
           end
     
    -      install_gemfile <<-G, :raise_on_error => false
    -        source "#{file_uri_for(gem_repo4)}"
    -        source "#{file_uri_for(gem_repo1)}" do
    +      install_gemfile <<-G, :artifice => "compact_index", :raise_on_error => false
    +        source "https://gem.repo4" do
    +          gem "depends_on_rack"
    +        end
    +        source "https://gem.repo1" do
               gem "thin"
             end
    -        gem "depends_on_rack"
           G
           expect(last_command).to be_failure
           expect(err).to eq strip_whitespace(<<-EOS).strip
             The gem 'rack' was found in multiple relevant sources.
    -          * rubygems repository #{file_uri_for(gem_repo1)}/ or installed locally
    -          * rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally
    +          * rubygems repository https://gem.repo1/ or installed locally
    +          * rubygems repository https://gem.repo4/ or installed locally
             You must add this gem to the source block for the source you wish it to be installed from.
           EOS
           expect(the_bundle).not_to be_locked
    
  • bundler/spec/install/gemfile/specific_platform_spec.rb+2 2 modified
    @@ -141,10 +141,10 @@
                2.1.4
           L
     
    -      bundle "install --verbose", :artifice => :compact_index, :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
    +      bundle "install --verbose", :artifice => "compact_index", :env => { "BUNDLER_VERSION" => "2.1.4", "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
           expect(out).to include("Installing libv8 8.4.255.0 (universal-darwin)")
     
    -      bundle "add mini_racer --verbose", :artifice => :compact_index, :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
    +      bundle "add mini_racer --verbose", :artifice => "compact_index", :env => { "BUNDLER_SPEC_GEM_REPO" => gem_repo2.to_s }
           expect(out).to include("Using libv8 8.4.255.0 (universal-darwin)")
         end
     
    
  • bundler/spec/install/gems/compact_index_spec.rb+3 28 modified
    @@ -366,31 +366,6 @@ def require(*args)
         expect(the_bundle).to include_gems "activesupport 1.2.3"
       end
     
    -  it "considers all possible versions of dependencies from all api gem sources when using blocks", :bundler => "< 3" do
    -    # In this scenario, the gem "somegem" only exists in repo4.  It depends on specific version of activesupport that
    -    # exists only in repo1.  There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
    -    # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
    -    # repo and installs it.
    -    build_repo4 do
    -      build_gem "activesupport", "1.2.0"
    -      build_gem "somegem", "1.0.0" do |s|
    -        s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
    -      end
    -    end
    -
    -    gemfile <<-G
    -      source "#{source_uri}"
    -      source "#{source_uri}/extra" do
    -        gem 'somegem', '1.0.0'
    -      end
    -    G
    -
    -    bundle :install, :artifice => "compact_index_extra_api"
    -
    -    expect(the_bundle).to include_gems "somegem 1.0.0"
    -    expect(the_bundle).to include_gems "activesupport 1.2.3"
    -  end
    -
       it "prints API output properly with back deps" do
         build_repo2 do
           build_gem "back_deps" do |s|
    @@ -467,7 +442,7 @@ def require(*args)
         expect(the_bundle).to include_gems "foo 1.0"
       end
     
    -  it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 3" do
    +  it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do
         build_repo2 do
           build_gem "back_deps" do |s|
             s.add_dependency "foo"
    @@ -482,8 +457,8 @@ def require(*args)
         G
     
         bundle :install, :artifice => "compact_index_extra"
    -
    -    bundle "install --deployment", :artifice => "compact_index_extra"
    +    bundle "config --set local deployment true"
    +    bundle :install, :artifice => "compact_index_extra"
         expect(the_bundle).to include_gems "back_deps 1.0"
       end
     
    
  • bundler/spec/install/gems/dependency_api_spec.rb+3 29 modified
    @@ -338,31 +338,6 @@ def require(*args)
         expect(the_bundle).to include_gems "activesupport 1.2.3"
       end
     
    -  it "considers all possible versions of dependencies from all api gem sources using blocks" do
    -    # In this scenario, the gem "somegem" only exists in repo4.  It depends on specific version of activesupport that
    -    # exists only in repo1.  There happens also be a version of activesupport in repo4, but not the one that version 1.0.0
    -    # of somegem wants. This test makes sure that bundler actually finds version 1.2.3 of active support in the other
    -    # repo and installs it.
    -    build_repo4 do
    -      build_gem "activesupport", "1.2.0"
    -      build_gem "somegem", "1.0.0" do |s|
    -        s.add_dependency "activesupport", "1.2.3" # This version exists only in repo1
    -      end
    -    end
    -
    -    gemfile <<-G
    -      source "#{source_uri}"
    -      source "#{source_uri}/extra" do
    -        gem 'somegem', '1.0.0'
    -      end
    -    G
    -
    -    bundle :install, :artifice => "endpoint_extra_api"
    -
    -    expect(the_bundle).to include_gems "somegem 1.0.0"
    -    expect(the_bundle).to include_gems "activesupport 1.2.3"
    -  end
    -
       it "prints API output properly with back deps" do
         build_repo2 do
           build_gem "back_deps" do |s|
    @@ -438,7 +413,7 @@ def require(*args)
         expect(the_bundle).to include_gems "foo 1.0"
       end
     
    -  it "fetches again when more dependencies are found in subsequent sources using --deployment", :bundler => "< 3" do
    +  it "fetches again when more dependencies are found in subsequent sources using deployment mode", :bundler => "< 3" do
         build_repo2 do
           build_gem "back_deps" do |s|
             s.add_dependency "foo"
    @@ -453,8 +428,8 @@ def require(*args)
         G
     
         bundle :install, :artifice => "endpoint_extra"
    -
    -    bundle "install --deployment", :artifice => "endpoint_extra"
    +    bundle "config set --local deployment true"
    +    bundle :install, :artifice => "endpoint_extra"
         expect(the_bundle).to include_gems "back_deps 1.0"
       end
     
    @@ -474,7 +449,6 @@ def require(*args)
         G
     
         bundle :install, :artifice => "endpoint_extra"
    -
         bundle "config set --local deployment true"
         bundle "install", :artifice => "endpoint_extra"
         expect(the_bundle).to include_gems "back_deps 1.0"
    
  • bundler/spec/support/artifice/endpoint.rb+4 0 modified
    @@ -45,10 +45,14 @@ def default_gem_repo
             Pathname.new(ENV["BUNDLER_SPEC_GEM_REPO"])
           else
             case request.host
    +        when "gem.repo1"
    +          Spec::Path.gem_repo1
             when "gem.repo2"
               Spec::Path.gem_repo2
             when "gem.repo3"
               Spec::Path.gem_repo3
    +        when "gem.repo4"
    +          Spec::Path.gem_repo4
             else
               Spec::Path.gem_repo1
             end
    
  • bundler/spec/support/indexes.rb+1 2 modified
    @@ -20,12 +20,11 @@ def resolve(args = [])
           default_source = instance_double("Bundler::Source::Rubygems", :specs => @index)
           source_requirements = { :default => default_source }
           @deps.each do |d|
    +        source_requirements[d.name] = d.source = default_source
             @platforms.each do |p|
    -          source_requirements[d.name] = d.source = default_source
               deps << Bundler::DepProxy.get_proxy(d, p)
             end
           end
    -      source_requirements ||= {}
           args[0] ||= [] # base
           args[1] ||= Bundler::GemVersionPromoter.new # gem_version_promoter
           args[2] ||= [] # additional_base_requirements
    
  • bundler/spec/support/matchers.rb+1 1 modified
    @@ -156,7 +156,7 @@ def indent(string, padding = 4, indent_character = " ")
                 actual_source = out.split("\n").last
                 next "Expected #{name} (#{version}) to be installed from `#{source}`, was actually from `#{actual_source}`"
               end
    -          next "Command to check forgem inclusion of gem #{full_name} failed"
    +          next "Command to check for inclusion of gem #{full_name} failed"
             end.compact
     
             @errors.empty?
    
  • Manifest.txt+2 0 modified
    @@ -180,7 +180,9 @@ bundler/lib/bundler/source/path.rb
     bundler/lib/bundler/source/path/installer.rb
     bundler/lib/bundler/source/rubygems.rb
     bundler/lib/bundler/source/rubygems/remote.rb
    +bundler/lib/bundler/source/rubygems_aggregate.rb
     bundler/lib/bundler/source_list.rb
    +bundler/lib/bundler/source_map.rb
     bundler/lib/bundler/spec_set.rb
     bundler/lib/bundler/stub_specification.rb
     bundler/lib/bundler/templates/.document
    

Vulnerability mechanics

Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.

References

15

News mentions

0

No linked articles in our index yet.