bundler 4.0.11 → 4.0.13

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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +32 -0
  3. data/lib/bundler/build_metadata.rb +1 -1
  4. data/lib/bundler/cli/add.rb +3 -0
  5. data/lib/bundler/cli/common.rb +6 -0
  6. data/lib/bundler/cli/config.rb +8 -3
  7. data/lib/bundler/cli/install.rb +3 -0
  8. data/lib/bundler/cli/outdated.rb +42 -2
  9. data/lib/bundler/cli/update.rb +2 -0
  10. data/lib/bundler/cli.rb +16 -12
  11. data/lib/bundler/compact_index_client/parser.rb +4 -1
  12. data/lib/bundler/definition.rb +26 -4
  13. data/lib/bundler/dsl.rb +6 -2
  14. data/lib/bundler/endpoint_specification.rb +11 -1
  15. data/lib/bundler/installer.rb +5 -0
  16. data/lib/bundler/lockfile_parser.rb +15 -0
  17. data/lib/bundler/man/bundle-add.1 +4 -1
  18. data/lib/bundler/man/bundle-add.1.ronn +6 -1
  19. data/lib/bundler/man/bundle-config.1 +120 -156
  20. data/lib/bundler/man/bundle-config.1.ronn +30 -0
  21. data/lib/bundler/man/bundle-install.1 +4 -1
  22. data/lib/bundler/man/bundle-install.1.ronn +9 -1
  23. data/lib/bundler/man/bundle-outdated.1 +16 -13
  24. data/lib/bundler/man/bundle-outdated.1.ronn +19 -12
  25. data/lib/bundler/man/bundle-update.1 +4 -1
  26. data/lib/bundler/man/bundle-update.1.ronn +8 -0
  27. data/lib/bundler/remote_specification.rb +1 -1
  28. data/lib/bundler/resolver.rb +42 -1
  29. data/lib/bundler/rubygems_ext.rb +22 -0
  30. data/lib/bundler/rubygems_gem_installer.rb +1 -1
  31. data/lib/bundler/settings.rb +1 -0
  32. data/lib/bundler/source/git/git_proxy.rb +7 -2
  33. data/lib/bundler/source/path.rb +3 -2
  34. data/lib/bundler/source/rubygems/remote.rb +12 -2
  35. data/lib/bundler/source/rubygems.rb +51 -3
  36. data/lib/bundler/source_list.rb +6 -2
  37. data/lib/bundler/version.rb +1 -1
  38. data/lib/bundler.rb +10 -0
  39. metadata +2 -2
@@ -184,6 +184,9 @@ module Bundler
184
184
 
185
185
  platforms_explanation = specs_matching_other_platforms.any? ? " for any resolution platforms (#{package.platforms.join(", ")})" : ""
186
186
  custom_explanation = "#{constraint} could not be found in #{repository_for(package)}#{platforms_explanation}"
187
+ if hint = cooldown_hint(specs_matching_other_platforms)
188
+ custom_explanation += " (#{hint})"
189
+ end
187
190
 
188
191
  label = "#{name} (#{constraint_string})"
189
192
  extended_explanation = other_specs_matching_message(specs_matching_other_platforms, label) if specs_matching_other_platforms.any?
@@ -353,6 +356,10 @@ module Bundler
353
356
  message << "\n#{other_specs_matching_message(specs, matching_part)}"
354
357
  end
355
358
 
359
+ if hint = cooldown_hint(specs_matching_requirement)
360
+ message << "\n\n#{hint}."
361
+ end
362
+
356
363
  if specs_matching_requirement.any? && (hint = platform_mismatch_hint)
357
364
  message << "\n\n#{hint}"
358
365
  end
@@ -396,7 +403,7 @@ module Bundler
396
403
  end
397
404
 
398
405
  def filter_specs(specs, package)
399
- filter_remote_specs(filter_prereleases(specs, package), package)
406
+ filter_remote_specs(filter_cooldown(filter_prereleases(specs, package)), package)
400
407
  end
401
408
 
402
409
  def filter_prereleases(specs, package)
@@ -405,6 +412,40 @@ module Bundler
405
412
  specs.reject {|s| s.version.prerelease? }
406
413
  end
407
414
 
