123456789_123456789_123456789_123456789_123456789_

Class: Bundler::Source::Git::GitProxy

Relationships & Source Files
Inherits: Object
Defined in: lib/bundler/source/git/git_proxy.rb

Overview

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

Class Method Summary

Instance Attribute Summary

Instance Method Summary

Constructor Details

.new(path, uri, options = {}, revision = nil, git = nil) ⇒ GitProxy

[ GitHub ]

  
# File 'lib/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

Instance Attribute Details

#allow?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 351

def allow?
  allowed = @git ? @git.allow_git_ops? : true

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

  allowed
end

#branch (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

#clone_needs_extra_fetch?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 174

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?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 204

def clone_needs_unshallow?
  return false unless path.join("shallow").exist?
  return true if full_clone?

  @revision && @revision != head_revision
end

#explicit_ref (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

#full_clone?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 445

def full_clone?
  depth.nil?
end

#has_revision_cached?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 306

def has_revision_cached?
  return unless @revision && path.exist?
  git("cat-file", "-e", @revision, dir: path)
  true
rescue GitError
  false
end

#locked_to_full_sha?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 272

def locked_to_full_sha?
  full_sha_revision?(@revision)
end

#needs_allow_any_sha1_in_want?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 453

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_a_repository?Boolean (readonly)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 87

def not_a_repository?
  _, status = git_null("rev-parse", "--resolve-git-dir", path.to_s, dir: path)

  !status.success?
end

#not_pinned?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 264

def not_pinned?
  branch || tag || ref.nil?
end

#path (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

#pinned_to_full_sha?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 268

def pinned_to_full_sha?
  full_sha_revision?(ref)
end

#ref (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

#revision (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 77

def revision
  @revision ||= allowed_with_path { find_local_revision }
end

#revision=(value) (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 58

attr_writer :revision

#supports_cloning_with_no_tags?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 461

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?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 457

def supports_fetching_unreachable_refs?
  @supports_fetching_unreachable_refs ||= Gem::Version.new(version) >= Gem::Version.new("2.5.0")
end

#supports_minus_c?Boolean (readonly, private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 449

def supports_minus_c?
  @supports_minus_c ||= Gem::Version.new(version) >= Gem::Version.new("1.8.5")
end

#tag (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

#uri (rw)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 57

attr_accessor :path, :uri, :branch, :tag, :ref, :explicit_ref

Instance Method Details

#allowed_with_path (private)

Raises:

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 364

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 394

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 407

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) (private)

Raises:

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 369

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

#checkout

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 108

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

#commit (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 250

def commit
  @commit ||= pinned_to_full_sha? ? ref : @revision
end

#configured_uri (private)

Adds credentials to the ::Bundler::URI

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 335

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

#contains?(commit) ⇒ Boolean

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 93

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)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 120

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

#credential_filtered_uri (private)

Removes credentials from the ::Bundler::URI

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 347

def credential_filtered_uri
  URICredentialsFilter.credential_filtered_uri(uri)
end

#current_branch

[ GitHub ]

  
# File 'lib/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

#depth (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 218

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 (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 433

def depth_args
  return [] if full_clone?

  ["--depth", depth.to_s]
end

#extra_clone_args (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 417

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 439

def extra_fetch_args(ref)
  extra_args = [path.to_s, *depth_args]
  extra_args.push(ref)
  extra_args
end

#extra_ref (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 211

def extra_ref
  return false if not_pinned?
  return true unless full_clone?

  ref.start_with?("refs/")
end

#find_local_revision (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 314

def find_local_revision
  return head_revision if explicit_ref.nil?

  find_revision_for(explicit_ref)
end

#find_revision_for(reference) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 324

def find_revision_for(reference)
  verify(reference)
rescue GitCommandError => e
  raise MissingGitRevisionError.new(e.command, path, reference, credential_filtered_uri)
end

#full_sha_revision?(ref) ⇒ Boolean (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 276

def full_sha_revision?(ref)
  ref&.match?(/\A\h{40}\z/)
end

#full_version

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 104

def full_version
  @full_version ||= git_local("--version").sub(/git version\s*/, "").strip
end

#fully_qualified_ref (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 254

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 294

def git(*command, dir: nil)
  run_command(*command, dir: dir) do |unredacted_command|
    check_allowed(unredacted_command)
  end
end

#git_local(*command, dir: nil) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 300

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 280

def git_null(*command, dir: nil)
  check_allowed(command)

  capture(command, dir, ignore_err: true)
end

#git_remote_fetch(args) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 158

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) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 286

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

#head_revision (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 320

def head_revision
  verify("HEAD")
end

#redact_and_check_presence(command) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 375

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 (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 231

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) (private)

Raises:

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 382

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

#verify(reference) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 330

def verify(reference)
  git("rev-parse", "--verify", reference, dir: path).strip
end

#version

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 100

def version
  @version ||= full_version.match(/((\.?\d)).*/)[1]
end

#with_path(&blk) (private)

[ GitHub ]

  
# File 'lib/bundler/source/git/git_proxy.rb', line 359

def with_path(&blk)
  checkout unless path.exist?
  blk.call
end