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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b0e3f983942dcb2716e77e6a4390dd075677fa30e80cf274016ccd88a880d9fb
4
- data.tar.gz: 7064ea395d8cf247a1a15049be7826ad64cca2b7160db34e8e39e8d99f04c46c
3
+ metadata.gz: 7b92e30b98746b2c1f5856c15463bd7c0c8c9cc072027364feaa931946945a74
4
+ data.tar.gz: 35a7af97d0d7225b0e390cdb705eb0f58650d1895a73c399d8328926b5bff74d
5
5
  SHA512:
6
- metadata.gz: f2d486cd013b48ef06a956cb0496fe55c63b43b70b20aca5f13d4fdd0ef680743d75abc476b01a7582e46b557c13de405355203614ab902b964434cf371bbc00
7
- data.tar.gz: 495215f3e0cbe0fe06ad86078fe9402afc46a9795cf8f20149c9cd65a86173c4cbccb027f26beaf2dbda27f176fa3b527b5e5cc5ce23a3b3bfc51e6b17a18931
6
+ metadata.gz: b65a8cf1a67737b11fb3db6c40e29220b51cbdcf7d0daa65aae05282febe4ee7d2e6bb5fca4d34623b1328bca60eca0fd7f6db177f5cb7baa2c77b87343bd89a
7
+ data.tar.gz: d2ca3a0197a030d0383f5b67d7146e207d97770c156604e8b0da0a3aba91e4a8720b3a187ae161f404155cf3b81046f7f4ba86320e3a732004f1a5652bfc2d32
@@ -149,7 +149,7 @@ module Dependabot
149
149
  requirements: [{
150
150
  requirement: nil,
151
151
  file: "flake.lock",
152
- source: { type: "git", url: url, branch: ref, ref: ref },
152
+ source: { type: "git", url: url, branch: nil, ref: ref },
153
153
  groups: []
154
154
  }]
155
155
  )
@@ -1,4 +1,4 @@
1
- # typed: strong
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
- updated_lockfile_content = update_flake_lock
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
- [updated_file(file: flake_lock, content: updated_lockfile_content)]
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
- sig { returns(String) }
36
- def update_flake_lock
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: strong
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
- # Flake input requirements are the URL and branch — we never update those.
43
- dependency.requirements
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.368.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.368.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.368.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.368.0
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