bundler 4.1.0.beta1 → 4.1.0.beta3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. checksums.yaml +4 -4
  2. data/lib/bundler/build_metadata.rb +2 -2
  3. data/lib/bundler/cli/common.rb +4 -0
  4. data/lib/bundler/cli/doctor/ssl.rb +4 -8
  5. data/lib/bundler/cli/info.rb +1 -1
  6. data/lib/bundler/cli/lock.rb +19 -4
  7. data/lib/bundler/cli/open.rb +7 -1
  8. data/lib/bundler/cli/outdated.rb +8 -10
  9. data/lib/bundler/cli/platform.rb +15 -1
  10. data/lib/bundler/cli/show.rb +5 -1
  11. data/lib/bundler/cli/update.rb +1 -1
  12. data/lib/bundler/cli.rb +22 -7
  13. data/lib/bundler/constants.rb +1 -1
  14. data/lib/bundler/definition.rb +102 -16
  15. data/lib/bundler/dsl.rb +76 -5
  16. data/lib/bundler/fetcher/downloader.rb +14 -4
  17. data/lib/bundler/fetcher.rb +4 -1
  18. data/lib/bundler/gem_helper.rb +1 -1
  19. data/lib/bundler/installer/parallel_installer.rb +2 -1
  20. data/lib/bundler/installer.rb +5 -1
  21. data/lib/bundler/lazy_specification.rb +1 -1
  22. data/lib/bundler/lockfile_generator.rb +6 -0
  23. data/lib/bundler/lockfile_parser.rb +18 -2
  24. data/lib/bundler/man/bundle-info.1 +4 -1
  25. data/lib/bundler/man/bundle-info.1.ronn +4 -0
  26. data/lib/bundler/man/bundle-lock.1 +2 -2
  27. data/lib/bundler/man/bundle-lock.1.ronn +4 -3
  28. data/lib/bundler/man/bundle-open.1 +4 -1
  29. data/lib/bundler/man/bundle-open.1.ronn +4 -1
  30. data/lib/bundler/man/bundle-outdated.1 +1 -1
  31. data/lib/bundler/man/bundle-outdated.1.ronn +1 -1
  32. data/lib/bundler/man/bundle-show.1 +4 -1
  33. data/lib/bundler/man/bundle-show.1.ronn +4 -0
  34. data/lib/bundler/man/bundle-update.1 +3 -3
  35. data/lib/bundler/man/bundle-update.1.ronn +5 -3
  36. data/lib/bundler/man/gemfile.5 +43 -2
  37. data/lib/bundler/man/gemfile.5.ronn +69 -4
  38. data/lib/bundler/match_platform.rb +12 -3
  39. data/lib/bundler/materialization.rb +3 -1
  40. data/lib/bundler/override.rb +83 -2
  41. data/lib/bundler/resolver.rb +28 -4
  42. data/lib/bundler/rubygems_ext.rb +15 -4
  43. data/lib/bundler/self_manager.rb +15 -9
  44. data/lib/bundler/settings.rb +10 -26
  45. data/lib/bundler/source/git/git_proxy.rb +39 -2
  46. data/lib/bundler/source/git.rb +34 -7
  47. data/lib/bundler/source/rubygems.rb +2 -2
  48. data/lib/bundler/source/rubygems_aggregate.rb +1 -1
  49. data/lib/bundler/source_map.rb +7 -3
  50. data/lib/bundler/spec_set.rb +8 -4
  51. data/lib/bundler/templates/newgem/ext/newgem/Cargo.toml.tt +1 -1
  52. data/lib/bundler/vendor/fileutils/lib/fileutils.rb +54 -29
  53. data/lib/bundler/version.rb +1 -1
  54. data/lib/bundler.rb +1 -3
  55. data/lib/rubygems/compact_index_client/http_fetcher.rb +5 -3
  56. data/lib/rubygems/compact_index_client/parser.rb +1 -0
  57. data/lib/rubygems/vendor/uri/lib/uri/common.rb +1 -1
  58. data/lib/rubygems/vendor/uri/lib/uri/generic.rb +15 -3
  59. data/lib/rubygems/vendor/uri/lib/uri/version.rb +1 -1
  60. data/lib/rubygems/yaml_serializer.rb +131 -103
  61. metadata +391 -390
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 64a16d95664584fa37c3a29d228d05929ff6ab31adfb8784a8625b242e27563d
4
- data.tar.gz: fcf44920ce08c9436bb0076a8a000f2344487858db6ee94644f4d6acced91cc4
3
+ metadata.gz: 6be81cd8812d25bec243ccd7e0ebf5c5ca61ff4dbfa3cc312ee38dd5f4199576
4
+ data.tar.gz: 44ff77dfafe81511dd56a2c7eb508ecccfcfffca1985aab4a048adeaf063f061
5
5
  SHA512:
