Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
# frozen_string_literal: true

require "sorbet-runtime"
require "dependabot/package/release_cooldown_options"

module Dependabot
module PreCommit
Expand Down Expand Up @@ -43,14 +44,16 @@ class Base
source: T::Hash[Symbol, T.untyped],
credentials: T::Array[Dependabot::Credential],
requirements: T::Array[T::Hash[Symbol, T.untyped]],
current_version: T.nilable(String)
current_version: T.nilable(String),
cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
).void
end
def initialize(source:, credentials:, requirements:, current_version:)
def initialize(source:, credentials:, requirements:, current_version:, cooldown_options: nil)
@source = source
@credentials = credentials
@requirements = requirements
@current_version = current_version
@cooldown_options = cooldown_options
end

# Find the latest available version for this dependency
Expand Down Expand Up @@ -79,6 +82,9 @@ def updated_requirements(latest_version); end
sig { returns(T.nilable(String)) }
attr_reader :current_version

sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
attr_reader :cooldown_options

sig { returns(T.nilable(String)) }
def package_name
source[:package_name]&.to_s
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ def build_pub_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,8 @@ def build_go_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def build_npm_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,8 @@ def build_pip_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ def build_bundler_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,8 @@ def build_cargo_update_checker
credentials: credentials,
ignored_versions: [],
security_advisories: [],
raise_on_ignored: false
raise_on_ignored: false,
update_cooldown: cooldown_options
)
end

Expand Down
2 changes: 1 addition & 1 deletion pre_commit/lib/dependabot/pre_commit/metadata_finder.rb
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ def look_up_source
if info.nil?
dependency.name
else
info[:url] || info.fetch("url")
info[:url] || info[:repo_url] || info["url"] || dependency.name
end
Source.from_url(url)
end
Expand Down
3 changes: 2 additions & 1 deletion pre_commit/lib/dependabot/pre_commit/update_checker.rb
Original file line number Diff line number Diff line change
Expand Up @@ -349,7 +349,8 @@ def additional_dependency_checker(language, source)
source: source,
credentials: credentials,
requirements: dependency.requirements,
current_version: dependency.version
current_version: dependency.version,
cooldown_options: update_cooldown
)
rescue StandardError => e
Dependabot.logger.error("Error creating checker for #{language}: #{e.message}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ def initialize(
@raise_on_ignored = raise_on_ignored
@options = options
@cooldown_options = cooldown_options
@cooldown_selected_tag = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))

