aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorHiroshi SHIBATA <hsbt@ruby-lang.org>2022-12-09 14:45:51 +0900
committerHiroshi SHIBATA <hsbt@ruby-lang.org>2022-12-09 16:36:22 +0900
commita4e14b9d9d58391fb7d7a10be8d883690860373b (patch)
tree08f9c871583bd0a0d98b9cac3389ad52631400be
parentd928ebacb23639cbf3f28201304f0451e5bd45a7 (diff)
downloadruby-a4e14b9d9d58391fb7d7a10be8d883690860373b.tar.gz
Merge RubyGems/Bundler master
Pick from https://github.com/rubygems/rubygems/commit/823c776d951f3c35094611473ec77f94e8bf6610
-rw-r--r--lib/bundler/definition.rb11
-rw-r--r--lib/bundler/env.rb2
-rw-r--r--lib/bundler/fetcher.rb7
-rw-r--r--lib/bundler/man/bundle-platform.12
-rw-r--r--lib/bundler/man/bundle-platform.1.ronn2
-rw-r--r--lib/bundler/resolver.rb118
-rw-r--r--lib/bundler/resolver/incompatibility.rb15
-rw-r--r--lib/bundler/resolver/package.rb16
-rw-r--r--lib/bundler/source/git.rb16
-rw-r--r--lib/bundler/source/git/git_proxy.rb167
-rw-r--r--spec/bundler/bundler/env_spec.rb2
-rw-r--r--spec/bundler/bundler/source/git/git_proxy_spec.rb39
-rw-r--r--spec/bundler/cache/git_spec.rb1
-rw-r--r--spec/bundler/commands/lock_spec.rb70
-rw-r--r--spec/bundler/install/allow_offline_install_spec.rb5
-rw-r--r--spec/bundler/install/gemfile/git_spec.rb21
-rw-r--r--spec/bundler/install/gemfile/specific_platform_spec.rb14
-rw-r--r--spec/bundler/install/gems/resolving_spec.rb51
-rw-r--r--spec/bundler/lock/git_spec.rb8
-rw-r--r--spec/bundler/support/filters.rb2
-rw-r--r--spec/bundler/support/path.rb8
-rw-r--r--spec/bundler/update/git_spec.rb1
22 files changed, 420 insertions, 158 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb
index 9043efe408..fe807605e0 100644
--- a/lib/bundler/definition.rb
+++ b/lib/bundler/definition.rb
@@ -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
diff --git a/lib/bundler/env.rb b/lib/bundler/env.rb
index 1763035a8a..8b2e292fd6 100644
--- a/lib/bundler/env.rb
+++ b/lib/bundler/env.rb
@@ -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
diff --git a/lib/bundler/fetcher.rb b/lib/bundler/fetcher.rb
index 61241b6523..a073bae278 100644
--- a/lib/bundler/fetcher.rb
+++ b/lib/bundler/fetcher.rb
@@ -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
diff --git a/lib/bundler/man/bundle-platform.1 b/lib/bundler/man/bundle-platform.1
index 2d2450780a..41db8f72f0 100644
--- a/lib/bundler/man/bundle-platform.1
+++ b/lib/bundler/man/bundle-platform.1
@@ -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
diff --git a/lib/bundler/man/bundle-platform.1.ronn b/lib/bundler/man/bundle-platform.1.ronn
index eb9baa1c62..744acd1b43 100644
--- a/lib/bundler/man/bundle-platform.1.ronn
+++ b/lib/bundler/man/bundle-platform.1.ronn
@@ -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)
diff --git a/lib/bundler/resolver.rb b/lib/bundler/resolver.rb
index 07607813ec..c175ea4354 100644
--- a/lib/bundler/resolver.rb
+++ b/lib/bundler/resolver.rb
@@ -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(" ")
diff --git a/lib/bundler/resolver/incompatibility.rb b/lib/bundler/resolver/incompatibility.rb
new file mode 100644
index 0000000000..c61151fbeb
--- /dev/null
+++ b/lib/bundler/resolver/incompatibility.rb
@@ -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
diff --git a/lib/bundler/resolver/package.rb b/lib/bundler/resolver/package.rb
index fa283baca8..7d64632860 100644
--- a/lib/bundler/resolver/package.rb
+++ b/lib/bundler/resolver/package.rb
@@ -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
diff --git a/lib/bundler/source/git.rb b/lib/bundler/source/git.rb
index fd34edffb7..745ae05c5c 100644
--- a/lib/bundler/source/git.rb
+++ b/lib/bundler/source/git.rb
@@ -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
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 745a7fe118..5468536f86 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -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
diff --git a/spec/bundler/bundler/env_spec.rb b/spec/bundler/bundler/env_spec.rb
index a6f4b2ba85..fb950c3c60 100644
--- a/spec/bundler/bundler/env_spec.rb
+++ b/spec/bundler/bundler/env_spec.rb
@@ -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
diff --git a/spec/bundler/bundler/source/git/git_proxy_spec.rb b/spec/bundler/bundler/source/git/git_proxy_spec.rb
index cffd72cc3f..841b8651e4 100644
--- a/spec/bundler/bundler/source/git/git_proxy_spec.rb
+++ b/spec/bundler/bundler/source/git/git_proxy_spec.rb
@@ -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"
diff --git a/spec/bundler/cache/git_spec.rb b/spec/bundler/cache/git_spec.rb
index fed8ba43f4..d2671b5f15 100644
--- a/spec/bundler/cache/git_spec.rb
+++ b/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
diff --git a/spec/bundler/commands/lock_spec.rb b/spec/bundler/commands/lock_spec.rb
index 007e53f4e2..fd331d2f1b 100644
--- a/spec/bundler/commands/lock_spec.rb
+++ b/spec/bundler/commands/lock_spec.rb
@@ -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
diff --git a/spec/bundler/install/allow_offline_install_spec.rb b/spec/bundler/install/allow_offline_install_spec.rb
index 524363fde5..4c6c77a61e 100644
--- a/spec/bundler/install/allow_offline_install_spec.rb
+++ b/spec/bundler/install/allow_offline_install_spec.rb
@@ -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
diff --git a/spec/bundler/install/gemfile/git_spec.rb b/spec/bundler/install/gemfile/git_spec.rb
index 636a3daaad..4ac9f186ec 100644
--- a/spec/bundler/install/gemfile/git_spec.rb
+++ b/spec/bundler/install/gemfile/git_spec.rb
@@ -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|
diff --git a/spec/bundler/install/gemfile/specific_platform_spec.rb b/spec/bundler/install/gemfile/specific_platform_spec.rb
index 1f615ec8a5..628ae89431 100644
--- a/spec/bundler/install/gemfile/specific_platform_spec.rb
+++ b/spec/bundler/install/gemfile/specific_platform_spec.rb
@@ -363,21 +363,17 @@ 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.
-
- 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,
+ 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
simulate_platform "arm64-darwin-21" do
diff --git a/spec/bundler/install/gems/resolving_spec.rb b/spec/bundler/install/gems/resolving_spec.rb
index 3977122568..279d3422c1 100644
--- a/spec/bundler/install/gems/resolving_spec.rb
+++ b/spec/bundler/install/gems/resolving_spec.rb
@@ -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|
diff --git a/spec/bundler/lock/git_spec.rb b/spec/bundler/lock/git_spec.rb
index 56db5d8305..df1564d614 100644
--- a/spec/bundler/lock/git_spec.rb
+++ b/spec/bundler/lock/git_spec.rb
@@ -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
diff --git a/spec/bundler/support/filters.rb b/spec/bundler/support/filters.rb
index 523f7c742c..78545d2e64 100644
--- a/spec/bundler/support/filters.rb
+++ b/spec/bundler/support/filters.rb
@@ -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])
diff --git a/spec/bundler/support/path.rb b/spec/bundler/support/path.rb
index 7443e78d52..c9538f6b36 100644
--- a/spec/bundler/support/path.rb
+++ b/spec/bundler/support/path.rb
@@ -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)
diff --git a/spec/bundler/update/git_spec.rb b/spec/bundler/update/git_spec.rb
index 427a0bb713..59e3d2f5fb 100644
--- a/spec/bundler/update/git_spec.rb
+++ b/spec/bundler/update/git_spec.rb
@@ -120,6 +120,7 @@ RSpec.describe "bundle update" do
G
bundle "update", :all => true
+ expect(err).to be_empty
end
describe "with submodules" do