class Dependabot::MetadataFinders::Base::ReleaseFinder

Attributes

credentials[R]
dependency[R]
source[R]

Public Class Methods

new(source:, dependency:, credentials:) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 14
def initialize(source:, dependency:, credentials:)
  @source = source
  @dependency = dependency
  @credentials = credentials
end

Public Instance Methods

releases_text() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 31
def releases_text
  return unless relevant_releases.any?
  return if relevant_releases.all? { |r| r.body.nil? || r.body == "" }

  relevant_releases.map { |r| serialize_release(r) }.join("\n\n")
end
releases_url() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 20
def releases_url
  return unless source

  case source.provider
  when "github" then "#{source.url}/releases"
  when "gitlab", "azure" then "#{source.url}/tags"
  when "bitbucket" then nil
  else raise "Unexpected repo provider '#{source.provider}'"
  end
end

Private Instance Methods

all_dep_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 40
def all_dep_releases
  releases = all_releases
  dep_prefix = dependency.name.downcase

  releases_with_dependency_name =
    releases.
    reject { |r| r.tag_name.nil? }.
    select { |r| r.tag_name.downcase.include?(dep_prefix) }

  return releases unless releases_with_dependency_name.any?

  releases_with_dependency_name
end
all_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 54
def all_releases
  @all_releases ||= fetch_dependency_releases
end
fetch_dependency_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 192
def fetch_dependency_releases
  return [] unless source

  case source.provider
  when "github" then fetch_github_releases
  # Bitbucket doesn't support releases and Azure can't list API for annotated tags
  when "bitbucket", "azure" then []
  when "gitlab" then fetch_gitlab_releases
  else raise "Unexpected repo provider '#{source.provider}'"
  end
end
fetch_github_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 204
def fetch_github_releases
  releases = github_client.releases(source.repo, per_page: 100)

  # Remove any releases without a tag name. These are draft releases and
  # aren't yet associated with a tag, so shouldn't be used.
  releases = releases.reject { |r| r.tag_name.nil? }

  clean_release_names =
    releases.map { |r| r.tag_name.gsub(/^[^0-9\.]*/, "") }

  if clean_release_names.all? { |nm| version_class.correct?(nm) }
    releases.sort_by do |r|
      version_class.new(r.tag_name.gsub(/^[^0-9\.]*/, ""))
    end.reverse
  else
    releases.sort_by(&:id).reverse
  end
rescue Octokit::NotFound, Octokit::UnavailableForLegalReasons
  []
end
fetch_gitlab_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 225
def fetch_gitlab_releases
  releases =
    gitlab_client.
    tags(source.repo).
    select(&:release).
    sort_by { |r| r.commit.authored_date }.
    reverse

  releases.map do |tag|
    OpenStruct.new(
      name: tag.name,
      tag_name: tag.release.tag_name,
      body: tag.release.description,
      html_url: "#{source.url}/tags/#{tag.name}"
    )
  end
rescue Gitlab::Error::NotFound
  []
end
filter_releases_using_previous_release(releases) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 96
def filter_releases_using_previous_release(releases)
  return releases if releases.index(previous_release).nil?

  releases.first(releases.index(previous_release))
end
filter_releases_using_previous_version(releases, conservative:) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 108
def filter_releases_using_previous_version(releases, conservative:)
  releases.reject do |release|
    cleaned_tag = release.tag_name.gsub(/^[^0-9]*/, "")
    cleaned_name = release.name&.gsub(/^[^0-9]*/, "")
    dot_count = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
                map { |nm| nm.chars.count(".") }.max

    tag_version = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
                  select { |nm| version_class.correct?(nm) }.
                  select { |nm| nm.chars.count(".") == dot_count }.
                  map { |nm| version_class.new(nm) }.max

    next conservative unless tag_version

    # Reject any releases that are less than the previous version
    # (e.g., if two major versions are being maintained)
    tag_version <= version_class.new(previous_version)
  end
end
filter_releases_using_updated_release(releases) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 102
def filter_releases_using_updated_release(releases)
  return releases if releases.index(updated_release).nil?

  releases[releases.index(updated_release)..-1]
end
filter_releases_using_updated_version(releases, conservative:) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 128
def filter_releases_using_updated_version(releases, conservative:)
  updated_version = version_class.new(new_version)

  releases.reject do |release|
    cleaned_tag = release.tag_name.gsub(/^[^0-9]*/, "")
    cleaned_name = release.name&.gsub(/^[^0-9]*/, "")
    dot_count = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
                map { |nm| nm.chars.count(".") }.max

    tag_version = [cleaned_tag, cleaned_name].compact.reject(&:empty?).
                  select { |nm| version_class.correct?(nm) }.
                  select { |nm| nm.chars.count(".") == dot_count }.
                  map { |nm| version_class.new(nm) }.min

    next conservative unless tag_version

    # Reject any releases that are greater than the updated version
    # (e.g., if two major versions are being maintained)
    tag_version > updated_version
  end