6
- metadata.gz: f39b20fc90c76be12671a4f4e5dfe73f697d9d46183786fbf0de925f977675e188fa74db50f3cf010053fdfc62724bdeca2ad65341167ee7f2b2c434a77b9773
7
- data.tar.gz: 0f7806fa9c0e09b79eeab58116f92d7f3b0bfd395a091de186a1c129e5cda7595ad02f891f993d0d1436cdfc993692783b8ff3a506d2bbbfbe7028976b0e53cd
6
+ metadata.gz: 9b8ed99236c76ef3e8c24778dfd06f7c35bb13700b22fab1f025996c712f50517e011323f0bf25066a919b5d5b4c4e685c229227a1210e30134fa8aa259b7d66
7
+ data.tar.gz: 4291294d5cee7e331dc8b6626908fa7aeff05bce9379ab59b97a3e0552aaa6c8fb136113151db97105f9e527a541ff1362a5bf14077dd136ed455d46b18d2759
@@ -4,8 +4,8 @@ module Bundler
4
4
  # Represents metadata from when the Bundler gem was built.
5
5
  module BuildMetadata
6
6
  # begin ivars
7
- @built_at = "2026-09-09".freeze
8
- @git_commit_sha = "98e4d8083a7".freeze
7
+ @built_at = "2026-10-28".freeze
8
+ @git_commit_sha = "cd7536a".freeze
9
9
  # end ivars
10
10
 
11
11
  # A hash representation of the build metadata.
@@ -122,6 +122,10 @@ module Bundler
122
122
  raise GemNotFound, gem_not_found_message(name, Bundler.definition.dependencies)
123
123
  end
124
124
 
125
+ def self.select_spec_with_match_type(name, options)
126
+ select_spec(name, options["exact-match"] ? nil : :regex_match)
127
+ end
128
+
125
129
  def self.default_gem_spec(name)
126
130
  gem_spec = Gem::Specification.find_all_by_name(name).last
127
131
  gem_spec if gem_spec&.default_gem?
@@ -124,14 +124,10 @@ module Bundler
124
124
  ctx = OpenSSL::SSL::SSLContext.new
125
125
  supported = true
126
126
 
127
- if ctx.respond_to?(:min_version=)
128
- begin
129
- ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
130
- rescue OpenSSL::SSL::SSLError, NameError
131
- supported = false
132
- end
133
- else
134
- supported = OpenSSL::SSL::SSLContext::METHODS.include?(:TLSv1_2) # rubocop:disable Naming/VariableNumber
127
+ begin
128
+ ctx.min_version = ctx.max_version = OpenSSL::SSL::TLS1_2_VERSION
129
+ rescue OpenSSL::SSL::SSLError, NameError
130
+ supported = false
135
131
  end
136
132
 
137
133
  Bundler.ui.warn(<<~EOM) unless supported
@@ -26,7 +26,7 @@ module Bundler
26
26
  private
27
27
 
28
28
  def spec_for_gem(name)
29
- Bundler::CLI::Common.select_spec(name, :regex_match)
29
+ Bundler::CLI::Common.select_spec_with_match_type(name, options)
30
30
  end
31
31
 
32
32
  def print_gem_version(spec)
@@ -42,7 +42,6 @@ module Bundler
42
42
 
43
43
  Bundler.settings.temporary(frozen: false) do
44
44
  definition = Bundler.definition(update, Bundler.default_lockfile)
45
- definition.add_checksums if options["add-checksums"]
46
45
 
