Pick from 823c776d95
This commit is contained in:
Hiroshi SHIBATA 2022-12-09 14:45:51 +09:00
Родитель d928ebacb2
Коммит a4e14b9d9d
22 изменённых файлов: 422 добавлений и 160 удалений

Просмотреть файл

@ -524,13 +524,22 @@ module Bundler
raise GemNotFound, "Could not find #{missing_specs_list.join(" nor ")}"
end
incomplete_specs = specs.incomplete_specs
loop do
incomplete_specs = specs.incomplete_specs
break if incomplete_specs.empty?
Bundler.ui.debug("The lockfile does not have all gems needed for the current platform though, Bundler will still re-resolve dependencies")
@resolve = start_resolution(:exclude_specs => incomplete_specs)
specs = resolve.materialize(dependencies)
still_incomplete_specs = specs.incomplete_specs
if still_incomplete_specs == incomplete_specs
package = resolution_packages[incomplete_specs.first.name]
resolver.raise_not_found! package
end
incomplete_specs = still_incomplete_specs
end
bundler = sources.metadata_source.specs.search(["bundler", Bundler.gem_version]).last

Просмотреть файл

@ -75,7 +75,7 @@ module Bundler
end
def self.git_version
Bundler::Source::Git::GitProxy.new(nil, nil, nil).full_version
Bundler::Source::Git::GitProxy.new(nil, nil).full_version
rescue Bundler::Source::Git::GitNotInstalledError
"not installed"
end

Просмотреть файл

@ -29,9 +29,7 @@ module Bundler
" is a chance you are experiencing a man-in-the-middle attack, but" \
" most likely your system doesn't have the CA certificates needed" \
" for verification. For information about OpenSSL certificates, see" \
" https://railsapps.github.io/openssl-certificate-verify-failed.html." \
" To connect without using SSL, edit your Gemfile" \
" sources and change 'https' to 'http'."
" https://railsapps.github.io/openssl-certificate-verify-failed.html."
end
end
@ -39,8 +37,7 @@ module Bundler
class SSLError < HTTPError
def initialize(msg = nil)
super msg || "Could not load OpenSSL.\n" \
"You must recompile Ruby with OpenSSL support or change the sources in your " \
"Gemfile from 'https' to 'http'."
"You must recompile Ruby with OpenSSL support."
end
end

Просмотреть файл

@ -65,7 +65,7 @@ It will display the ruby directive information, so you don\'t have to parse it f
.SH "SEE ALSO"
.
.IP "\(bu" 4
bundle\-lock(1) \fIbundle\-lock\.1\.ronn\fR
bundle\-lock(1) \fIbundle\-lock\.1\.html\fR
.
.IP "" 0

Просмотреть файл

@ -46,4 +46,4 @@ match the running Ruby VM, it tells you what part does not.
## SEE ALSO
* [bundle-lock(1)](bundle-lock.1.ronn)
* [bundle-lock(1)](bundle-lock.1.html)

Просмотреть файл