415
+ def filter_cooldown(specs)
416
+ return specs if specs.empty?
417
+ excluded_versions = cooldown_excluded_versions(specs)
418
+ return specs if excluded_versions.empty?
419
+ specs.reject {|s| excluded_versions.include?([s.name, s.version]) }
420
+ end
421
+
422
+ def cooldown_excluded_versions(specs)
423
+ excluded = {}
424
+ specs.each do |spec|
425
+ next unless cooldown_excluded?(spec)
426
+ excluded[[spec.name, spec.version]] = true
427
+ end
428
+ excluded
429
+ end
430
+
431
+ def cooldown_hint(specs)
432
+ excluded_versions = cooldown_excluded_versions(specs)
433
+ return nil if excluded_versions.empty?
434
+ "#{excluded_versions.size} version#{"s" if excluded_versions.size > 1} excluded by the cooldown setting; pass `--cooldown 0` to bypass"
435
+ end
436
+
437
+ def cooldown_excluded?(spec)
438
+ return false unless spec.respond_to?(:created_at) && spec.created_at
439
+ return false unless spec.respond_to?(:remote) && spec.remote
440
+ days = spec.remote.effective_cooldown
441
+ return false if days.nil? || days <= 0
442
+ (cooldown_now - spec.created_at) < (days * 86_400)
443
+ end
444
+
445
+ def cooldown_now
446
+ @cooldown_now ||= Time.now
447
+ end
448
+
408
449
  def filter_remote_specs(specs, package)
409
450
  if package.prefer_local?
410
451
  local_specs = specs.select {|s| s.is_a?(StubSpecification) }
@@ -465,6 +465,28 @@ module Gem
465
465
  Resolver::APISet::GemParser.prepend(UnfreezeCompactIndexParsedResponse)
466
466
  end
467
467
 
468
+ # RubyGems before 4.0.13 split compact index dependency/requirement entries
469
+ # on every colon, which mangles metadata values that contain colons such as
470
+ # the `created_at` timestamps the cooldown feature relies on. Split only on
471
+ # the first colon so those values survive on older RubyGems.
472
+ #
473
+ # The module is defined unconditionally so it stays testable on any RubyGems,
474
+ # but only prepended when the host RubyGems still has the buggy behavior.
475
+ module SplitCompactIndexEntryOnFirstColon
476
+ private
477
+
478
+ def parse_dependency(string)
479
+ dependency = string.split(":", 2)
480
+ dependency[-1] = dependency[-1].split("&") if dependency.size > 1
481
+ dependency[0] = -dependency[0]
482
+ dependency
483
+ end
484
+ end
485
+
486
+ unless Gem.rubygems_version >= Gem::Version.new("4.0.13")
487
+ Resolver::APISet::GemParser.prepend(SplitCompactIndexEntryOnFirstColon)
488
+ end
489
+
468
490
  if Gem.rubygems_version < Gem::Version.new("3.6.0")
469
491
  class Package; end
470
492
  require "rubygems/package/tar_reader"
@@ -20,7 +20,7 @@ module Bundler
20
20
  strict_rm_rf spec.extension_dir
21
21
 
22
22
  SharedHelpers.filesystem_access(gem_dir, :create) do
23
- FileUtils.mkdir_p gem_dir, mode: 0o755
23
+ FileUtils.mkdir_p gem_dir
24
24
  end
25
25
 
26
26
  SharedHelpers.filesystem_access(gem_dir, :write) do
@@ -42,6 +42,7 @@ module Bundler
42
42
  ].freeze
43
43
 
