dependabot-common 0.384.0 → 0.386.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/dependabot/command_helpers.rb +9 -9
- data/lib/dependabot/dependency_graphers/base.rb +103 -0
- data/lib/dependabot/errors.rb +5 -5
- data/lib/dependabot/experiments.rb +5 -5
- data/lib/dependabot/file_parsers/base.rb +2 -2
- data/lib/dependabot/file_updaters/base.rb +2 -2
- data/lib/dependabot/git_commit_checker.rb +163 -4
- data/lib/dependabot/package/package_latest_version_finder.rb +3 -3
- data/lib/dependabot/package/package_release.rb +3 -3
- data/lib/dependabot/pull_request_creator/branch_name_template.rb +138 -0
- data/lib/dependabot/pull_request_creator/branch_namer/base.rb +62 -2
- data/lib/dependabot/pull_request_creator/branch_namer/dependency_group_strategy.rb +37 -2
- data/lib/dependabot/pull_request_creator/branch_namer/multi_ecosystem_strategy.rb +32 -2
- data/lib/dependabot/pull_request_creator/branch_namer/solo_strategy.rb +42 -16
- data/lib/dependabot/pull_request_creator/branch_namer.rb +30 -2
- data/lib/dependabot/pull_request_creator/message_builder.rb +7 -7
- data/lib/dependabot/pull_request_creator.rb +29 -8
- data/lib/dependabot/registry_client.rb +4 -4
- data/lib/dependabot/simple_instrumentor.rb +4 -4
- data/lib/dependabot/source.rb +25 -5
- data/lib/dependabot/update_checkers/base.rb +4 -4
- data/lib/dependabot.rb +1 -1
- metadata +3 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 4e4c26ec485eed8ebb4e35d9edfa4ab996658f450708439941ff8ca253ce0457
|
|
4
|
+
data.tar.gz: '045951912dda5a814efcf305b11ae28e5c6cd97a1e7f72f1c610650d2f9031cb'
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: bb49cbf946335c722ca445b0c314909eb0cf1790b241b68461881873cf254afddb7b7b10a168879ded348af64bdb30f5093882c1221b8f3c7e3ca69bbf6bd59b
|
|
7
|
+
data.tar.gz: ed0b8a8a019adeed81707ee2aae27e1e7e50d8f4c0da2d66cfca4a952df713173706d091062d02cb5a5aa4c18b819747b76ec5f114595fdbb804c4bef9df208b
|
|
@@ -20,46 +20,46 @@ module Dependabot
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
OutputObserver = T.type_alias do
|
|
23
|
-
T.nilable(T.proc.params(data: String).returns(T::Hash[Symbol, T.
|
|
23
|
+
T.nilable(T.proc.params(data: String).returns(T::Hash[Symbol, T.anything]))
|
|
24
24
|
end
|
|
25
25
|
|
|
26
26
|
EnvCmdItem = T.type_alias do
|
|
27
27
|
T.any(
|
|
28
28
|
String,
|
|
29
|
-
T::Hash[T.any(String, Symbol), T.
|
|
29
|
+
T::Hash[T.any(String, Symbol), T.anything]
|
|
30
30
|
)
|
|
31
31
|
end
|
|
32
32
|
|
|
33
33
|
class ProcessStatus
|
|
34
34
|
extend T::Sig
|
|
35
35
|
|
|
36
|
-
sig { params(process_status: Process::Status, custom_exitstatus: T.nilable(Integer)).void }
|
|
36
|
+
sig { params(process_status: T.nilable(Process::Status), custom_exitstatus: T.nilable(Integer)).void }
|
|
37
37
|
def initialize(process_status, custom_exitstatus = nil)
|
|
38
|
-
@process_status = process_status
|
|
38
|
+
@process_status = T.let(process_status, T.nilable(Process::Status))
|
|
39
39
|
@custom_exitstatus = custom_exitstatus
|
|
40
40
|
end
|
|
41
41
|
|
|
42
42
|
# Return the exit status, either from the process status or the custom one
|
|
43
43
|
sig { returns(Integer) }
|
|
44
44
|
def exitstatus
|
|
45
|
-
@custom_exitstatus || @process_status
|
|
45
|
+
@custom_exitstatus || @process_status&.exitstatus || 0
|
|
46
46
|
end
|
|
47
47
|
|
|
48
48
|
# Determine if the process was successful
|
|
49
49
|
sig { returns(T::Boolean) }
|
|
50
50
|
def success?
|
|
51
|
-
@custom_exitstatus.nil? ? @process_status
|
|
51
|
+
@custom_exitstatus.nil? ? @process_status&.success? || false : @custom_exitstatus.zero?
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# Return the PID of the process (if available)
|
|
55
55
|
sig { returns(T.nilable(Integer)) }
|
|
56
56
|
def pid
|
|
57
|
-
@process_status
|
|
57
|
+
@process_status&.pid
|
|
58
58
|
end
|
|
59
59
|
|
|
60
60
|
sig { returns(T.nilable(Integer)) }
|
|
61
61
|
def termsig
|
|
62
|
-
@process_status
|
|
62
|
+
@process_status&.termsig
|
|
63
63
|
end
|
|
64
64
|
|
|
65
65
|
# String representation of the status
|
|
@@ -68,7 +68,7 @@ module Dependabot
|
|
|
68
68
|
if @custom_exitstatus
|
|
69
69
|
"pid #{pid || 'unknown'}: exit #{@custom_exitstatus} (custom status)"
|
|
70
70
|
else
|
|
71
|
-
@process_status
|
|
71
|
+
@process_status&.to_s || "unknown status"
|
|
72
72
|
end
|
|
73
73
|
end
|
|
74
74
|
end
|
|
@@ -3,6 +3,8 @@
|
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
5
5
|
|
|
6
|
+
require "dependabot/dependency_file"
|
|
7
|
+
|
|
6
8
|
module Dependabot
|
|
7
9
|
module DependencyGraphers
|
|
8
10
|
# This is a small value class that specifies the information we expect to be returned for each
|
|
@@ -20,6 +22,29 @@ module Dependabot
|
|
|
20
22
|
const :dependencies, T::Array[String]
|
|
21
23
|
end
|
|
22
24
|
|
|
25
|
+
# A manifest group is a subset of a directory's dependency files that stands on its own as a valid
|
|
26
|
+
# parser input, plus the single file that the group's dependencies should be attributed to.
|
|
27
|
+
#
|
|
28
|
+
# - `primary` is the attribution target (the file that owns the group's dependencies in the snapshot).
|
|
29
|
+
# - `files` is everything the parser needs to resolve the group, including the primary and any sibling
|
|
30
|
+
# files pulled in only to satisfy cross-references.
|
|
31
|
+
#
|
|
32
|
+
# Most ecosystems have exactly one group per directory (a manifest + optional lockfile).
|
|
33
|
+
#
|
|
34
|
+
# Ecosystems where multiple independent manifests routinely share a directory override `manifest_groups`
|
|
35
|
+
# to return one group per independent manifest using ecosystem-specific rules (e.g. Python layered requirements)
|
|
36
|
+
class ManifestGroup < T::ImmutableStruct
|
|
37
|
+
const :primary, Dependabot::DependencyFile
|
|
38
|
+
const :files, T::Array[Dependabot::DependencyFile]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# The resolved output for a single manifest group: the file to attribute to, and the dependencies
|
|
42
|
+
# resolved from that group's files.
|
|
43
|
+
class ManifestGroupSnapshot < T::ImmutableStruct
|
|
44
|
+
const :manifest_file, Dependabot::DependencyFile
|
|
45
|
+
const :resolved_dependencies, T::Hash[String, ResolvedDependency]
|
|
46
|
+
end
|
|
47
|
+
|
|
23
48
|
class Base
|
|
24
49
|
extend T::Sig
|
|
25
50
|
extend T::Helpers
|
|
@@ -78,8 +103,86 @@ module Dependabot
|
|
|
78
103
|
end
|
|
79
104
|
end
|
|
80
105
|
|
|
106
|
+
# Partitions the parsed directory into one or more manifest groups.
|
|
107
|
+
#
|
|
108
|
+
# The default is a single group spanning the whole directory, attributed to `relevant_dependency_file`.
|
|
109
|
+
#
|
|
110
|
+
# Ecosystems where multiple independent manifests share a directory should override this to return
|
|
111
|
+
# one group per manifest.
|
|
112
|
+
sig { overridable.returns(T::Array[ManifestGroup]) }
|
|
113
|
+
def manifest_groups
|
|
114
|
+
[ManifestGroup.new(primary: relevant_dependency_file, files: dependency_files)]
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Resolves each manifest group into a snapshot.
|
|
118
|
+
#
|
|
119
|
+
# When there is a single group, the common case for most ecosystems, we attribute all resolved dependencies
|
|
120
|
+
# to the group's primary without further work required.
|
|
121
|
+
#
|
|
122
|
+
# When there are multiple groups, we instantiate a new scoped grapher for the group's files and resolve each
|
|
123
|
+
# independently.
|
|
124
|
+
sig { returns(T::Array[ManifestGroupSnapshot]) }
|
|
125
|
+
def manifest_group_snapshots
|
|
126
|
+
@manifest_group_snapshots ||= T.let(
|
|
127
|
+
build_manifest_group_snapshots,
|
|
128
|
+
T.nilable(T::Array[ManifestGroupSnapshot])
|
|
129
|
+
)
|
|
130
|
+
end
|
|
131
|
+
|
|
81
132
|
private
|
|
82
133
|
|
|
134
|
+
sig { returns(T::Array[ManifestGroupSnapshot]) }
|
|
135
|
+
def build_manifest_group_snapshots
|
|
136
|
+
groups = manifest_groups
|
|
137
|
+
|
|
138
|
+
if groups.one?
|
|
139
|
+
group = T.must(groups.first)
|
|
140
|
+
return [ManifestGroupSnapshot.new(
|
|
141
|
+
manifest_file: group.primary,
|
|
142
|
+
resolved_dependencies: resolved_dependencies
|
|
143
|
+
)]
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
groups.map do |group|
|
|
147
|
+
scoped = scoped_grapher(group.files)
|
|
148
|
+
snapshot = ManifestGroupSnapshot.new(
|
|
149
|
+
manifest_file: group.primary,
|
|
150
|
+
resolved_dependencies: scoped.resolved_dependencies
|
|
151
|
+
)
|
|
152
|
+
# Ensure we propagate any error flags from the scoped grapher.
|
|
153
|
+
absorb_error_state(scoped)
|
|
154
|
+
snapshot
|
|
155
|
+
end
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Propagates a scoped grapher's subdependency error state onto this grapher so callers can inspect the
|
|
159
|
+
# aggregate result of resolving every group.
|
|
160
|
+
sig { params(scoped: Dependabot::DependencyGraphers::Base).void }
|
|
161
|
+
def absorb_error_state(scoped)
|
|
162
|
+
return unless scoped.errored_fetching_subdependencies
|
|
163
|
+
|
|
164
|
+
errored_fetching_subdependencies!
|
|
165
|
+
# We keep the last subdependency error we see - the full error dialogue will be present in the logs,
|
|
166
|
+
# this just ensures job summary dialogues have something to show.
|
|
167
|
+
@subdependency_error = scoped.subdependency_error unless scoped.subdependency_error.nil?
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Builds a grapher of the same class scoped to a subset of the directory's files, reusing the current
|
|
171
|
+
# file parser's configuration. Used to resolve a single manifest group in isolation.
|
|
172
|
+
sig { params(files: T::Array[Dependabot::DependencyFile]).returns(Dependabot::DependencyGraphers::Base) }
|
|
173
|
+
def scoped_grapher(files)
|
|
174
|
+
scoped_parser = file_parser.class.new(
|
|
175
|
+
dependency_files: files,
|
|
176
|
+
source: file_parser.source,
|
|
177
|
+
repo_contents_path: file_parser.repo_contents_path,
|
|
178
|
+
credentials: file_parser.credentials,
|
|
179
|
+
reject_external_code: file_parser.reject_external_code?,
|
|
180
|
+
options: file_parser.options
|
|
181
|
+
)
|
|
182
|
+
|
|
183
|
+
self.class.new(file_parser: scoped_parser)
|
|
184
|
+
end
|
|
185
|
+
|
|
83
186
|
sig { returns(Dependabot::FileParsers::Base) }
|
|
84
187
|
attr_reader :file_parser
|
|
85
188
|
|
data/lib/dependabot/errors.rb
CHANGED
|
@@ -23,7 +23,7 @@ module Dependabot
|
|
|
23
23
|
|
|
24
24
|
# rubocop:disable Metrics/MethodLength
|
|
25
25
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
26
|
-
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.
|
|
26
|
+
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
|
|
27
27
|
def self.fetcher_error_details(error)
|
|
28
28
|
case error
|
|
29
29
|
when Dependabot::ToolVersionNotSupported
|
|
@@ -142,7 +142,7 @@ module Dependabot
|
|
|
142
142
|
end
|
|
143
143
|
# rubocop:enable Metrics/CyclomaticComplexity
|
|
144
144
|
|
|
145
|
-
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.
|
|
145
|
+
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
|
|
146
146
|
def self.parser_error_details(error)
|
|
147
147
|
case error
|
|
148
148
|
when Dependabot::ToolFeatureNotSupported
|
|
@@ -226,7 +226,7 @@ module Dependabot
|
|
|
226
226
|
# rubocop:disable Lint/RedundantCopDisableDirective
|
|
227
227
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
228
228
|
# rubocop:disable Metrics/AbcSize
|
|
229
|
-
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.
|
|
229
|
+
sig { params(error: StandardError).returns(T.nilable(T::Hash[Symbol, T.anything])) }
|
|
230
230
|
def self.updater_error_details(error)
|
|
231
231
|
case error
|
|
232
232
|
when Dependabot::ToolFeatureNotSupported
|
|
@@ -416,7 +416,7 @@ module Dependabot
|
|
|
416
416
|
|
|
417
417
|
interface!
|
|
418
418
|
|
|
419
|
-
sig { abstract.returns(T::Hash[Symbol, T.
|
|
419
|
+
sig { abstract.returns(T::Hash[Symbol, T.anything]) }
|
|
420
420
|
def sentry_context; end
|
|
421
421
|
end
|
|
422
422
|
|
|
@@ -484,7 +484,7 @@ module Dependabot
|
|
|
484
484
|
super(message || error_type)
|
|
485
485
|
end
|
|
486
486
|
|
|
487
|
-
sig { params(hash: T.nilable(T::Hash[Symbol, T.
|
|
487
|
+
sig { params(hash: T.nilable(T::Hash[Symbol, T.anything])).returns(T::Hash[Symbol, T.anything]) }
|
|
488
488
|
def detail(hash = nil)
|
|
489
489
|
{
|
|
490
490
|
"error-type": error_type,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strong
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
@@ -7,21 +7,21 @@ module Dependabot
|
|
|
7
7
|
module Experiments
|
|
8
8
|
extend T::Sig
|
|
9
9
|
|
|
10
|
-
@experiments = T.let({}, T::Hash[T.any(String, Symbol), T.
|
|
10
|
+
@experiments = T.let({}, T::Hash[T.any(String, Symbol), T.anything])
|
|
11
11
|
|
|
12
|
-
sig { returns(T::Hash[T.any(String, Symbol), T.
|
|
12
|
+
sig { returns(T::Hash[T.any(String, Symbol), T.anything]) }
|
|
13
13
|
def self.reset!
|
|
14
14
|
@experiments = {}
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
-
sig { params(name: T.any(String, Symbol), value: T.
|
|
17
|
+
sig { params(name: T.any(String, Symbol), value: T.anything).void }
|
|
18
18
|
def self.register(name, value)
|
|
19
19
|
@experiments[name.to_sym] = value
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
sig { params(name: T.any(String, Symbol)).returns(T::Boolean) }
|
|
23
23
|
def self.enabled?(name)
|
|
24
|
-
|
|
24
|
+
@experiments[name.to_sym] ? true : false
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
@@ -25,7 +25,7 @@ module Dependabot
|
|
|
25
25
|
sig { returns(T.nilable(Dependabot::Source)) }
|
|
26
26
|
attr_reader :source
|
|
27
27
|
|
|
28
|
-
sig { returns(T::Hash[Symbol, T.
|
|
28
|
+
sig { returns(T::Hash[Symbol, T.anything]) }
|
|
29
29
|
attr_reader :options
|
|
30
30
|
|
|
31
31
|
sig { returns(T::Boolean) }
|
|
@@ -40,7 +40,7 @@ module Dependabot
|
|
|
40
40
|
repo_contents_path: T.nilable(String),
|
|
41
41
|
credentials: T::Array[Dependabot::Credential],
|
|
42
42
|
reject_external_code: T::Boolean,
|
|
43
|
-
options: T::Hash[Symbol, T.
|
|
43
|
+
options: T::Hash[Symbol, T.anything]
|
|
44
44
|
)
|
|
45
45
|
.void
|
|
46
46
|
end
|
|
@@ -26,7 +26,7 @@ module Dependabot
|
|
|
26
26
|
sig { returns(T::Array[Dependabot::Credential]) }
|
|
27
27
|
attr_reader :credentials
|
|
28
28
|
|
|
29
|
-
sig { returns(T::Hash[Symbol, T.
|
|
29
|
+
sig { returns(T::Hash[Symbol, T.anything]) }
|
|
30
30
|
attr_reader :options
|
|
31
31
|
|
|
32
32
|
sig do
|
|
@@ -35,7 +35,7 @@ module Dependabot
|
|
|
35
35
|
dependency_files: T::Array[Dependabot::DependencyFile],
|
|
36
36
|
credentials: T::Array[Dependabot::Credential],
|
|
37
37
|
repo_contents_path: T.nilable(String),
|
|
38
|
-
options: T::Hash[Symbol, T.
|
|
38
|
+
options: T::Hash[Symbol, T.anything]
|
|
39
39
|
).void
|
|
40
40
|
end
|
|
41
41
|
def initialize(dependencies:, dependency_files:, credentials:, repo_contents_path: nil, options: {})
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "excon"
|
|
5
|
+
require "time"
|
|
5
6
|
require "sorbet-runtime"
|
|
6
7
|
require "gitlab"
|
|
7
8
|
require "dependabot/clients/github_with_retries"
|
|
@@ -15,10 +16,15 @@ require "dependabot/dependency"
|
|
|
15
16
|
require "dependabot/credential"
|
|
16
17
|
require "dependabot/git_metadata_fetcher"
|
|
17
18
|
require "dependabot/git_tag_details"
|
|
19
|
+
require "dependabot/package/package_release"
|
|
20
|
+
require "dependabot/package/release_cooldown_options"
|
|
21
|
+
require "dependabot/update_checkers/cooldown_calculation"
|
|
22
|
+
require "dependabot/git_cooldown_date_resolver"
|
|
18
23
|
module Dependabot
|
|
19
24
|
# rubocop:disable Metrics/ClassLength
|
|
20
25
|
class GitCommitChecker
|
|
21
26
|
extend T::Sig
|
|
27
|
+
include Dependabot::GitCooldownDateResolver
|
|
22
28
|
|
|
23
29
|
VERSION_REGEX = /
|
|
24
30
|
(?<version>
|
|
@@ -174,9 +180,35 @@ module Dependabot
|
|
|
174
180
|
max_local_tag_for_lower_precision(allowed_refs)
|
|
175
181
|
end
|
|
176
182
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
183
|
+
# Returns the latest allowed version tag, or nil when every candidate tag is
|
|
184
|
+
# still within its cooldown window. All cooldown handling — the nil /
|
|
185
|
+
# disabled / excluded short-circuits and the logging — lives in
|
|
186
|
+
# #apply_cooldown, so this method stays a thin orchestration step.
|
|
187
|
+
sig do
|
|
188
|
+
params(cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions))
|
|
189
|
+
.returns(T.nilable(Dependabot::GitTagDetails))
|
|
190
|
+
end
|
|
191
|
+
def local_tag_for_latest_version(cooldown_options = nil)
|
|
192
|
+
filtered_tags = apply_cooldown(allowed_version_tags, cooldown_options)
|
|
193
|
+
return nil if filtered_tags.nil?
|
|
194
|
+
|
|
195
|
+
max_local_tag(filtered_tags)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
# Returns the latest allowed version tag, but only when the dependency is
|
|
199
|
+
# pinned to a ref that looks like a version. Most ecosystems share this
|
|
200
|
+
# precondition before resolving a newer version tag for a git dependency,
|
|
201
|
+
# so it lives here instead of being repeated at each call site. Returns nil
|
|
202
|
+
# for dependencies that aren't pinned to a version (e.g. branch- or
|
|
203
|
+
# SHA-pinned), which never want a version-tag update.
|
|
204
|
+
sig do
|
|
205
|
+
params(cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions))
|
|
206
|
+
.returns(T.nilable(Dependabot::GitTagDetails))
|
|
207
|
+
end
|
|
208
|
+
def local_tag_for_pinned_version_ref(cooldown_options = nil)
|
|
209
|
+
return nil unless pinned_ref_looks_like_version?
|
|
210
|
+
|
|
211
|
+
local_tag_for_latest_version(cooldown_options)
|
|
180
212
|
end
|
|
181
213
|
|
|
182
214
|
sig { returns(T::Array[Dependabot::GitTagDetails]) }
|
|
@@ -285,6 +317,30 @@ module Dependabot
|
|
|
285
317
|
allowed_versions(local_tags, filter_by_prefix: false)
|
|
286
318
|
end
|
|
287
319
|
|
|
320
|
+
# Returns the allowed version tags paired with their parsed release
|
|
321
|
+
# dates as a single list, so callers don't have to re-correlate two
|
|
322
|
+
# separate lists (tags and tag-dates) by tag name themselves.
|
|
323
|
+
sig { returns(T::Array[Dependabot::Package::PackageRelease]) }
|
|
324
|
+
def allowed_version_tags_with_release_dates
|
|
325
|
+
allowed_version_tags.map do |tag|
|
|
326
|
+
Dependabot::Package::PackageRelease.new(
|
|
327
|
+
version: T.cast(version_from_tag(tag), Dependabot::Version),
|
|
328
|
+
tag: tag.name,
|
|
329
|
+
released_at: release_date_for_tag_name(tag.name)
|
|
330
|
+
)
|
|
331
|
+
end
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
sig { override.returns(T.nilable(String)) }
|
|
335
|
+
def cooldown_source_url
|
|
336
|
+
dependency_source_details&.fetch(:url, nil)
|
|
337
|
+
end
|
|
338
|
+
|
|
339
|
+
sig { override.returns(T::Array[Dependabot::Credential]) }
|
|
340
|
+
def cooldown_credentials
|
|
341
|
+
credentials
|
|
342
|
+
end
|
|
343
|
+
|
|
288
344
|
private
|
|
289
345
|
|
|
290
346
|
sig { returns(Dependabot::Dependency) }
|
|
@@ -689,7 +745,9 @@ module Dependabot
|
|
|
689
745
|
source: T.must(source),
|
|
690
746
|
credentials: credentials
|
|
691
747
|
)
|
|
692
|
-
|
|
748
|
+
# The Octokit RBI types this as non-nil, but it can be nil at runtime
|
|
749
|
+
# (e.g. an empty/unexpected registry response), so guard against it.
|
|
750
|
+
T.unsafe(client.releases(T.must(source).repo, per_page: 100)) || []
|
|
693
751
|
rescue Octokit::Error
|
|
694
752
|
[]
|
|
695
753
|
end,
|
|
@@ -712,6 +770,107 @@ module Dependabot
|
|
|
712
770
|
T.must(T.must(name.match(VERSION_REGEX)).named_captures.fetch("version"))
|
|
713
771
|
end
|
|
714
772
|
|
|
773
|
+
# Filters out git tags that are still within their cooldown window. Returns
|
|
774
|
+
# the candidate tags unchanged when cooldown does not apply (no options,
|
|
775
|
+
# disabled, or the dependency is excluded), and nil when every candidate tag
|
|
776
|
+
# is still within its cooldown window, so the caller proposes no update.
|
|
777
|
+
sig do
|
|
778
|
+
params(
|
|
779
|
+
candidate_tags: T::Array[Dependabot::GitRef],
|
|
780
|
+
cooldown_options: T.nilable(Dependabot::Package::ReleaseCooldownOptions)
|
|
781
|
+
).returns(T.nilable(T::Array[Dependabot::GitRef]))
|
|
782
|
+
end
|
|
783
|
+
def apply_cooldown(candidate_tags, cooldown_options)
|
|
784
|
+
if Dependabot::UpdateCheckers::CooldownCalculation.skip_cooldown?(cooldown_options, dependency.name)
|
|
785
|
+
return candidate_tags
|
|
786
|
+
end
|
|
787
|
+
|
|
788
|
+
cooldown = T.must(cooldown_options)
|
|
789
|
+
filtered_tags = candidate_tags.reject { |tag| tag_in_cooldown_period?(tag, cooldown) }
|
|
790
|
+
|
|
791
|
+
if filtered_tags.empty? && candidate_tags.any?
|
|
792
|
+
Dependabot.logger.info(
|
|
793
|
+
"All git tags for #{dependency.name} are within their cooldown period; skipping update"
|
|
794
|
+
)
|
|
795
|
+
return nil
|
|
796
|
+
end
|
|
797
|
+
|
|
798
|
+
filtered_tags
|
|
799
|
+
end
|
|
800
|
+
|
|
801
|
+
sig do
|
|
802
|
+
params(tag: Dependabot::GitRef, cooldown: Dependabot::Package::ReleaseCooldownOptions)
|
|
803
|
+
.returns(T::Boolean)
|
|
804
|
+
end
|
|
805
|
+
def tag_in_cooldown_period?(tag, cooldown)
|
|
806
|
+
released_at = release_date_for_tag_name(tag.name)
|
|
807
|
+
return false unless released_at
|
|
808
|
+
|
|
809
|
+
cooldown_days = Dependabot::UpdateCheckers::CooldownCalculation.cooldown_days_for(
|
|
810
|
+
cooldown,
|
|
811
|
+
T.cast(current_version, T.nilable(Dependabot::Version)),
|
|
812
|
+
T.cast(version_from_tag(tag), Dependabot::Version)
|
|
813
|
+
)
|
|
814
|
+
|
|
815
|
+
in_cooldown = Dependabot::UpdateCheckers::CooldownCalculation
|
|
816
|
+
.within_cooldown_window?(released_at, cooldown_days)
|
|
817
|
+
|
|
818
|
+
if in_cooldown
|
|
819
|
+
passed_days = (Time.now.to_i - released_at.to_i) /
|
|
820
|
+
Dependabot::UpdateCheckers::CooldownCalculation::DAY_IN_SECONDS
|
|
821
|
+
Dependabot.logger.info(
|
|
822
|
+
"Filtered git tag #{tag.name} for #{dependency.name}: released " \
|
|
823
|
+
"#{released_at.strftime('%Y-%m-%d')} (#{passed_days}/#{cooldown_days} cooldown days)"
|
|
824
|
+
)
|
|
825
|
+
end
|
|
826
|
+
|
|
827
|
+
in_cooldown
|
|
828
|
+
end
|
|
829
|
+
|
|
830
|
+
# The allowed version tags may carry a "tags/" prefix (when the dependency
|
|
831
|
+
# is pinned via "tags/<ref>"), whereas refs_for_tag_with_detail returns the
|
|
832
|
+
# raw tag names, so we try both forms to line dates up regardless of prefix.
|
|
833
|
+
#
|
|
834
|
+
# A GitHub Release's published_at is preferred when available, falling back
|
|
835
|
+
# to the tag creation date from refs_for_tag_with_detail. This mirrors the
|
|
836
|
+
# priority used by Dependabot::GitCooldownDateResolver.
|
|
837
|
+
sig { params(tag_name: String).returns(T.nilable(Time)) }
|
|
838
|
+
def release_date_for_tag_name(tag_name)
|
|
839
|
+
normalized = normalize_tag_name(tag_name)
|
|
840
|
+
|
|
841
|
+
github_release_published_at(normalized) ||
|
|
842
|
+
release_dates_by_tag_name[tag_name] ||
|
|
843
|
+
release_dates_by_tag_name[normalized]
|
|
844
|
+
end
|
|
845
|
+
|
|
846
|
+
sig { returns(T::Hash[String, Time]) }
|
|
847
|
+
def release_dates_by_tag_name
|
|
848
|
+
@release_dates_by_tag_name ||= T.let(
|
|
849
|
+
build_release_dates_by_tag_name,
|
|
850
|
+
T.nilable(T::Hash[String, Time])
|
|
851
|
+
)
|
|
852
|
+
end
|
|
853
|
+
|
|
854
|
+
sig { returns(T::Hash[String, Time]) }
|
|
855
|
+
def build_release_dates_by_tag_name
|
|
856
|
+
dates = T.let({}, T::Hash[String, Time])
|
|
857
|
+
refs_for_tag_with_detail.each do |detail|
|
|
858
|
+
released_at = parse_release_date(detail.release_date)
|
|
859
|
+
dates[detail.tag] = released_at if released_at
|
|
860
|
+
end
|
|
861
|
+
dates
|
|
862
|
+
end
|
|
863
|
+
|
|
864
|
+
sig { params(release_date: T.nilable(String)).returns(T.nilable(Time)) }
|
|
865
|
+
def parse_release_date(release_date)
|
|
866
|
+
return nil if release_date.nil? || release_date.empty?
|
|
867
|
+
|
|
868
|
+
Time.parse(release_date)
|
|
869
|
+
rescue ArgumentError => e
|
|
870
|
+
Dependabot.logger.error("Invalid release date format: #{release_date} (#{e.message})")
|
|
871
|
+
nil
|
|
872
|
+
end
|
|
873
|
+
|
|
715
874
|
sig { returns(T.class_of(Gem::Version)) }
|
|
716
875
|
def version_class
|
|
717
876
|
@version_class ||= T.let(
|
|
@@ -39,7 +39,7 @@ module Dependabot
|
|
|
39
39
|
sig { returns(T.nilable(ReleaseCooldownOptions)) }
|
|
40
40
|
attr_reader :cooldown_options
|
|
41
41
|
|
|
42
|
-
sig { returns(T::Hash[Symbol, T.
|
|
42
|
+
sig { returns(T::Hash[Symbol, T.anything]) }
|
|
43
43
|
attr_reader :options
|
|
44
44
|
|
|
45
45
|
sig do
|
|
@@ -51,7 +51,7 @@ module Dependabot
|
|
|
51
51
|
security_advisories: T::Array[Dependabot::SecurityAdvisory],
|
|
52
52
|
cooldown_options: T.nilable(ReleaseCooldownOptions),
|
|
53
53
|
raise_on_ignored: T::Boolean,
|
|
54
|
-
options: T::Hash[Symbol, T.
|
|
54
|
+
options: T::Hash[Symbol, T.anything]
|
|
55
55
|
).void
|
|
56
56
|
end
|
|
57
57
|
def initialize(
|
|
@@ -335,7 +335,7 @@ module Dependabot
|
|
|
335
335
|
end
|
|
336
336
|
end
|
|
337
337
|
|
|
338
|
-
sig { returns(T::Array[
|
|
338
|
+
sig { returns(T::Array[Dependabot::Requirement]) }
|
|
339
339
|
def ignore_requirements
|
|
340
340
|
ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
|
|
341
341
|
end
|
|
@@ -24,7 +24,7 @@ module Dependabot
|
|
|
24
24
|
package_type: T.nilable(String),
|
|
25
25
|
language: T.nilable(Dependabot::Package::PackageLanguage),
|
|
26
26
|
tag: T.nilable(String),
|
|
27
|
-
details: T::Hash[String, T.
|
|
27
|
+
details: T::Hash[String, T.anything]
|
|
28
28
|
).void
|
|
29
29
|
end
|
|
30
30
|
def initialize(
|
|
@@ -50,7 +50,7 @@ module Dependabot
|
|
|
50
50
|
@package_type = T.let(package_type, T.nilable(String))
|
|
51
51
|
@language = T.let(language, T.nilable(Dependabot::Package::PackageLanguage))
|
|
52
52
|
@tag = T.let(tag, T.nilable(String))
|
|
53
|
-
@details = T.let(details, T::Hash[String, T.
|
|
53
|
+
@details = T.let(details, T::Hash[String, T.anything])
|
|
54
54
|
end
|
|
55
55
|
|
|
56
56
|
sig { returns(Dependabot::Version) }
|
|
@@ -83,7 +83,7 @@ module Dependabot
|
|
|
83
83
|
sig { returns(T.nilable(String)) }
|
|
84
84
|
attr_reader :tag
|
|
85
85
|
|
|
86
|
-
sig { returns(T::Hash[String, T.
|
|
86
|
+
sig { returns(T::Hash[String, T.anything]) }
|
|
87
87
|
attr_reader :details
|
|
88
88
|
|
|
89
89
|
sig { returns(T::Boolean) }
|