@ -11,6 +11,7 @@ module Bundler
require_relative "resolver/base"
require_relative "resolver/package"
require_relative "resolver/candidate"
require_relative "resolver/incompatibility"
require_relative "resolver/root"
include GemHelpers
@ -29,6 +30,10 @@ module Bundler
root = Resolver::Root.new(name_for_explicit_dependency_source)
root_version = Resolver::Candidate.new(0)
@all_specs = Hash.new do |specs, name|
specs[name] = source_for(name).specs.search(name).sort_by {|s| [s.version, s.platform.to_s] }
end
@sorted_versions = Hash.new do |candidates, package|
candidates[package] = if package.root?
[root_version]
@ -60,7 +65,7 @@ module Bundler
incompatibility = e.incompatibility
names_to_unlock = []
conflict_on_bundler = nil
extended_explanation = nil
while incompatibility.conflict?
cause = incompatibility.cause
@ -69,12 +74,11 @@ module Bundler
incompatibility.terms.each do |term|
name = term.package.name
names_to_unlock << name if base_requirements[name]
next unless name == "bundler"
no_versions_incompat = [cause.incompatibility, cause.satisfier].find {|incompat| incompat.cause.is_a?(PubGrub::Incompatibility::NoVersions) }
next unless no_versions_incompat
conflict_on_bundler ||= Gem::Requirement.new(no_versions_incompat.cause.constraint.constraint.constraint_string.split(","))
extended_explanation = no_versions_incompat.extended_explanation
end
end
@ -85,9 +89,9 @@ module Bundler
explanation = e.message
if conflict_on_bundler
if extended_explanation
explanation << "\n\n"
explanation << bundler_not_found_message(conflict_on_bundler)
explanation << extended_explanation
end
raise SolveFailure.new(explanation)
@ -111,14 +115,25 @@ module Bundler
def no_versions_incompatibility_for(package, unsatisfied_term)
cause = PubGrub::Incompatibility::NoVersions.new(unsatisfied_term)
name = package.name
constraint = unsatisfied_term.constraint
requirement = Gem::Requirement.new(constraint.constraint_string.split(","))
custom_explanation = if package.name == "bundler"
"the current Bundler version (#{Bundler::VERSION}) does not satisfy #{cause.constraint}"
if name == "bundler"
custom_explanation = "the current Bundler version (#{Bundler::VERSION}) does not satisfy #{constraint}"
extended_explanation = bundler_not_found_message(requirement)
else
"#{cause.constraint} could not be found in #{repository_for(package)}"
specs_matching_other_platforms = filter_matching_specs(@all_specs[name], requirement)
platforms_explanation = specs_matching_other_platforms.any? ? " for any resolution platforms (#{package.platforms.join(", ")})" : ""
custom_explanation = "#{constraint} could not be found in #{repository_for(package)}#{platforms_explanation}"
dependency = Dependency.new(name, requirement)
label = SharedHelpers.pretty_dependency(dependency)
extended_explanation = other_specs_matching_message(specs_matching_other_platforms, label) if specs_matching_other_platforms.any?
end
PubGrub::Incompatibility.new([unsatisfied_term], :cause => cause, :custom_explanation => custom_explanation)
Incompatibility.new([unsatisfied_term], :cause => cause, :custom_explanation => custom_explanation, :extended_explanation => extended_explanation)
end
def debug?
@ -187,9 +202,9 @@ module Bundler
def all_versions_for(package)
name = package.name
results = @base[name] + results_for(name)
results = @base[name] + @all_specs[name]
locked_requirement = base_requirements[name]
results = results.select {|spec| requirement_satisfied_by?(locked_requirement, spec) } if locked_requirement
results = filter_matching_specs(results, locked_requirement) if locked_requirement
versions = results.group_by(&:version).reduce([]) do |groups, (version, specs)|
platform_specs = package.platforms.flat_map {|platform| select_best_platform_match(specs, platform) }
@ -208,30 +223,56 @@ module Bundler
sort_versions(package, versions)
end
def index_for(name)
source_for(name).specs
end
def source_for(name)
@source_requirements[name] || @source_requirements[:default]
end
def results_for(name)
index_for(name).search(name)
end
def name_for_explicit_dependency_source
Bundler.default_gemfile.basename.to_s
rescue StandardError
"Gemfile"
end
def requirement_satisfied_by?(requirement, spec)
requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
def raise_not_found!(package)
name = package.name
source = source_for(name)
specs = @all_specs[name]
matching_part = name
requirement_label = SharedHelpers.pretty_dependency(package.dependency)
cache_message = begin
" or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
rescue GemfileNotFound
nil
end
specs_matching_requirement = filter_matching_specs(specs, package.dependency.requirement)
if specs_matching_requirement.any?
specs = specs_matching_requirement
matching_part = requirement_label
platforms = package.platforms
platform_label = platforms.size == 1 ? "platform '#{platforms.first}" : "platforms '#{platforms.join("', '")}"
requirement_label = "#{requirement_label}' with #{platform_label}"
end
message = String.new("Could not find gem '#{requirement_label}' in #{source}#{cache_message}.\n")
if specs.any?
message << "\n#{other_specs_matching_message(specs, matching_part)}"
end
raise GemNotFound, message
end
private
def filter_matching_specs(specs, requirement)
specs.select {| spec| requirement_satisfied_by?(requirement, spec) }
end
def requirement_satisfied_by?(requirement, spec)
requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
end
def sort_versions(package, versions)
if versions.size > 1
@gem_version_promoter.sort_versions(package, versions).reverse
@ -260,38 +301,13 @@ module Bundler
next [dep_package, dep_constraint] unless versions_for(dep_package, dep_constraint.range).empty?
next unless dep_package.current_platform?
raise GemNotFound, gem_not_found_message(dep_package, dep_constraint)
raise_not_found!(dep_package)
end.compact.to_h
end
def gem_not_found_message(package, requirement)
name = package.name
source = source_for(name)
specs = source.specs.search(name).sort_by {|s| [s.version, s.platform.to_s] }
matching_part = name
requirement_label = SharedHelpers.pretty_dependency(package.dependency)
cache_message = begin
" or in gems cached in #{Bundler.settings.app_cache_path}" if Bundler.app_cache.exist?
rescue GemfileNotFound
nil
end
specs_matching_requirement = specs.select {| spec| requirement_satisfied_by?(package.dependency.requirement, spec) }
if specs_matching_requirement.any?
specs = specs_matching_requirement
matching_part = requirement_label
platforms = package.platforms
platform_label = platforms.size == 1 ? "platform '#{platforms.first}" : "platforms '#{platforms.join("', '")}"
requirement_label = "#{requirement_label}' with #{platform_label}"
end
message = String.new("Could not find gem '#{requirement_label}' in #{source}#{cache_message}.\n")
if specs.any?
message << "\nThe source contains the following gems matching '#{matching_part}':\n"
message << specs.map {|s| " * #{s.full_name}" }.join("\n")
end
def other_specs_matching_message(specs, requirement)
message = String.new("The source contains the following gems matching '#{requirement}':\n")
message << specs.map {|s| " * #{s.full_name}" }.join("\n")
message
end
@ -342,7 +358,7 @@ module Bundler
end
def bundler_not_found_message(conflict_dependency)
candidate_specs = source_for(:default_bundler).specs.search("bundler").select {|spec| requirement_satisfied_by?(conflict_dependency, spec) }
candidate_specs = filter_matching_specs(source_for(:default_bundler).specs.search("bundler"), conflict_dependency)
if candidate_specs.any?
target_version = candidate_specs.last.version
new_command = [File.basename($PROGRAM_NAME), "_#{target_version}_", *ARGV].join(" ")