47
46
  Bundler::CLI::Common.configure_gem_version_promoter(definition, options) if options[:update]
48
47
 
@@ -64,6 +63,11 @@ module Bundler
64
63
  raise InvalidOption, "Removing all platforms from the bundle is not allowed"
65
64
  end
66
65
 
66
+ # Adding checksums resolves the bundle to fetch them, so it has to
67
+ # run after every platform change above has been recorded, or the
68
+ # new platforms are listed in the lockfile without their specs.
69
+ definition.add_checksums if options["add-checksums"]
70
+
67
71
  definition.remotely! unless options[:local]
68
72
 
69
73
  if options["normalize-platforms"]
@@ -74,10 +78,21 @@ module Bundler
74
78
  puts definition.to_lock
75
79
  else
76
80
  file = options[:lockfile]
77
- file = file ? Pathname.new(file).expand_path : Bundler.default_lockfile
81
+ file = if file
82
+ Pathname.new(file).expand_path
83
+ elsif ENV["BUNDLE_LOCKFILE"].to_s.empty?
84
+ definition.lockfile
85
+ else
86
+ # BUNDLE_LOCKFILE outranks the lockfile method in the Gemfile, see gemfile(5)
87
+ Bundler.default_lockfile
88
+ end
78
89
 
79
- puts "Writing lockfile to #{file}"
80
- definition.write_lock(file, false)
90
+ if file
91
+ puts "Writing lockfile to #{file}"
92
+ definition.write_lock(file, false)
93
+ else
94
+ Bundler.ui.warn "Not writing a lockfile because the Gemfile sets `lockfile false`"
95
+ end
81
96
  end
82
97
 
83
98
  Bundler::CLI::Common.output_cooldown_skipped_summary(definition)
@@ -13,7 +13,7 @@ module Bundler
13
13
  raise InvalidOption, "Cannot specify `--path` option without a value" if !@path.nil? && @path.empty?
14
14
  editor = [ENV["BUNDLER_EDITOR"], ENV["VISUAL"], ENV["EDITOR"]].find {|e| !e.nil? && !e.empty? }
15
15
  return Bundler.ui.info("To open a bundled gem, set $EDITOR or $BUNDLER_EDITOR") unless editor
16
- return unless spec = Bundler::CLI::Common.select_spec(name, :regex_match)
16
+ return unless spec = spec_for_gem(name)
17
17
  if spec.default_gem?
18
18
  Bundler.ui.info "Unable to open #{name} because it's a default gem, so the directory it would normally be installed to does not exist."
19
19
  else
@@ -35,5 +35,11 @@ module Bundler
35
35
  require "shellwords"
36
36
  Shellwords.split(editor)
37
37
  end
38
+
39
+ private
40
+
41
+ def spec_for_gem(name)
42
+ Bundler::CLI::Common.select_spec_with_match_type(name, options)
43
+ end
38
44
  end
39
45
  end
@@ -90,7 +90,9 @@ module Bundler
90
90
  dependency = current_dependencies[current_spec.name]
91
91
  groups = ""
92
92
  if dependency && !options[:parseable]
93
- groups = dependency.groups.join(", ")
93
+ groups = dependency.groups
94
+ groups = groups.sort if options_include_groups
95
+ groups = groups.join(", ")
94
96
  end
95
97
 
96
98
  outdated_gems << {
@@ -101,13 +103,11 @@ module Bundler
101
103
  }
102
104
  end
103
105
 
104
- relevant_outdated_gems = if options_include_groups
105
- outdated_gems.group_by {|g| g[:groups] }.sort.flat_map do |groups, gems|
106
- contains_group = groups.split(", ").include?(options[:group])
107
- next unless options[:groups] || contains_group
108
-
109
- gems
110
- end.compact
106
+ relevant_outdated_gems = if options[:groups]
107
+ without_groups, with_groups = outdated_gems.partition {|g| g[:groups].empty? }
108
+ with_groups.group_by {|g| g[:groups] }.sort.flat_map(&:last) + without_groups
109
+ elsif options_include_groups
110
+ outdated_gems.select {|g| g[:groups].split(", ").include?(options[:group]) }
111
111
  else