@git_helper = T.let(git_helper, Dependabot::PreCommit::Helpers::Githelper)
super(
Expand Down Expand Up @@ -85,22 +86,35 @@ def latest_release_version

Dependabot.logger.info("Available release version/ref is #{release}")

release = cooldown_filter(release)
if release.nil?
Dependabot.logger.info("Returning current version/ref (no viable filtered release) #{current_version}")
return current_version
end

release
filter_release_with_cooldown(release)
end

sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
def latest_version_tag
available_latest_version_tag
@cooldown_selected_tag || available_latest_version_tag
end

private

sig do
params(release: T.any(Dependabot::Version, String))
.returns(T.nilable(T.any(Dependabot::Version, String)))
end
def filter_release_with_cooldown(release)
return release unless cooldown_enabled?
return release unless cooldown_options
# Commit SHA releases have no version ordering to fall back through
return release if release_type_sha?

Dependabot.logger.info("Applying cooldown filter for #{dependency.name}")

result = find_latest_version_outside_cooldown
return result if result

Dependabot.logger.info("All candidate versions are in cooldown, keeping current version #{current_version}")
current_version
end

sig { returns(T.nilable(Dependabot::PreCommit::Package::PackageDetailsFetcher)) }
def package_details_fetcher
@package_details_fetcher ||= T.let(
Expand Down Expand Up @@ -136,58 +150,89 @@ def cooldown_enabled?
true
end

sig do
params(release: T.nilable(T.any(Dependabot::Version, String)))
.returns(T.nilable(T.any(Dependabot::Version, String)))
end
def cooldown_filter(release)
return release unless cooldown_enabled?
return release unless cooldown_options
# Checks versions from latest downward (among versions > current_version)
# in a single bare clone. Returns the newest version outside cooldown,
# or nil if all candidates are within cooldown.
sig { returns(T.nilable(Dependabot::Version)) }
def find_latest_version_outside_cooldown
candidates = version_candidates_descending
return nil if candidates.empty?

Dependabot.logger.info("Initializing cooldown filter")
release_date = commit_metadata_details
url = @git_helper.git_commit_checker.dependency_source_details&.fetch(:url)
source = T.must(Source.from_url(url))

unless release_date
Dependabot.logger.info("No release date found, skipping cooldown filtering")
return release
SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
repo_contents_path = File.join(temp_dir, File.basename(source.repo))
SharedHelpers.run_shell_command("git clone --bare --no-recurse-submodules #{url} #{repo_contents_path}")

Dir.chdir(repo_contents_path) do
return check_candidates_cooldown(candidates)
end
end
rescue StandardError => e
Dependabot.logger.error("Error checking cooldown for #{dependency.name}: #{e.message}")
nil
end

if release_in_cooldown_period?(Time.parse(release_date))
Dependabot.logger.info("Filtered out (cooldown) #{dependency.name}, #{release}")
return nil
# Iterates candidate tags inside a bare clone directory, returning the first
# version whose release date falls outside the cooldown window.
sig do
params(candidates: T::Array[T::Hash[Symbol, T.untyped]])
.returns(T.nilable(Dependabot::Version))
end
def check_candidates_cooldown(candidates)
filtered_count = 0

candidates.each do |tag|
commit_sha = tag[:commit_sha]
next unless commit_sha

date_str = SharedHelpers.run_shell_command(
"git show --no-patch --format=\"%cd\" --date=iso #{commit_sha}",
fingerprint: "git show --no-patch --format=\"%cd\" --date=iso <commit_sha>"
)
release_date = Time.parse(date_str)

if release_in_cooldown_period?(release_date)
filtered_count += 1
else
log_cooldown_result(filtered_count, tag[:version], release_date)
@cooldown_selected_tag = tag
return T.cast(tag[:version], Dependabot::Version)
end
end

release
end

sig { returns(T.nilable(String)) }
def commit_metadata_details
@commit_metadata_details ||= T.let(
begin
url = @git_helper.git_commit_checker.dependency_source_details&.fetch(:url)
source = T.must(Source.from_url(url))

SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
repo_contents_path = File.join(temp_dir, File.basename(source.repo))

SharedHelpers.run_shell_command("git clone --bare --no-recurse-submodules #{url} #{repo_contents_path}")
Dir.chdir(repo_contents_path) do
date = SharedHelpers.run_shell_command(
"git show --no-patch --format=\"%cd\" " \
"--date=iso #{commit_ref}"
)
Dependabot.logger.info("Found release date : #{Time.parse(date)}")
return date
end
end
rescue StandardError => e
Dependabot.logger.error("Error (pre_commit) while checking release date for #{dependency.name}")
Dependabot.logger.error(e.message)

nil
end,
T.nilable(String)
Dependabot.logger.info(
"Filtered #{filtered_count} version(s) due to cooldown for #{dependency.name}, " \
"no eligible version found"
)
nil
end

sig do
params(filtered_count: Integer, version: T.untyped, release_date: Time).void
end
def log_cooldown_result(filtered_count, version, release_date)
if filtered_count.positive?
Dependabot.logger.info(
"Filtered #{filtered_count} version(s) due to cooldown for #{dependency.name}"
)
end
Dependabot.logger.info("Selected version #{version} (released #{release_date})")
end

# Returns all version tags > current_version, sorted descending (latest first).
# This ensures we evaluate from the newest candidate downward.
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
def version_candidates_descending
all_tags = @git_helper.git_commit_checker.local_tags_for_allowed_versions_matching_existing_precision
cur_version = current_version

all_tags
.select { |tag| tag[:version].is_a?(Gem::Version) }
.select { |tag| cur_version.nil? || tag[:version] > cur_version }
Comment thread
AbhishekBhaskar marked this conversation as resolved.
.sort_by { |tag| tag[:version] }
.reverse
end

sig { params(release_date: Time).returns(T::Boolean) }
Expand All @@ -198,25 +243,26 @@ def release_in_cooldown_period?(release_date)

days = T.must(cooldown).default_days

Dependabot.logger.info(
"Days since release : #{(Time.now.to_i - release_date.to_i) / (24 * 60 * 60)} " \
"(cooldown days #{days})"
)

Dependabot::UpdateCheckers::CooldownCalculation
.within_cooldown_window?(release_date, days)
end

sig { returns(String) }
def commit_ref
T.cast(latest_version_tag&.fetch(:commit_sha), String)
end

sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
def current_version
return dependency.source_details(allowed_types: ["git"])&.fetch(:ref) if release_type_sha?

T.let(dependency.numeric_version, T.nilable(Dependabot::Version))
# numeric_version handles plain versions like "4.4.0"
numeric = dependency.numeric_version
return numeric if numeric

# Handle v-prefixed tags like "v4.4.0" common in pre-commit
version_str = dependency.version
return nil unless version_str

stripped = version_str.sub(/\Av/i, "")
return nil unless Dependabot::PreCommit::Version.correct?(stripped)

Dependabot::PreCommit::Version.new(stripped)
end

sig { returns(T::Boolean) }
Expand Down
Loading
Loading