Просмотреть файл

@ -0,0 +1,15 @@
# frozen_string_literal: true
module Bundler
class Resolver
class Incompatibility < PubGrub::Incompatibility
attr_reader :extended_explanation
def initialize(terms, cause:, custom_explanation: nil, extended_explanation: nil)
@extended_explanation = extended_explanation
super(terms, :cause => cause, :custom_explanation => custom_explanation)
end
end
end
end

Просмотреть файл

@ -13,14 +13,14 @@ module Bundler
# * The dependency explicit set in the Gemfile for this gem (if any).
#
class Package
attr_reader :name, :platforms, :dependency
attr_reader :name, :platforms, :dependency, :locked_version
def initialize(name, platforms, locked_specs, unlock, dependency: nil)
@name = name
@platforms = platforms
@locked_specs = locked_specs
@locked_version = locked_specs[name].first&.version
@unlock = unlock
@dependency = dependency
@dependency = dependency || Dependency.new(name, @locked_version)
end
def to_s
@ -43,24 +43,20 @@ module Bundler
@name.hash
end
def locked_version
@locked_specs[name].first&.version
end
def unlock?
@unlock.empty? || @unlock.include?(name)
end
def prerelease_specified?
@dependency&.prerelease?
@dependency.prerelease?
end
def force_ruby_platform?
@dependency&.force_ruby_platform
@dependency.force_ruby_platform
end
def current_platform?
@dependency&.current_platform?
@dependency.current_platform?
end
end
end

Просмотреть файл

