summaryrefslogtreecommitdiff
path: root/lib/bundler/source/git/git_proxy.rb
diff options
context:
space:
mode:
Diffstat (limited to 'lib/bundler/source/git/git_proxy.rb')
-rw-r--r--lib/bundler/source/git/git_proxy.rb177
1 files changed, 126 insertions, 51 deletions
diff --git a/lib/bundler/source/git/git_proxy.rb b/lib/bundler/source/git/git_proxy.rb
index 2a7c8473f5..cd352c22a7 100644
--- a/lib/bundler/source/git/git_proxy.rb
+++ b/lib/bundler/source/git/git_proxy.rb
@@ -16,7 +16,7 @@ module Bundler
def initialize(command)
msg = String.new
msg << "Bundler is trying to run `#{command}` at runtime. You probably need to run `bundle install`. However, "
- msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/rubygems/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md "
+ msg << "this error message could probably be more useful. Please submit a ticket at https://github.com/ruby/rubygems/issues/new?labels=Bundler&template=bundler-related-issue.md "
msg << "with steps to reproduce as well as the following\n\nCALLER: #{caller.join("\n")}"
super msg
end
@@ -28,8 +28,9 @@ module Bundler
def initialize(command, path, extra_info = nil)
@command = command
- msg = String.new
- msg << "Git error: command `#{command}` in directory #{path} has failed."
+ msg = String.new("Git error: command `#{command}`")
+ msg << " in directory #{path}" if path
+ msg << " has failed."
msg << "\n#{extra_info}" if extra_info
super msg
end
@@ -42,6 +43,13 @@ module Bundler
end
end
+ class AmbiguousGitReference < GitError
+ def initialize(options)
+ msg = "Specification of branch or ref with tag is ambiguous. You specified #{options.inspect}"
+ super msg
+ end
+ end
+
# The GitProxy is responsible to interact with git repositories.
# All actions required by the Git source is encapsulated in this
# object.
@@ -52,10 +60,15 @@ module Bundler
def initialize(path, uri, options = {}, revision = nil, git = nil)
@path = path
@uri = uri
- @branch = options["branch"]
@tag = options["tag"]
+ @branch = options["branch"]
@ref = options["ref"]
- @explicit_ref = branch || tag || ref
+ if @tag
+ raise AmbiguousGitReference.new(options) if @branch || @ref
+ @explicit_ref = @tag
+ else
+ @explicit_ref = @ref || @branch
+ end
@revision = revision
@git = git
@commit_ref = nil
@@ -66,15 +79,15 @@ module Bundler
end
def current_branch
- @current_branch ||= allowed_with_path do
- git("rev-parse", "--abbrev-ref", "HEAD", :dir => path).strip
+ @current_branch ||= with_path do
+ git_local("rev-parse", "--abbrev-ref", "HEAD", dir: path).strip
end
end
def contains?(commit)
allowed_with_path do
- result, status = git_null("branch", "--contains", commit, :dir => path)
- status.success? && result =~ /^\* (.*)$/
+ result, status = git_null("branch", "--contains", commit, dir: path)
+ status.success? && result.match?(/^\* (.*)$/)
end
end
@@ -83,7 +96,7 @@ module Bundler
end
def full_version
- @full_version ||= git("--version").sub(/git version\s*/, "").strip
+ @full_version ||= git_local("--version").sub(/git version\s*/, "").strip
end
def checkout
@@ -108,7 +121,7 @@ module Bundler
FileUtils.rm_rf(p)
end
git "clone", "--no-checkout", "--quiet", path.to_s, destination.to_s
- File.chmod(((File.stat(destination).mode | 0o777) & ~File.umask), destination)
+ File.chmod((File.stat(destination).mode | 0o777) & ~File.umask, destination)
rescue Errno::EEXIST => e
file_path = e.message[%r{.*?((?:[a-zA-Z]:)?/.*)}, 1]
raise GitError, "Bundler could not install a gem because it needs to " \
@@ -117,18 +130,29 @@ module Bundler
end
end
- git "fetch", "--force", "--quiet", *extra_fetch_args, :dir => destination if @commit_ref
+ ref = @commit_ref || (locked_to_full_sha? && @revision)
+ if ref
+ git "config", "uploadpack.allowAnySHA1InWant", "true", dir: path.to_s if @commit_ref.nil? && needs_allow_any_sha1_in_want?
+
+ git "fetch", "--force", "--quiet", *extra_fetch_args(ref), dir: destination
+ end
- git "reset", "--hard", @revision, :dir => destination
+ git "reset", "--hard", @revision, dir: destination
if submodules
- git_retry "submodule", "update", "--init", "--recursive", :dir => destination
+ git_retry "submodule", "update", "--init", "--recursive", dir: destination
elsif Gem::Version.create(version) >= Gem::Version.create("2.9.0")
inner_command = "git -C $toplevel submodule deinit --force $sm_path"
- git_retry "submodule", "foreach", "--quiet", inner_command, :dir => destination
+ git_retry "submodule", "foreach", "--quiet", inner_command, dir: destination
end
end
+ def installed_to?(destination)
+ # if copy_to is interrupted, it may leave a partially installed directory that
+ # contains .git but no other files -- consider this not to be installed
+ Dir.exist?(destination) && (Dir.children(destination) - [".git"]).any?
+ end
+
private
def git_remote_fetch(args)
@@ -139,8 +163,8 @@ module Bundler
out, err, status = capture(command, path)
return out if status.success?
- if err.include?("couldn't find remote ref")
- raise MissingGitRevisionError.new(command_with_no_credentials, path, explicit_ref, credential_filtered_uri)
+ if err.include?("couldn't find remote ref") || err.include?("not our ref")
+ raise MissingGitRevisionError.new(command_with_no_credentials, path, commit || explicit_ref, credential_filtered_uri)
else
raise GitCommandError.new(command_with_no_credentials, path, err)
end
@@ -153,9 +177,29 @@ module Bundler
SharedHelpers.filesystem_access(path.dirname) do |p|
FileUtils.mkdir_p(p)
end
- git_retry "clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s
- extra_ref
+ command = ["clone", "--bare", "--no-hardlinks", "--quiet", *extra_clone_args, "--", configured_uri, path.to_s]
+ command_with_no_credentials = check_allowed(command)
+
+ Bundler::Retry.new("`#{command_with_no_credentials}`", [MissingGitRevisionError]).attempts do
+ _, err, status = capture(command, nil)
+ return extra_ref if status.success?
+
+ if err.include?("Could not find remote branch") || # git up to 2.49
+ err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher
+ raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri)
+ else
+ idx = command.index("--depth")
+ if idx
+ command.delete_at(idx)
+ command.delete_at(idx)
+ command_with_no_credentials = check_allowed(command)
+
+ err += "Retrying without --depth argument."
+ end
+ raise GitCommandError.new(command_with_no_credentials, path, err)
+ end
+ end
end
def clone_needs_unshallow?
@@ -186,8 +230,6 @@ module Bundler
end
def refspec
- commit = pinned_to_full_sha? ? ref : @revision
-
if commit
@commit_ref = "refs/#{commit}-sha"
return "#{commit}:#{@commit_ref}"
@@ -206,6 +248,10 @@ module Bundler
"#{reference}:#{reference}"
end
+ def commit
+ @commit ||= pinned_to_full_sha? ? ref : @revision
+ end
+
def fully_qualified_ref
if branch
"refs/heads/#{branch}"
@@ -217,42 +263,50 @@ module Bundler
end
def not_pinned?
- branch || tag || ref.nil?
+ branch_option || ref.nil?
end
def pinned_to_full_sha?
- ref =~ /\A\h{40}\z/
+ full_sha_revision?(ref)
+ end
+
+ def locked_to_full_sha?
+ full_sha_revision?(@revision)
+ end
+
+ def full_sha_revision?(ref)
+ ref&.match?(/\A\h{40}\z/)
end
def git_null(*command, dir: nil)
check_allowed(command)
- capture(command, dir, :ignore_err => true)
+ capture(command, dir, ignore_err: true)
end
def git_retry(*command, dir: nil)
command_with_no_credentials = check_allowed(command)
Bundler::Retry.new("`#{command_with_no_credentials}` at #{dir || SharedHelpers.pwd}").attempts do
- git(*command, :dir => dir)
+ git(*command, dir: dir)
end
end
def git(*command, dir: nil)
- command_with_no_credentials = check_allowed(command)
-
- out, err, status = capture(command, dir)
-
- raise GitCommandError.new(command_with_no_credentials, dir || SharedHelpers.pwd, err) unless status.success?
-
- Bundler.ui.warn err unless err.empty?
+ run_command(*command, dir: dir) do |unredacted_command|
+ check_allowed(unredacted_command)
+ end
+ end
- out
+ def git_local(*command, dir: nil)
+ run_command(*command, dir: dir) do |unredacted_command|
+ redact_and_check_presence(unredacted_command)
+ end
end
def has_revision_cached?
- return unless @revision && path.exist?
- git("cat-file", "-e", @revision, :dir => path)
+ return unless commit && path.exist?
+ git("cat-file", "-e", commit, dir: path)
true
rescue GitError
false
@@ -275,18 +329,16 @@ module Bundler
end
def verify(reference)
- git("rev-parse", "--verify", reference, :dir => path).strip
+ git("rev-parse", "--verify", reference, dir: path).strip
end
# Adds credentials to the URI
def configured_uri
if /https?:/.match?(uri)
- remote = Bundler::URI(uri)
+ remote = Gem::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.to_s
end
@@ -316,12 +368,30 @@ module Bundler
end
def check_allowed(command)
- require "shellwords"
- command_with_no_credentials = URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri)
+ command_with_no_credentials = redact_and_check_presence(command)
raise GitNotAllowedError.new(command_with_no_credentials) unless allow?
command_with_no_credentials
end
+ def redact_and_check_presence(command)
+ raise GitNotInstalledError.new unless Bundler.git_present?
+
+ require "shellwords"
+ URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri)
+ end
+
+ def run_command(*command, dir: nil)
+ command_with_no_credentials = yield(command)
+
+ out, err, status = capture(command, dir)
+
+ raise GitCommandError.new(command_with_no_credentials, dir || SharedHelpers.pwd, err) unless status.success?
+
+ Bundler.ui.warn err unless err.empty?
+
+ out
+ end
+
def capture(cmd, dir, ignore_err: false)
SharedHelpers.with_clean_git_env do
require "open3"
@@ -338,11 +408,7 @@ module Bundler
def capture3_args_for(cmd, dir)
return ["git", *cmd] unless dir
- if Bundler.feature_flag.bundler_3_mode? || supports_minus_c?
- ["git", "-C", dir.to_s, *cmd]
- else
- ["git", *cmd, { :chdir => dir.to_s }]
- end
+ ["git", "-C", dir.to_s, *cmd]
end
def extra_clone_args
@@ -352,7 +418,12 @@ module Bundler
args += ["--single-branch"]
args.unshift("--no-tags") if supports_cloning_with_no_tags?
- args += ["--branch", branch || tag] if branch || tag
+ # If there's a locked revision, no need to clone any specific branch
+ # or tag, since we will end up checking out that locked revision
+ # anyways.
+ return args if @revision
+
+ args += ["--branch", branch_option] if branch_option
args
end
@@ -362,18 +433,22 @@ module Bundler
["--depth", depth.to_s]
end
- def extra_fetch_args
+ def extra_fetch_args(ref)
extra_args = [path.to_s, *depth_args]
- extra_args.push(@commit_ref)
+ extra_args.push(ref)
extra_args
end
+ def branch_option
+ branch || tag
+ end
+
def full_clone?
depth.nil?
end
- def supports_minus_c?
- @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5")
+ def needs_allow_any_sha1_in_want?
+ @needs_allow_any_sha1_in_want ||= Gem::Version.new(version) <= Gem::Version.new("2.13.7")
end
def supports_fetching_unreachable_refs?