bundler 4.0.22 → 4.1.0.beta2
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/{lib/bundler/vendor/connection_pool/LICENSE → MIT.txt} +8 -6
- data/bundler.gemspec +23 -6
- data/lib/bundler/build_metadata.rb +2 -2
- data/lib/bundler/checksum.rb +40 -25
- data/lib/bundler/ci_detector.rb +1 -1
- data/lib/bundler/cli/add.rb +3 -4
- data/lib/bundler/cli/cache.rb +4 -0
- data/lib/bundler/cli/clean.rb +5 -0
- data/lib/bundler/cli/common.rb +41 -1
- data/lib/bundler/cli/config.rb +4 -2
- data/lib/bundler/cli/doctor/ssl.rb +4 -8
- data/lib/bundler/cli/gem.rb +0 -17
- data/lib/bundler/cli/info.rb +1 -1
- data/lib/bundler/cli/install.rb +4 -5
- data/lib/bundler/cli/lock.rb +15 -5
- data/lib/bundler/cli/open.rb +7 -1
- data/lib/bundler/cli/outdated.rb +16 -13
- data/lib/bundler/cli/pristine.rb +2 -1
- data/lib/bundler/cli/show.rb +5 -1
- data/lib/bundler/cli/update.rb +20 -11
- data/lib/bundler/cli.rb +15 -9
- data/lib/bundler/constants.rb +1 -1
- data/lib/bundler/definition.rb +102 -15
- data/lib/bundler/dependency.rb +4 -0
- data/lib/bundler/dsl.rb +137 -13
- data/lib/bundler/endpoint_specification.rb +51 -14
- data/lib/bundler/env.rb +7 -24
- data/lib/bundler/errors.rb +34 -5
- data/lib/bundler/fetcher/compact_index.rb +14 -5
- data/lib/bundler/fetcher/connection_pools.rb +150 -0
- data/lib/bundler/fetcher/downloader.rb +35 -16
- data/lib/bundler/fetcher.rb +8 -47
- data/lib/bundler/gem_helper.rb +1 -1
- data/lib/bundler/injector.rb +3 -3
- data/lib/bundler/inline.rb +44 -4
- data/lib/bundler/installer/parallel_installer.rb +27 -31
- data/lib/bundler/installer.rb +21 -7
- data/lib/bundler/lazy_specification.rb +45 -18
- data/lib/bundler/lockfile_generator.rb +24 -2
- data/lib/bundler/lockfile_parser.rb +44 -6
- data/lib/bundler/man/bundle-add.1 +7 -4
- data/lib/bundler/man/bundle-add.1.ronn +8 -4
- data/lib/bundler/man/bundle-binstubs.1 +1 -1
- data/lib/bundler/man/bundle-cache.1 +2 -2
- data/lib/bundler/man/bundle-cache.1.ronn +2 -2
- 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 +38 -6
- data/lib/bundler/man/bundle-config.1.ronn +143 -10
- data/lib/bundler/man/bundle-console.1 +1 -1
- 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 +5 -2
- data/lib/bundler/man/bundle-info.1.ronn +4 -0
- data/lib/bundler/man/bundle-init.1 +1 -1
- data/lib/bundler/man/bundle-install.1 +2 -2
- data/lib/bundler/man/bundle-install.1.ronn +4 -4
- 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 +2 -2
- data/lib/bundler/man/bundle-lock.1.ronn +3 -2
- data/lib/bundler/man/bundle-open.1 +5 -2
- data/lib/bundler/man/bundle-open.1.ronn +4 -1
- data/lib/bundler/man/bundle-outdated.1 +1 -1
- data/lib/bundler/man/bundle-platform.1 +1 -1
- data/lib/bundler/man/bundle-plugin.1 +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 +5 -2
- data/lib/bundler/man/bundle-show.1.ronn +4 -0
- data/lib/bundler/man/bundle-update.1 +2 -2
- data/lib/bundler/man/bundle-update.1.ronn +2 -2
- data/lib/bundler/man/bundle-version.1 +1 -1
- data/lib/bundler/man/bundle.1 +1 -1
- data/lib/bundler/man/gemfile.5 +77 -1
- data/lib/bundler/man/gemfile.5.ronn +104 -0
- data/lib/bundler/match_metadata.rb +20 -0
- data/lib/bundler/match_platform.rb +25 -2
- data/lib/bundler/match_remote_metadata.rb +21 -6
- data/lib/bundler/materialization.rb +5 -2
- data/lib/bundler/override.rb +97 -0
- data/lib/bundler/plugin/dsl.rb +3 -5
- data/lib/bundler/plugin/events.rb +55 -13
- data/lib/bundler/plugin/index.rb +87 -11
- data/lib/bundler/plugin/installer/path.rb +1 -1
- data/lib/bundler/plugin/installer.rb +13 -5
- data/lib/bundler/plugin.rb +58 -30
- data/lib/bundler/remote_specification.rb +6 -3
- data/lib/bundler/resolver/base.rb +2 -1
- data/lib/bundler/resolver/incompatibility.rb +1 -1
- data/lib/bundler/resolver.rb +97 -23
- data/lib/bundler/ruby_version.rb +0 -2
- data/lib/bundler/rubygems_ext.rb +71 -1
- data/lib/bundler/rubygems_gem_installer.rb +41 -2
- data/lib/bundler/rubygems_integration.rb +7 -2
- data/lib/bundler/runtime.rb +69 -2
- data/lib/bundler/settings.rb +383 -56
- data/lib/bundler/shared_helpers.rb +2 -1
- data/lib/bundler/source/git/git_proxy.rb +31 -2
- data/lib/bundler/source/git.rb +49 -13
- data/lib/bundler/source/path/installer.rb +1 -1
- data/lib/bundler/source/rubygems/remote.rb +10 -5
- data/lib/bundler/source/rubygems.rb +48 -12
- data/lib/bundler/source/rubygems_aggregate.rb +1 -1
- data/lib/bundler/source_map.rb +7 -3
- data/lib/bundler/spec_set.rb +23 -7
- data/lib/bundler/stub_specification.rb +2 -1
- data/lib/bundler/templates/newgem/Gemfile.tt +4 -4
- data/lib/bundler/templates/newgem/newgem.gemspec.tt +3 -3
- data/lib/bundler/templates/newgem/spec/newgem_spec.rb.tt +1 -1
- data/lib/bundler/templates/newgem/test/minitest/test_newgem.rb.tt +1 -1
- data/lib/bundler/ui/shell.rb +0 -4
- data/lib/bundler/ui/silent.rb +0 -4
- data/lib/bundler/vendor/thor/lib/thor/actions/inject_into_file.rb +35 -2
- data/lib/bundler/vendor/thor/lib/thor/base.rb +2 -1
- data/lib/bundler/vendor/thor/lib/thor/shell/basic.rb +14 -6
- data/lib/bundler/vendor/thor/lib/thor/shell/color.rb +2 -0
- data/lib/bundler/vendor/thor/lib/thor/version.rb +1 -1
- data/lib/bundler/vendor/thor/lib/thor.rb +34 -1
- data/lib/bundler/vendored_pub_grub.rb +5 -2
- data/lib/bundler/vendored_securerandom.rb +11 -8
- data/lib/bundler/vendored_uri.rb +14 -14
- data/lib/bundler/version.rb +1 -1
- data/lib/bundler/worker.rb +3 -2
- data/lib/bundler.rb +1 -1
- data/lib/rubygems/compact_index_client/cache.rb +122 -0
- data/lib/rubygems/compact_index_client/cache_file.rb +147 -0
- data/lib/rubygems/compact_index_client/http_fetcher.rb +90 -0
- data/lib/rubygems/compact_index_client/parser.rb +90 -0
- data/lib/rubygems/compact_index_client/updater.rb +113 -0
- data/lib/rubygems/compact_index_client.rb +111 -0
- data/lib/rubygems/cooldown_settings.rb +57 -0
- data/lib/rubygems/credential_store/native/linux.rb +97 -0
- data/lib/rubygems/credential_store/native/macos.rb +88 -0
- data/lib/rubygems/credential_store/native/windows.rb +130 -0
- data/lib/rubygems/credential_store.rb +332 -0
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/assignment.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +7 -7
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/failure_writer.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/incompatibility.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/package.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/partial_solution.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/rubygems.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/solve_failure.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/static_package_source.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/strategy.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/term.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_constraint.rb +3 -3
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_range.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_solver.rb +2 -2
- data/lib/{bundler → rubygems}/vendor/pub_grub/lib/pub_grub/version_union.rb +1 -1
- data/lib/rubygems/vendor/pub_grub/lib/pub_grub.rb +57 -0
- data/lib/{bundler → rubygems}/vendor/securerandom/lib/securerandom.rb +10 -6
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/common.rb +119 -119
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/file.rb +13 -13
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ftp.rb +24 -24
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/generic.rb +132 -132
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/http.rb +18 -18
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/https.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldap.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ldaps.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/mailto.rb +21 -21
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc2396_parser.rb +39 -39
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/rfc3986_parser.rb +14 -14
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/version.rb +1 -1
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/ws.rb +12 -12
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri/wss.rb +4 -4
- data/lib/{bundler → rubygems}/vendor/uri/lib/uri.rb +35 -31
- data/lib/rubygems/yaml_serializer.rb +880 -0
- metadata +391 -381
- data/CHANGELOG.md +0 -5801
- data/LICENSE.md +0 -22
- data/README.md +0 -58
- data/lib/bundler/compact_index_client/cache.rb +0 -107
- data/lib/bundler/compact_index_client/cache_file.rb +0 -148
- data/lib/bundler/compact_index_client/parser.rb +0 -87
- data/lib/bundler/compact_index_client/updater.rb +0 -105
- data/lib/bundler/compact_index_client.rb +0 -92
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +0 -227
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +0 -3
- data/lib/bundler/vendor/connection_pool/lib/connection_pool/wrapper.rb +0 -56
- data/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +0 -230
- data/lib/bundler/vendor/net-http-persistent/README.rdoc +0 -82
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/connection.rb +0 -41
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/pool.rb +0 -65
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent/timed_stack_multi.rb +0 -80
- data/lib/bundler/vendor/net-http-persistent/lib/net/http/persistent.rb +0 -1153
- data/lib/bundler/vendor/pub_grub/lib/pub_grub.rb +0 -31
- data/lib/bundler/vendored_persistent.rb +0 -11
- data/lib/bundler/yaml_serializer.rb +0 -98
- /data/lib/{bundler → rubygems}/vendor/pub_grub/LICENSE.txt +0 -0
- /data/lib/{bundler → rubygems}/vendor/securerandom/COPYING +0 -0
- /data/lib/{bundler → rubygems}/vendor/uri/COPYING +0 -0
|
@@ -625,7 +625,7 @@ class Bundler::Thor
|
|
|
625
625
|
# alias name.
|
|
626
626
|
def find_command_possibilities(meth)
|
|
627
627
|
len = meth.to_s.length
|
|
628
|
-
possibilities = all_commands.reject {
|
|
628
|
+
possibilities = all_commands.reject {|k, v| v.is_a?(HiddenCommand) }.merge(map).keys.select { |n| meth == n[0, len] }.sort
|
|
629
629
|
unique_possibilities = possibilities.map { |k| map[k] || k }.uniq
|
|
630
630
|
|
|
631
631
|
if possibilities.include?(meth)
|
|
@@ -671,4 +671,37 @@ class Bundler::Thor
|
|
|
671
671
|
self.class.help(shell, subcommand)
|
|
672
672
|
end
|
|
673
673
|
end
|
|
674
|
+
|
|
675
|
+
map TREE_MAPPINGS => :tree
|
|
676
|
+
|
|
677
|
+
desc "tree", "Print a tree of all available commands"
|
|
678
|
+
def tree
|
|
679
|
+
build_command_tree(self.class, "")
|
|
680
|
+
end
|
|
681
|
+
|
|
682
|
+
private
|
|
683
|
+
|
|
684
|
+
def build_command_tree(klass, indent)
|
|
685
|
+
# Print current class name if it's not the root Bundler::Thor class
|
|
686
|
+
unless klass == Bundler::Thor
|
|
687
|
+
say "#{indent}#{klass.namespace || 'default'}", :blue
|
|
688
|
+
indent = "#{indent} "
|
|
689
|
+
end
|
|
690
|
+
|
|
691
|
+
# Print all commands for this class
|
|
692
|
+
visible_commands = klass.commands.reject { |_, cmd| cmd.hidden? || cmd.name == "help" }
|
|
693
|
+
commands_count = visible_commands.count
|
|
694
|
+
visible_commands.sort.each_with_index do |(command_name, command), i|
|
|
695
|
+
description = command.description.split("\n").first || ""
|
|
696
|
+
icon = i == (commands_count - 1) ? "└─" : "├─"
|
|
697
|
+
say "#{indent}#{icon} ", nil, false
|
|
698
|
+
say command_name, :green, false
|
|
699
|
+
say " (#{description})" unless description.empty?
|
|
700
|
+
end
|
|
701
|
+
|
|
702
|
+
# Print all subcommands (from registered Bundler::Thor subclasses)
|
|
703
|
+
klass.subcommand_classes.each do |_, subclass|
|
|
704
|
+
build_command_tree(subclass, indent)
|
|
705
|
+
end
|
|
706
|
+
end
|
|
674
707
|
end
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
# Reuse RubyGems' vendored PubGrub (Gem::PubGrub). The Bundler gem ships a copy
|
|
4
|
+
# under lib/rubygems/vendor, so this resolves even on RubyGems versions that
|
|
5
|
+
# predate it.
|
|
6
|
+
|
|
7
|
+
require "rubygems/vendor/pub_grub/lib/pub_grub"
|
|
@@ -1,12 +1,15 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
3
|
+
# Reuse RubyGems' vendored SecureRandom (Gem::SecureRandom). The Bundler gem
|
|
4
|
+
# ships a copy under lib/rubygems/vendor, so this resolves even on RubyGems
|
|
5
|
+
# versions that predate it. Fall back to the stdlib only when no vendored copy
|
|
6
|
+
# is available at all.
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
unless defined?(Gem::SecureRandom)
|
|
9
|
+
begin
|
|
10
|
+
require "rubygems/vendor/securerandom/lib/securerandom"
|
|
11
|
+
rescue LoadError
|
|
12
|
+
require "securerandom"
|
|
13
|
+
Gem::SecureRandom = SecureRandom
|
|
14
|
+
end
|
|
12
15
|
end
|
data/lib/bundler/vendored_uri.rb
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Reuse RubyGems' vendored URI (Gem::URI). The Bundler gem ships a copy under
|
|
4
|
+
# lib/rubygems/vendor, so this resolves even on RubyGems versions that predate
|
|
5
|
+
# it. Fall back to the stdlib only when no vendored copy is available at all.
|
|
4
6
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
unless defined?(Gem::URI)
|
|
8
|
+
begin
|
|
9
|
+
require "rubygems/vendor/uri/lib/uri"
|
|
10
|
+
rescue LoadError
|
|
11
|
+
require "uri"
|
|
12
|
+
Gem::URI = URI
|
|
8
13
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
module Gem
|
|
16
|
-
def URI(uri) # rubocop:disable Naming/MethodName
|
|
17
|
-
Bundler::URI(uri)
|
|
14
|
+
module Gem
|
|
15
|
+
def URI(uri) # rubocop:disable Naming/MethodName
|
|
16
|
+
Kernel.URI(uri)
|
|
17
|
+
end
|
|
18
|
+
module_function :URI
|
|
18
19
|
end
|
|
19
|
-
module_function :URI
|
|
20
20
|
end
|
|
21
21
|
end
|
data/lib/bundler/version.rb
CHANGED
data/lib/bundler/worker.rb
CHANGED
|
@@ -19,11 +19,12 @@ module Bundler
|
|
|
19
19
|
# @param size [Integer] Size of pool
|
|
20
20
|
# @param name [String] name the name of the worker
|
|
21
21
|
# @param func [Proc] job to run in inside the worker pool
|
|
22
|
-
|
|
22
|
+
# @param response_queue [Thread::Queue] queue that receives completed jobs
|
|
23
|
+
def initialize(size, name, func, response_queue: Thread::Queue.new)
|
|
23
24
|
@name = name
|
|
24
25
|
@request_queue = Thread::Queue.new
|
|
25
26
|
@request_queue_with_priority = Thread::Queue.new
|
|
26
|
-
@response_queue =
|
|
27
|
+
@response_queue = response_queue
|
|
27
28
|
@func = func
|
|
28
29
|
@size = size
|
|
29
30
|
@threads = nil
|
data/lib/bundler.rb
CHANGED
|
@@ -41,7 +41,6 @@ module Bundler
|
|
|
41
41
|
autoload :Checksum, File.expand_path("bundler/checksum", __dir__)
|
|
42
42
|
autoload :CLI, File.expand_path("bundler/cli", __dir__)
|
|
43
43
|
autoload :CIDetector, File.expand_path("bundler/ci_detector", __dir__)
|
|
44
|
-
autoload :CompactIndexClient, File.expand_path("bundler/compact_index_client", __dir__)
|
|
45
44
|
autoload :Definition, File.expand_path("bundler/definition", __dir__)
|
|
46
45
|
autoload :Dependency, File.expand_path("bundler/dependency", __dir__)
|
|
47
46
|
autoload :Deprecate, File.expand_path("bundler/deprecate", __dir__)
|
|
@@ -62,6 +61,7 @@ module Bundler
|
|
|
62
61
|
autoload :MatchRemoteMetadata, File.expand_path("bundler/match_remote_metadata", __dir__)
|
|
63
62
|
autoload :Materialization, File.expand_path("bundler/materialization", __dir__)
|
|
64
63
|
autoload :NULL, File.expand_path("bundler/constants", __dir__)
|
|
64
|
+
autoload :Override, File.expand_path("bundler/override", __dir__)
|
|
65
65
|
autoload :ProcessLock, File.expand_path("bundler/process_lock", __dir__)
|
|
66
66
|
autoload :RemoteSpecification, File.expand_path("bundler/remote_specification", __dir__)
|
|
67
67
|
autoload :Resolver, File.expand_path("bundler/resolver", __dir__)
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "pathname" unless defined?(Pathname)
|
|
6
|
+
require "set"
|
|
7
|
+
|
|
8
|
+
class Gem::CompactIndexClient
|
|
9
|
+
# Calls the Updater to update the cached files on disk, reads the
|
|
10
|
+
# cached files and returns their contents.
|
|
11
|
+
class Cache
|
|
12
|
+
attr_reader :directory
|
|
13
|
+
|
|
14
|
+
def initialize(directory, fetcher = nil)
|
|
15
|
+
@directory = Pathname.new(directory).expand_path
|
|
16
|
+
@updater = Updater.new(fetcher) if fetcher
|
|
17
|
+
@mutex = Thread::Mutex.new
|
|
18
|
+
@endpoints = Set.new
|
|
19
|
+
|
|
20
|
+
@info_root = mkdir("info")
|
|
21
|
+
@special_characters_info_root = mkdir("info-special-characters")
|
|
22
|
+
@info_etag_root = mkdir("info-etags")
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def names
|
|
26
|
+
fetch("names", names_path, names_etag_path)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def versions
|
|
30
|
+
fetch("versions", versions_path, versions_etag_path)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def info(name, remote_checksum = nil)
|
|
34
|
+
path = info_path(name)
|
|
35
|
+
|
|
36
|
+
if remote_checksum && remote_checksum != checksum_for_file(path)
|
|
37
|
+
fetch("info/#{name}", path, info_etag_path(name))
|
|
38
|
+
else
|
|
39
|
+
Gem::CompactIndexClient.debug { "update skipped info/#{name} (#{remote_checksum ? "versions index checksum matches local" : "versions index checksum is nil"})" }
|
|
40
|
+
read(path)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Fetch a single gem's info file without consulting the versions
|
|
45
|
+
# index, refreshing the cached file with a conditional request.
|
|
46
|
+
def fetch_info(name)
|
|
47
|
+
fetch("info/#{name}", info_path(name), info_etag_path(name))
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def reset!
|
|
51
|
+
@mutex.synchronize { @endpoints.clear }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
def names_path = directory.join("names")
|
|
57
|
+
def names_etag_path = directory.join("names.etag")
|
|
58
|
+
def versions_path = directory.join("versions")
|
|
59
|
+
def versions_etag_path = directory.join("versions.etag")
|
|
60
|
+
|
|
61
|
+
def info_path(name)
|
|
62
|
+
name = name.to_s
|
|
63
|
+
validate_name!(name)
|
|
64
|
+
if /[^a-z0-9_-]/.match?(name)
|
|
65
|
+
name += "-#{Digest::MD5.hexdigest(name).downcase}"
|
|
66
|
+
@special_characters_info_root.join(name)
|
|
67
|
+
else
|
|
68
|
+
@info_root.join(name)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def info_etag_path(name)
|
|
73
|
+
name = name.to_s
|
|
74
|
+
validate_name!(name)
|
|
75
|
+
@info_etag_root.join("#{name}-#{Digest::MD5.hexdigest(name).downcase}")
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# Gem names come from the remote index and are not otherwise
|
|
79
|
+
# validated, so refuse anything that would escape the cache
|
|
80
|
+
# directory when used as a path component.
|
|
81
|
+
def validate_name!(name)
|
|
82
|
+
return unless name.empty? || name.include?("\0") || name == "." || name == ".." || File.basename(name) != name
|
|
83
|
+
|
|
84
|
+
raise Gem::Exception, "malformed gem name: #{name.inspect}"
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def checksum_for_file(path)
|
|
88
|
+
return unless path.file?
|
|
89
|
+
Gem::CompactIndexClient.filesystem_access(path, :read) do
|
|
90
|
+
Digest::MD5.file(path).hexdigest
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def mkdir(name)
|
|
95
|
+
directory.join(name).tap do |dir|
|
|
96
|
+
Gem::CompactIndexClient.filesystem_access(dir) do
|
|
97
|
+
FileUtils.mkdir_p(dir)
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def fetch(remote_path, path, etag_path)
|
|
103
|
+
if already_fetched?(remote_path)
|
|
104
|
+
Gem::CompactIndexClient.debug { "already fetched #{remote_path}" }
|
|
105
|
+
else
|
|
106
|
+
Gem::CompactIndexClient.debug { "fetching #{remote_path}" }
|
|
107
|
+
@updater&.update(remote_path, path, etag_path)
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
read(path)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def already_fetched?(remote_path)
|
|
114
|
+
@mutex.synchronize { !@endpoints.add?(remote_path) }
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def read(path)
|
|
118
|
+
return unless path.file?
|
|
119
|
+
Gem::CompactIndexClient.filesystem_access(path, :read, &:read)
|
|
120
|
+
end
|
|
121
|
+
end
|
|
122
|
+
end
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "digest"
|
|
4
|
+
require "fileutils"
|
|
5
|
+
require "rubygems/package"
|
|
6
|
+
|
|
7
|
+
class Gem::CompactIndexClient
|
|
8
|
+
# write cache files in a way that is robust to concurrent modifications
|
|
9
|
+
# if digests are given, the checksums will be verified
|
|
10
|
+
class CacheFile
|
|
11
|
+
DEFAULT_FILE_MODE = 0o644
|
|
12
|
+
private_constant :DEFAULT_FILE_MODE
|
|
13
|
+
|
|
14
|
+
class Error < RuntimeError; end
|
|
15
|
+
class ClosedError < Error; end
|
|
16
|
+
|
|
17
|
+
class DigestMismatchError < Error
|
|
18
|
+
def initialize(digests, expected_digests)
|
|
19
|
+
super "Calculated checksums #{digests.inspect} did not match expected #{expected_digests.inspect}."
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Initialize with a copy of the original file, then yield the instance.
|
|
24
|
+
def self.copy(path, &block)
|
|
25
|
+
new(path) do |file|
|
|
26
|
+
file.initialize_digests
|
|
27
|
+
|
|
28
|
+
Gem::CompactIndexClient.filesystem_access(path, :read) do
|
|
29
|
+
path.open("rb") do |s|
|
|
30
|
+
file.open {|f| IO.copy_stream(s, f) }
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
yield file
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Write data to a temp file, then replace the original file with it verifying the digests if given.
|
|
39
|
+
def self.write(path, data, digests = nil)
|
|
40
|
+
return unless data
|
|
41
|
+
new(path) do |file|
|
|
42
|
+
file.digests = digests
|
|
43
|
+
file.write(data)
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :original_path, :path
|
|
48
|
+
|
|
49
|
+
def initialize(original_path, &block)
|
|
50
|
+
@original_path = original_path
|
|
51
|
+
@perm = original_path.file? ? original_path.stat.mode : DEFAULT_FILE_MODE
|
|
52
|
+
@path = original_path.sub(/$/, ".#{$$}.tmp")
|
|
53
|
+
return unless block_given?
|
|
54
|
+
begin
|
|
55
|
+
yield self
|
|
56
|
+
ensure
|
|
57
|
+
close
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def size
|
|
62
|
+
path.size
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# initialize the digests using CompactIndexClient::SUPPORTED_DIGESTS, or a subset based on keys.
|
|
66
|
+
def initialize_digests(keys = nil)
|
|
67
|
+
@digests = keys ? SUPPORTED_DIGESTS.slice(*keys) : SUPPORTED_DIGESTS.dup
|
|
68
|
+
@digests.transform_values! {|algo_class| Digest(algo_class).new }
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
# reset the digests so they don't contain any previously read data
|
|
72
|
+
def reset_digests
|
|
73
|
+
@digests&.each_value(&:reset)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# set the digests that will be verified at the end
|
|
77
|
+
def digests=(expected_digests)
|
|
78
|
+
@expected_digests = expected_digests
|
|
79
|
+
|
|
80
|
+
if @expected_digests.nil?
|
|
81
|
+
@digests = nil
|
|
82
|
+
elsif @digests
|
|
83
|
+
@digests = @digests.slice(*@expected_digests.keys)
|
|
84
|
+
else
|
|
85
|
+
initialize_digests(@expected_digests.keys)
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
def digests?
|
|
90
|
+
@digests&.any?
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# Open the temp file for writing, reusing original permissions, yielding the IO object.
|
|
94
|
+
def open(write_mode = "wb", perm = @perm, &block)
|
|
95
|
+
raise ClosedError, "Cannot reopen closed file" if @closed
|
|
96
|
+
Gem::CompactIndexClient.filesystem_access(path, :write) do
|
|
97
|
+
path.open(write_mode, perm) do |f|
|
|
98
|
+
yield digests? ? Gem::Package::DigestIO.new(f, @digests) : f
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns false without appending when no digests since appending is too error prone to do without digests.
|
|
104
|
+
def append(data)
|
|
105
|
+
return false unless digests?
|
|
106
|
+
open("ab") {|f| f.write data }
|
|
107
|
+
verify && commit
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def write(data)
|
|
111
|
+
reset_digests
|
|
112
|
+
open {|f| f.write data }
|
|
113
|
+
commit!
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
def commit!
|
|
117
|
+
verify || raise(DigestMismatchError.new(@base64digests, @expected_digests))
|
|
118
|
+
commit
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Verify the digests, returning true on match, false on mismatch.
|
|
122
|
+
def verify
|
|
123
|
+
return true unless @expected_digests && digests?
|
|
124
|
+
@base64digests = @digests.transform_values!(&:base64digest)
|
|
125
|
+
@digests = nil
|
|
126
|
+
@base64digests.all? {|algo, digest| @expected_digests[algo] == digest }
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
# Replace the original file with the temp file without verifying digests.
|
|
130
|
+
# The file is permanently closed.
|
|
131
|
+
def commit
|
|
132
|
+
raise ClosedError, "Cannot commit closed file" if @closed
|
|
133
|
+
Gem::CompactIndexClient.filesystem_access(original_path, :write) do
|
|
134
|
+
FileUtils.mv(path, original_path)
|
|
135
|
+
end
|
|
136
|
+
@closed = true
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Remove the temp file without replacing the original file.
|
|
140
|
+
# The file is permanently closed.
|
|
141
|
+
def close
|
|
142
|
+
return if @closed
|
|
143
|
+
FileUtils.remove_file(path) if @path&.file?
|
|
144
|
+
@closed = true
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems/remote_fetcher"
|
|
4
|
+
|
|
5
|
+
class Gem::CompactIndexClient
|
|
6
|
+
# Fetches compact index files relative to +base_uri+ using
|
|
7
|
+
# Gem::RemoteFetcher's connection infrastructure (proxy, TLS,
|
|
8
|
+
# connection pooling). Implements the fetcher interface expected by
|
|
9
|
+
# Gem::CompactIndexClient: #call(path, headers) returning a
|
|
10
|
+
# Gem::Net::HTTP response.
|
|
11
|
+
class HTTPFetcher
|
|
12
|
+
REDIRECT_LIMIT = 10
|
|
13
|
+
private_constant :REDIRECT_LIMIT
|
|
14
|
+
|
|
15
|
+
def initialize(base_uri, remote_fetcher = Gem::RemoteFetcher.fetcher)
|
|
16
|
+
base_uri = base_uri.to_s
|
|
17
|
+
base_uri += "/" unless base_uri.end_with?("/")
|
|
18
|
+
@base_uri = Gem::URI(base_uri)
|
|
19
|
+
@remote_fetcher = remote_fetcher
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def call(path, headers = {})
|
|
23
|
+
fetch(@base_uri + path, headers, REDIRECT_LIMIT)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
private
|
|
27
|
+
|
|
28
|
+
def fetch(uri, headers, redirects_remaining)
|
|
29
|
+
response = request(uri, headers)
|
|
30
|
+
|
|
31
|
+
case response
|
|
32
|
+
when Gem::Net::HTTPNotModified
|
|
33
|
+
response
|
|
34
|
+
when Gem::Net::HTTPSuccess
|
|
35
|
+
# The callers write the body into the cache, so a body-less success
|
|
36
|
+
# such as 204 would truncate the cached file.
|
|
37
|
+
raise bad_response(response, uri) unless response.class.body_permitted?
|
|
38
|
+
|
|
39
|
+
response
|
|
40
|
+
when Gem::Net::HTTPMovedPermanently, Gem::Net::HTTPFound, Gem::Net::HTTPSeeOther,
|
|
41
|
+
Gem::Net::HTTPTemporaryRedirect, Gem::Net::HTTPPermanentRedirect
|
|
42
|
+
raise Gem::RemoteFetcher::FetchError.new("too many redirects", uri) if redirects_remaining.zero?
|
|
43
|
+
|
|
44
|
+
location = response["Location"]
|
|
45
|
+
raise Gem::RemoteFetcher::FetchError.new("redirecting but no redirect location was given", uri) unless location
|
|
46
|
+
|
|
47
|
+
redirect = uri + location
|
|
48
|
+
if https?(uri) && !https?(redirect)
|
|
49
|
+
raise Gem::RemoteFetcher::FetchError.new("redirecting to non-https resource: #{Gem::Uri.redact(redirect)}", uri)
|
|
50
|
+
end
|
|
51
|
+
# An absolute Location on the same origin drops the credentials that a
|
|
52
|
+
# relative one would have kept. Another port or scheme on the same host
|
|
53
|
+
# is a different protection space (RFC 9110, Section 11.5).
|
|
54
|
+
same_origin = [redirect.scheme, redirect.host, redirect.port] == [uri.scheme, uri.host, uri.port]
|
|
55
|
+
redirect.userinfo = uri.userinfo if same_origin && !redirect.userinfo
|
|
56
|
+
|
|
57
|
+
fetch(redirect, headers, redirects_remaining - 1)
|
|
58
|
+
when Gem::Net::HTTPRangeNotSatisfiable
|
|
59
|
+
raise bad_response(response, uri) unless headers.key?("Range")
|
|
60
|
+
|
|
61
|
+
# The local cache is longer than the remote file, refetch it whole. A
|
|
62
|
+
# matching ETag would otherwise turn the retry into a 304 and keep the
|
|
63
|
+
# oversized cache.
|
|
64
|
+
fetch(uri, headers.except("Range", "If-None-Match"), redirects_remaining)
|
|
65
|
+
else
|
|
66
|
+
raise bad_response(response, uri)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# The callers fall back to the Marshal index when a fetch fails, and they
|
|
71
|
+
# only recognize a failure that arrives as a FetchError.
|
|
72
|
+
def request(uri, headers)
|
|
73
|
+
@remote_fetcher.request(uri, Gem::Net::HTTP::Get) do |req|
|
|
74
|
+
headers.each {|name, value| req[name] = value }
|
|
75
|
+
end
|
|
76
|
+
rescue Gem::Timeout::Error, IOError, SocketError, SystemCallError,
|
|
77
|
+
*(OpenSSL::SSL::SSLError if Gem::HAVE_OPENSSL) => e
|
|
78
|
+
raise Gem::RemoteFetcher::FetchError.new("#{e.class}: #{e}", uri)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def bad_response(response, uri)
|
|
82
|
+
detail = response["X-Error-Message"] || response.message
|
|
83
|
+
Gem::RemoteFetcher::FetchError.new("bad response #{detail} #{response.code}", uri)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def https?(uri)
|
|
87
|
+
uri.scheme == "https"
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "rubygems/resolver/api_set/gem_parser"
|
|
4
|
+
|
|
5
|
+
class Gem::CompactIndexClient
|
|
6
|
+
class Parser
|
|
7
|
+
# `compact_index` - an object responding to #names, #versions, #info(name, checksum),
|
|
8
|
+
# returning the file contents as a string
|
|
9
|
+
def initialize(compact_index)
|
|
10
|
+
@compact_index = compact_index
|
|
11
|
+
@info_checksums = nil
|
|
12
|
+
@versions_by_name = nil
|
|
13
|
+
@available = nil
|
|
14
|
+
@gem_parser = nil
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def names
|
|
18
|
+
lines(@compact_index.names)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def versions
|
|
22
|
+
@versions_by_name ||= Hash.new {|hash, key| hash[key] = [] }
|
|
23
|
+
@info_checksums = {}
|
|
24
|
+
|
|
25
|
+
lines(@compact_index.versions).each do |line|
|
|
26
|
+
name, versions_string, checksum = line.split(" ", 3)
|
|
27
|
+
@info_checksums[name] = checksum || ""
|
|
28
|
+
versions_string.split(",") do |version|
|
|
29
|
+
delete = version.delete_prefix!("-")
|
|
30
|
+
version = version.split("-", 2).unshift(name)
|
|
31
|
+
if delete
|
|
32
|
+
@versions_by_name[name].delete(version)
|
|
33
|
+
else
|
|
34
|
+
@versions_by_name[name] << version
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
@versions_by_name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def info(name)
|
|
43
|
+
parse_info(@compact_index.info(name, info_checksums[name]), name)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parse_info(data, name)
|
|
47
|
+
lines(data).map {|line| gem_parser.parse(line).unshift(name) }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def available?
|
|
51
|
+
return @available unless @available.nil?
|
|
52
|
+
@available = !info_checksums.empty?
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
private
|
|
56
|
+
|
|
57
|
+
def info_checksums
|
|
58
|
+
@info_checksums ||= lines(@compact_index.versions).each_with_object({}) do |line, checksums|
|
|
59
|
+
parse_version_checksum(line, checksums)
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def lines(data)
|
|
64
|
+
return [] if data.nil? || data.empty?
|
|
65
|
+
lines = data.split("\n")
|
|
66
|
+
header = lines.index("---")
|
|
67
|
+
header ? lines[header + 1..-1] : lines
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def gem_parser
|
|
71
|
+
@gem_parser ||= Gem::Resolver::APISet::GemParser.new
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# This is mostly the same as `split(" ", 3)` but it avoids allocating extra objects.
|
|
75
|
+
# This method gets called at least once for every gem when parsing versions.
|
|
76
|
+
def parse_version_checksum(line, checksums)
|
|
77
|
+
return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactory index files
|
|
78
|
+
checksum_start = line.index(" ", name_end + 1)
|
|
79
|
+
return unless checksum_start
|
|
80
|
+
checksum_start += 1
|
|
81
|
+
|
|
82
|
+
checksum_end = line.size - checksum_start
|
|
83
|
+
|
|
84
|
+
line.freeze # allows slicing into the string to not allocate a copy of the line
|
|
85
|
+
name = line[0, name_end]
|
|
86
|
+
checksum = line[checksum_start, checksum_end]
|
|
87
|
+
checksums[name.freeze] = checksum # freeze name since it is used as a hash key
|
|
88
|
+
end
|
|
89
|
+
end
|
|
90
|
+
end
|