@ -72,7 +72,7 @@ module Bundler
elsif ref
ref
else
git_proxy.branch
current_branch
end
rev = "at #{at}@#{shortref_for_display(revision)}"
@ -126,7 +126,7 @@ module Bundler
path = Pathname.new(path)
path = path.expand_path(Bundler.root) unless path.relative?
unless options["branch"] || Bundler.settings[:disable_local_branch_check]
unless branch || Bundler.settings[:disable_local_branch_check]
raise GitError, "Cannot use local override for #{name} at #{path} because " \
":branch is not specified in Gemfile. Specify a branch or run " \
"`bundle config unset local.#{override_for(original_path)}` to remove the local override"
@ -141,11 +141,11 @@ module Bundler
# Create a new git proxy without the cached revision
# so the Gemfile.lock always picks up the new revision.
@git_proxy = GitProxy.new(path, uri, ref)
@git_proxy = GitProxy.new(path, uri, options)
if git_proxy.branch != options["branch"] && !Bundler.settings[:disable_local_branch_check]
if current_branch != branch && !Bundler.settings[:disable_local_branch_check]
raise GitError, "Local override for #{name} at #{path} is using branch " \
"#{git_proxy.branch} but Gemfile specifies #{options["branch"]}"
"#{current_branch} but Gemfile specifies #{branch}"
end
changed = cached_revision && cached_revision != git_proxy.revision
@ -228,6 +228,10 @@ module Bundler
git_proxy.revision
end
def current_branch
git_proxy.current_branch
end
def allow_git_ops?
@allow_remote || @allow_cached
end
@ -313,7 +317,7 @@ module Bundler
end
def git_proxy
@git_proxy ||= GitProxy.new(cache_path, uri, ref, cached_revision, self)
@git_proxy ||= GitProxy.new(cache_path, uri, options, cached_revision, self)
end
def fetch

Просмотреть файл

