class Dependabot::GitMetadataFetcher

Constants

KNOWN_HOSTS

Attributes

credentials[R]
url[R]

Public Class Methods

new(url:, credentials:) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 11
def initialize(url:, credentials:)
  @url = url
  @credentials = credentials
end

Public Instance Methods

head_commit_for_ref(ref) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 32
def head_commit_for_ref(ref)
  if ref == "HEAD"
    # Remove the opening clause of the upload pack as this isn't always
    # followed by a line break. When it isn't (e.g., with Bitbucket) it
    # causes problems for our `sha_for_update_pack_line` logic
    line = upload_pack.gsub(/.*git-upload-pack/, "").
           lines.find { |l| l.include?(" HEAD") }
    return sha_for_update_pack_line(line) if line
  end

  refs_for_upload_pack.
    find { |r| r.name == ref }&.
    commit_sha
end
ref_names() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 28
def ref_names
  refs_for_upload_pack.map(&:name)
end
tags() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 22
def tags
  return [] unless upload_pack

  @tags ||= tags_for_upload_pack
end
upload_pack() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 16
def upload_pack
  @upload_pack ||= fetch_upload_pack_for(url)
rescue Octokit::ClientError
  raise Dependabot::GitDependenciesNotReachable, [url]
end

Private Instance Methods

excon_defaults() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 202
def excon_defaults
  # Some git hosts are slow when returning a large number of tags
  SharedHelpers.excon_defaults(read_timeout: 20)
end
fetch_raw_upload_pack_for(uri) click to toggle source

rubocop:enable Metrics/PerceivedComplexity

# File lib/dependabot/git_metadata_fetcher.rb, line 83
def fetch_raw_upload_pack_for(uri)
  url = service_pack_uri(uri)
  url = url.rpartition("@").tap { |a| a.first.gsub!("@", "%40") }.join
  Excon.get(
    url,
    idempotent: true,
    **excon_defaults
  )
end
fetch_raw_upload_pack_with_git_for(uri) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 93
def fetch_raw_upload_pack_with_git_for(uri)
  service_pack_uri = uri
  service_pack_uri += ".git" unless service_pack_uri.end_with?(".git")

  env = { "PATH" => ENV["PATH"] }
  command = "git ls-remote #{service_pack_uri}"
  command = SharedHelpers.escape_command(command)

  stdout, stderr, process = Open3.capture3(env, command)
  # package the command response like a HTTP response so error handling
  # remains unchanged
  if process.success?
    OpenStruct.new(body: stdout, status: 200)
  else
    OpenStruct.new(body: stderr, status: 500)
  end
end
fetch_upload_pack_for(uri) click to toggle source

rubocop:disable Metrics/PerceivedComplexity

# File lib/dependabot/git_metadata_fetcher.rb, line 52
def fetch_upload_pack_for(uri)
  response = fetch_raw_upload_pack_for(uri)
  return response.body if response.status == 200

  response_with_git = fetch_raw_upload_pack_with_git_for(uri)
  return response_with_git.body if response_with_git.status == 200

  raise Dependabot::GitDependenciesNotReachable, [uri] unless uri.match?(KNOWN_HOSTS)

  raise "Unexpected response: #{response.status} - #{response.body}" if response.status < 400

  if uri.match?(/github\.com/i)
    response = response.data
    response[:response_headers] = response[:headers]
    raise Octokit::Error.from_response(response)
  end

  raise "Server error at #{uri}: #{response.body}" if response.status >= 500

  raise Dependabot::GitDependenciesNotReachable, [uri]
rescue Excon::Error::Socket, Excon::Error::Timeout
  retry_count ||= 0
  retry_count += 1

  sleep(rand(0.9)) && retry if retry_count <= 2 && uri.match?(KNOWN_HOSTS)
  raise if uri.match?(KNOWN_HOSTS)

  raise Dependabot::GitDependenciesNotReachable, [uri]
end
parse_refs_for_upload_pack() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 127
def parse_refs_for_upload_pack
  peeled_lines = []

  result = upload_pack.lines.each_with_object({}) do |line, res|
    full_ref_name = line.split.last
    next unless full_ref_name.start_with?("refs/tags", "refs/heads")

    peeled_lines << line && next if line.strip.end_with?("^{}")

    ref_name = full_ref_name.sub(%r{^refs/(tags|heads)/}, "").strip
    sha = sha_for_update_pack_line(line)

    res[ref_name] = OpenStruct.new(
      name: ref_name,
      ref_sha: sha,
      ref_type: full_ref_name.start_with?("refs/tags") ? :tag : :head,
      commit_sha: sha
    )
  end

  # Loop through the peeled lines, updating the commit_sha for any
  # matching tags in our results hash
  peeled_lines.each do |line|
    ref_name = line.split(%r{ refs/(tags|heads)/}).
               last.strip.gsub(/\^{}$/, "")
    next unless result[ref_name]

    result[ref_name].commit_sha = sha_for_update_pack_line(line)
  end

  result.values
end
refs_for_upload_pack() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 123
def refs_for_upload_pack
  @refs_for_upload_pack ||= parse_refs_for_upload_pack
end
scheme_for_uri(uri) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 190
def scheme_for_uri(uri)
  if uri.match?(%r{^http://})
    "http"
  else
    "https"
  end
end
service_pack_uri(uri) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 160
def service_pack_uri(uri)
  service_pack_uri = uri_with_auth(uri)
  service_pack_uri = service_pack_uri.gsub(%r{/$}, "")
  service_pack_uri += ".git" unless service_pack_uri.end_with?(".git")
  service_pack_uri + "/info/refs?service=git-upload-pack"
end
sha_for_update_pack_line(line) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 198
def sha_for_update_pack_line(line)
  line.split.first.chars.last(40).join
end
tags_for_upload_pack() click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 111
def tags_for_upload_pack
  refs_for_upload_pack.
    select { |ref| ref.ref_type == :tag }.
    map do |ref|
      OpenStruct.new(
        name: ref.name,
        tag_sha: ref.ref_sha,
        commit_sha: ref.commit_sha
      )
    end
end
uri_with_auth(uri) click to toggle source
# File lib/dependabot/git_metadata_fetcher.rb, line 167
def uri_with_auth(uri)
  bare_uri =
    if uri.include?("git@") then uri.split("git@").last.sub(%r{:/?}, "/")
    else uri.sub(%r{.*?://}, "")
    end
  cred = credentials.select { |c| c["type"] == "git_source" }.
         find { |c| bare_uri.start_with?(c["host"]) }

  scheme = scheme_for_uri(uri)

  if bare_uri.match?(%r{[^/]+:[^/]+@})
    # URI already has authentication details
    "#{scheme}://#{bare_uri}"
  elsif cred&.fetch("username", nil) && cred&.fetch("password", nil)
    # URI doesn't have authentication details, but we have credentials
    auth_string = "#{cred.fetch('username')}:#{cred.fetch('password')}"
    "#{scheme}://#{auth_string}@#{bare_uri}"
  else
    # No credentials, so just return the http(s) URI
    "#{scheme}://#{bare_uri}"
  end
end