class Bundler::Source::Git::GitProxy

The GitProxy is responsible to interact with git repositories. All actions required by the Git source is encapsulated in this object.

Attributes

branch[RW]
explicit_ref[RW]
path[RW]
ref[RW]
revision[W]
tag[RW]
uri[RW]

Public Class Methods

new(path, uri, options = {}, revision = nil, git = nil) click to toggle source
# File bundler/source/git/git_proxy.rb, line 60
def initialize(path, uri, options = {}, revision = nil, git = nil)
  @path     = path
  @uri      = uri
  @tag      = options["tag"]
  @branch   = options["branch"]
  @ref      = options["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
end

Public Instance Methods

checkout() click to toggle source
# File bundler/source/git/git_proxy.rb, line 102
def checkout
  return if has_revision_cached?

  Bundler.ui.info "Fetching #{credential_filtered_uri}"

  extra_fetch_needed = clone_needs_extra_fetch?
  unshallow_needed = clone_needs_unshallow?
  return unless extra_fetch_needed || unshallow_needed

  git_remote_fetch(unshallow_needed ? ["--unshallow"] : depth_args)
end
contains?(commit) click to toggle source
# File bundler/source/git/git_proxy.rb, line 87
def contains?(commit)
  allowed_with_path do
    result, status = git_null("branch", "--contains", commit, dir: path)
    status.success? && result.match?(/^\* (.*)$/)
  end
end
copy_to(destination, submodules = false) click to toggle source
# File bundler/source/git/git_proxy.rb, line 114
def copy_to(destination, submodules = false)
  unless File.exist?(destination.join(".git"))
    begin
      SharedHelpers.filesystem_access(destination.dirname) do |p|
        FileUtils.mkdir_p(p)
      end
      SharedHelpers.filesystem_access(destination) do |p|
        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)
    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 " \
        "create a directory, but a file exists - #{file_path}. Please delete " \
        "this file and try again."
    end
  end

  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

  if submodules
    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
  end
end
current_branch() click to toggle source
# File bundler/source/git/git_proxy.rb, line 81
def current_branch
  @current_branch ||= with_path do
    git_local("rev-parse", "--abbrev-ref", "HEAD", dir: path).strip
  end
end
full_version() click to toggle source
# File bundler/source/git/git_proxy.rb, line 98
def full_version
  @full_version ||= git_local("--version").sub(/git version\s*/, "").strip
end
revision() click to toggle source
# File bundler/source/git/git_proxy.rb, line 77
def revision
  @revision ||= allowed_with_path { find_local_revision }
end
version() click to toggle source
# File bundler/source/git/git_proxy.rb, line 94
def version
  @version ||= full_version.match(/((\.?\d+)+).*/)[1]
end

Private Instance Methods

allow?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 345
def allow?
  allowed = @git ? @git.allow_git_ops? : true

  raise GitNotInstalledError.new if allowed && !Bundler.git_present?

  allowed
end
allowed_with_path() { || ... } click to toggle source
# File bundler/source/git/git_proxy.rb, line 358
def allowed_with_path
  return with_path { yield } if allow?
  raise GitError, "The git source #{uri} is not yet checked out. Please run `bundle install` before trying to start your application"
end
capture(cmd, dir, ignore_err: false) click to toggle source
# File bundler/source/git/git_proxy.rb, line 388
def capture(cmd, dir, ignore_err: false)
  SharedHelpers.with_clean_git_env do
    require "open3"
    out, err, status = Open3.capture3(*capture3_args_for(cmd, dir))

    filtered_out = URICredentialsFilter.credential_filtered_string(out, uri)
    return [filtered_out, status] if ignore_err

    filtered_err = URICredentialsFilter.credential_filtered_string(err, uri)
    [filtered_out, filtered_err, status]
  end
