bundler 4.0.10 → 4.0.20
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +174 -0
- data/bundler.gemspec +2 -2
- data/lib/bundler/build_metadata.rb +2 -2
- data/lib/bundler/cli/add.rb +3 -0
- data/lib/bundler/cli/check.rb +1 -3
- data/lib/bundler/cli/common.rb +17 -0
- data/lib/bundler/cli/config.rb +8 -3
- data/lib/bundler/cli/doctor/diagnose.rb +1 -1
- data/lib/bundler/cli/exec.rb +15 -1
- data/lib/bundler/cli/gem.rb +2 -3
- data/lib/bundler/cli/init.rb +4 -0
- data/lib/bundler/cli/install.rb +4 -0
- data/lib/bundler/cli/issue.rb +2 -2
- data/lib/bundler/cli/lock.rb +10 -2
- data/lib/bundler/cli/open.rb +12 -2
- data/lib/bundler/cli/outdated.rb +76 -6
- data/lib/bundler/cli/update.rb +3 -0
- data/lib/bundler/cli.rb +21 -15
- data/lib/bundler/compact_index_client/cache.rb +11 -0
- data/lib/bundler/compact_index_client/cache_file.rb +1 -1
- data/lib/bundler/compact_index_client/parser.rb +4 -1
- data/lib/bundler/definition.rb +64 -15
- data/lib/bundler/dsl.rb +11 -4
- data/lib/bundler/endpoint_specification.rb +30 -2
- data/lib/bundler/fetcher/base.rb +3 -0
- data/lib/bundler/fetcher/compact_index.rb +13 -0
- data/lib/bundler/fetcher/gem_remote_fetcher.rb +2 -0
- data/lib/bundler/fetcher.rb +12 -2
- data/lib/bundler/gem_helper.rb +2 -2
- data/lib/bundler/installer/parallel_installer.rb +68 -23
- data/lib/bundler/installer.rb +18 -1
- data/lib/bundler/lockfile_generator.rb +55 -1
- data/lib/bundler/lockfile_parser.rb +23 -1
- data/lib/bundler/man/bundle-add.1 +6 -3
- data/lib/bundler/man/bundle-add.1.ronn +7 -2
- data/lib/bundler/man/bundle-binstubs.1 +1 -1
- data/lib/bundler/man/bundle-cache.1 +4 -1
- data/lib/bundler/man/bundle-cache.1.ronn +6 -0
- data/lib/bundler/man/bundle-check.1 +1 -1
- data/lib/bundler/man/bundle-clean.1 +1 -1
- data/lib/bundler/man/bundle-config.1 +121 -157
- data/lib/bundler/man/bundle-config.1.ronn +38 -6
- data/lib/bundler/man/bundle-console.1 +3 -3
- data/lib/bundler/man/bundle-console.1.ronn +2 -2
- data/lib/bundler/man/bundle-doctor.1 +1 -1
- data/lib/bundler/man/bundle-env.1 +1 -1
- data/lib/bundler/man/bundle-exec.1 +1 -1
- data/lib/bundler/man/bundle-fund.1 +1 -1
- data/lib/bundler/man/bundle-gem.1 +1 -1
- data/lib/bundler/man/bundle-help.1 +1 -1
- data/lib/bundler/man/bundle-info.1 +1 -1
- data/lib/bundler/man/bundle-init.1 +2 -2
- data/lib/bundler/man/bundle-init.1.ronn +1 -1
- data/lib/bundler/man/bundle-install.1 +5 -2
- data/lib/bundler/man/bundle-install.1.ronn +9 -1
- data/lib/bundler/man/bundle-issue.1 +1 -1
- data/lib/bundler/man/bundle-licenses.1 +1 -1
- data/lib/bundler/man/bundle-list.1 +1 -1
- data/lib/bundler/man/bundle-lock.1 +5 -2
- data/lib/bundler/man/bundle-lock.1.ronn +6 -0
- data/lib/bundler/man/bundle-open.1 +1 -1
- data/lib/bundler/man/bundle-outdated.1 +17 -14
- data/lib/bundler/man/bundle-outdated.1.ronn +25 -12
- data/lib/bundler/man/bundle-platform.1 +5 -1
- data/lib/bundler/man/bundle-platform.1.ronn +4 -0
- data/lib/bundler/man/bundle-plugin.1 +2 -2
- data/lib/bundler/man/bundle-plugin.1.ronn +1 -1
- data/lib/bundler/man/bundle-pristine.1 +1 -1
- data/lib/bundler/man/bundle-remove.1 +1 -1
- data/lib/bundler/man/bundle-show.1 +1 -1
- data/lib/bundler/man/bundle-update.1 +5 -2
- data/lib/bundler/man/bundle-update.1.ronn +8 -0
- data/lib/bundler/man/bundle-version.1 +1 -1
- data/lib/bundler/man/bundle.1 +23 -2
- data/lib/bundler/man/bundle.1.ronn +22 -1
- data/lib/bundler/man/gemfile.5 +14 -3
- data/lib/bundler/man/gemfile.5.ronn +23 -2
- data/lib/bundler/mirror.rb +7 -7
- data/lib/bundler/plugin/unloaded_source.rb +25 -0
- data/lib/bundler/plugin.rb +12 -6
- data/lib/bundler/remote_specification.rb +1 -1
- data/lib/bundler/resolver/base.rb +9 -0
- data/lib/bundler/resolver.rb +116 -2
- data/lib/bundler/rubygems_ext.rb +22 -0
- data/lib/bundler/rubygems_gem_installer.rb +35 -2
- data/lib/bundler/runtime.rb +11 -11
- data/lib/bundler/self_manager.rb +6 -1
- data/lib/bundler/settings.rb +1 -0
- data/lib/bundler/shared_helpers.rb +21 -1
- data/lib/bundler/source/git/git_proxy.rb +83 -10
- data/lib/bundler/source/git.rb +4 -4
- data/lib/bundler/source/metadata.rb +4 -0
- data/lib/bundler/source/path.rb +4 -3
- data/lib/bundler/source/rubygems/remote.rb +12 -2
- data/lib/bundler/source/rubygems.rb +81 -13
- data/lib/bundler/source_list.rb +44 -3
- data/lib/bundler/spec_set.rb +2 -2
- data/lib/bundler/stub_specification.rb +3 -1
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +8 -2
- data/lib/bundler/vendored_tsort.rb +19 -2
- data/lib/bundler/version.rb +1 -1
- data/lib/bundler.rb +26 -6
- metadata +5 -6
- data/lib/bundler/vendor/tsort/LICENSE.txt +0 -22
- data/lib/bundler/vendor/tsort/lib/tsort.rb +0 -455
data/lib/bundler/self_manager.rb
CHANGED
|
@@ -75,7 +75,12 @@ module Bundler
|
|
|
75
75
|
|
|
76
76
|
argv0 = File.exist?($PROGRAM_NAME) ? $PROGRAM_NAME : Process.argv0
|
|
77
77
|
cmd = [argv0, *ARGV]
|
|
78
|
-
|
|
78
|
+
unless File.executable?(argv0)
|
|
79
|
+
# Gem.ruby is quoted if it contains whitespace, so split it into argv
|
|
80
|
+
# elements to keep the quotes out of the exec'd command.
|
|
81
|
+
require "shellwords"
|
|
82
|
+
cmd.unshift(*Shellwords.split(Gem.ruby))
|
|
83
|
+
end
|
|
79
84
|
|
|
80
85
|
Bundler.with_original_env do
|
|
81
86
|
Kernel.exec(
|
data/lib/bundler/settings.rb
CHANGED
|
@@ -231,6 +231,21 @@ module Bundler
|
|
|
231
231
|
destination
|
|
232
232
|
end
|
|
233
233
|
|
|
234
|
+
# Globs for entries matching +glob+ inside of +base_path+, returning
|
|
235
|
+
# absolute paths. Glob metacharacters in +base_path+ are not treated as
|
|
236
|
+
# part of the pattern, and matched entries are joined literally, so an
|
|
237
|
+
# entry starting with `~` is not expanded into the home directory.
|
|
238
|
+
#
|
|
239
|
+
# Bundler runs against whatever RubyGems the host provides, so this cannot
|
|
240
|
+
# delegate to Gem::Util.glob_files_in_dir, which only gained the literal
|
|
241
|
+
# join in RubyGems 4.1.
|
|
242
|
+
def glob_files_in_dir(glob, base_path)
|
|
243
|
+
expanded_path = nil
|
|
244
|
+
Dir.glob(glob, base: base_path).map! do |f|
|
|
245
|
+
File.join(expanded_path ||= File.expand_path(base_path), f)
|
|
246
|
+
end
|
|
247
|
+
end
|
|
248
|
+
|
|
234
249
|
private
|
|
235
250
|
|
|
236
251
|
def validate_bundle_path
|
|
@@ -346,7 +361,12 @@ module Bundler
|
|
|
346
361
|
|
|
347
362
|
def set_rubyopt
|
|
348
363
|
rubyopt = [ENV["RUBYOPT"]].compact
|
|
349
|
-
|
|
364
|
+
setup_path = File.expand_path("setup", __dir__)
|
|
365
|
+
# RUBYOPT is split on whitespace with no quoting mechanism, so an
|
|
366
|
+
# absolute path containing spaces would be torn apart. Fall back to
|
|
367
|
+
# requiring by feature name; set_rubylib puts our lib directory first
|
|
368
|
+
# on the child's load path.
|
|
369
|
+
setup_require = /\s/.match?(setup_path) ? "-rbundler/setup" : "-r#{setup_path}"
|
|
350
370
|
return if !rubyopt.empty? && rubyopt.first.include?(setup_require)
|
|
351
371
|
rubyopt.unshift setup_require
|
|
352
372
|
Bundler::SharedHelpers.set_env "RUBYOPT", rubyopt.join(" ")
|
|
@@ -144,6 +144,12 @@ module Bundler
|
|
|
144
144
|
FileUtils.rm_rf(p)
|
|
145
145
|
end
|
|
146
146
|
git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s
|
|
147
|
+
# The copy is cloned from the local bare cache, which holds no Git LFS
|
|
148
|
+
# objects, so point origin back at the real remote and let git-lfs derive
|
|
149
|
+
# its endpoint from there when checking out. Use the credential-filtered
|
|
150
|
+
# URI to avoid persisting secrets in the copy's .git/config; auth is left
|
|
151
|
+
# to git's credential helper.
|
|
152
|
+
git "remote", "set-url", "origin", credential_filtered_uri, dir: destination
|
|
147
153
|
File.chmod((File.stat(destination).mode | 0o777) & ~File.umask, destination)
|
|
148
154
|
rescue Errno::EEXIST => e
|
|
149
155
|
file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1]
|
|
@@ -187,7 +193,13 @@ module Bundler
|
|
|
187
193
|
return out if status.success?
|
|
188
194
|
|
|
189
195
|
if err.include?("couldn't find remote ref") || err.include?("not our ref")
|
|
190
|
-
|
|
196
|
+
default_branch = renamed_remote_default_branch if tracking_remote_default_branch?
|
|
197
|
+
if default_branch
|
|
198
|
+
out = follow_remote_default_branch(args, default_branch)
|
|
199
|
+
return out if out
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref || current_branch, credential_filtered_uri)
|
|
191
203
|
else
|
|
192
204
|
if shallow?
|
|
193
205
|
args -= depth_args
|
|
@@ -292,6 +304,61 @@ module Bundler
|
|
|
292
304
|
branch_option || ref.nil?
|
|
293
305
|
end
|
|
294
306
|
|
|
307
|
+
def tracking_remote_default_branch?
|
|
308
|
+
explicit_ref.nil? && commit.nil?
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
# Returns nil on any failure, leaving HEAD untouched so the caller reports
|
|
312
|
+
# the original fetch failure. Runs inside the retry block, so it must not
|
|
313
|
+
# touch the caller's command locals.
|
|
314
|
+
def follow_remote_default_branch(args, default_branch)
|
|
315
|
+
reference = "refs/heads/#{default_branch}"
|
|
316
|
+
command = fetch_command(args, "#{reference}:#{reference}")
|
|
317
|
+
check_allowed(command)
|
|
318
|
+
|
|
319
|
+
out, err, status = capture(command, path)
|
|
320
|
+
unless status.success?
|
|
321
|
+
Bundler.ui.debug "Could not fetch #{reference} from #{credential_filtered_uri}: #{err}"
|
|
322
|
+
return
|
|
323
|
+
end
|
|
324
|
+
|
|
325
|
+
previous_branch = current_branch
|
|
326
|
+
begin
|
|
327
|
+
git "symbolic-ref", "HEAD", reference, dir: path
|
|
328
|
+
rescue GitError => e
|
|
329
|
+
Bundler.ui.debug "Could not repoint the cached clone at #{reference}: #{e.message}"
|
|
330
|
+
return
|
|
331
|
+
end
|
|
332
|
+
@current_branch = nil
|
|
333
|
+
Bundler.ui.warn "#{credential_filtered_uri} no longer has #{previous_branch}, " \
|
|
334
|
+
"now following its default branch #{default_branch}"
|
|
335
|
+
out
|
|
336
|
+
end
|
|
337
|
+
|
|
338
|
+
# The cached clone's HEAD branch is gone from the remote, so the remote's
|
|
339
|
+
# own idea of its default branch is the only thing left to follow.
|
|
340
|
+
def renamed_remote_default_branch
|
|
341
|
+
default_branch = remote_default_branch
|
|
342
|
+
return if default_branch.nil? || default_branch == current_branch
|
|
343
|
+
|
|
344
|
+
default_branch
|
|
345
|
+
end
|
|
346
|
+
|
|
347
|
+
def remote_default_branch
|
|
348
|
+
command = ["ls-remote", "--symref", "--", configured_uri, "HEAD"]
|
|
349
|
+
check_allowed(command)
|
|
350
|
+
|
|
351
|
+
out, err, status = capture(command, path)
|
|
352
|
+
unless status.success?
|
|
353
|
+
Bundler.ui.debug "Could not ask #{credential_filtered_uri} for its default branch: #{err}"
|
|
354
|
+
return
|
|
355
|
+
end
|
|
356
|
+
|
|
357
|
+
# A remote is free to advertise a ref name that is not valid UTF-8, and
|
|
358
|
+
# matching that as text raises out of the GitError family.
|
|
359
|
+
out.b[%r{^ref:\s+refs/heads/(.+?)\s+HEAD}, 1]
|
|
360
|
+
end
|
|
361
|
+
|
|
295
362
|
def pinned_to_full_sha?
|
|
296
363
|
full_sha_revision?(ref)
|
|
297
364
|
end
|
|
@@ -358,9 +425,10 @@ module Bundler
|
|
|
358
425
|
git("rev-parse", "--verify", reference, dir: path).strip
|
|
359
426
|
end
|
|
360
427
|
|
|
361
|
-
# Adds credentials to the URI
|
|
428
|
+
# Adds credentials to the URI. This is the URI given to git, so it's
|
|
429
|
+
# also the one command output must be filtered against.
|
|
362
430
|
def configured_uri
|
|
363
|
-
if /https?:/.match?(uri)
|
|
431
|
+
@configured_uri ||= if /https?:/.match?(uri)
|
|
364
432
|
remote = Gem::URI(uri)
|
|
365
433
|
config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
|
|
366
434
|
remote.userinfo ||= config_auth
|
|
@@ -403,7 +471,7 @@ module Bundler
|
|
|
403
471
|
raise GitNotInstalledError.new unless Bundler.git_present?
|
|
404
472
|
|
|
405
473
|
require "shellwords"
|
|
406
|
-
URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}",
|
|
474
|
+
URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", configured_uri)
|
|
407
475
|
end
|
|
408
476
|
|
|
409
477
|
def run_command(*command, dir: nil)
|
|
@@ -423,18 +491,23 @@ module Bundler
|
|
|
423
491
|
require "open3"
|
|
424
492
|
out, err, status = Open3.capture3(*capture3_args_for(cmd, dir))
|
|
425
493
|
|
|
426
|
-
filtered_out = URICredentialsFilter.credential_filtered_string(out,
|
|
494
|
+
filtered_out = URICredentialsFilter.credential_filtered_string(out, configured_uri)
|
|
427
495
|
return [filtered_out, status] if ignore_err
|
|
428
496
|
|
|
429
|
-
filtered_err = URICredentialsFilter.credential_filtered_string(err,
|
|
497
|
+
filtered_err = URICredentialsFilter.credential_filtered_string(err, configured_uri)
|
|
430
498
|
[filtered_out, filtered_err, status]
|
|
431
499
|
end
|
|
432
500
|
end
|
|
433
501
|
|
|
434
502
|
def capture3_args_for(cmd, dir)
|
|
435
|
-
|
|
503
|
+
# Disable automatic maintenance so a background commit-graph write in
|
|
504
|
+
# the source repo can't race the hardlinking local clone and fail with
|
|
505
|
+
# "hardlink different from source".
|
|
506
|
+
opts = ["-c", "gc.auto=0", "-c", "maintenance.auto=false"]
|
|
507
|
+
|
|
508
|
+
return ["git", *opts, *cmd] unless dir
|
|
436
509
|
|
|
437
|
-
["git", "-C", dir.to_s, *cmd]
|
|
510
|
+
["git", "-C", dir.to_s, *opts, *cmd]
|
|
438
511
|
end
|
|
439
512
|
|
|
440
513
|
def extra_clone_args
|
|
@@ -453,8 +526,8 @@ module Bundler
|
|
|
453
526
|
args
|
|
454
527
|
end
|
|
455
528
|
|
|
456
|
-
def fetch_command(args)
|
|
457
|
-
["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri,
|
|
529
|
+
def fetch_command(args, spec = refspec)
|
|
530
|
+
["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, spec].compact
|
|
458
531
|
end
|
|
459
532
|
|
|
460
533
|
def clone_command(args)
|
data/lib/bundler/source/git.rb
CHANGED
|
@@ -91,9 +91,9 @@ module Bundler
|
|
|
91
91
|
|
|
92
92
|
def to_s
|
|
93
93
|
begin
|
|
94
|
-
at = humanized_ref
|
|
95
|
-
|
|
96
|
-
rev = "at #{at}
|
|
94
|
+
at = humanized_ref
|
|
95
|
+
at = "#{at}@" if at
|
|
96
|
+
rev = "at #{at}#{shortref_for_display(revision)}"
|
|
97
97
|
rescue GitError
|
|
98
98
|
""
|
|
99
99
|
end
|
|
@@ -320,7 +320,7 @@ module Bundler
|
|
|
320
320
|
|
|
321
321
|
def serialize_gemspecs_in(destination)
|
|
322
322
|
destination = destination.expand_path(Bundler.root) if destination.relative?
|
|
323
|
-
|
|
323
|
+
SharedHelpers.glob_files_in_dir(@glob, destination.to_s).each do |spec_path|
|
|
324
324
|
# Evaluate gemspecs and cache the result. Gemspecs
|
|
325
325
|
# in git might require git or other dependencies.
|
|
326
326
|
# The gemspecs we cache should already be evaluated.
|
data/lib/bundler/source/path.rb
CHANGED
|
@@ -161,7 +161,7 @@ module Bundler
|
|
|
161
161
|
|
|
162
162
|
if File.directory?(expanded_path)
|
|
163
163
|
# We sort depth-first since `<<` will override the earlier-found specs
|
|
164
|
-
|
|
164
|
+
SharedHelpers.glob_files_in_dir(@glob, expanded_path).sort_by {|p| -p.split(File::SEPARATOR).size }.each do |file|
|
|
165
165
|
next unless spec = load_gemspec(file)
|
|
166
166
|
spec.source = self
|
|
167
167
|
|
|
@@ -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
|
-
|
|
223
|
+
pathname = Pathname.new(path)
|
|
224
|
+
next path unless pathname.absolute?
|
|
224
225
|
next if File.directory?(path)
|
|
225
226
|
begin
|
|
226
|
-
|
|
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
|
|
@@ -11,11 +11,12 @@ module Bundler
|
|
|
11
11
|
API_REQUEST_SIZE = 100
|
|
12
12
|
REQUIRE_MUTEX = Mutex.new
|
|
13
13
|
|
|
14
|
-
attr_accessor :remotes
|
|
14
|
+
attr_accessor :remotes, :remote_cooldowns
|
|
15
15
|
|
|
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
|
|
@@ -24,8 +25,10 @@ module Bundler
|
|
|
24
25
|
@checksum_store = Checksum::Store.new
|
|
25
26
|
@gem_installers = {}
|
|
26
27
|
@gem_installers_mutex = Mutex.new
|
|
28
|
+
@remote_specs_mutex = Mutex.new
|
|
27
29
|
|
|
28
|
-
|
|
30
|
+
cooldown = options["cooldown"]
|
|
31
|
+
Array(options["remotes"]).reverse_each {|r| add_remote(r, cooldown: cooldown) }
|
|
29
32
|
|
|
30
33
|
@lockfile_remotes = @remotes if options["from_lockfile"]
|
|
31
34
|
end
|
|
@@ -148,6 +151,13 @@ module Bundler
|
|
|
148
151
|
# sources, and large_idx.merge! small_idx is way faster than
|
|
149
152
|
# small_idx.merge! large_idx.
|
|
150
153
|
index = @allow_remote ? remote_specs.dup : Index.new
|
|
154
|
+
|
|
155
|
+
# Snapshot per-version `created_at` from the remote info before installed
|
|
156
|
+
# / cached specs overwrite the EndpointSpecification objects that carry
|
|
157
|
+
# it. The cooldown filter consults `created_at` on every candidate, so
|
|
158
|
+
# local stubs need the published date back-filled to participate.
|
|
159
|
+
remote_created_at = collect_remote_created_at(index)
|
|
160
|
+
|
|
151
161
|
index.merge!(cached_specs) if @allow_cached
|
|
152
162
|
index.merge!(installed_specs) if @allow_local
|
|
153
163
|
|
|
@@ -161,6 +171,8 @@ module Bundler
|
|
|
161
171
|
end
|
|
162
172
|
end
|
|
163
173
|
|
|
174
|
+
backfill_created_at(index, remote_created_at) unless remote_created_at.empty?
|
|
175
|
+
|
|
164
176
|
index
|
|
165
177
|
end
|
|
166
178
|
end
|
|
@@ -232,7 +244,7 @@ module Bundler
|
|
|
232
244
|
def cached_built_in_gem(spec, local: false)
|
|
233
245
|
cached_path = cached_gem(spec)
|
|
234
246
|
if cached_path.nil? && !local
|
|
235
|
-
remote_spec =
|
|
247
|
+
remote_spec = remote_spec_for(spec)
|
|
236
248
|
if remote_spec
|
|
237
249
|
cached_path = fetch_gem(remote_spec)
|
|
238
250
|
spec.remote = remote_spec.remote
|
|
@@ -243,9 +255,14 @@ module Bundler
|
|
|
243
255
|
cached_path
|
|
244
256
|
end
|
|
245
257
|
|
|
246
|
-
def add_remote(source)
|
|
258
|
+
def add_remote(source, cooldown: nil)
|
|
247
259
|
uri = normalize_uri(source)
|
|
248
260
|
@remotes.unshift(uri) unless @remotes.include?(uri)
|
|
261
|
+
@remote_cooldowns[uri] = cooldown if cooldown
|
|
262
|
+
end
|
|
263
|
+
|
|
264
|
+
def cooldown_for(uri)
|
|
265
|
+
@remote_cooldowns[uri]
|
|
249
266
|
end
|
|
250
267
|
|
|
251
268
|
def spec_names
|
|
@@ -266,7 +283,7 @@ module Bundler
|
|
|
266
283
|
|
|
267
284
|
def remote_fetchers
|
|
268
285
|
@remote_fetchers ||= remotes.to_h do |uri|
|
|
269
|
-
remote = Source::Rubygems::Remote.new(uri)
|
|
286
|
+
remote = Source::Rubygems::Remote.new(uri, cooldown: cooldown_for(uri))
|
|
270
287
|
[remote, Bundler::Fetcher.new(remote)]
|
|
271
288
|
end.freeze
|
|
272
289
|
end
|
|
@@ -314,6 +331,19 @@ module Bundler
|
|
|
314
331
|
@allow_remote && api_fetchers.any?
|
|
315
332
|
end
|
|
316
333
|
|
|
334
|
+
def clear_cache
|
|
335
|
+
@specs = nil
|
|
336
|
+
@installed_specs = nil
|
|
337
|
+
@default_specs = nil
|
|
338
|
+
@cached_specs = nil
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def release_resolution_memory!
|
|
342
|
+
@specs = nil
|
|
343
|
+
@remote_specs_mutex.synchronize { @remote_specs = nil }
|
|
344
|
+
@fetchers&.each(&:release_resolution_memory!)
|
|
345
|
+
end
|
|
346
|
+
|
|
317
347
|
protected
|
|
318
348
|
|
|
319
349
|
def remote_names
|
|
@@ -376,7 +406,7 @@ module Bundler
|
|
|
376
406
|
@cached_specs ||= begin
|
|
377
407
|
idx = Index.new
|
|
378
408
|
|
|
379
|
-
|
|
409
|
+
SharedHelpers.glob_files_in_dir("*.gem", cache_path.to_s).each do |gemfile|
|
|
380
410
|
s ||= Bundler.rubygems.spec_from_gem(gemfile)
|
|
381
411
|
s.source = self
|
|
382
412
|
idx << s
|
|
@@ -391,17 +421,30 @@ module Bundler
|
|
|
391
421
|
end
|
|
392
422
|
|
|
393
423
|
def remote_specs
|
|
394
|
-
@remote_specs ||=
|
|
395
|
-
|
|
424
|
+
@remote_specs ||= @remote_specs_mutex.synchronize do
|
|
425
|
+
@remote_specs ||= Index.build do |idx|
|
|
426
|
+
index_fetchers = fetchers - api_fetchers
|
|
396
427
|
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
428
|
+
if index_fetchers.empty?
|
|
429
|
+
fetch_names(api_fetchers, dependency_names, idx)
|
|
430
|
+
else
|
|
431
|
+
fetch_names(fetchers, nil, idx)
|
|
432
|
+
end
|
|
401
433
|
end
|
|
402
434
|
end
|
|
403
435
|
end
|
|
404
436
|
|
|
437
|
+
# Looks up a single spec in the remote sources, fetching only its own
|
|
438
|
+
# name when the full remote index is not already materialized.
|
|
439
|
+
def remote_spec_for(spec)
|
|
440
|
+
return remote_specs.search(spec).first if @remote_specs || api_fetchers.empty?
|
|
441
|
+
|
|
442
|
+
index = Index.build do |idx|
|
|
443
|
+
fetch_names(api_fetchers, [spec.name], idx)
|
|
444
|
+
end
|
|
445
|
+
index.search(spec).first
|
|
446
|
+
end
|
|
447
|
+
|
|
405
448
|
def fetch_names(fetchers, dependency_names, index)
|
|
406
449
|
fetchers.each do |f|
|
|
407
450
|
if dependency_names
|
|
@@ -456,6 +499,31 @@ module Bundler
|
|
|
456
499
|
|
|
457
500
|
private
|
|
458
501
|
|
|
502
|
+
def collect_remote_created_at(index)
|
|
503
|
+
return {} unless @allow_remote
|
|
504
|
+
|
|
505
|
+
snapshot = {}
|
|
506
|
+
index.each do |spec|
|
|
507
|
+
next unless spec.respond_to?(:created_at) && spec.created_at
|
|
508
|
+
# Remember the remote that supplied the date too: when a source has
|
|
509
|
+
# several remotes with different per-URI cooldown settings we must
|
|
510
|
+
# restore the same one during backfill so `effective_cooldown` agrees.
|
|
511
|
+
snapshot[[spec.name, spec.version]] = [spec.created_at, spec.remote]
|
|
512
|
+
end
|
|
513
|
+
snapshot
|
|
514
|
+
end
|
|
515
|
+
|
|
516
|
+
def backfill_created_at(index, snapshot)
|
|
517
|
+
index.each do |spec|
|
|
518
|
+
next unless spec.respond_to?(:created_at=)
|
|
519
|
+
next if spec.created_at
|
|
520
|
+
remote_created_at, remote = snapshot[[spec.name, spec.version]]
|
|
521
|
+
next unless remote_created_at
|
|
522
|
+
spec.created_at = remote_created_at
|
|
523
|
+
spec.remote ||= remote if remote && spec.respond_to?(:remote=)
|
|
524
|
+
end
|
|
525
|
+
end
|
|
526
|
+
|
|
459
527
|
def lockfile_remotes
|
|
460
528
|
@lockfile_remotes || credless_remotes
|
|
461
529
|
end
|
|
@@ -508,7 +576,7 @@ module Bundler
|
|
|
508
576
|
|
|
509
577
|
# We are using a mutex to reaed and write from/to the hash.
|
|
510
578
|
# The reason this double synchronization was added is for performance
|
|
511
|
-
# and lock the mutex for the shortest possible amount of time. Otherwise,
|
|
579
|
+
# and to lock the mutex for the shortest possible amount of time. Otherwise,
|
|
512
580
|
# all threads are fighting over this mutex and when it gets acquired it gets locked
|
|
513
581
|
# until a threads finishes downloading a gem, leaving the other threads waiting
|
|
514
582
|
# doing nothing.
|
data/lib/bundler/source_list.rb
CHANGED
|
@@ -50,7 +50,13 @@ module Bundler
|
|
|
50
50
|
|
|
51
51
|
def add_rubygems_source(options = {})
|
|
52
52
|
new_source = Source::Rubygems.new(options)
|
|
53
|
-
|
|
53
|
+
if @global_rubygems_source == new_source
|
|
54
|
+
warn_on_cooldown_conflict(new_source, @global_rubygems_source)
|
|
55
|
+
return @global_rubygems_source
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
existing_source = @rubygems_sources.find {|s| s == new_source }
|
|
59
|
+
warn_on_cooldown_conflict(new_source, existing_source) if existing_source
|
|
54
60
|
|
|
55
61
|
add_source_to_list new_source, @rubygems_sources
|
|
56
62
|
end
|
|
@@ -59,8 +65,15 @@ module Bundler
|
|
|
59
65
|
add_source_to_list Plugin.source(source).new(options), @plugin_sources
|
|
60
66
|
end
|
|
61
67
|
|
|
62
|
-
def add_global_rubygems_remote(uri)
|
|
63
|
-
|
|
68
|
+
def add_global_rubygems_remote(uri, cooldown: nil)
|
|
69
|
+
unless cooldown.nil?
|
|
70
|
+
new_source = source_class.new("remotes" => uri, "cooldown" => cooldown)
|
|
71
|
+
[global_rubygems_source, *@rubygems_sources].find do |existing_source|
|
|
72
|
+
warn_on_cooldown_conflict(new_source, existing_source)
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
global_rubygems_source.add_remote(uri, cooldown: cooldown)
|
|
64
77
|
global_rubygems_source
|
|
65
78
|
end
|
|
66
79
|
|
|
@@ -136,6 +149,14 @@ module Bundler
|
|
|
136
149
|
all_sources.each(&:remote!)
|
|
137
150
|
end
|
|
138
151
|
|
|
152
|
+
def clear_cache
|
|
153
|
+
rubygems_sources.each(&:clear_cache)
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def release_resolution_memory!
|
|
157
|
+
rubygems_sources.each(&:release_resolution_memory!)
|
|
158
|
+
end
|
|
159
|
+
|
|
139
160
|
private
|
|
140
161
|
|
|
141
162
|
def map_sources(replacement_sources)
|
|
@@ -165,6 +186,10 @@ module Bundler
|
|
|
165
186
|
# locked sources never include credentials so always prefer remotes from the gemfile
|
|
166
187
|
replacement_source.remotes = gemfile_source.remotes
|
|
167
188
|
|
|
189
|
+
# cooldowns are only ever declared in the Gemfile, so carry them over
|
|
190
|
+
# along with the remotes they apply to
|
|
191
|
+
replacement_source.remote_cooldowns = gemfile_source.remote_cooldowns
|
|
192
|
+
|
|
168
193
|
yield replacement_source if block_given?
|
|
169
194
|
|
|
170
195
|
replacement_source
|
|
@@ -210,6 +235,22 @@ module Bundler
|
|
|
210
235
|
end
|
|
211
236
|
end
|
|
212
237
|
|
|
238
|
+
def warn_on_cooldown_conflict(new_source, existing_source)
|
|
239
|
+
new_source.remote_cooldowns.any? do |uri, cooldown|
|
|
240
|
+
next false unless existing_source.remotes.include?(uri)
|
|
241
|
+
|
|
242
|
+
existing_cooldown = existing_source.cooldown_for(uri)
|
|
243
|
+
next false if existing_cooldown == cooldown
|
|
244
|
+
|
|
245
|
+
previous = existing_cooldown ? "`cooldown: #{existing_cooldown}`" : "no cooldown"
|
|
246
|
+
Bundler.ui.warn "The source #{uri} is declared more than once with different cooldown " \
|
|
247
|
+
"values (`cooldown: #{cooldown}` here, #{previous} previously). All declarations of " \
|
|
248
|
+
"the same source URL share a single cooldown, so only one of these values will apply " \
|
|
249
|
+
"to all gems from this source."
|
|
250
|
+
true
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
|
|
213
254
|
def warn_on_git_protocol(source)
|
|
214
255
|
return if Bundler.settings["git.allow_insecure"]
|
|
215
256
|
|
data/lib/bundler/spec_set.rb
CHANGED
|
@@ -5,7 +5,7 @@ require_relative "vendored_tsort"
|
|
|
5
5
|
module Bundler
|
|
6
6
|
class SpecSet
|
|
7
7
|
include Enumerable
|
|
8
|
-
include TSort
|
|
8
|
+
include Gem::TSort
|
|
9
9
|
|
|
10
10
|
def initialize(specs)
|
|
11
11
|
@specs = specs
|
|
@@ -315,7 +315,7 @@ module Bundler
|
|
|
315
315
|
|
|
316
316
|
def sorted
|
|
317
317
|
@sorted ||= ([@specs.find {|s| s.name == "rake" }] + tsort).compact.uniq
|
|
318
|
-
rescue TSort::Cyclic => error
|
|
318
|
+
rescue Gem::TSort::Cyclic => error
|
|
319
319
|
cgems = extract_circular_gems(error)
|
|
320
320
|
raise CyclicDependencyError, "Your bundle requires gems that depend" \
|
|
321
321
|
" on each other, creating an infinite loop. Please remove either" \
|
|
@@ -126,7 +126,9 @@ module Bundler
|
|
|
126
126
|
def _remote_specification
|
|
127
127
|
@_remote_specification ||= begin
|
|
128
128
|
rs = stub.to_spec
|
|
129
|
-
|
|
129
|
+
# Gem::StubSpecification#to_spec may return an activated specification
|
|
130
|
+
# with the same name and version from a different gem installation.
|
|
131
|
+
if rs.equal?(self) || rs.loaded_from != loaded_from
|
|
130
132
|
rs = Gem::Specification.load(loaded_from)
|
|
131
133
|
Bundler.rubygems.stub_set_spec(stub, rs)
|
|
132
134
|
end
|
|
@@ -22,6 +22,12 @@ Gem::Specification.new do |spec|
|
|
|
22
22
|
spec.metadata["changelog_uri"] = "<%= config[:changelog_uri] %>"
|
|
23
23
|
<%- end -%>
|
|
24
24
|
|
|
25
|
+
# Uncomment the line below to require MFA for gem pushes.
|
|
26
|
+
# This helps protect your gem from supply chain attacks by ensuring
|
|
27
|
+
# no one can publish a new version without multi-factor authentication.
|
|
28
|
+
# See: https://guides.rubygems.org/mfa-requirement-opt-in/
|
|
29
|
+
# spec.metadata["rubygems_mfa_required"] = "true"
|
|
30
|
+
|
|
25
31
|
# Specify which files should be added to the gem when it is released.
|
|
26
32
|
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
27
33
|
gemspec = File.basename(__FILE__)
|
|
@@ -41,12 +47,12 @@ Gem::Specification.new do |spec|
|
|
|
41
47
|
# Uncomment to register a new dependency of your gem
|
|
42
48
|
# spec.add_dependency "example-gem", "~> 1.0"
|
|
43
49
|
<%- if config[:ext] == 'rust' -%>
|
|
44
|
-
spec.add_dependency "rb_sys", "~> 0.9.
|
|
50
|
+
spec.add_dependency "rb_sys", "~> 0.9.128"
|
|
45
51
|
<%- end -%>
|
|
46
52
|
<%- if config[:ext] == 'go' -%>
|
|
47
53
|
spec.add_dependency "go_gem", "~> 0.2"
|
|
48
54
|
<%- end -%>
|
|
49
55
|
|
|
50
56
|
# For more information and examples about making a new gem, check out our
|
|
51
|
-
# guide at: https://
|
|
57
|
+
# guide at: https://guides.rubygems.org/make-your-own-gem/
|
|
52
58
|
end
|
|
@@ -1,4 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# The defined? guard avoids reopening Gem::TSort when an old RubyGems has
|
|
4
|
+
# already loaded its own copy, e.g. through rubygems/request_set from
|
|
5
|
+
# Gem.activate_bin_path in binstubs.
|
|
6
|
+
#
|
|
7
|
+
unless defined?(Gem::TSort)
|
|
8
|
+
begin
|
|
9
|
+
require "rubygems/vendored_tsort"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
begin
|
|
12
|
+
# RubyGems 3.4 and 3.5 ship the same file under its pre-3.6 name.
|
|
13
|
+
# Requiring the real tsort here instead would activate the tsort
|
|
14
|
+
# default gem, and `bundler/setup` must not activate any gems.
|
|
15
|
+
require "rubygems/tsort"
|
|
16
|
+
rescue LoadError
|
|
17
|
+
require "tsort"
|
|
18
|
+
Gem::TSort = TSort
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|