112
112
  outdated_gems
113
113
  end
@@ -333,8 +333,6 @@ module Bundler
333
333
 
334
334
  Bundler.ui.info justify(header, column_sizes)
335
335
 
336
- data.sort_by! {|row| row[0] }
337
-
338
336
  data.each do |row|
339
337
  Bundler.ui.info justify(row, column_sizes)
340
338
  end
@@ -25,7 +25,7 @@ module Bundler
25
25
  else
26
26
  platforms = Bundler.definition.platforms.map {|p| "* #{p}" }
27
27
 
28
- output << "Your platform is: #{Gem::Platform.local}"
28
+ output << platform_info
29
29
  output << "Your app has gems that work on these platforms:\n#{platforms.join("\n")}"
30
30
 
31
31
  if ruby_version
@@ -44,5 +44,19 @@ module Bundler
44
44
 
45
45
  Bundler.ui.info output.join("\n\n")
46
46
  end
47
+
48
+ private
49
+
50
+ def platform_info
51
+ info = "Your platform is: #{Gem::Platform.local}"
52
+ return info if Bundler.local_platform == Gem::Platform.local
53
+
54
+ info + "\nHowever, your effective platform is: #{Bundler.local_platform} " \
55
+ "(because force_ruby_platform is #{force_ruby_platform_origin})"
56
+ end
57
+
58
+ def force_ruby_platform_origin
59
+ Bundler.settings.pretty_values_for(:force_ruby_platform).first.sub(/\ASet /, "set ")
60
+ end
47
61
  end
48
62
  end
@@ -20,7 +20,7 @@ module Bundler
20
20
  if gem_name == "bundler"
21
21
  path = File.expand_path("../../..", __dir__)
22
22
  else
23
- spec = Bundler::CLI::Common.select_spec(gem_name, :regex_match)
23
+ spec = spec_for_gem(gem_name)
24
24
  return unless spec
25
25
  path = spec.full_gem_path
26
26
  unless File.directory?(path)
@@ -55,6 +55,10 @@ module Bundler
55
55
 
56
56
  private
57
57
 
58
+ def spec_for_gem(name)
59
+ Bundler::CLI::Common.select_spec_with_match_type(name, options)
60
+ end
61
+
58
62
  def fetch_latest_specs
59
63
  definition = Bundler.definition(true)
60
64
  Bundler.ui.info "Fetching remote specs for outdated check...\n\n"
@@ -13,7 +13,7 @@ module Bundler
13
13
 
14
14
  update_bundler = options[:bundler]
15
15
 
16
- Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler) if update_bundler
16
+ Bundler.self_manager.update_bundler_and_restart_with_it_if_needed(update_bundler, pre: options[:pre]) if update_bundler
17
17
 
18
18
  sources = Array(options[:source])
19
19
  groups = Array(options[:group]).map(&:to_sym)
data/lib/bundler/cli.rb CHANGED
@@ -177,7 +177,7 @@ module Bundler
177
177
  else
178
178
  puts File.read("#{man_path}/#{File.basename(man_page)}.ronn")
179
179
  end
180
- elsif command_path = Bundler.which("bundler-#{cli}")
180
+ elsif command_path = self.class.bundler_command_path(cli)
181
181
  Kernel.exec(command_path, "--help")
182
182
  else
183
183
  super
@@ -189,11 +189,23 @@ module Bundler
189
189
  return Bundler::Plugin.exec_command(command, ARGV[1..-1])
190
190
  end
191
191
 
192
- return super unless command_path = Bundler.which("bundler-#{command}")
192
+ return super unless command_path = bundler_command_path(command)
193
193
 
194
194
  Kernel.exec(command_path, *ARGV[1..-1])
195
195
  end
196
196
 
197
+ # Only PATH is searched, unlike Bundler.which, which also finds a file in
198
+ # the current directory and returns a bare name that Kernel.exec cannot run.
199
+ def self.bundler_command_path(command)
200
+ ENV.fetch("PATH", "").split(File::PATH_SEPARATOR).each do |path|
201
+ path = path[1..-2] if path.start_with?('"') && path.end_with?('"')
202
+ command_path = Bundler.find_executable(File.expand_path("bundler-#{command}", path))
203
+ return command_path if command_path
204
+ end
205
+
206
+ nil
207
+ end
208
+
197
209
  desc "init [OPTIONS]", "Generates a Gemfile into the current working directory"