end
capture3_args_for(cmd, dir) click to toggle source
# File bundler/source/git/git_proxy.rb, line 401
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
end
check_allowed(command) click to toggle source
# File bundler/source/git/git_proxy.rb, line 363
def check_allowed(command)
  command_with_no_credentials = redact_and_check_presence(command)
  raise GitNotAllowedError.new(command_with_no_credentials) unless allow?
  command_with_no_credentials
end
clone_needs_extra_fetch?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 168
def clone_needs_extra_fetch?
  return true if path.exist?

  SharedHelpers.filesystem_access(path.dirname) do |p|
    FileUtils.mkdir_p(p)
  end

  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")
      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
clone_needs_unshallow?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 198
def clone_needs_unshallow?
  return false unless path.join("shallow").exist?
  return true if full_clone?

  @revision && @revision != head_revision
end
commit() click to toggle source
# File bundler/source/git/git_proxy.rb, line 244
def commit
  @commit ||= pinned_to_full_sha? ? ref : @revision
end
configured_uri() click to toggle source

Adds credentials to the URI

# File bundler/source/git/git_proxy.rb, line 329
def configured_uri
  if /https?:/.match?(uri)
    remote = Gem::URI(uri)
    config_auth = Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
    remote.userinfo ||= config_auth
    remote.to_s
  else
    uri.to_s
  end
end
credential_filtered_uri() click to toggle source

Removes credentials from the URI

# File bundler/source/git/git_proxy.rb, line 341
def credential_filtered_uri
  URICredentialsFilter.credential_filtered_uri(uri)
end
depth() click to toggle source
# File bundler/source/git/git_proxy.rb, line 212
def depth
  return @depth if defined?(@depth)

  @depth = if !supports_fetching_unreachable_refs?
    nil
  elsif not_pinned? || pinned_to_full_sha?
    1
  elsif ref.include?("~")
    parsed_depth = ref.split("~").last
    parsed_depth.to_i + 1
  end
end
depth_args() click to toggle source
# File bundler/source/git/git_proxy.rb, line 427
def depth_args
  return [] if full_clone?

  ["--depth", depth.to_s]
end
extra_clone_args() click to toggle source
# File bundler/source/git/git_proxy.rb, line 411
def extra_clone_args
  args = depth_args
  return [] if args.empty?

  args += ["--single-branch"]
  args.unshift("--no-tags") if supports_cloning_with_no_tags?

  # 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 || tag] if branch || tag
  args
end
extra_fetch_args(ref) click to toggle source
# File bundler/source/git/git_proxy.rb, line 433
def extra_fetch_args(ref)
  extra_args = [path.to_s, *depth_args]
  extra_args.push(ref)
  extra_args
end
extra_ref() click to toggle source
# File bundler/source/git/git_proxy.rb, line 205
def extra_ref
  return false if not_pinned?
  return true unless full_clone?

  ref.start_with?("refs/")
end
find_local_revision() click to toggle source
# File bundler/source/git/git_proxy.rb, line 308
def find_local_revision
  return head_revision if explicit_ref.nil?

  find_revision_for(explicit_ref)
end
find_revision_for(reference) click to toggle source
# File bundler/source/git/git_proxy.rb, line 318
def find_revision_for(reference)
  verify(reference)
rescue GitCommandError => e
  raise MissingGitRevisionError.new(e.command, path, reference, credential_filtered_uri)
end
full_clone?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 439
def full_clone?
  depth.nil?
end
full_sha_revision?(ref) click to toggle source
# File bundler/source/git/git_proxy.rb, line 270
def full_sha_revision?(ref)
  ref&.match?(/\A\h{40}\z/)
end
fully_qualified_ref() click to toggle source
# File bundler/source/git/git_proxy.rb, line 248
def fully_qualified_ref
  if branch
    "refs/heads/#{branch}"
  elsif tag
    "refs/tags/#{tag}"
  elsif ref.nil?
    "refs/heads/#{current_branch}"
  end
end
git(*command, dir: nil) click to toggle source
# File bundler/source/git/git_proxy.rb, line 288
def git(*command, dir: nil)
  run_command(*command, dir: dir) do |unredacted_command|
    check_allowed(unredacted_command)
  end