@ -47,13 +47,15 @@ module Bundler
# All actions required by the Git source is encapsulated in this
# object.
class GitProxy
attr_accessor :path, :uri, :ref
attr_accessor :path, :uri, :branch, :tag, :ref
attr_writer :revision
def initialize(path, uri, ref, revision = nil, git = nil)
def initialize(path, uri, options = {}, revision = nil, git = nil)
@path = path
@uri = uri
@ref = ref
@branch = options["branch"]
@tag = options["tag"]
@ref = options["ref"]
@revision = revision
@git = git
end
@ -62,8 +64,8 @@ module Bundler
@revision ||= find_local_revision
end
def branch
@branch ||= allowed_with_path do
def current_branch
@current_branch ||= allowed_with_path do
git("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip
end
end
@ -76,36 +78,33 @@ module Bundler
end
def version
git("--version").match(/(git version\s*)?((\.?\d+)+).*/)[2]
@version ||= full_version.match(/((\.?\d+)+).*/)[1]
end
def full_version
git("--version").sub("git version", "").strip
@full_version ||= git("--version").sub(/git version\s*/, "").strip
end
def checkout
return if path.exist? && has_revision_cached?
extra_ref = "#{ref}:#{ref}" if ref && ref.start_with?("refs/")
return if has_revision_cached?
Bundler.ui.info "Fetching #{URICredentialsFilter.credential_filtered_uri(uri)}"
configured_uri = configured_uri_for(uri).to_s
Bundler.ui.info "Fetching #{credential_filtered_uri}"
unless path.exist?
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
git_retry "clone", "--bare", "--no-hardlinks", "--quiet", "--", configured_uri, path.to_s
git_retry "clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s
return unless extra_ref
end
with_path do
git_retry(*["fetch", "--force", "--quiet", "--tags", "--", configured_uri, "refs/heads/*:refs/heads/*", extra_ref].compact, :dir => path)
end
fetch_args = extra_fetch_args
fetch_args.unshift("--unshallow") if path.join("shallow").exist? && full_clone?
git_retry(*["fetch", "--force", "--quiet", "--no-tags", *fetch_args, "--", configured_uri, refspec].compact, :dir => path)
end
def copy_to(destination, submodules = false)
# method 1
unless File.exist?(destination.join(".git"))
begin
SharedHelpers.filesystem_access(destination.dirname) do |p|
@ -114,7 +113,7 @@ module Bundler
SharedHelpers.filesystem_access(destination) do |p|
FileUtils.rm_rf(p)
end
git_retry "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s
git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s
File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination)
rescue Errno::EEXIST => e
file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1]
@ -123,14 +122,10 @@ module Bundler
"this file and try again."
end
end
# method 2
git_retry "fetch", "--force", "--quiet", "--tags", path.to_s, :dir => destination
begin
git "reset", "--hard", @revision, :dir => destination
rescue GitCommandError => e
raise MissingGitRevisionError.new(e.command, destination, @revision, URICredentialsFilter.credential_filtered_uri(uri))
end
git(*["fetch", "--force", "--quiet", *extra_fetch_args, path.to_s, revision_refspec].compact, :dir => destination)
git "reset", "--hard", @revision, :dir => destination
if submodules
git_retry "submodule", "update", "--init", "--recursive", :dir => destination
@ -142,6 +137,69 @@ module Bundler
private
def extra_ref
return false if not_pinned?
return true unless full_clone?
ref.start_with?("refs/")
end
def depth
return @depth if defined?(@depth)
@depth = if legacy_locked_revision? || !supports_fetching_unreachable_refs?
nil
elsif not_pinned?
1
elsif ref.include?("~")
parsed_depth = ref.split("~").last
parsed_depth.to_i + 1
elsif abbreviated_ref?
nil
else
1
end
end
def refspec
if fully_qualified_ref
"#{fully_qualified_ref}:#{fully_qualified_ref}"
elsif ref.include?("~")
parsed_ref = ref.split("~").first
"#{parsed_ref}:#{parsed_ref}"
elsif ref.start_with?("refs/")
"#{ref}:#{ref}"
elsif abbreviated_ref?
nil
else
ref
end
end
def fully_qualified_ref
return @fully_qualified_ref if defined?(@fully_qualified_ref)
@fully_qualified_ref = if branch
"refs/heads/#{branch}"
elsif tag
"refs/tags/#{tag}"
elsif ref.nil?
"refs/heads/#{current_branch}"
end
end
def not_pinned?
branch || tag || ref.nil?
end
def abbreviated_ref?
ref =~ /\A\h+\z/ && ref !~ /\A\h{40}\z/
end
def legacy_locked_revision?
!@revision.nil? && @revision =~ /\A\h{7}\z/
end
def git_null(*command, dir: nil)
check_allowed(command)
@ -175,37 +233,40 @@ module Bundler
end
def has_revision_cached?
return unless @revision
with_path { git("cat-file", "-e", @revision, :dir => path) }
return unless @revision && path.exist?
git("cat-file", "-e", @revision, :dir => path)
true
rescue GitError
false
end
def remove_cache
FileUtils.rm_rf(path)
end
def find_local_revision
allowed_with_path do
git("rev-parse", "--verify", ref || "HEAD", :dir => path).strip
git("rev-parse", "--verify", branch || tag || ref || "HEAD", :dir => path).strip
end
rescue GitCommandError => e
raise MissingGitRevisionError.new(e.command, path, ref, URICredentialsFilter.credential_filtered_uri(uri))
raise MissingGitRevisionError.new(e.command, path, branch || tag || ref, credential_filtered_uri)
end
# Adds credentials to the URI as Fetcher#configured_uri_for does
def configured_uri_for(uri)
# Adds credentials to the URI
def configured_uri
if /https?:/ =~ uri
remote = Bundler::URI(uri)
config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
remote.userinfo ||= config_auth
remote.to_s
elsif File.exist?(uri)
"file://#{uri}"
else
uri
uri.to_s
end
end
# Removes credentials from the URI
def credential_filtered_uri
URICredentialsFilter.credential_filtered_uri(uri)
end
def allow?
allowed = @git ? @git.allow_git_ops? : true
@ -254,9 +315,43 @@ module Bundler
end
end
def extra_clone_args
return [] if full_clone?
args = ["--depth", depth.to_s, "--single-branch"]
args.unshift("--no-tags") if supports_cloning_with_no_tags?
args += ["--branch", branch || tag] if branch || tag
args
end
def extra_fetch_args
return [] if full_clone?
["--depth", depth.to_s]
end
def revision_refspec
return if legacy_locked_revision?
revision
end
def full_clone?
depth.nil?
end
def supports_minus_c?
@supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5")
end
def supports_fetching_unreachable_refs?
@supports_fetching_unreachable_refs ||= Gem::Version.new(version) >= Gem::Version.new("2.5.0")
end
def supports_cloning_with_no_tags?
@supports_cloning_with_no_tags ||= Gem::Version.new(version) >= Gem::Version.new("2.14.0-rc0")
end
end
end
end