198
210
  long_desc <<-D
199
211
  Init generates a default Gemfile in the current working directory. When adding a
@@ -319,10 +331,10 @@ module Bundler
319
331
  method_option "source", type: :array, banner: "Update a specific source (and all gems associated with it)"
320
332
  method_option "force", type: :boolean, aliases: "--redownload", banner: "Force reinstalling every gem, even if already installed"
321
333
  method_option "ruby", type: :boolean, banner: "Update ruby specified in Gemfile.lock"
322
- method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler"
334
+ method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", banner: "Update the locked version of bundler"
323
335
  method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version"
324
336
  method_option "minor", type: :boolean, banner: "Prefer updating only to next minor version"
325
- method_option "major", type: :boolean, banner: "Prefer updating to next major version (default)"
337
+ method_option "major", type: :boolean, banner: "Prefer updating to latest major version (default)"
326
338
  method_option "pre", type: :boolean, banner: "Always choose the highest allowed version when updating gems, regardless of prerelease status"
327
339
  method_option "strict", type: :boolean, banner: "Do not allow any gem to be updated past latest --patch | --minor | --major"
328
340
  method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
@@ -342,6 +354,7 @@ module Bundler
342
354
  D
343
355
  method_option "paths", type: :boolean, banner: "List the paths of all gems that are required by your Gemfile."
344
356
  method_option "outdated", type: :boolean, banner: "Show verbose output including whether gems are outdated (removed)."
357
+ method_option "exact-match", type: :boolean, banner: "Only match gems whose names exactly match the given name"
345
358
  def show(gem_name = nil)
346
359
  if ARGV.include?("--outdated")
347
360
  removed_message = "the `--outdated` flag to `bundle show` has been removed in favor of `bundle show --verbose`"
@@ -367,6 +380,7 @@ module Bundler
367
380
  desc "info GEM [OPTIONS]", "Show information for the given gem"
368
381
  method_option "path", type: :boolean, banner: "Print full path to gem"
369
382
  method_option "version", type: :boolean, banner: "Print gem version"
383
+ method_option "exact-match", type: :boolean, banner: "Only match gems whose names exactly match the given name"
370
384
  def info(gem_name)
371
385
  require_relative "cli/info"
372
386
  Info.new(options, gem_name).run
@@ -434,7 +448,7 @@ module Bundler
434
448
  method_option "filter-strict", type: :boolean, aliases: "--strict", banner: "Only list newer versions allowed by your Gemfile requirements"
435
449
  method_option "update-strict", type: :boolean, banner: "Strict conservative resolution, do not allow any gem to be updated past latest --patch | --minor | --major"
436
450
  method_option "minor", type: :boolean, banner: "Prefer updating only to next minor version"
437
- method_option "major", type: :boolean, banner: "Prefer updating to next major version (default)"
451
+ method_option "major", type: :boolean, banner: "Prefer updating to latest major version (default)"
438
452
  method_option "patch", type: :boolean, banner: "Prefer updating only to next patch version"
439
453
  method_option "filter-major", type: :boolean, banner: "Only list major newer versions"
440
454
  method_option "filter-minor", type: :boolean, banner: "Only list minor newer versions"
@@ -530,6 +544,7 @@ module Bundler
530
544
 
531
545
  desc "open GEM", "Opens the source directory of the given bundled gem"
532
546
  method_option "path", type: :string, lazy_default: "", banner: "Open relative path of the gem source."
547
+ method_option "exact-match", type: :boolean, banner: "Only match gems whose names exactly match the given name"
533
548
  def open(name)
534
549
  require_relative "cli/open"
535
550
  Open.new(options, name).run
@@ -640,11 +655,11 @@ module Bundler
640
655
  method_option "normalize-platforms", type: :boolean, default: false, banner: "Normalize lockfile platforms"
641
656
  method_option "patch", type: :boolean, banner: "If updating, prefer updating only to next patch version"