end
github_client() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 300
def github_client
  @github_client ||= Dependabot::Clients::GithubWithRetries.
                     for_github_dot_com(credentials: credentials)
end
gitlab_client() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 295
def gitlab_client
  @gitlab_client ||= Dependabot::Clients::GitlabWithRetries.
                     for_gitlab_dot_com(credentials: credentials)
end
new_ref() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 283
def new_ref
  new_refs = dependency.requirements.map do |r|
    r.dig(:source, "ref") || r.dig(:source, :ref)
  end.compact.uniq
  return new_refs.first if new_refs.count == 1
end
new_version() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 264
def new_version
  # New version looks like a git SHA and there's a new ref, guarding
  # against changes to a nil new_ref (not certain this can actually
  # happen atm)
  if dependency.version.match?(/^[0-9a-f]{40}$/) && ref_changed? &&
     new_ref
    return new_ref
  end

  dependency.version
end
previous_ref() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 276
def previous_ref
  previous_refs = dependency.previous_requirements.map do |r|
    r.dig(:source, "ref") || r.dig(:source, :ref)
  end.compact.uniq
  return previous_refs.first if previous_refs.count == 1
end
previous_release() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 154
def previous_release
  release_for_version(previous_version)
end
previous_version() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 245
def previous_version
  # If we don't have a previous version, we *may* still be able to
  # figure one out if a ref was provided and has been changed (in which
  # case the previous ref was essentially the version).
  if dependency.previous_version.nil?
    return ref_changed? ? previous_ref : nil
  end

  # Previous version looks like a git SHA and there's a previous ref, we
  # could be changing to a nil previous ref in which case we want to
  # fall back to tge sha version
  if dependency.previous_version.match?(/^[0-9a-f]{40}$/) &&
     ref_changed? && previous_ref
    previous_ref
  else
    dependency.previous_version
  end
end
ref_changed?() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 290
def ref_changed?
  # We could go from multiple previous refs (nil) to a single new ref
  previous_ref != new_ref
end
release_body_includes_title?(release) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 179
def release_body_includes_title?(release)
  title = release.name.to_s == "" ? release.tag_name : release.name
  release.body.to_s.match?(/\A\s*\#*\s*#{Regexp.quote(title)}/m)
end
release_for_version(version) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 158
def release_for_version(version)
  return nil unless version

  release_regex = version_regex(version)
  # Doing two loops looks inefficient, but it ensures consistency
  all_dep_releases.find { |r| release_regex.match?(r.tag_name.to_s) } ||
    all_dep_releases.find { |r| release_regex.match?(r.name.to_s) }
end
releases_since_previous_version() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 78
def releases_since_previous_version
  return [updated_release].compact unless previous_version

  if previous_release && version_class.correct?(previous_version)
    releases = filter_releases_using_previous_release(all_dep_releases)
    filter_releases_using_previous_version(releases, conservative: true)
  elsif previous_release
    filter_releases_using_previous_release(all_dep_releases)
  elsif version_class.correct?(previous_version)
    filter_releases_using_previous_version(
      all_dep_releases,
      conservative: false
    )
  else
    [updated_release].compact
  end
end
relevant_releases() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 58
def relevant_releases
  releases = releases_since_previous_version

  # Sometimes we can't filter the releases properly (if they're
  # prefixed by a number that gets confused with the version). In this
  # case, the best we can do is return nil.
  return [] unless releases.any?

  if updated_release && version_class.correct?(new_version)
    releases = filter_releases_using_updated_release(releases)
    filter_releases_using_updated_version(releases, conservative: true)
  elsif updated_release
    filter_releases_using_updated_release(releases)
  elsif version_class.correct?(new_version)
    filter_releases_using_updated_version(releases, conservative: false)
  else
    [updated_release].compact
  end
end
serialize_release(release) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 167
def serialize_release(release)
  rel = release
  title = "## #{rel.name.to_s == '' ? rel.tag_name : rel.name}\n"
  body = if rel.body.to_s.gsub(/\n*\z/m, "") == ""
           "No release notes provided."
         else
           rel.body.gsub(/\n*\z/m, "")
         end

  release_body_includes_title?(rel) ? body : title + body
end
updated_release() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 150
def updated_release
  release_for_version(new_version)
end
version_class() click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 188
def version_class
  Utils.version_class_for_package_manager(dependency.package_manager)
end
version_regex(version) click to toggle source
# File lib/dependabot/metadata_finders/base/release_finder.rb, line 184
def version_regex(version)
  /(?:[^0-9\.]|\A)#{Regexp.escape(version || "unknown")}\z/
end