Просмотреть файл

@ -4,7 +4,7 @@ require "bundler/settings"
require "openssl"
RSpec.describe Bundler::Env do
let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil, nil) }
let(:git_proxy_stub) { Bundler::Source::Git::GitProxy.new(nil, nil) }
describe "#report" do
it "prints the environment" do

Просмотреть файл

@ -11,21 +11,24 @@ RSpec.describe Bundler::Source::Git::GitProxy do
context "with configured credentials" do
it "adds username and password to URI" do
Bundler.settings.temporary(uri => "u:p") do
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s)
allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0")
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s)
subject.checkout
end
end
it "adds username and password to URI for host" do
Bundler.settings.temporary("github.com" => "u:p") do
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s)
allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0")
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch", "--", "https://u:p@github.com/rubygems/rubygems.git", path.to_s)
subject.checkout
end
end
it "does not add username and password to mismatched URI" do
Bundler.settings.temporary("https://u:p@github.com/rubygems/rubygems-mismatch.git" => "u:p") do
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", uri, path.to_s)
allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0")
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch", "--", uri, path.to_s)
subject.checkout
end
end
@ -34,7 +37,8 @@ RSpec.describe Bundler::Source::Git::GitProxy do
Bundler.settings.temporary("github.com" => "u:p") do
original = "https://orig:info@github.com/rubygems/rubygems.git"
subject = described_class.new(Pathname("path"), original, "HEAD")
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--", original, path.to_s)
allow(subject).to receive(:git).with("--version").and_return("git version 2.14.0")
expect(subject).to receive(:git_retry).with("clone", "--bare", "--no-hardlinks", "--quiet", "--no-tags", "--depth", "1", "--single-branch", "--", original, path.to_s)
subject.checkout
end
end
@ -122,33 +126,6 @@ RSpec.describe Bundler::Source::Git::GitProxy do
end
end
describe "#copy_to" do
let(:cache) { tmpdir("cache_path") }
let(:destination) { tmpdir("copy_to_path") }
let(:submodules) { false }
context "when given a SHA as a revision" do
let(:revision) { "abcd" * 10 }
let(:command) { ["reset", "--hard", revision] }
let(:command_for_display) { "git #{command.shelljoin}" }
it "fails gracefully when resetting to the revision fails" do
expect(subject).to receive(:git_retry).with("clone", any_args) { destination.mkpath }
expect(subject).to receive(:git_retry).with("fetch", any_args, :dir => destination)
expect(subject).to receive(:git).with(*command, :dir => destination).and_raise(Bundler::Source::Git::GitCommandError.new(command_for_display, destination))
expect(subject).not_to receive(:git)
expect { subject.copy_to(destination, submodules) }.
to raise_error(
Bundler::Source::Git::MissingGitRevisionError,
"Git error: command `#{command_for_display}` in directory #{destination} has failed.\n" \
"Revision #{revision} does not exist in the repository #{uri}. Maybe you misspelled it?\n" \
"If this error persists you could try removing the cache directory '#{destination}'"
)
end
end
end
it "doesn't allow arbitrary code execution through Gemfile uris with a leading dash" do
gemfile <<~G
gem "poc", git: "-u./pay:load.sh"

1
spec/bundler/cache/git_spec.rb поставляемый
Просмотреть файл

@ -90,7 +90,6 @@ RSpec.describe "bundle cache with git" do
expect(ref).not_to eq(old_ref)
bundle "update", :all => true
bundle "config set cache_all true"
bundle :cache
expect(bundled_app("vendor/cache/foo-1.0-#{ref}")).to exist