642
657
  method_option "minor", type: :boolean, banner: "If updating, prefer updating only to next minor version"
643
- method_option "major", type: :boolean, banner: "If updating, prefer updating to next major version (default)"
658
+ method_option "major", type: :boolean, banner: "If updating, prefer updating to latest major version (default)"
644
659
  method_option "pre", type: :boolean, banner: "If updating, always choose the highest allowed version, regardless of prerelease status"
645
660
  method_option "strict", type: :boolean, banner: "If updating, do not allow any gem to be updated past latest --patch | --minor | --major"
646
661
  method_option "conservative", type: :boolean, banner: "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated"
647
- method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler"
662
+ method_option "bundler", type: :string, lazy_default: ">= #{Bundler::VERSION}", banner: "Update the locked version of bundler"
648
663
  method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
649
664
  def lock
650
665
  require_relative "cli/lock"
@@ -3,7 +3,7 @@
3
3
  require "rbconfig"
4
4
 
5
5
  module Bundler
6
- WINDOWS = RbConfig::CONFIG["host_os"] =~ /(msdos|mswin|djgpp|mingw)/
6
+ WINDOWS = RbConfig::CONFIG["host_os"] =~ /(mswin|mingw)/
7
7
  deprecate_constant :WINDOWS
8
8
 
9
9
  FREEBSD = RbConfig::CONFIG["host_os"].to_s.include?("bsd")
@@ -167,6 +167,7 @@ module Bundler
167
167
 
168
168
  @dependency_changes = converge_dependencies
169
169
  @local_changes = converge_locals
170
+ @override_changes = converge_overrides
170
171
 
171
172
  check_lockfile
172
173
  end
@@ -198,7 +199,7 @@ module Bundler
198
199
 
199
200
  sources.cached!
200
201
 
201
- if options[:add_checksums] || (!options[:local] && (install_needed? || refetch_needed?(options)))
202
+ if options[:add_checksums] || (!options[:local] && (install_needed? || refetch_needed?(options) || @locked_spec_with_empty_checksums))
202
203
  sources.remote!
203
204
  true
204
205
  else
@@ -346,7 +347,9 @@ module Bundler
346
347
  #
347
348
  # @return [SpecSet] resolved dependencies
348
349
  def resolve
349
- @resolve ||= if Bundler.frozen_bundle?
350
+ return @resolve if @resolve
351
+
352
+ @resolve = if Bundler.frozen_bundle?
350
353
  Bundler.ui.debug "Frozen, using resolution from the lockfile"
351
354
  @locked_specs
352
355
  elsif no_resolve_needed?
@@ -366,6 +369,8 @@ module Bundler
366
369
 
367
370
  start_resolution
368
371
  end
372
+ warn_inert_overrides(@resolve)
373
+ @resolve
369
374
  end
370
375
 
371
376
  def spec_git_paths
@@ -473,6 +478,11 @@ module Bundler
473
478
  @resolved_bundler_version || Bundler.gem_version
474
479
  end
475
480
 
481
+ # The lines for the OVERRIDES section of Gemfile.lock.
482
+ def overrides_to_lock
483
+ @overrides.map(&:to_lock).uniq
484
+ end
485
+
476
486
  def to_lock
477
487
  require_relative "lockfile_generator"
478
488
  LockfileGenerator.generate(self)
@@ -595,6 +605,11 @@ module Bundler
595
605
  added.concat new_deps.map {|d| "* #{pretty_dep(d)}" } if new_deps.any?
596
606
  deleted.concat deleted_deps.map {|d| "* #{pretty_dep(d)}" } if deleted_deps.any?
597
607
 
608
+ if @override_changes
609
+ added.concat((overrides_to_lock - locked_overrides_to_lock).sort.map {|o| "* override #{o}" })
610
+ deleted.concat((locked_overrides_to_lock - overrides_to_lock).sort.map {|o| "* override #{o}" })
611
+ end
612
+
598
613
  both_sources = Hash.new {|h, k| h[k] = [] }
599
614
  current_dependencies.each {|d| both_sources[d.name][0] = d }
