Skip to content

Commit f5e555f

Browse files
Fix cooldown ignored in additional_dependencies issue (#15124)
* handle pubspec validation errors gracefully * fix cooldown incorrect update in pre-commit * consolidate git clone calls into one and add specs * add specs for passing cooldown to all ecosystems --------- Co-authored-by: Hariharan Thavachelvam <164553783+thavaahariharangit@users.noreply.github.com>
1 parent 5f43948 commit f5e555f

17 files changed

Lines changed: 535 additions & 74 deletions

File tree

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/base.rb

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# frozen_string_literal: true
33

44
require "sorbet-runtime"
5+
require "dependabot/package/release_cooldown_options"
56

67
module Dependabot
78
module PreCommit
@@ -43,14 +44,16 @@ class Base
4344
source: T::Hash[Symbol, T.untyped],
4445
credentials: T::Array[Dependabot::Credential],
4546
requirements: T::Array[T::Hash[Symbol, T.untyped]],
46-
current_version: T.nilable(String)
47+
current_version: T.nilable(String),
48+
cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
4749
).void
4850
end
49-
def initialize(source:, credentials:, requirements:, current_version:)
51+
def initialize(source:, credentials:, requirements:, current_version:, cooldown_options: nil)
5052
@source = source
5153
@credentials = credentials
5254
@requirements = requirements
5355
@current_version = current_version
56+
@cooldown_options = cooldown_options
5457
end
5558

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

85+
sig { returns(T.nilable(Dependabot::Package::ReleaseCooldownOptions)) }
86+
attr_reader :cooldown_options
87+
8288
sig { returns(T.nilable(String)) }
8389
def package_name
8490
source[:package_name]&.to_s

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/dart.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,8 @@ def build_pub_update_checker
8585
credentials: credentials,
8686
ignored_versions: [],
8787
security_advisories: [],
88-
raise_on_ignored: false
88+
raise_on_ignored: false,
89+
update_cooldown: cooldown_options
8990
)
9091
end
9192

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/go.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,8 @@ def build_go_update_checker
8989
credentials: credentials,
9090
ignored_versions: [],
9191
security_advisories: [],
92-
raise_on_ignored: false
92+
raise_on_ignored: false,
93+
update_cooldown: cooldown_options
9394
)
9495
end
9596

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/node.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ def build_npm_update_checker
8686
credentials: credentials,
8787
ignored_versions: [],
8888
security_advisories: [],
89-
raise_on_ignored: false
89+
raise_on_ignored: false,
90+
update_cooldown: cooldown_options
9091
)
9192
end
9293

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/python.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,8 @@ def build_pip_update_checker
9292
credentials: credentials,
9393
ignored_versions: [],
9494
security_advisories: [],
95-
raise_on_ignored: false
95+
raise_on_ignored: false,
96+
update_cooldown: cooldown_options
9697
)
9798
end
9899

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/ruby.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,8 @@ def build_bundler_update_checker
8686
credentials: credentials,
8787
ignored_versions: [],
8888
security_advisories: [],
89-
raise_on_ignored: false
89+
raise_on_ignored: false,
90+
update_cooldown: cooldown_options
9091
)
9192
end
9293

pre_commit/lib/dependabot/pre_commit/additional_dependency_checkers/rust.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,8 @@ def build_cargo_update_checker
8787
credentials: credentials,
8888
ignored_versions: [],
8989
security_advisories: [],
90-
raise_on_ignored: false
90+
raise_on_ignored: false,
91+
update_cooldown: cooldown_options
9192
)
9293
end
9394

pre_commit/lib/dependabot/pre_commit/metadata_finder.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def look_up_source
2020
if info.nil?
2121
dependency.name
2222
else
23-
info[:url] || info.fetch("url")
23+
info[:url] || info[:repo_url] || info["url"] || dependency.name
2424
end
2525
Source.from_url(url)
2626
end

pre_commit/lib/dependabot/pre_commit/update_checker.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,8 @@ def additional_dependency_checker(language, source)
349349
source: source,
350350
credentials: credentials,
351351
requirements: dependency.requirements,
352-
current_version: dependency.version
352+
current_version: dependency.version,
353+
cooldown_options: update_cooldown
353354
)
354355
rescue StandardError => e
355356
Dependabot.logger.error("Error creating checker for #{language}: #{e.message}")

pre_commit/lib/dependabot/pre_commit/update_checker/latest_version_finder.rb

