@@ -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 ( /\A v/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