Просмотреть файл

@ -595,4 +595,74 @@ RSpec.describe "bundle lock" do
expect(read_lockfile).to eq(@lockfile.sub("foo (1.0)", "foo (2.0)").sub(/foo$/, "foo (= 2.0)"))
end
end
context "when a system gem has incorrect dependencies, different from the lockfile" do
before do
build_repo4 do
build_gem "debug", "1.6.3" do |s|
s.add_dependency "irb", ">= 1.3.6"
end
build_gem "irb", "1.5.0"
end
system_gems "irb-1.5.0", :gem_repo => gem_repo4
system_gems "debug-1.6.3", :gem_repo => gem_repo4
# simulate gemspec with wrong empty dependencies
debug_gemspec_path = system_gem_path("specifications/debug-1.6.3.gemspec")
debug_gemspec = Gem::Specification.load(debug_gemspec_path.to_s)
debug_gemspec.dependencies.clear
File.write(debug_gemspec_path, debug_gemspec.to_ruby)
end
it "respects the existing lockfile, even when reresolving" do
gemfile <<~G
source "#{file_uri_for(gem_repo4)}"
gem "debug"
G
lockfile <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
debug (1.6.3)
irb (>= 1.3.6)
irb (1.5.0)
PLATFORMS
x86_64-linux
DEPENDENCIES
debug
BUNDLED WITH
#{Bundler::VERSION}
L
simulate_platform "arm64-darwin-22" do
bundle "lock"
end
expect(lockfile).to eq <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
debug (1.6.3)
irb (>= 1.3.6)
irb (1.5.0)
PLATFORMS
arm64-darwin-22
x86_64-linux
DEPENDENCIES
debug
BUNDLED WITH
#{Bundler::VERSION}
L
end
end
end

Просмотреть файл