600
615
  current_locked_dependencies.each {|d| both_sources[d.name][1] = d }
@@ -650,7 +665,7 @@ module Bundler
650
665
  @missing_lockfile_dep ||
651
666
  @unlocking_bundler ||
652
667
  @locked_spec_with_missing_checksums ||
653
- @locked_spec_with_empty_checksums ||
668
+ empty_checksums_actionable? ||
654
669
  @locked_spec_with_missing_deps ||
655
670
  @locked_spec_with_invalid_deps
656
671
  end
@@ -659,6 +674,16 @@ module Bundler
659
674
  unlocking? || something_changed?
660
675
  end
661
676
 
677
+ # Only a remote fetch can fill an empty CHECKSUMS entry, so it justifies a
678
+ # resolution only when one is coming. Resolving locally for it would repeat
679
+ # on every `Bundler.setup` without changing the lockfile. Frozen mode still
680
+ # has to refuse the entry.
681
+ def empty_checksums_actionable?
682
+ return false unless @locked_spec_with_empty_checksums
683
+
684
+ Bundler.frozen_bundle? || !sources.local_mode?
685
+ end
686
+
662
687
  def should_add_extra_platforms?
663
688
  !lockfile_exists? && Bundler::MatchPlatform.generic_local_platform_is_ruby? && !Bundler.settings[:force_ruby_platform]
664
689
  end
@@ -894,6 +919,8 @@ module Bundler
894
919
  else
895
920
  lockfile_changed_reason
896
921
  end
922
+ elsif @override_changes
923
+ "the overrides in your gemfile changed"
897
924
  else
898
925
  "some dependencies were deleted from your gemfile"
899
926
  end
@@ -929,6 +956,7 @@ module Bundler
929
956
  [@locked_spec_with_empty_checksums, "your lockfile has an empty CHECKSUMS entry for \"#{@locked_spec_with_empty_checksums}\""],
930
957
  [@locked_spec_with_missing_deps, "your lockfile includes \"#{@locked_spec_with_missing_deps}\" but not some of its dependencies"],
931
958
  [@locked_spec_with_invalid_deps, "your lockfile does not satisfy dependencies of \"#{@locked_spec_with_invalid_deps}\""],
959
+ [@override_changes, "the overrides in your gemfile changed"],
932
960
  ].select(&:first).map(&:last).join(", ")
933
961
  end
934
962
 
@@ -1095,25 +1123,77 @@ module Bundler
1095
1123
  end
1096
1124
 
1097
1125
  converge_overrides_outside_dependencies
1126
+ converge_rewritten_dependencies
1098
1127
 
1099
1128
  @changed_dependencies.any?
1100
1129
  end
1101
1130
 
1102
- def converge_overrides_outside_dependencies
1131
+ # The lockfile keeps the upstream dependencies of every gem, so a gem that
1132
+ # a `with:`/`in:` override cut loose stays locked until a resolution drops
1133
+ # it.
1134
+ def converge_rewritten_dependencies
1103
1135
  @overrides.each do |override|
1104
- # :all overrides are intentionally not pre-unlocked. They take effect on
1105
- # fresh resolution (no lockfile) or when the user runs `bundle update`.
1106
- # Forcing a full re-resolve from a single :all directive would surprise
1107
- # users with unrelated dependency churn.
1108
- next unless override.target.is_a?(String)
1136
+ next unless override.from
1109
1137
 
1110
1138
  name = override.target
1111
- next if @changed_dependencies.include?(name)
1112
1139
  next if @originally_locked_specs[name].empty?