end
git_local(*command, dir: nil) click to toggle source
# File bundler/source/git/git_proxy.rb, line 294
def git_local(*command, dir: nil)
  run_command(*command, dir: dir) do |unredacted_command|
    redact_and_check_presence(unredacted_command)
  end
end
git_null(*command, dir: nil) click to toggle source
# File bundler/source/git/git_proxy.rb, line 274
def git_null(*command, dir: nil)
  check_allowed(command)

  capture(command, dir, ignore_err: true)
end
git_remote_fetch(args) click to toggle source
# File bundler/source/git/git_proxy.rb, line 152
def git_remote_fetch(args)
  command = ["fetch", "--force", "--quiet", "--no-tags", *args, "--", configured_uri, refspec].compact
  command_with_no_credentials = check_allowed(command)

  Bundler::Retry.new("`#{command_with_no_credentials}` at #{path}", [MissingGitRevisionError]).attempts do
    out, err, status = capture(command, path)
    return out if status.success?

    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
  end
end
git_retry(*command, dir: nil) click to toggle source
# File bundler/source/git/git_proxy.rb, line 280
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)
  end
end
has_revision_cached?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 300
def has_revision_cached?
  return unless @revision && path.exist?
  git("cat-file", "-e", @revision, dir: path)
  true
rescue GitError
  false
end
head_revision() click to toggle source
# File bundler/source/git/git_proxy.rb, line 314
def head_revision
  verify("HEAD")
end
locked_to_full_sha?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 266
def locked_to_full_sha?
  full_sha_revision?(@revision)
end
needs_allow_any_sha1_in_want?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 447
def needs_allow_any_sha1_in_want?
  @needs_allow_any_sha1_in_want ||= Gem::Version.new(version) <= Gem::Version.new("2.13.7")
end
not_pinned?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 258
def not_pinned?
  branch || tag || ref.nil?
end
pinned_to_full_sha?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 262
def pinned_to_full_sha?
  full_sha_revision?(ref)
end
redact_and_check_presence(command) click to toggle source
# File bundler/source/git/git_proxy.rb, line 369
def redact_and_check_presence(command)
  raise GitNotInstalledError.new unless Bundler.git_present?

  require "shellwords"
  URICredentialsFilter.credential_filtered_string("git #{command.shelljoin}", uri)
end
refspec() click to toggle source
# File bundler/source/git/git_proxy.rb, line 225
def refspec
  if commit
    @commit_ref = "refs/#{commit}-sha"
    return "#{commit}:#{@commit_ref}"
  end

  reference = fully_qualified_ref

  reference ||= if ref.include?("~")
    ref.split("~").first
  elsif ref.start_with?("refs/")
    ref
  else
    "refs/*"
  end

  "#{reference}:#{reference}"
end
run_command(*command, dir: nil) { |command| ... } click to toggle source
# File bundler/source/git/git_proxy.rb, line 376
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
supports_cloning_with_no_tags?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 455
def supports_cloning_with_no_tags?
  @supports_cloning_with_no_tags ||= Gem::Version.new(version) >= Gem::Version.new("2.14.0-rc0")
end
supports_fetching_unreachable_refs?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 451
def supports_fetching_unreachable_refs?
  @supports_fetching_unreachable_refs ||= Gem::Version.new(version) >= Gem::Version.new("2.5.0")
end
supports_minus_c?() click to toggle source
# File bundler/source/git/git_proxy.rb, line 443
def supports_minus_c?
  @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5")
end
verify(reference) click to toggle source
# File bundler/source/git/git_proxy.rb, line 324
def verify(reference)
  git("rev-parse", "--verify", reference, dir: path).strip
end
with_path(&blk) click to toggle source
# File bundler/source/git/git_proxy.rb, line 353
def with_path(&blk)
  checkout unless path.exist?
  blk.call
end