@ -53,7 +53,10 @@ RSpec.describe "bundle install with :allow_offline_install" do
File.open(tmp("broken_path/git"), "w", 0o755) do |f|
f.puts strip_whitespace(<<-RUBY)
#!/usr/bin/env ruby
if %w(fetch --force --quiet --tags refs/heads/*:refs/heads/*).-(ARGV).empty? || %w(clone --bare --no-hardlinks --quiet).-(ARGV).empty?
fetch_args = %w(fetch --force --quiet)
clone_args = %w(clone --bare --no-hardlinks --quiet)
if (fetch_args.-(ARGV).empty? || clone_args.-(ARGV).empty?) && ARGV.any? {|arg| arg.start_with?("file://") }
warn "git remote ops have been disabled"
exit 1
end

Просмотреть файл

@ -52,8 +52,9 @@ RSpec.describe "bundle install with git sources" do
bundle "update foo"
sha = git.ref_for("main", 11)
spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec").to_s
ruby_code = Gem::Specification.load(spec_file).to_ruby
spec_file = default_bundle_path.join("bundler/gems/foo-1.0-#{sha}/foo.gemspec")
expect(spec_file).to exist
ruby_code = Gem::Specification.load(spec_file.to_s).to_ruby
file_code = File.read(spec_file)
expect(file_code).to eq(ruby_code)
end
@ -218,6 +219,22 @@ RSpec.describe "bundle install with git sources" do
expect(out).to eq("WIN")
end
it "works when an abbreviated revision is added after an initial, potentially shallow clone" do
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
git "#{lib_path("foo-1.0")}" do
gem "foo"
end
G
install_gemfile <<-G
source "#{file_uri_for(gem_repo1)}"
git "#{lib_path("foo-1.0")}", :ref => #{@revision[0..7].inspect} do
gem "foo"
end
G
end
it "works when the revision is a non-head ref" do
# want to ensure we don't fallback to main
update_git "foo", :path => lib_path("foo-1.0") do |s|

Просмотреть файл

@ -363,23 +363,19 @@ RSpec.describe "bundle install with specific platforms" do
G
error_message = <<~ERROR.strip
Could not find gem 'sorbet-static (= 0.5.6433)' with platform 'arm64-darwin-21', which is required by gem 'sorbet (= 0.5.6433)', in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally.
Could not find compatible versions
Because every version of sorbet depends on sorbet-static = 0.5.6433
and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally for any resolution platforms (arm64-darwin-21),
every version of sorbet is forbidden.
So, because Gemfile depends on sorbet = 0.5.6433,
version solving has failed.
The source contains the following gems matching 'sorbet-static (= 0.5.6433)':
* sorbet-static-0.5.6433-universal-darwin-20
* sorbet-static-0.5.6433-x86_64-linux
ERROR
error_message = <<~ERROR.strip
Could not find compatible versions
Because every version of sorbet depends on sorbet-static = 0.5.6433
and sorbet-static = 0.5.6433 could not be found in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally,
every version of sorbet is forbidden.
So, because Gemfile depends on sorbet = 0.5.6433,
version solving has failed.
ERROR
simulate_platform "arm64-darwin-21" do
bundle "lock", :raise_on_error => false
end

Просмотреть файл

@ -374,6 +374,57 @@ RSpec.describe "bundle install with install-time dependencies" do
end
end
context "with a Gemfile and lock file that don't resolve under the current platform" do
before do
build_repo4 do
build_gem "sorbet", "0.5.10554" do |s|
s.add_dependency "sorbet-static", "0.5.10554"
end
build_gem "sorbet-static", "0.5.10554" do |s|
s.platform = "universal-darwin-21"
end
end
gemfile <<~G
source "#{file_uri_for(gem_repo4)}"
gem 'sorbet', '= 0.5.10554'
G
lockfile <<~L
GEM
remote: #{file_uri_for(gem_repo4)}/
specs:
sorbet (0.5.10554)
sorbet-static (= 0.5.10554)
sorbet-static (0.5.10554-universal-darwin-21)
PLATFORMS
arm64-darwin-21
DEPENDENCIES
sorbet (= 0.5.10554)
BUNDLED WITH
#{Bundler::VERSION}
L
end
it "raises a proper error" do
simulate_platform "aarch64-linux" do
bundle "install", :raise_on_error => false
end
nice_error = strip_whitespace(<<-E).strip
Could not find gem 'sorbet-static (= 0.5.10554)' with platforms 'arm64-darwin-21', 'aarch64-linux' in rubygems repository #{file_uri_for(gem_repo4)}/ or installed locally.
The source contains the following gems matching 'sorbet-static (= 0.5.10554)':
* sorbet-static-0.5.10554-universal-darwin-21
E
expect(err).to end_with(nice_error)
end
end
it "gives a meaningful error on ruby version mismatches between dependencies" do
build_repo4 do
build_gem "requires-old-ruby" do |s|

Просмотреть файл

@ -14,6 +14,14 @@ RSpec.describe "bundle lock with git gems" do
expect(the_bundle).to include_gems "foo 1.0.0"
end
it "doesn't print errors even if running lock after removing the cache" do
FileUtils.rm_rf(Dir[default_cache_path("git/foo-1.0-*")].first)
bundle "lock --verbose"
expect(err).to be_empty
end
it "locks a git source to the current ref" do
update_git "foo"
bundle :install

Просмотреть файл

@ -21,7 +21,7 @@ end
RSpec.configure do |config|
config.filter_run_excluding :realworld => true
git_version = Bundler::Source::Git::GitProxy.new(nil, nil, nil).version
git_version = Bundler::Source::Git::GitProxy.new(nil, nil).version
config.filter_run_excluding :git => RequirementChecker.against(git_version)
config.filter_run_excluding :bundler => RequirementChecker.against(Bundler::VERSION.split(".")[0])

Просмотреть файл

@ -118,6 +118,14 @@ module Spec
end
end
def default_cache_path(*path)
if Bundler.feature_flag.global_gem_cache?
home(".bundle/cache", *path)
else
default_bundle_path("cache/bundler", *path)
end
end
def bundled_app(*path)
root = tmp.join("bundled_app")
FileUtils.mkdir_p(root)

Просмотреть файл

@ -120,6 +120,7 @@ RSpec.describe "bundle update" do
G
bundle "update", :all => true
expect(err).to be_empty
end
describe "with submodules" do