Lines changed: 110 additions & 64 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ def initialize(
4646
@raise_on_ignored = raise_on_ignored
4747
@options = options
4848
@cooldown_options = cooldown_options
49+
@cooldown_selected_tag = T.let(nil, T.nilable(T::Hash[Symbol, T.untyped]))
4950

5051
@git_helper = T.let(git_helper, Dependabot::PreCommit::Helpers::Githelper)
5152
super(
@@ -85,22 +86,35 @@ def latest_release_version
8586

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

88-
release = cooldown_filter(release)
89-
if release.nil?
90-
Dependabot.logger.info("Returning current version/ref (no viable filtered release) #{current_version}")
91-
return current_version
92-
end
93-
94-
release
89+
filter_release_with_cooldown(release)
9590
end
9691

9792
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
9893
def latest_version_tag
99-
available_latest_version_tag
94+
@cooldown_selected_tag || available_latest_version_tag
10095
end
10196

10297
private
10398

99+
sig do
100+
params(release: T.any(Dependabot::Version, String))
101+
.returns(T.nilable(T.any(Dependabot::Version, String)))
102+
end
103+
def filter_release_with_cooldown(release)
104+
return release unless cooldown_enabled?
105+
return release unless cooldown_options
106+
# Commit SHA releases have no version ordering to fall back through
107+
return release if release_type_sha?
108+
109+
Dependabot.logger.info("Applying cooldown filter for #{dependency.name}")
110+
111+
result = find_latest_version_outside_cooldown
112+
return result if result
113+
114+
Dependabot.logger.info("All candidate versions are in cooldown, keeping current version #{current_version}")
115+
current_version
116+
end
117+
104118
sig { returns(T.nilable(Dependabot::PreCommit::Package::PackageDetailsFetcher)) }
105119
def package_details_fetcher
106120
@package_details_fetcher ||= T.let(
@@ -136,58 +150,89 @@ def cooldown_enabled?
136150
true
137151
end
138152

139-
sig do
140-
params(release: T.nilable(T.any(Dependabot::Version, String)))
141-
.returns(T.nilable(T.any(Dependabot::Version, String)))
142-
end
143-
def cooldown_filter(release)
144-
return release unless cooldown_enabled?
145-
return release unless cooldown_options
153+
# Checks versions from latest downward (among versions > current_version)
154+
# in a single bare clone. Returns the newest version outside cooldown,
155+
# or nil if all candidates are within cooldown.
156+
sig { returns(T.nilable(Dependabot::Version)) }
157+
def find_latest_version_outside_cooldown
158+
candidates = version_candidates_descending
159+
return nil if candidates.empty?
146160

147-
Dependabot.logger.info("Initializing cooldown filter")
148-
release_date = commit_metadata_details
161+
url = @git_helper.git_commit_checker.dependency_source_details&.fetch(:url)
162+
source = T.must(Source.from_url(url))
149163

150-
unless release_date
151-
Dependabot.logger.info("No release date found, skipping cooldown filtering")
152-
return release
164+
SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
165+
repo_contents_path = File.join(temp_dir, File.basename(source.repo))
166+
SharedHelpers.run_shell_command("git clone --bare --no-recurse-submodules #{url} #{repo_contents_path}")
167+
168+
Dir.chdir(repo_contents_path) do
169+
return check_candidates_cooldown(candidates)
170+
end
153171
end
172+
rescue StandardError => e
173+
Dependabot.logger.error("Error checking cooldown for #{dependency.name}: #{e.message}")
174+
nil
175+
end
154176

155-
if release_in_cooldown_period?(Time.parse(release_date))
156-
Dependabot.logger.info("Filtered out (cooldown) #{dependency.name}, #{release}")
157-
return nil
177+
# Iterates candidate tags inside a bare clone directory, returning the first
178+
# version whose release date falls outside the cooldown window.
179+
sig do
180+
params(candidates: T::Array[T::Hash[Symbol, T.untyped]])
181+
.returns(T.nilable(Dependabot::Version))
182+
end
183+
def check_candidates_cooldown(candidates)
184+
filtered_count = 0
185+
186+
candidates.each do |tag|
187+
commit_sha = tag[:commit_sha]
188+
next unless commit_sha
189+
190+
date_str = SharedHelpers.run_shell_command(
191+
"git show --no-patch --format=\"%cd\" --date=iso #{commit_sha}",
192+
fingerprint: "git show --no-patch --format=\"%cd\" --date=iso <commit_sha>"
193+
)
194+
release_date = Time.parse(date_str)
195+
196+
if release_in_cooldown_period?(release_date)
197+
filtered_count += 1
198+
else
199+
log_cooldown_result(filtered_count, tag[:version], release_date)
200+
@cooldown_selected_tag = tag
201+
return T.cast(tag[:version], Dependabot::Version)
202+
end
158203
end
159204

160-
release
161-
end
162-
163-
sig { returns(T.nilable(String)) }
164-
def commit_metadata_details
165-
@commit_metadata_details ||= T.let(
166-
begin
167-
url = @git_helper.git_commit_checker.dependency_source_details&.fetch(:url)
168-
source = T.must(Source.from_url(url))
169-
170-
SharedHelpers.in_a_temporary_directory(File.dirname(source.repo)) do |temp_dir|
171-
repo_contents_path = File.join(temp_dir, File.basename(source.repo))
172-
173-
SharedHelpers.run_shell_command("git clone --bare --no-recurse-submodules #{url} #{repo_contents_path}")
174-
Dir.chdir(repo_contents_path) do
175-
date = SharedHelpers.run_shell_command(
176-
"git show --no-patch --format=\"%cd\" " \
177-
"--date=iso #{commit_ref}"
178-
)
179-
Dependabot.logger.info("Found release date : #{Time.parse(date)}")
180-
return date
181-
end
182-
end
183-
rescue StandardError => e
184-
Dependabot.logger.error("Error (pre_commit) while checking release date for #{dependency.name}")
185-
Dependabot.logger.error(e.message)
186-
187-
nil
188-
end,
189-
T.nilable(String)
205+
Dependabot.logger.info(
206+
"Filtered #{filtered_count} version(s) due to cooldown for #{dependency.name}, " \
207+
"no eligible version found"
190208
)
209+
nil
210+
end
211+
212+
sig do
213+
params(filtered_count: Integer, version: T.untyped, release_date: Time).void
214+
end
215+
def log_cooldown_result(filtered_count, version, release_date)
216+
if filtered_count.positive?
217+
Dependabot.logger.info(
218+
"Filtered #{filtered_count} version(s) due to cooldown for #{dependency.name}"
219+
)
220+
end
221+
Dependabot.logger.info("Selected version #{version} (released #{release_date})")
222+
end
223+
224+
# Returns all version tags > current_version, sorted descending (latest first).
225+
# This ensures we evaluate from the newest candidate downward.
226+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
227+
def version_candidates_descending
228+
all_tags = @git_helper.git_commit_checker.local_tags_for_allowed_versions_matching_existing_precision
229+
cur_version = current_version
230+
231+
all_tags
232+
.select { |tag| tag[:version].is_a?(Gem::Version) }
233+
.select { |tag| cur_version.nil? || tag[:version] > cur_version }
234+
.sort_by { |tag| tag[:version] }
235+
.reverse
191236
end
192237

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

199244
days = T.must(cooldown).default_days
200245

201-
Dependabot.logger.info(
202-
"Days since release : #{(Time.now.to_i - release_date.to_i) / (24 * 60 * 60)} " \
203-
"(cooldown days #{days})"
204-
)
205-
206246
Dependabot::UpdateCheckers::CooldownCalculation
207247
.within_cooldown_window?(release_date, days)
208248
end
209249

210-
sig { returns(String) }
211-
def commit_ref
212-
T.cast(latest_version_tag&.fetch(:commit_sha), String)
213-
end
214-
215250
sig { returns(T.nilable(T.any(Dependabot::Version, String))) }
216251
def current_version
217252
return dependency.source_details(allowed_types: ["git"])&.fetch(:ref) if release_type_sha?
218253

219-
T.let(dependency.numeric_version, T.nilable(Dependabot::Version))
254+
# numeric_version handles plain versions like "4.4.0"
255+
numeric = dependency.numeric_version
256+
return numeric if numeric
257+
258+
# Handle v-prefixed tags like "v4.4.0" common in pre-commit
259+
version_str = dependency.version
260+
return nil unless version_str
261+
262+
stripped = version_str.sub(/\Av/i, "")
263+
return nil unless Dependabot::PreCommit::Version.correct?(stripped)
264+
265+
Dependabot::PreCommit::Version.new(stripped)
220266
end
221267

222268
sig { returns(T::Boolean) }

0 commit comments

Comments
 (0)