1113
- # version: overrides on direct deps are detected in the per-dep
1140
+ next if @dependencies.any? {|d| d.name == name }
1141
+
1142
+ still_required = @originally_locked_specs.any? do |s|
1143
+ Override.rewrite_dependencies(@overrides, s.name, s.runtime_dependencies).any? {|d| d.name == name }
1144
+ end
1145
+ next if still_required
1146
+
1147
+ @gems_to_unlock << name
1148
+ @changed_dependencies << name
1149
+ end
1150
+ end
1151
+
1152
+ # A misspelled `in:` gem leaves the override with nothing to rewrite.
1153
+ def warn_inert_overrides(specs)
1154
+ @overrides.each do |override|
1155
+ next unless override.from
1156
+ next if specs[override.from].any? {|s| s.dependencies.any? {|d| d.name == override.target } }
1157
+
1158
+ Bundler.ui.warn "#{override} has no effect because the bundle has no #{override.from} that depends on #{override.target}"
1159
+ end
1160
+ end
1161
+
1162
+ # The lockfile records the Gemfile's overrides, so a change to them is a
1163
+ # change to the lockfile even when every locked version still satisfies
1164
+ # them. Rewriting the section needs no resolution, so this stays out of
1165
+ # #something_changed?. #write_lock rewrites it because the generated
1166
+ # contents differ, and the frozen check reports it.
1167
+ def converge_overrides
1168
+ return false unless @locked_gems
1169
+
1170
+ overrides_to_lock.sort != locked_overrides_to_lock.sort
1171
+ end
1172
+
1173
+ def locked_overrides_to_lock
1174
+ Array(@locked_gems&.overrides).map(&:to_lock).uniq
1175
+ end
1176
+
1177
+ def converge_overrides_outside_dependencies
1178
+ @overrides.each do |override|
1179
+ # :all and metadata overrides are intentionally not pre-unlocked.
1180
+ # Unlocking for them would re-resolve gems the user did not ask to
1181
+ # move. Install-time compatibility checks already honor them for locked
1182
+ # specs, and they affect resolution on a fresh lock or when the user
1183
+ # runs `bundle update`. Changing one still rewrites the OVERRIDES
1184
+ # section (see #converge_overrides).
1185
+ next unless override.target.is_a?(String) && override.field == :version
1186
+
1187
+ name = override.target
1188
+ # Overrides on direct deps are detected in the per-dep
1114
1189
  # converge_dependencies loop via apply_override_to + matches_spec?.
1115
- # Other fields are not visible there, so they always reach here.
1116
- next if override.field == :version && @dependencies.any? {|d| d.name == name }
1190
+ next if @dependencies.any? {|d| d.name == name }
1191
+
1192
+ locked_specs = @originally_locked_specs[name]
1193
+ next if locked_specs.empty?
1194
+ # Only a version string constrains on its own. :ignore_upper and nil
1195
+ # loosen requirements the lockfile already satisfies.
1196
+ next if override.apply_to(Gem::Requirement.default).satisfied_by?(locked_specs.first.version)
1117
1197
 
1118
1198
  @gems_to_unlock << name
1119
1199
  @changed_dependencies << name
@@ -1216,7 +1296,7 @@ module Bundler
1216
1296
  def preload_git_source_worker
1217
1297
  workers = Bundler.settings.installation_parallelization
1218
1298
 
1219
- @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(source, _) { source.specs })
1299
+ @preload_git_source_worker ||= Bundler::Worker.new(workers, "Git source preloading", ->(sources, _) { sources.each(&:specs) })
1220
1300
  end
1221
1301
 
1222
1302
  def preload_git_sources
@@ -1227,7 +1307,13 @@ module Bundler
1227
1307
  end
1228
1308
 
1229
1309
  begin
1230
- needed_git_sources.each {|source| preload_git_source_worker.enq(source) }
1310
+ # Sources from the same repository share one cache directory, and
1311
+ # running git in that directory from two workers at once makes the
1312
+ # fetches collide. Preload sources that share a cache in one job, one
1313
+ # after the other, and run the separate caches in parallel.
1314
+ needed_git_sources.group_by(&:cache_path).each_value do |sources|
1315
+ preload_git_source_worker.enq(sources)
1316
+ end
1231
1317
  ensure
1232
1318
  preload_git_source_worker.stop
1233
1319
  end
@@ -1342,7 +1428,7 @@ module Bundler
1342
1428
  end
1343
1429
 
1344
1430
  def source_map
1345
- @source_map ||= SourceMap.new(sources, dependencies, @locked_specs)
1431
+ @source_map ||= SourceMap.new(sources, dependencies, @locked_specs, @overrides.filter_map {|o| o.operation if o.from })
1346
1432
  end
1347
1433
 
1348
1434
  def new_resolver_for_full_update