dependabot-nix 0.368.0 → 0.370.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/nix/file_parser.rb +1 -1
- data/lib/dependabot/nix/file_updater.rb +36 -6
- data/lib/dependabot/nix/flake_nix_parser.rb +206 -0
- data/lib/dependabot/nix/update_checker/versioned_branch_finder.rb +184 -0
- data/lib/dependabot/nix/update_checker.rb +132 -6
- metadata +6 -4
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7b92e30b98746b2c1f5856c15463bd7c0c8c9cc072027364feaa931946945a74
|
|
4
|
+
data.tar.gz: 35a7af97d0d7225b0e390cdb705eb0f58650d1895a73c399d8328926b5bff74d
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: b65a8cf1a67737b11fb3db6c40e29220b51cbdcf7d0daa65aae05282febe4ee7d2e6bb5fca4d34623b1328bca60eca0fd7f6db177f5cb7baa2c77b87343bd89a
|
|
7
|
+
data.tar.gz: d2ca3a0197a030d0383f5b67d7146e207d97770c156604e8b0da0a3aba91e4a8720b3a187ae161f404155cf3b81046f7f4ba86320e3a732004f1a5652bfc2d32
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
@@ -7,6 +7,7 @@ require "dependabot/errors"
|
|
|
7
7
|
require "dependabot/file_updaters"
|
|
8
8
|
require "dependabot/file_updaters/base"
|
|
9
9
|
require "dependabot/shared_helpers"
|
|
10
|
+
require "dependabot/nix/flake_nix_parser"
|
|
10
11
|
|
|
11
12
|
module Dependabot
|
|
12
13
|
module Nix
|
|
@@ -15,14 +16,20 @@ module Dependabot
|
|
|
15
16
|
|
|
16
17
|
sig { override.returns(T::Array[Dependabot::DependencyFile]) }
|
|
17
18
|
def updated_dependency_files
|
|
18
|
-
|
|
19
|
+
updated_files = []
|
|
20
|
+
|
|
21
|
+
updated_flake_nix_content = update_flake_nix
|
|
22
|
+
updated_files << updated_file(file: flake_nix, content: updated_flake_nix_content) if updated_flake_nix_content
|
|
23
|
+
|
|
24
|
+
updated_lockfile_content = update_flake_lock(updated_flake_nix_content)
|
|
19
25
|
|
|
20
26
|
if updated_lockfile_content == flake_lock.content
|
|
21
27
|
raise Dependabot::DependencyFileContentNotChanged,
|
|
22
28
|
"Expected flake.lock to change for #{dependency.name}, but it didn't"
|
|
23
29
|
end
|
|
24
30
|
|
|
25
|
-
|
|
31
|
+
updated_files << updated_file(file: flake_lock, content: updated_lockfile_content)
|
|
32
|
+
updated_files
|
|
26
33
|
end
|
|
27
34
|
|
|
28
35
|
private
|
|
@@ -32,13 +39,26 @@ module Dependabot
|
|
|
32
39
|
T.must(dependencies.first)
|
|
33
40
|
end
|
|
34
41
|
|
|
35
|
-
|
|
36
|
-
|
|
42
|
+
# Returns updated flake.nix content if the ref changed, nil otherwise.
|
|
43
|
+
sig { returns(T.nilable(String)) }
|
|
44
|
+
def update_flake_nix
|
|
45
|
+
new_ref = new_source_ref
|
|
46
|
+
return unless new_ref
|
|
47
|
+
|
|
48
|
+
old_ref = old_source_ref
|
|
49
|
+
return unless old_ref
|
|
50
|
+
return if old_ref == new_ref
|
|
51
|
+
|
|
52
|
+
FlakeNixParser.update_input_ref(T.must(flake_nix.content), dependency.name, new_ref)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
sig { params(updated_nix_content: T.nilable(String)).returns(String) }
|
|
56
|
+
def update_flake_lock(updated_nix_content)
|
|
37
57
|
SharedHelpers.in_a_temporary_repo_directory(
|
|
38
58
|
flake_lock.directory,
|
|
39
59
|
repo_contents_path
|
|
40
60
|
) do
|
|
41
|
-
File.write("flake.nix", T.must(flake_nix.content))
|
|
61
|
+
File.write("flake.nix", updated_nix_content || T.must(flake_nix.content))
|
|
42
62
|
File.write("flake.lock", T.must(flake_lock.content))
|
|
43
63
|
|
|
44
64
|
SharedHelpers.run_shell_command(
|
|
@@ -50,6 +70,16 @@ module Dependabot
|
|
|
50
70
|
end
|
|
51
71
|
end
|
|
52
72
|
|
|
73
|
+
sig { returns(T.nilable(String)) }
|
|
74
|
+
def new_source_ref
|
|
75
|
+
dependency.requirements.first&.dig(:source, :ref)
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
sig { returns(T.nilable(String)) }
|
|
79
|
+
def old_source_ref
|
|
80
|
+
dependency.previous_requirements&.first&.dig(:source, :ref)
|
|
81
|
+
end
|
|
82
|
+
|
|
53
83
|
sig { override.void }
|
|
54
84
|
def check_required_files
|
|
55
85
|
%w(flake.nix flake.lock).each do |filename|
|
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
module Dependabot
|
|
7
|
+
module Nix
|
|
8
|
+
# Parses flake.nix content to locate input URL declarations and extract
|
|
9
|
+
# their components (scheme, owner, repo, ref). Only handles the shorthand
|
|
10
|
+
# URL schemes (github:, gitlab:, sourcehut:) since those are the ones
|
|
11
|
+
# where the ref appears inline in the URL string.
|
|
12
|
+
class FlakeNixParser
|
|
13
|
+
extend T::Sig
|
|
14
|
+
|
|
15
|
+
# Matches shorthand flake URLs: github:owner/repo/ref or github:owner/repo
|
|
16
|
+
# Also handles gitlab: and sourcehut:~owner/repo/ref
|
|
17
|
+
FLAKE_URL_PATTERN = %r{
|
|
18
|
+
(?<scheme>github|gitlab|sourcehut):
|
|
19
|
+
(?<owner>~?[a-zA-Z0-9_\-\.]+)/
|
|
20
|
+
(?<repo>[a-zA-Z0-9_\-\.]+)
|
|
21
|
+
(?:/(?<ref>[a-zA-Z0-9_\-\./]+))?
|
|
22
|
+
(?:\?(?<query>[^"]*))?
|
|
23
|
+
}x
|
|
24
|
+
private_constant :FLAKE_URL_PATTERN
|
|
25
|
+
|
|
26
|
+
# Matches indirect/registry shorthand URLs: nixpkgs/nixos-24.11
|
|
27
|
+
# These have no scheme prefix (no ":") and resolve via the nix flake registry.
|
|
28
|
+
INDIRECT_URL_PATTERN = %r{
|
|
29
|
+
\A(?<id>[a-zA-Z0-9_\-]+)
|
|
30
|
+
/(?<ref>[a-zA-Z0-9_\-\./]+)\z
|
|
31
|
+
}x
|
|
32
|
+
private_constant :INDIRECT_URL_PATTERN
|
|
33
|
+
|
|
34
|
+
# Matches an input URL assignment tied to a specific input name.
|
|
35
|
+
# Covers the common syntactic forms:
|
|
36
|
+
# inputs.NAME.url = "URL";
|
|
37
|
+
# NAME.url = "URL";
|
|
38
|
+
# NAME = { ... url = "URL"; ... };
|
|
39
|
+
#
|
|
40
|
+
# We build these dynamically per input name so that the name is anchored
|
|
41
|
+
# in the regex.
|
|
42
|
+
sig { params(content: String, input_name: String).returns(T.nilable(InputUrl)) }
|
|
43
|
+
def self.find_input_url(content, input_name)
|
|
44
|
+
new(content, input_name).find
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
sig { params(content: String, input_name: String, new_ref: String).returns(T.nilable(String)) }
|
|
48
|
+
def self.update_input_ref(content, input_name, new_ref)
|
|
49
|
+
new(content, input_name).update_ref(new_ref)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
sig { params(content: String, input_name: String).void }
|
|
53
|
+
def initialize(content, input_name)
|
|
54
|
+
@content = content
|
|
55
|
+
@input_name = input_name
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
sig { returns(T.nilable(InputUrl)) }
|
|
59
|
+
def find
|
|
60
|
+
match = find_url_match
|
|
61
|
+
return unless match
|
|
62
|
+
|
|
63
|
+
url_str = match[:url]
|
|
64
|
+
|
|
65
|
+
# Try shorthand scheme first (github:, gitlab:, sourcehut:)
|
|
66
|
+
url_match = FLAKE_URL_PATTERN.match(url_str)
|
|
67
|
+
if url_match
|
|
68
|
+
return InputUrl.new(
|
|
69
|
+
full_url: url_str,
|
|
70
|
+
scheme: T.must(url_match[:scheme]),
|
|
71
|
+
owner: T.must(url_match[:owner]),
|
|
72
|
+
repo: T.must(url_match[:repo]),
|
|
73
|
+
ref: url_match[:ref],
|
|
74
|
+
query: url_match[:query],
|
|
75
|
+
match_start: match[:url_start],
|
|
76
|
+
match_end: match[:url_end]
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Try indirect/registry shorthand (e.g. nixpkgs/nixos-24.11)
|
|
81
|
+
indirect_match = INDIRECT_URL_PATTERN.match(url_str)
|
|
82
|
+
return unless indirect_match
|
|
83
|
+
|
|
84
|
+
InputUrl.new(
|
|
85
|
+
full_url: url_str,
|
|
86
|
+
scheme: "indirect",
|
|
87
|
+
owner: T.must(indirect_match[:id]),
|
|
88
|
+
repo: "",
|
|
89
|
+
ref: indirect_match[:ref],
|
|
90
|
+
query: nil,
|
|
91
|
+
match_start: match[:url_start],
|
|
92
|
+
match_end: match[:url_end]
|
|
93
|
+
)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
sig { params(new_ref: String).returns(T.nilable(String)) }
|
|
97
|
+
def update_ref(new_ref)
|
|
98
|
+
input_url = find
|
|
99
|
+
return unless input_url
|
|
100
|
+
return unless input_url.ref # nothing to update if no ref
|
|
101
|
+
|
|
102
|
+
old_url = input_url.full_url
|
|
103
|
+
new_url = build_updated_url(input_url, new_ref)
|
|
104
|
+
|
|
105
|
+
updated = @content.dup
|
|
106
|
+
# Replace within the known match boundaries to avoid accidental matches elsewhere
|
|
107
|
+
updated[input_url.match_start...input_url.match_end] =
|
|
108
|
+
T.must(updated[input_url.match_start...input_url.match_end]).sub(old_url, new_url)
|
|
109
|
+
updated
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
private
|
|
113
|
+
|
|
114
|
+
sig { returns(String) }
|
|
115
|
+
attr_reader :content
|
|
116
|
+
|
|
117
|
+
sig { returns(String) }
|
|
118
|
+
attr_reader :input_name
|
|
119
|
+
|
|
120
|
+
# Returns a hash with :url, :url_start, :url_end if found, or nil.
|
|
121
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
|
122
|
+
def find_url_match
|
|
123
|
+
escaped_name = Regexp.escape(input_name)
|
|
124
|
+
# Nix identifiers can contain letters, digits, underscores, hyphens, and apostrophes.
|
|
125
|
+
# Anchor the name so we don't match inside longer identifiers (e.g. "my-nixpkgs").
|
|
126
|
+
bounded_name = "(?<![A-Za-z0-9_'\\-])#{escaped_name}(?![A-Za-z0-9_'\\-])"
|
|
127
|
+
|
|
128
|
+
# Pattern 1: inputs.NAME.url = "URL";
|
|
129
|
+
# Pattern 2: NAME.url = "URL"; (inside inputs block)
|
|
130
|
+
url_assignment = /(?:inputs\.)?#{bounded_name}\.url\s*=\s*"(?<url>[^"]+)"/
|
|
131
|
+
match = find_uncommented_match(url_assignment)
|
|
132
|
+
return url_match_hash(match) if match
|
|
133
|
+
|
|
134
|
+
# Pattern 3: NAME = { ... url = "URL"; ... }
|
|
135
|
+
# Use a non-greedy match to find the url inside the attribute set
|
|
136
|
+
attr_set = /#{bounded_name}\s*=\s*\{[^}]*?\burl\s*=\s*"(?<url>[^"]+)"/m
|
|
137
|
+
match = find_uncommented_match(attr_set)
|
|
138
|
+
return url_match_hash(match) if match
|
|
139
|
+
|
|
140
|
+
nil
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
# Finds the first match that isn't inside a Nix comment (# or /* */).
|
|
144
|
+
sig { params(pattern: Regexp).returns(T.nilable(MatchData)) }
|
|
145
|
+
def find_uncommented_match(pattern)
|
|
146
|
+
content.to_enum(:scan, pattern).each do
|
|
147
|
+
match = T.must(Regexp.last_match)
|
|
148
|
+
next if inside_comment?(match.begin(0))
|
|
149
|
+
|
|
150
|
+
return match
|
|
151
|
+
end
|
|
152
|
+
nil
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
sig { params(pos: Integer).returns(T::Boolean) }
|
|
156
|
+
def inside_comment?(pos)
|
|
157
|
+
# Check for single-line comment: # at start of line before pos
|
|
158
|
+
line_start = content.rindex("\n", pos)&.+(1) || 0
|
|
159
|
+
line_before_pos = content[line_start...pos]
|
|
160
|
+
return true if line_before_pos&.match?(/(?:^|[^&])#/)
|
|
161
|
+
|
|
162
|
+
# Check for block comment: /* before pos without a closing */ between them
|
|
163
|
+
last_open = content.rindex("/*", pos)
|
|
164
|
+
return false unless last_open
|
|
165
|
+
|
|
166
|
+
last_close = content.rindex("*/", pos)
|
|
167
|
+
last_open > (last_close || -1)
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
sig { params(match: MatchData).returns(T::Hash[Symbol, T.untyped]) }
|
|
171
|
+
def url_match_hash(match)
|
|
172
|
+
url_capture_start = match.begin(:url)
|
|
173
|
+
url_capture_end = match.end(:url)
|
|
174
|
+
{
|
|
175
|
+
url: match[:url],
|
|
176
|
+
url_start: url_capture_start,
|
|
177
|
+
url_end: url_capture_end
|
|
178
|
+
}
|
|
179
|
+
end
|
|
180
|
+
|
|
181
|
+
sig { params(input_url: InputUrl, new_ref: String).returns(String) }
|
|
182
|
+
def build_updated_url(input_url, new_ref)
|
|
183
|
+
if input_url.scheme == "indirect"
|
|
184
|
+
"#{input_url.owner}/#{new_ref}"
|
|
185
|
+
else
|
|
186
|
+
base = "#{input_url.scheme}:#{input_url.owner}/#{input_url.repo}/#{new_ref}"
|
|
187
|
+
input_url.query ? "#{base}?#{input_url.query}" : base
|
|
188
|
+
end
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# Represents a parsed flake input URL from flake.nix
|
|
192
|
+
class InputUrl < T::Struct
|
|
193
|
+
const :full_url, String
|
|
194
|
+
const :scheme, String
|
|
195
|
+
const :owner, String
|
|
196
|
+
const :repo, String
|
|
197
|
+
const :ref, T.nilable(String)
|
|
198
|
+
const :query, T.nilable(String)
|
|
199
|
+
# Character positions of the URL string within the flake.nix content
|
|
200
|
+
# (inside the quotes, not including the quotes themselves)
|
|
201
|
+
const :match_start, Integer
|
|
202
|
+
const :match_end, Integer
|
|
203
|
+
end
|
|
204
|
+
end
|
|
205
|
+
end
|
|
206
|
+
end
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# typed: strict
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "sorbet-runtime"
|
|
5
|
+
|
|
6
|
+
require "dependabot/nix/update_checker"
|
|
7
|
+
require "dependabot/nix/requirement"
|
|
8
|
+
require "dependabot/git_metadata_fetcher"
|
|
9
|
+
require "dependabot/git_ref"
|
|
10
|
+
|
|
11
|
+
module Dependabot
|
|
12
|
+
module Nix
|
|
13
|
+
class UpdateChecker
|
|
14
|
+
# Detects versioned branch naming patterns (e.g. nixos-24.11, release-24.11)
|
|
15
|
+
# and finds the latest branch matching the same prefix.
|
|
16
|
+
class VersionedBranchFinder
|
|
17
|
+
extend T::Sig
|
|
18
|
+
|
|
19
|
+
# Matches branch names with a YY.MM version segment and optional suffix.
|
|
20
|
+
# Captures: prefix (including separator), version, and optional suffix.
|
|
21
|
+
# Examples: "nixos-24.11" => prefix="nixos-", version="24.11", suffix=nil
|
|
22
|
+
# "nixos-24.11-small" => prefix="nixos-", version="24.11", suffix="-small"
|
|
23
|
+
# "release-24.11-aarch64" => prefix="release-", version="24.11", suffix="-aarch64"
|
|
24
|
+
VERSIONED_BRANCH_PATTERN = /\A(.+[.\-_])(\d{2}\.\d{2})(-[a-zA-Z0-9]+)?\z/
|
|
25
|
+
private_constant :VERSIONED_BRANCH_PATTERN
|
|
26
|
+
|
|
27
|
+
sig do
|
|
28
|
+
params(
|
|
29
|
+
current_ref: String,
|
|
30
|
+
dependency: Dependabot::Dependency,
|
|
31
|
+
credentials: T::Array[Dependabot::Credential],
|
|
32
|
+
ignored_versions: T::Array[String]
|
|
33
|
+
).void
|
|
34
|
+
end
|
|
35
|
+
def initialize(current_ref:, dependency:, credentials:, ignored_versions: [])
|
|
36
|
+
@current_ref = current_ref
|
|
37
|
+
@dependency = dependency
|
|
38
|
+
@credentials = credentials
|
|
39
|
+
@ignored_versions = ignored_versions
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns true if the current ref looks like a versioned branch.
|
|
43
|
+
sig { returns(T::Boolean) }
|
|
44
|
+
def versioned_branch?
|
|
45
|
+
!branch_version_match.nil?
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns the latest versioned branch info or nil if no newer branch exists.
|
|
49
|
+
# Returns { branch: "nixos-25.05", commit_sha: "abc123" } or nil.
|
|
50
|
+
sig { returns(T.nilable(T::Hash[Symbol, String])) }
|
|
51
|
+
def latest_versioned_branch
|
|
52
|
+
match = branch_version_match
|
|
53
|
+
return unless match
|
|
54
|
+
|
|
55
|
+
prefix = match[1]
|
|
56
|
+
current_version = parse_version(T.must(match[2]))
|
|
57
|
+
return unless current_version
|
|
58
|
+
|
|
59
|
+
suffix = match[3] # nil if no suffix, e.g. "-small" if present
|
|
60
|
+
find_latest_branch(T.must(prefix), current_version, suffix)
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
private
|
|
64
|
+
|
|
65
|
+
sig { returns(String) }
|
|
66
|
+
attr_reader :current_ref
|
|
67
|
+
|
|
68
|
+
sig { returns(Dependabot::Dependency) }
|
|
69
|
+
attr_reader :dependency
|
|
70
|
+
|
|
71
|
+
sig { returns(T::Array[Dependabot::Credential]) }
|
|
72
|
+
attr_reader :credentials
|
|
73
|
+
|
|
74
|
+
sig { returns(T::Array[String]) }
|
|
75
|
+
attr_reader :ignored_versions
|
|
76
|
+
|
|
77
|
+
sig { returns(T.nilable(MatchData)) }
|
|
78
|
+
def branch_version_match
|
|
79
|
+
@branch_version_match ||= T.let(
|
|
80
|
+
VERSIONED_BRANCH_PATTERN.match(current_ref),
|
|
81
|
+
T.nilable(MatchData)
|
|
82
|
+
)
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
sig do
|
|
86
|
+
params(
|
|
87
|
+
prefix: String,
|
|
88
|
+
current_version: T::Array[Integer],
|
|
89
|
+
suffix: T.nilable(String)
|
|
90
|
+
).returns(T.nilable(T::Hash[Symbol, String]))
|
|
91
|
+
end
|
|
92
|
+
def find_latest_branch(prefix, current_version, suffix)
|
|
93
|
+
candidates = remote_branches.filter_map do |ref|
|
|
94
|
+
build_candidate(ref, prefix, current_version, suffix)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
latest = candidates.max_by { |c| c[:version] }
|
|
98
|
+
return unless latest
|
|
99
|
+
|
|
100
|
+
{ branch: latest[:branch].to_s, commit_sha: latest[:commit_sha].to_s }
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
sig do
|
|
104
|
+
params(
|
|
105
|
+
ref: Dependabot::GitRef,
|
|
106
|
+
prefix: String,
|
|
107
|
+
current_version: T::Array[Integer],
|
|
108
|
+
suffix: T.nilable(String)
|
|
109
|
+
).returns(T.nilable(T::Hash[Symbol, T.untyped]))
|
|
110
|
+
end
|
|
111
|
+
def build_candidate(ref, prefix, current_version, suffix)
|
|
112
|
+
branch_match = VERSIONED_BRANCH_PATTERN.match(ref.name)
|
|
113
|
+
return unless branch_match
|
|
114
|
+
return unless branch_match[1] == prefix
|
|
115
|
+
return unless branch_match[3] == suffix
|
|
116
|
+
|
|
117
|
+
version_str = T.must(branch_match[2])
|
|
118
|
+
version = parse_version(version_str)
|
|
119
|
+
return unless version
|
|
120
|
+
return unless (version <=> current_version) == 1
|
|
121
|
+
return if version_ignored?(version_str)
|
|
122
|
+
|
|
123
|
+
{ branch: ref.name, commit_sha: ref.commit_sha, version: version }
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
sig { params(version_str: String).returns(T::Boolean) }
|
|
127
|
+
def version_ignored?(version_str)
|
|
128
|
+
return false if ignore_requirements.empty?
|
|
129
|
+
|
|
130
|
+
gem_version = Gem::Version.new(version_str)
|
|
131
|
+
ignore_requirements.any? { |req| req.satisfied_by?(gem_version) }
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
# Parses "YY.MM" into [year, month] for comparison.
|
|
135
|
+
sig { params(version_str: String).returns(T.nilable(T::Array[Integer])) }
|
|
136
|
+
def parse_version(version_str)
|
|
137
|
+
parts = version_str.split(".")
|
|
138
|
+
return unless parts.length == 2
|
|
139
|
+
|
|
140
|
+
year = Integer(T.must(parts[0]), 10)
|
|
141
|
+
month = Integer(T.must(parts[1]), 10)
|
|
142
|
+
return unless month.between?(1, 12)
|
|
143
|
+
|
|
144
|
+
[year, month]
|
|
145
|
+
rescue ArgumentError
|
|
146
|
+
nil
|
|
147
|
+
end
|
|
148
|
+
|
|
149
|
+
sig { returns(T::Array[Gem::Requirement]) }
|
|
150
|
+
def ignore_requirements
|
|
151
|
+
@ignore_requirements ||= T.let(
|
|
152
|
+
ignored_versions.flat_map do |req|
|
|
153
|
+
Dependabot::Nix::Requirement.requirements_array(req)
|
|
154
|
+
rescue Gem::Requirement::BadRequirementError
|
|
155
|
+
[]
|
|
156
|
+
end,
|
|
157
|
+
T.nilable(T::Array[Gem::Requirement])
|
|
158
|
+
)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
sig { returns(T::Array[Dependabot::GitRef]) }
|
|
162
|
+
def remote_branches
|
|
163
|
+
@remote_branches ||= T.let(
|
|
164
|
+
git_metadata_fetcher.refs_for_upload_pack.select do |ref|
|
|
165
|
+
ref.ref_type == Dependabot::RefType::Head
|
|
166
|
+
end,
|
|
167
|
+
T.nilable(T::Array[Dependabot::GitRef])
|
|
168
|
+
)
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
sig { returns(Dependabot::GitMetadataFetcher) }
|
|
172
|
+
def git_metadata_fetcher
|
|
173
|
+
@git_metadata_fetcher ||= T.let(
|
|
174
|
+
Dependabot::GitMetadataFetcher.new(
|
|
175
|
+
url: dependency.source_details&.fetch(:url, nil),
|
|
176
|
+
credentials: credentials
|
|
177
|
+
),
|
|
178
|
+
T.nilable(Dependabot::GitMetadataFetcher)
|
|
179
|
+
)
|
|
180
|
+
end
|
|
181
|
+
end
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# typed:
|
|
1
|
+
# typed: strict
|
|
2
2
|
# frozen_string_literal: true
|
|
3
3
|
|
|
4
4
|
require "sorbet-runtime"
|
|
@@ -15,6 +15,7 @@ module Dependabot
|
|
|
15
15
|
extend T::Sig
|
|
16
16
|
|
|
17
17
|
require_relative "update_checker/latest_version_finder"
|
|
18
|
+
require_relative "update_checker/versioned_branch_finder"
|
|
18
19
|
|
|
19
20
|
sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
|
|
20
21
|
def latest_version
|
|
@@ -27,27 +28,29 @@ module Dependabot
|
|
|
27
28
|
|
|
28
29
|
sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
|
|
29
30
|
def latest_resolvable_version
|
|
30
|
-
# Resolvability isn't an issue for flake inputs — they're independent.
|
|
31
31
|
latest_version
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
sig { override.returns(T.nilable(T.any(String, Dependabot::Version))) }
|
|
35
35
|
def latest_resolvable_version_with_no_unlock
|
|
36
|
-
# No concept of "unlocking" for flake inputs
|
|
37
36
|
latest_version
|
|
38
37
|
end
|
|
39
38
|
|
|
40
39
|
sig { override.returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
41
40
|
def updated_requirements
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
if ref_pinned_to_version_tag?
|
|
42
|
+
updated_requirements_for_tag
|
|
43
|
+
elsif ref_is_versioned_branch?
|
|
44
|
+
updated_requirements_for_versioned_branch
|
|
45
|
+
else
|
|
46
|
+
dependency.requirements
|
|
47
|
+
end
|
|
44
48
|
end
|
|
45
49
|
|
|
46
50
|
private
|
|
47
51
|
|
|
48
52
|
sig { override.returns(T::Boolean) }
|
|
49
53
|
def latest_version_resolvable_with_full_unlock?
|
|
50
|
-
# Full unlock checks aren't relevant for flake inputs
|
|
51
54
|
false
|
|
52
55
|
end
|
|
53
56
|
|
|
@@ -58,6 +61,77 @@ module Dependabot
|
|
|
58
61
|
|
|
59
62
|
sig { returns(T.nilable(String)) }
|
|
60
63
|
def fetch_latest_version
|
|
64
|
+
if ref_pinned_to_version_tag?
|
|
65
|
+
fetch_latest_version_for_tag
|
|
66
|
+
elsif ref_is_versioned_branch?
|
|
67
|
+
fetch_latest_version_for_versioned_branch || fetch_latest_version_for_commit
|
|
68
|
+
else
|
|
69
|
+
fetch_latest_version_for_commit
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
# --- Tag-pinned ref support ---
|
|
74
|
+
|
|
75
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
76
|
+
def updated_requirements_for_tag
|
|
77
|
+
new_tag = latest_version_tag
|
|
78
|
+
return dependency.requirements unless new_tag
|
|
79
|
+
|
|
80
|
+
dependency.requirements.map do |req|
|
|
81
|
+
source = req[:source]
|
|
82
|
+
next req unless source
|
|
83
|
+
|
|
84
|
+
req.merge(source: source.merge(ref: new_tag[:tag], branch: nil))
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
sig { returns(T.nilable(String)) }
|
|
89
|
+
def fetch_latest_version_for_tag
|
|
90
|
+
tag = latest_version_tag
|
|
91
|
+
tag&.fetch(:commit_sha)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
|
|
95
|
+
def latest_version_tag
|
|
96
|
+
@latest_version_tag ||= T.let(
|
|
97
|
+
git_commit_checker.local_tag_for_latest_version,
|
|
98
|
+
T.nilable(T::Hash[Symbol, T.untyped])
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# --- Versioned branch support ---
|
|
103
|
+
|
|
104
|
+
sig { returns(T::Array[T::Hash[Symbol, T.untyped]]) }
|
|
105
|
+
def updated_requirements_for_versioned_branch
|
|
106
|
+
result = latest_versioned_branch
|
|
107
|
+
return dependency.requirements unless result
|
|
108
|
+
|
|
109
|
+
dependency.requirements.map do |req|
|
|
110
|
+
source = req[:source]
|
|
111
|
+
next req unless source
|
|
112
|
+
|
|
113
|
+
req.merge(source: source.merge(ref: result[:branch], branch: nil))
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
sig { returns(T.nilable(String)) }
|
|
118
|
+
def fetch_latest_version_for_versioned_branch
|
|
119
|
+
result = latest_versioned_branch
|
|
120
|
+
result&.fetch(:commit_sha)
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
sig { returns(T.nilable(T::Hash[Symbol, String])) }
|
|
124
|
+
def latest_versioned_branch
|
|
125
|
+
@latest_versioned_branch ||= T.let(
|
|
126
|
+
versioned_branch_finder&.latest_versioned_branch,
|
|
127
|
+
T.nilable(T::Hash[Symbol, String])
|
|
128
|
+
)
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# --- Commit-tracking (existing behavior) ---
|
|
132
|
+
|
|
133
|
+
sig { returns(T.nilable(String)) }
|
|
134
|
+
def fetch_latest_version_for_commit
|
|
61
135
|
T.let(
|
|
62
136
|
LatestVersionFinder.new(
|
|
63
137
|
dependency: dependency,
|
|
@@ -71,6 +145,58 @@ module Dependabot
|
|
|
71
145
|
T.nilable(String)
|
|
72
146
|
)
|
|
73
147
|
end
|
|
148
|
+
|
|
149
|
+
# --- Ref classification ---
|
|
150
|
+
|
|
151
|
+
sig { returns(T::Boolean) }
|
|
152
|
+
def ref_pinned_to_version_tag?
|
|
153
|
+
return false unless git_commit_checker.git_dependency?
|
|
154
|
+
return false unless dependency_source_ref
|
|
155
|
+
|
|
156
|
+
git_commit_checker.pinned_ref_looks_like_version?
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
sig { returns(T::Boolean) }
|
|
160
|
+
def ref_is_versioned_branch?
|
|
161
|
+
finder = versioned_branch_finder
|
|
162
|
+
return false unless finder
|
|
163
|
+
|
|
164
|
+
finder.versioned_branch?
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
sig { returns(T.nilable(String)) }
|
|
168
|
+
def dependency_source_ref
|
|
169
|
+
dependency.source_details(allowed_types: ["git"])&.fetch(:ref, nil)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
sig { returns(T.nilable(VersionedBranchFinder)) }
|
|
173
|
+
def versioned_branch_finder
|
|
174
|
+
ref = dependency_source_ref
|
|
175
|
+
return unless ref
|
|
176
|
+
|
|
177
|
+
@versioned_branch_finder ||= T.let(
|
|
178
|
+
VersionedBranchFinder.new(
|
|
179
|
+
current_ref: ref,
|
|
180
|
+
dependency: dependency,
|
|
181
|
+
credentials: credentials,
|
|
182
|
+
ignored_versions: ignored_versions
|
|
183
|
+
),
|
|
184
|
+
T.nilable(VersionedBranchFinder)
|
|
185
|
+
)
|
|
186
|
+
end
|
|
187
|
+
|
|
188
|
+
sig { returns(Dependabot::GitCommitChecker) }
|
|
189
|
+
def git_commit_checker
|
|
190
|
+
@git_commit_checker ||= T.let(
|
|
191
|
+
Dependabot::GitCommitChecker.new(
|
|
192
|
+
dependency: dependency,
|
|
193
|
+
credentials: credentials,
|
|
194
|
+
ignored_versions: ignored_versions,
|
|
195
|
+
raise_on_ignored: raise_on_ignored
|
|
196
|
+
),
|
|
197
|
+
T.nilable(Dependabot::GitCommitChecker)
|
|
198
|
+
)
|
|
199
|
+
end
|
|
74
200
|
end
|
|
75
201
|
end
|
|
76
202
|
end
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-nix
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.370.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Dependabot
|
|
@@ -15,14 +15,14 @@ dependencies:
|
|
|
15
15
|
requirements:
|
|
16
16
|
- - '='
|
|
17
17
|
- !ruby/object:Gem::Version
|
|
18
|
-
version: 0.
|
|
18
|
+
version: 0.370.0
|
|
19
19
|
type: :runtime
|
|
20
20
|
prerelease: false
|
|
21
21
|
version_requirements: !ruby/object:Gem::Requirement
|
|
22
22
|
requirements:
|
|
23
23
|
- - '='
|
|
24
24
|
- !ruby/object:Gem::Version
|
|
25
|
-
version: 0.
|
|
25
|
+
version: 0.370.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -245,19 +245,21 @@ files:
|
|
|
245
245
|
- lib/dependabot/nix/file_fetcher.rb
|
|
246
246
|
- lib/dependabot/nix/file_parser.rb
|
|
247
247
|
- lib/dependabot/nix/file_updater.rb
|
|
248
|
+
- lib/dependabot/nix/flake_nix_parser.rb
|
|
248
249
|
- lib/dependabot/nix/metadata_finder.rb
|
|
249
250
|
- lib/dependabot/nix/package/package_details_fetcher.rb
|
|
250
251
|
- lib/dependabot/nix/package_manager.rb
|
|
251
252
|
- lib/dependabot/nix/requirement.rb
|
|
252
253
|
- lib/dependabot/nix/update_checker.rb
|
|
253
254
|
- lib/dependabot/nix/update_checker/latest_version_finder.rb
|
|
255
|
+
- lib/dependabot/nix/update_checker/versioned_branch_finder.rb
|
|
254
256
|
- lib/dependabot/nix/version.rb
|
|
255
257
|
homepage: https://github.com/dependabot/dependabot-core
|
|
256
258
|
licenses:
|
|
257
259
|
- MIT
|
|
258
260
|
metadata:
|
|
259
261
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
260
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
262
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.370.0
|
|
261
263
|
rdoc_options: []
|
|
262
264
|
require_paths:
|
|
263
265
|
- lib
|