44
44
  NUMBER_KEYS = %w[
45
+ cooldown
45
46
  jobs
46
47
  redirect
47
48
  retry
@@ -432,9 +432,14 @@ module Bundler
432
432
  end
433
433
 
434
434
  def capture3_args_for(cmd, dir)
435
- return ["git", *cmd] unless dir
435
+ # Disable automatic maintenance so a background commit-graph write in
436
+ # the source repo can't race the hardlinking local clone and fail with
437
+ # "hardlink different from source".
438
+ opts = ["-c", "gc.auto=0", "-c", "maintenance.auto=false"]
436
439
 
437
- ["git", "-C", dir.to_s, *cmd]
440
+ return ["git", *opts, *cmd] unless dir
441
+
442
+ ["git", "-C", dir.to_s, *opts, *cmd]
438
443
  end
439
444
 
440
445
  def extra_clone_args
@@ -220,10 +220,11 @@ module Bundler
220
220
  # Some gem authors put absolute paths in their gemspec
221
221
  # and we have to save them from themselves
222
222
  spec.files = spec.files.filter_map do |path|
223
- next path unless /\A#{Pathname::SEPARATOR_PAT}/o.match?(path)
223
+ pathname = Pathname.new(path)
224
+ next path unless pathname.absolute?
224
225
  next if File.directory?(path)
225
226
  begin
226
- Pathname.new(path).relative_path_from(gem_dir).to_s
227
+ pathname.relative_path_from(gem_dir).to_s
227
228
  rescue ArgumentError
228
229
  path
229
230
  end
@@ -4,9 +4,9 @@ module Bundler
4
4
  class Source
5
5
  class Rubygems
6
6
  class Remote
7
- attr_reader :uri, :anonymized_uri, :original_uri
7
+ attr_reader :uri, :anonymized_uri, :original_uri, :cooldown
8
8
 
9
- def initialize(uri)
9
+ def initialize(uri, cooldown: nil)
10
10
  orig_uri = uri
11
11
  uri = Bundler.settings.mirror_for(uri)
12
12
  @original_uri = orig_uri if orig_uri != uri
@@ -14,6 +14,16 @@ module Bundler
14
14
 
15
15
  @uri = apply_auth(uri, fallback_auth).freeze
16
16
  @anonymized_uri = remove_auth(@uri).freeze
17
+ @cooldown = cooldown
18
+ end
19
+
20
+ # Returns the cooldown days that apply to this remote, resolving the
21
+ # precedence CLI > config > Gemfile per-source. Returns nil if no
22
+ # cooldown applies.
23
+ def effective_cooldown
24
+ override = Bundler.settings[:cooldown]
25
+ return override if override
26
+ @cooldown
17
27
  end
18
28
 
19
29
  MAX_CACHE_SLUG_HOST_SIZE = 255 - 1 - 32 # 255 minus dot minus MD5 length
@@ -16,6 +16,7 @@ module Bundler
16
16
  def initialize(options = {})
17
17
  @options = options
18
18
  @remotes = []
19
+ @remote_cooldowns = {}
19
20
  @dependency_names = []
20
21
  @allow_remote = false
21
22
  @allow_cached = false
@@ -25,7 +26,8 @@ module Bundler
25
26
  @gem_installers = {}
26
27
  @gem_installers_mutex = Mutex.new
27
28
 
28
- Array(options["remotes"]).reverse_each {|r| add_remote(r) }
29
+ cooldown = options["cooldown"]
30
+ Array(options["remotes"]).reverse_each {|r| add_remote(r, cooldown: cooldown) }
29
31
 
30
32
  @lockfile_remotes = @remotes if options["from_lockfile"]
31
33
  end
@@ -148,6 +150,13 @@ module Bundler
148
150
  # sources, and large_idx.merge! small_idx is way faster than
149
151
  # small_idx.merge! large_idx.
150
152
  index = @allow_remote ? remote_specs.dup : Index.new
153
+
154
+ # Snapshot per-version `created_at` from the remote info before installed
155
+ # / cached specs overwrite the EndpointSpecification objects that carry
156
+ # it. The cooldown filter consults `created_at` on every candidate, so
157
+ # local stubs need the published date back-filled to participate.
158
+ remote_created_at = collect_remote_created_at(index)
159
+
151
160
  index.merge!(cached_specs) if @allow_cached
152
161
  index.merge!(installed_specs) if @allow_local
153
162
 
@@ -161,6 +170,8 @@ module Bundler
161
170
  end
162
171
  end
163
172
 
173
+ backfill_created_at(index, remote_created_at) unless remote_created_at.empty?
174
+
164
175
  index
165
176
  end
166
177
  end
@@ -243,9 +254,14 @@ module Bundler
243
254
  cached_path
244
255
  end
245
256
 
246
- def add_remote(source)
257
+ def add_remote(source, cooldown: nil)
247
258
  uri = normalize_uri(source)
248
259
  @remotes.unshift(uri) unless @remotes.include?(uri)
260
+ @remote_cooldowns[uri] = cooldown if cooldown
261
+ end
262
+
263
+ def cooldown_for(uri)
264
+ @remote_cooldowns[uri]
249
265
  end
250
266
 
251
267
  def spec_names
@@ -266,7 +282,7 @@ module Bundler
266
282
 
267
283
  def remote_fetchers
268
284
  @remote_fetchers ||= remotes.to_h do |uri|
269
- remote = Source::Rubygems::Remote.new(uri)
285
+ remote = Source::Rubygems::Remote.new(uri, cooldown: cooldown_for(uri))
270
286
  [remote, Bundler::Fetcher.new(remote)]
271
287
  end.freeze
272
288
  end
@@ -314,6 +330,13 @@ module Bundler
314
330
  @allow_remote && api_fetchers.any?
315
331
  end
316
332
 
333
+ def clear_cache
334
+ @specs = nil
335
+ @installed_specs = nil
336
+ @default_specs = nil
337
+ @cached_specs = nil
338
+ end
339
+
317
340
  protected
318
341
 
319
342
  def remote_names
@@ -456,6 +479,31 @@ module Bundler
456
479
 
457
480
  private
458
481
 
482
+ def collect_remote_created_at(index)
483
+ return {} unless @allow_remote
484
+
485
+ snapshot = {}
486
+ index.each do |spec|
487
+ next unless spec.respond_to?(:created_at) && spec.created_at
488
+ # Remember the remote that supplied the date too: when a source has
489
+ # several remotes with different per-URI cooldown settings we must
490
+ # restore the same one during backfill so `effective_cooldown` agrees.
491
+ snapshot[[spec.name, spec.version]] = [spec.created_at, spec.remote]
492
+ end
493
+ snapshot
494
+ end
495
+
496
+ def backfill_created_at(index, snapshot)
497
+ index.each do |spec|
498
+ next unless spec.respond_to?(:created_at=)
499
+ next if spec.created_at
500
+ remote_created_at, remote = snapshot[[spec.name, spec.version]]
501
+ next unless remote_created_at
502
+ spec.created_at = remote_created_at
503
+ spec.remote ||= remote if remote && spec.respond_to?(:remote=)
504
+ end
505
+ end
506
+
459
507
  def lockfile_remotes
460
508
  @lockfile_remotes || credless_remotes
461
509
  end
@@ -59,8 +59,8 @@ module Bundler
59
59
  add_source_to_list Plugin.source(source).new(options), @plugin_sources
60
60
  end
61
61
 
62
- def add_global_rubygems_remote(uri)
63
- global_rubygems_source.add_remote(uri)
62
+ def add_global_rubygems_remote(uri, cooldown: nil)
63
+ global_rubygems_source.add_remote(uri, cooldown: cooldown)
64
64
  global_rubygems_source
65
65
  end
66
66
 
@@ -136,6 +136,10 @@ module Bundler
136
136
  all_sources.each(&:remote!)
137
137
  end
138
138
 
139
+ def clear_cache
140
+ rubygems_sources.each(&:clear_cache)
141
+ end
142
+
139
143
  private
140
144
 
141
145
  def map_sources(replacement_sources)
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: false
2
2
 
3
3
  module Bundler
4
- VERSION = "4.0.11".freeze
4
+ VERSION = "4.0.13".freeze
5
5
 
6
6
  def self.bundler_major_version
7
7
  @bundler_major_version ||= gem_version.segments.first
data/lib/bundler.rb CHANGED
@@ -156,6 +156,7 @@ module Bundler
156
156
  # Return if all groups are already loaded
157
157
  return @setup if defined?(@setup) && @setup
158
158
 
159
+ configure_custom_gemfile
159
160
  definition.validate_runtime!
160
161
 
161
162
  SharedHelpers.print_major_deprecations!
@@ -586,6 +587,15 @@ module Bundler
586
587
  Bundler.rubygems.clear_paths
587
588
  end
588
589
 
590
+ def configure_custom_gemfile(custom_gemfile = nil)
591
+ custom_gemfile ||= Bundler.settings[:gemfile]
592
+
593
+ if custom_gemfile && !custom_gemfile.empty?
594
+ Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile)
595
+ reset_settings_and_root!
596
+ end
597
+ end
598
+
589
599
  def self_manager
590
600
  @self_manager ||= begin
591
601
  require_relative "bundler/self_manager"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 4.0.11
4
+ version: 4.0.13
5
5
  platform: ruby
6
6
  authors:
7
7
  - André Arko
@@ -402,7 +402,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
402
402
  - !ruby/object:Gem::Version
403
403
  version: 3.4.1
404
404
  requirements: []
405
- rubygems_version: 4.0.6
405
+ rubygems_version: 4.0.10
406
406
  specification_version: 4
407
407
  summary: The best way to manage your application's dependencies
408
408
  test_files: []