dependabot-cargo 0.389.0 → 0.390.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:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7c5589c34a68938c08db740569195c7374cc0f991efb982533f5525703dcafd8
|
|
4
|
+
data.tar.gz: 47e442e693cbbe9c2dde8882a08f5b52e72e11f1821f72704ce9e5a77b553da1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 413ed1890c6b9fbaaba4f22315ca2c58d16293b8f50e20edfbc93f18eacea2c9242c5079eb1f6de745e89e08c5be1bae727ea8d466b6b3bffc8e411157f9f552
|
|
7
|
+
data.tar.gz: cc1c93979034f78bd6ac629708d55ff76a3802f0d0d6de6215aa093dfc710cab96caf082c4e50c917c13e9cf30eb0cfe22257764dca1f5087b2fb2406137eb30
|
|
@@ -19,7 +19,7 @@ require "dependabot/cargo/package_manager"
|
|
|
19
19
|
# - https://doc.rust-lang.org/cargo/reference/specifying-dependencies.html
|
|
20
20
|
module Dependabot
|
|
21
21
|
module Cargo
|
|
22
|
-
class FileParser < Dependabot::FileParsers::Base
|
|
22
|
+
class FileParser < Dependabot::FileParsers::Base # rubocop:disable Metrics/ClassLength
|
|
23
23
|
require "dependabot/file_parsers/base/dependency_set"
|
|
24
24
|
|
|
25
25
|
DEPENDENCY_TYPES =
|
|
@@ -34,6 +34,7 @@ module Dependabot
|
|
|
34
34
|
dependency_set += lockfile_dependencies if lockfile
|
|
35
35
|
|
|
36
36
|
dependencies = dependency_set.dependencies
|
|
37
|
+
add_lockfile_package_metadata(dependencies) if lockfile
|
|
37
38
|
|
|
38
39
|
# TODO: Handle patched dependencies
|
|
39
40
|
dependencies.reject! { |d| patched_dependencies.include?(d.name) }
|
|
@@ -208,8 +209,6 @@ module Dependabot
|
|
|
208
209
|
T.cast(lockfile_content.fetch("package", []), T::Array[T::Hash[String, T.anything]]).each do |package_details|
|
|
209
210
|
next unless T.cast(package_details["source"], T.nilable(String))
|
|
210
211
|
|
|
211
|
-
# TODO: This isn't quite right, as it will only give us one
|
|
212
|
-
# version of each dependency (when in fact there are many)
|
|
213
212
|
dependency_set << Dependency.new(
|
|
214
213
|
name: T.cast(package_details["name"], String),
|
|
215
214
|
version: version_from_lockfile_details(package_details),
|
|
@@ -221,6 +220,29 @@ module Dependabot
|
|
|
221
220
|
dependency_set
|
|
222
221
|
end
|
|
223
222
|
|
|
223
|
+
sig { params(dependencies: T::Array[Dependabot::Dependency]).void }
|
|
224
|
+
def add_lockfile_package_metadata(dependencies)
|
|
225
|
+
packages_by_name = lockfile_packages(parsed_file(T.must(lockfile)))
|
|
226
|
+
.select { |package| package_field(package, "source") }
|
|
227
|
+
.group_by { |package| T.must(package_field(package, "name")) }
|
|
228
|
+
|
|
229
|
+
dependencies.each do |dependency|
|
|
230
|
+
packages = packages_by_name.fetch(dependency.name, [])
|
|
231
|
+
next if dependency.top_level?
|
|
232
|
+
next unless packages.length > 1
|
|
233
|
+
|
|
234
|
+
dependency.metadata[:all_versions] = packages.map do |package|
|
|
235
|
+
Dependency.new(
|
|
236
|
+
name: dependency.name,
|
|
237
|
+
version: version_from_lockfile_details(package),
|
|
238
|
+
package_manager: "cargo",
|
|
239
|
+
requirements: [],
|
|
240
|
+
metadata: { cargo_package_source: T.must(package_field(package, "source")) }
|
|
241
|
+
)
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
end
|
|
245
|
+
|
|
224
246
|
sig { returns(T::Array[String]) }
|
|
225
247
|
def patched_dependencies
|
|
226
248
|
root_manifest = manifest_files.find { |f| f.name == "Cargo.toml" }
|
|
@@ -35,7 +35,8 @@ module Dependabot
|
|
|
35
35
|
@dependencies = T.let(dependencies, T::Array[Dependabot::Dependency])
|
|
36
36
|
@dependency_files = T.let(dependency_files, T::Array[Dependabot::DependencyFile])
|
|
37
37
|
@credentials = T.let(credentials, T::Array[Dependabot::Credential])
|
|
38
|
-
@
|
|
38
|
+
@custom_specifications = T.let({}, T::Hash[String, String])
|
|
39
|
+
@current_dependency = T.let(nil, T.nilable(Dependabot::Dependency))
|
|
39
40
|
@git_ssh_requirements_to_swap = T.let(nil, T.nilable(T::Hash[String, String]))
|
|
40
41
|
@manifest_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
|
|
41
42
|
@path_dependency_files = T.let(nil, T.nilable(T::Array[Dependabot::DependencyFile]))
|
|
@@ -49,31 +50,11 @@ module Dependabot
|
|
|
49
50
|
base_directory = T.must(dependency_files.first).directory
|
|
50
51
|
SharedHelpers.in_a_temporary_directory(base_directory) do
|
|
51
52
|
write_temporary_dependency_files
|
|
52
|
-
|
|
53
|
-
SharedHelpers.with_git_configured(credentials: credentials) do
|
|
54
|
-
# Shell out to Cargo, which handles everything for us, and does
|
|
55
|
-
# so without doing an install (so it's fast).
|
|
56
|
-
run_cargo_command("cargo update -p #{dependency_spec}", fingerprint: "cargo update -p <dependency_spec>")
|
|
57
|
-
end
|
|
53
|
+
run_updates
|
|
58
54
|
|
|
59
55
|
updated_lockfile = File.read("Cargo.lock")
|
|
60
56
|
updated_lockfile = post_process_lockfile(updated_lockfile)
|
|
61
|
-
|
|
62
|
-
next updated_lockfile if updated_lockfile.include?(desired_lockfile_content)
|
|
63
|
-
|
|
64
|
-
# If exact version match fails, accept any update
|
|
65
|
-
if dependency_updated?(updated_lockfile, dependency)
|
|
66
|
-
actual_version = extract_actual_version(updated_lockfile, dependency.name)
|
|
67
|
-
if actual_version && actual_version != dependency.version
|
|
68
|
-
Dependabot.logger.info(
|
|
69
|
-
"Cargo selected version #{actual_version} instead of #{dependency.version} for #{dependency.name} " \
|
|
70
|
-
"due to dependency constraints"
|
|
71
|
-
)
|
|
72
|
-
end
|
|
73
|
-
next updated_lockfile
|
|
74
|
-
end
|
|
75
|
-
|
|
76
|
-
raise "Failed to update #{dependency.name}!"
|
|
57
|
+
validate_updates(updated_lockfile)
|
|
77
58
|
end
|
|
78
59
|
rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
|
|
79
60
|
retry if better_specification_needed?(e)
|
|
@@ -91,10 +72,67 @@ module Dependabot
|
|
|
91
72
|
sig { returns(T::Array[Dependabot::Credential]) }
|
|
92
73
|
attr_reader :credentials
|
|
93
74
|
|
|
94
|
-
# Currently, there will only be a single updated dependency
|
|
95
75
|
sig { returns(Dependabot::Dependency) }
|
|
96
76
|
def dependency
|
|
97
|
-
T.must(dependencies.first)
|
|
77
|
+
@current_dependency || T.must(dependencies.first)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
sig { void }
|
|
81
|
+
def run_updates
|
|
82
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
|
83
|
+
# Shell out to Cargo, which handles everything for us, and does
|
|
84
|
+
# so without doing an install (so it's fast).
|
|
85
|
+
dependencies.each do |dependency_to_update|
|
|
86
|
+
@current_dependency = dependency_to_update
|
|
87
|
+
next if previous_line_already_replaced?
|
|
88
|
+
|
|
89
|
+
run_cargo_command(
|
|
90
|
+
"cargo update -p #{dependency_spec}",
|
|
91
|
+
fingerprint: "cargo update -p <dependency_spec>"
|
|
92
|
+
)
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# An earlier command in this run may already have resolved this
|
|
98
|
+
# dependency's line, in which case `cargo update -p name:version`
|
|
99
|
+
# would match no package and fail hard. Skip the command when the
|
|
100
|
+
# working lockfile has no entry left at the previous version and let
|
|
101
|
+
# validate_updates check the outcome.
|
|
102
|
+
sig { returns(T::Boolean) }
|
|
103
|
+
def previous_line_already_replaced?
|
|
104
|
+
previous_version = dependency.previous_version
|
|
105
|
+
return false unless previous_version
|
|
106
|
+
return false if previous_version == dependency.version || git_dependency?
|
|
107
|
+
return false unless version_class.correct?(previous_version)
|
|
108
|
+
|
|
109
|
+
package_version_count(File.read("Cargo.lock"), dependency, previous_version).zero?
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
sig { params(updated_lockfile: String).returns(String) }
|
|
113
|
+
def validate_updates(updated_lockfile)
|
|
114
|
+
dependencies.each do |updated_dependency|
|
|
115
|
+
@current_dependency = updated_dependency
|
|
116
|
+
validate_dependency_update(updated_lockfile)
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
updated_lockfile
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
sig { params(updated_lockfile: String).void }
|
|
123
|
+
def validate_dependency_update(updated_lockfile)
|
|
124
|
+
return if desired_version_present?(updated_lockfile)
|
|
125
|
+
return if git_line_resolved_differently?(updated_lockfile)
|
|
126
|
+
|
|
127
|
+
raise "Failed to update #{dependency.name}!" unless dependency_updated?(updated_lockfile, dependency)
|
|
128
|
+
|
|
129
|
+
actual_version = extract_actual_version(updated_lockfile, dependency)
|
|
130
|
+
return unless actual_version && actual_version != dependency.version
|
|
131
|
+
|
|
132
|
+
Dependabot.logger.info(
|
|
133
|
+
"Cargo selected version #{actual_version} instead of #{dependency.version} " \
|
|
134
|
+
"for #{dependency.name} due to dependency constraints"
|
|
135
|
+
)
|
|
98
136
|
end
|
|
99
137
|
|
|
100
138
|
sig { params(error: StandardError).returns(T.noreturn) }
|
|
@@ -144,7 +182,7 @@ module Dependabot
|
|
|
144
182
|
# rubocop:disable Metrics/AbcSize
|
|
145
183
|
sig { params(error: StandardError).returns(T::Boolean) }
|
|
146
184
|
def better_specification_needed?(error)
|
|
147
|
-
return false if
|
|
185
|
+
return false if custom_specification
|
|
148
186
|
return false unless error.message.match?(/specification .* is ambigu/)
|
|
149
187
|
|
|
150
188
|
spec_options = error.message.gsub(/.*following:\n/m, "")
|
|
@@ -157,7 +195,7 @@ module Dependabot
|
|
|
157
195
|
end
|
|
158
196
|
|
|
159
197
|
if ver && spec_options.one? { |s| s.end_with?(ver) }
|
|
160
|
-
@
|
|
198
|
+
@custom_specifications[dependency_identity] = T.must(spec_options.find { |s| s.end_with?(ver) })
|
|
161
199
|
return true
|
|
162
200
|
elsif ver && spec_options.count { |s| s.end_with?(ver) } > 1
|
|
163
201
|
spec_options.select! { |s| s.end_with?(ver) }
|
|
@@ -168,7 +206,7 @@ module Dependabot
|
|
|
168
206
|
spec_options.select! { |s| s.include?(T.must(git_source_url)) }
|
|
169
207
|
end
|
|
170
208
|
|
|
171
|
-
@
|
|
209
|
+
@custom_specifications[dependency_identity] = T.must(spec_options.first)
|
|
172
210
|
true
|
|
173
211
|
end
|
|
174
212
|
# rubocop:enable Metrics/AbcSize
|
|
@@ -177,7 +215,7 @@ module Dependabot
|
|
|
177
215
|
|
|
178
216
|
sig { returns(String) }
|
|
179
217
|
def dependency_spec
|
|
180
|
-
return
|
|
218
|
+
return T.must(custom_specification) if custom_specification
|
|
181
219
|
|
|
182
220
|
spec = dependency.name
|
|
183
221
|
|
|
@@ -190,12 +228,27 @@ module Dependabot
|
|
|
190
228
|
spec
|
|
191
229
|
end
|
|
192
230
|
|
|
231
|
+
sig { returns(T.nilable(String)) }
|
|
232
|
+
def custom_specification
|
|
233
|
+
@custom_specifications[dependency_identity]
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
sig { returns(String) }
|
|
237
|
+
def dependency_identity
|
|
238
|
+
[
|
|
239
|
+
dependency.name,
|
|
240
|
+
dependency.previous_version,
|
|
241
|
+
dependency.version,
|
|
242
|
+
dependency.metadata[:cargo_package_source]
|
|
243
|
+
].join("\0")
|
|
244
|
+
end
|
|
245
|
+
|
|
193
246
|
sig { returns(T.nilable(String)) }
|
|
194
247
|
def git_previous_version
|
|
195
248
|
TomlRB.parse(lockfile.content)
|
|
196
249
|
.fetch("package", [])
|
|
197
250
|
.select { |p| p["name"] == dependency.name }
|
|
198
|
-
.find { |p| p["source"]
|
|
251
|
+
.find { |p| p["source"]&.end_with?(dependency.previous_version) }
|
|
199
252
|
&.fetch("version")
|
|
200
253
|
end
|
|
201
254
|
|
|
@@ -213,6 +266,24 @@ module Dependabot
|
|
|
213
266
|
%(name = "#{dependency.name}"\nversion = "#{dependency.version}")
|
|
214
267
|
end
|
|
215
268
|
|
|
269
|
+
# The desired name/version pair may also appear under another source
|
|
270
|
+
# (e.g. a git fork at the released version), so scope the check to the
|
|
271
|
+
# locked package's source identity for numeric versions. Git
|
|
272
|
+
# dependencies keep the SHA substring check.
|
|
273
|
+
sig { params(updated_lockfile: String).returns(T::Boolean) }
|
|
274
|
+
def desired_version_present?(updated_lockfile)
|
|
275
|
+
version = T.must(dependency.version)
|
|
276
|
+
if git_dependency?
|
|
277
|
+
# Scope to the dependency's own git identity: another ref of the
|
|
278
|
+
# same repository already at the expected commit must not satisfy
|
|
279
|
+
# the check for the targeted line.
|
|
280
|
+
return git_source_shas(updated_lockfile).any? { |sha| sha.start_with?(version) }
|
|
281
|
+
end
|
|
282
|
+
return updated_lockfile.include?(desired_lockfile_content) unless version_class.correct?(version)
|
|
283
|
+
|
|
284
|
+
package_version_count(updated_lockfile, dependency, version).positive?
|
|
285
|
+
end
|
|
286
|
+
|
|
216
287
|
sig { params(command: String, fingerprint: String).void }
|
|
217
288
|
def run_cargo_command(command, fingerprint:)
|
|
218
289
|
start = Time.now
|
|
@@ -578,43 +649,149 @@ module Dependabot
|
|
|
578
649
|
def dependency_updated?(lockfile_content, dependency)
|
|
579
650
|
return false unless dependency.previous_version
|
|
580
651
|
|
|
581
|
-
|
|
582
|
-
|
|
652
|
+
target_line_versions(lockfile_content, dependency).any? do |version|
|
|
653
|
+
dependency.version_class.new(version) > dependency.version_class.new(T.must(dependency.previous_version))
|
|
654
|
+
end
|
|
655
|
+
end
|
|
656
|
+
|
|
657
|
+
sig do
|
|
658
|
+
params(lockfile_content: String, dependency: Dependabot::Dependency)
|
|
659
|
+
.returns(T.nilable(String))
|
|
660
|
+
end
|
|
661
|
+
def extract_actual_version(lockfile_content, dependency)
|
|
662
|
+
target_line_versions(lockfile_content, dependency).max_by do |version|
|
|
663
|
+
dependency.version_class.new(version)
|
|
664
|
+
end
|
|
665
|
+
end
|
|
666
|
+
|
|
667
|
+
sig do
|
|
668
|
+
params(lockfile_content: String, dependency: Dependabot::Dependency)
|
|
669
|
+
.returns(T::Array[String])
|
|
670
|
+
end
|
|
671
|
+
def target_line_versions(lockfile_content, dependency)
|
|
672
|
+
target_version = dependency.version
|
|
673
|
+
return [] unless target_version && dependency.version_class.correct?(target_version)
|
|
674
|
+
|
|
675
|
+
requirements = [dependency.previous_version, target_version].compact.uniq.filter_map do |version|
|
|
676
|
+
dependency.requirement_class.new(version) if dependency.version_class.correct?(version)
|
|
677
|
+
end
|
|
678
|
+
dependency_lockfile_entries(lockfile_content, dependency).filter_map do |entry|
|
|
679
|
+
version = entry[/^version = "([^"]+)"$/, 1]
|
|
680
|
+
next unless version && dependency.version_class.correct?(version)
|
|
681
|
+
|
|
682
|
+
parsed_version = dependency.version_class.new(version)
|
|
683
|
+
next unless requirements.any? { |requirement| requirement.satisfied_by?(parsed_version) }
|
|
684
|
+
|
|
685
|
+
version
|
|
686
|
+
end
|
|
687
|
+
end
|
|
688
|
+
|
|
689
|
+
sig do
|
|
690
|
+
params(lockfile_content: String, dependency: Dependabot::Dependency)
|
|
691
|
+
.returns(T::Array[String])
|
|
692
|
+
end
|
|
693
|
+
def dependency_lockfile_entries(lockfile_content, dependency)
|
|
583
694
|
entries = T.let([], T::Array[String])
|
|
584
695
|
lockfile_content.scan(LOCKFILE_ENTRY_REGEX) do
|
|
585
696
|
entries << Regexp.last_match.to_s
|
|
586
697
|
end
|
|
587
|
-
entries.select! { |entry| entry.
|
|
698
|
+
entries.select! { |entry| entry.match?(/^name = "#{Regexp.escape(dependency.name)}"$/) }
|
|
699
|
+
|
|
700
|
+
source = dependency.metadata[:cargo_package_source]
|
|
701
|
+
if source
|
|
702
|
+
entries.select! { |entry| entry.include?(%(source = "#{source}")) }
|
|
703
|
+
else
|
|
704
|
+
# Without an exact source identity, restrict to registry-style
|
|
705
|
+
# entries: git-sourced copies and source-less workspace or path
|
|
706
|
+
# packages must not satisfy the checks. Registry entries of any
|
|
707
|
+
# protocol (registry+, sparse+) stay visible.
|
|
708
|
+
entries.select! { |entry| entry.match?(/^source = "(?!git\+)/) }
|
|
709
|
+
end
|
|
710
|
+
entries
|
|
711
|
+
end
|
|
588
712
|
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
713
|
+
# A git dependency can legitimately resolve to a different commit than
|
|
714
|
+
# the checker expected (e.g. the tracked branch advanced between the
|
|
715
|
+
# check and this update). Accept the update when the dependency's git
|
|
716
|
+
# line moved off the previous commit, mirroring the version tolerance
|
|
717
|
+
# for registry dependencies.
|
|
718
|
+
sig { params(updated_lockfile: String).returns(T::Boolean) }
|
|
719
|
+
def git_line_resolved_differently?(updated_lockfile)
|
|
720
|
+
return false unless git_dependency?
|
|
593
721
|
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
722
|
+
previous_sha = dependency.previous_version
|
|
723
|
+
return false unless previous_sha
|
|
724
|
+
|
|
725
|
+
shas = git_source_shas(updated_lockfile)
|
|
726
|
+
# SHA comparisons use prefix semantics, matching the update
|
|
727
|
+
# checkers: previous_version may be an abbreviated commit.
|
|
728
|
+
return false if shas.empty? || shas.any? { |sha| sha.start_with?(previous_sha) }
|
|
729
|
+
|
|
730
|
+
Dependabot.logger.info(
|
|
731
|
+
"Cargo resolved #{dependency.name} to #{shas.join(', ')} " \
|
|
732
|
+
"instead of #{dependency.version} for the tracked git reference"
|
|
733
|
+
)
|
|
734
|
+
true
|
|
599
735
|
end
|
|
600
736
|
|
|
601
|
-
sig { params(lockfile_content: String
|
|
602
|
-
def
|
|
737
|
+
sig { params(lockfile_content: String).returns(T::Array[String]) }
|
|
738
|
+
def git_source_shas(lockfile_content)
|
|
603
739
|
entries = T.let([], T::Array[String])
|
|
604
740
|
lockfile_content.scan(LOCKFILE_ENTRY_REGEX) do
|
|
605
741
|
entries << Regexp.last_match.to_s
|
|
606
742
|
end
|
|
607
|
-
entries.select! { |entry| entry.
|
|
743
|
+
entries.select! { |entry| entry.match?(/^name = "#{Regexp.escape(dependency.name)}"$/) }
|
|
744
|
+
entries.select! { |entry| entry.match?(/^source = "git\+/) }
|
|
608
745
|
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
version_match&.captures&.first
|
|
613
|
-
end
|
|
746
|
+
scope_entries_to_git_identity(entries)
|
|
747
|
+
.filter_map { |entry| entry[/^source = "git\+[^"]*#([0-9a-f]+)"$/, 1] }
|
|
748
|
+
end
|
|
614
749
|
|
|
615
|
-
|
|
750
|
+
# Fail closed: the current identity serializes deterministically into
|
|
751
|
+
# the lockfile source string, so entries that don't match it are other
|
|
752
|
+
# identities, never fallbacks. An empty result means the expected
|
|
753
|
+
# identity is absent and validation must fail.
|
|
754
|
+
sig { params(entries: T::Array[String]).returns(T::Array[String]) }
|
|
755
|
+
def scope_entries_to_git_identity(entries)
|
|
756
|
+
url = git_source_detail(:url)
|
|
757
|
+
return entries unless url
|
|
758
|
+
|
|
759
|
+
entries.select { |entry| entry.match?(git_identity_pattern(url)) }
|
|
760
|
+
end
|
|
616
761
|
|
|
617
|
-
|
|
762
|
+
# Anchors the URL and ref together at the start of the source value so
|
|
763
|
+
# a prefix-related repository URL cannot satisfy the match.
|
|
764
|
+
sig { params(url: String).returns(Regexp) }
|
|
765
|
+
def git_identity_pattern(url)
|
|
766
|
+
base = "^source = \"git\\+#{Regexp.escape(url)}"
|
|
767
|
+
branch = git_source_detail(:branch)
|
|
768
|
+
return /#{base}\?branch=#{Regexp.escape(branch)}#/ if branch
|
|
769
|
+
|
|
770
|
+
ref = git_source_detail(:ref)
|
|
771
|
+
return /#{base}\?(?:tag|rev)=#{Regexp.escape(ref)}#/ if ref
|
|
772
|
+
|
|
773
|
+
/#{base}#/
|
|
774
|
+
end
|
|
775
|
+
|
|
776
|
+
# The updated lockfile is serialized with the dependency's current git
|
|
777
|
+
# identity (a ref change updates the source string), so prefer the
|
|
778
|
+
# current requirement's git source as a whole and use the previous one
|
|
779
|
+
# only when no current git source exists.
|
|
780
|
+
sig { params(key: Symbol).returns(T.nilable(String)) }
|
|
781
|
+
def git_source_detail(key)
|
|
782
|
+
source = dependency.requirements.find { |r| r.dig(:source, :type) == "git" }&.dig(:source) ||
|
|
783
|
+
dependency.previous_requirements&.find { |r| r.dig(:source, :type) == "git" }&.dig(:source)
|
|
784
|
+
source&.dig(key)
|
|
785
|
+
end
|
|
786
|
+
|
|
787
|
+
sig do
|
|
788
|
+
params(lockfile_content: String, dependency: Dependabot::Dependency, version: String)
|
|
789
|
+
.returns(Integer)
|
|
790
|
+
end
|
|
791
|
+
def package_version_count(lockfile_content, dependency, version)
|
|
792
|
+
dependency_lockfile_entries(lockfile_content, dependency).count do |entry|
|
|
793
|
+
entry.match?(/^version = "#{Regexp.escape(version)}"$/)
|
|
794
|
+
end
|
|
618
795
|
end
|
|
619
796
|
end
|
|
620
797
|
# rubocop:enable Metrics/ClassLength
|
|
@@ -106,22 +106,140 @@ module Dependabot
|
|
|
106
106
|
versions = TomlRB.parse(lockfile_content).fetch("package")
|
|
107
107
|
.select { |p| p["name"] == dependency.name }
|
|
108
108
|
|
|
109
|
-
updated_version =
|
|
110
|
-
if dependency.top_level?
|
|
111
|
-
versions.max_by { |p| version_class.new(p.fetch("version")) }
|
|
112
|
-
else
|
|
113
|
-
versions.min_by { |p| version_class.new(p.fetch("version")) }
|
|
114
|
-
end
|
|
109
|
+
updated_version = updated_package(versions)
|
|
115
110
|
|
|
116
111
|
return unless updated_version
|
|
117
112
|
|
|
118
113
|
if git_dependency?
|
|
119
|
-
updated_version.fetch("source").split("#").last
|
|
114
|
+
T.cast(updated_version.fetch("source"), String).split("#").last
|
|
120
115
|
else
|
|
121
|
-
updated_version.fetch("version")
|
|
116
|
+
T.cast(updated_version.fetch("version"), String)
|
|
122
117
|
end
|
|
123
118
|
end
|
|
124
119
|
|
|
120
|
+
sig do
|
|
121
|
+
params(
|
|
122
|
+
packages: T::Array[T::Hash[String, T.anything]]
|
|
123
|
+
).returns(T.nilable(T::Hash[String, T.anything]))
|
|
124
|
+
end
|
|
125
|
+
def updated_package(packages)
|
|
126
|
+
if dependency.top_level?
|
|
127
|
+
packages.max_by do |package|
|
|
128
|
+
version_class.new(T.cast(package.fetch("version"), String))
|
|
129
|
+
end
|
|
130
|
+
else
|
|
131
|
+
updated_package_for_locked_identity(packages) ||
|
|
132
|
+
packages.min_by do |package|
|
|
133
|
+
version_class.new(T.cast(package.fetch("version"), String))
|
|
134
|
+
end
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
sig do
|
|
139
|
+
params(
|
|
140
|
+
updated_packages: T::Array[T::Hash[String, T.anything]]
|
|
141
|
+
).returns(T.nilable(T::Hash[String, T.anything]))
|
|
142
|
+
end
|
|
143
|
+
def updated_package_for_locked_identity(updated_packages)
|
|
144
|
+
target = original_target_package
|
|
145
|
+
return unless target
|
|
146
|
+
|
|
147
|
+
source = T.cast(target["source"], T.nilable(String))
|
|
148
|
+
candidates = updated_package_candidates(updated_packages, target)
|
|
149
|
+
|
|
150
|
+
# Cargo can split a unified line across a version boundary, adding a
|
|
151
|
+
# new package while retaining the old one for dependents that still
|
|
152
|
+
# need it. A newly introduced identity is the update; the retained
|
|
153
|
+
# line only answers when nothing new appeared.
|
|
154
|
+
fresh = candidates.reject { |package| package_identity(package) == package_identity(target) }
|
|
155
|
+
return fresh.max_by { |package| version_class.new(T.cast(package.fetch("version"), String)) } if fresh.any?
|
|
156
|
+
|
|
157
|
+
candidates.find { |package| version_from_package(package) == dependency.version } ||
|
|
158
|
+
compatible_packages(updated_packages, source).max_by do |package|
|
|
159
|
+
version_class.new(T.cast(package.fetch("version"), String))
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
sig { returns(T.nilable(T::Hash[String, T.anything])) }
|
|
164
|
+
def original_target_package
|
|
165
|
+
return unless dependency.version
|
|
166
|
+
|
|
167
|
+
targets = original_packages_for_dependency.select do |package|
|
|
168
|
+
version_from_package(package) == dependency.version
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
package_source = T.cast(dependency.metadata[:cargo_package_source], T.nilable(String))
|
|
172
|
+
if package_source
|
|
173
|
+
targets.select! do |package|
|
|
174
|
+
T.cast(package["source"], T.nilable(String)) == package_source
|
|
175
|
+
end
|
|
176
|
+
end
|
|
177
|
+
targets.first if targets.one?
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
sig do
|
|
181
|
+
params(
|
|
182
|
+
updated_packages: T::Array[T::Hash[String, T.anything]],
|
|
183
|
+
target: T::Hash[String, T.anything]
|
|
184
|
+
).returns(T::Array[T::Hash[String, T.anything]])
|
|
185
|
+
end
|
|
186
|
+
def updated_package_candidates(updated_packages, target)
|
|
187
|
+
target_source = T.cast(target["source"], T.nilable(String))
|
|
188
|
+
other_identities = original_packages_for_dependency
|
|
189
|
+
.map { |package| package_identity(package) }
|
|
190
|
+
target_index = other_identities.index(package_identity(target))
|
|
191
|
+
other_identities.delete_at(target_index) if target_index
|
|
192
|
+
|
|
193
|
+
updated_packages
|
|
194
|
+
.select { |package| T.cast(package["source"], T.nilable(String)) == target_source }
|
|
195
|
+
.reject { |package| other_identities.include?(package_identity(package)) }
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
sig { returns(T::Array[T::Hash[String, T.anything]]) }
|
|
199
|
+
def original_packages_for_dependency
|
|
200
|
+
original_lockfile_packages.select do |package|
|
|
201
|
+
T.cast(package["name"], T.nilable(String)) == dependency.name
|
|
202
|
+
end
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
sig { returns(T::Array[T::Hash[String, T.anything]]) }
|
|
206
|
+
def original_lockfile_packages
|
|
207
|
+
original_lockfile = original_dependency_files.find { |file| file.name == "Cargo.lock" }
|
|
208
|
+
return [] unless original_lockfile
|
|
209
|
+
|
|
210
|
+
T.cast(
|
|
211
|
+
TomlRB.parse(T.must(original_lockfile.content)).fetch("package", []),
|
|
212
|
+
T::Array[T::Hash[String, T.anything]]
|
|
213
|
+
)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
sig do
|
|
217
|
+
params(
|
|
218
|
+
packages: T::Array[T::Hash[String, T.anything]],
|
|
219
|
+
source: T.nilable(String)
|
|
220
|
+
).returns(T::Array[T::Hash[String, T.anything]])
|
|
221
|
+
end
|
|
222
|
+
def compatible_packages(packages, source)
|
|
223
|
+
requirement = Requirement.new(T.must(dependency.version))
|
|
224
|
+
packages.select do |package|
|
|
225
|
+
T.cast(package["source"], T.nilable(String)) == source &&
|
|
226
|
+
requirement.satisfied_by?(version_class.new(T.cast(package.fetch("version"), String)))
|
|
227
|
+
end
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
sig { params(package: T::Hash[String, T.anything]).returns(T::Array[T.anything]) }
|
|
231
|
+
def package_identity(package)
|
|
232
|
+
[package["name"], package["version"], package["source"]]
|
|
233
|
+
end
|
|
234
|
+
|
|
235
|
+
sig { params(package: T::Hash[String, T.anything]).returns(String) }
|
|
236
|
+
def version_from_package(package)
|
|
237
|
+
source = T.cast(package["source"], T.nilable(String))
|
|
238
|
+
return T.must(source.split("#").last) if source&.start_with?("git+")
|
|
239
|
+
|
|
240
|
+
T.cast(package.fetch("version"), String)
|
|
241
|
+
end
|
|
242
|
+
|
|
125
243
|
# rubocop:disable Metrics/PerceivedComplexity
|
|
126
244
|
# rubocop:disable Metrics/CyclomaticComplexity
|
|
127
245
|
# rubocop:disable Metrics/AbcSize
|
|
@@ -491,8 +609,8 @@ module Dependabot
|
|
|
491
609
|
TomlRB.parse(T.must(lockfile).content)
|
|
492
610
|
.fetch("package", [])
|
|
493
611
|
.select { |p| p["name"] == dependency.name }
|
|
494
|
-
.find { |p| p["source"]
|
|
495
|
-
|
|
612
|
+
.find { |p| p["source"]&.end_with?(dependency.version) }
|
|
613
|
+
&.fetch("version")
|
|
496
614
|
end
|
|
497
615
|
|
|
498
616
|
sig { returns(T.nilable(String)) }
|
|
@@ -10,7 +10,7 @@ require "dependabot/update_checkers/base"
|
|
|
10
10
|
|
|
11
11
|
module Dependabot
|
|
12
12
|
module Cargo
|
|
13
|
-
class UpdateChecker < Dependabot::UpdateCheckers::Base
|
|
13
|
+
class UpdateChecker < Dependabot::UpdateCheckers::Base # rubocop:disable Metrics/ClassLength
|
|
14
14
|
extend T::Sig
|
|
15
15
|
|
|
16
16
|
require_relative "update_checker/latest_version_finder"
|
|
@@ -18,10 +18,50 @@ module Dependabot
|
|
|
18
18
|
require_relative "update_checker/version_resolver"
|
|
19
19
|
require_relative "update_checker/file_preparer"
|
|
20
20
|
|
|
21
|
+
sig { override.returns(T::Boolean) }
|
|
22
|
+
def up_to_date?
|
|
23
|
+
return super unless multiple_locked_versions?
|
|
24
|
+
|
|
25
|
+
raise Dependabot::AllVersionsIgnored if only_ignored_updates_remaining?
|
|
26
|
+
|
|
27
|
+
all_lines_current?
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
sig { override.params(requirements_to_unlock: T.nilable(Symbol)).returns(T::Boolean) }
|
|
31
|
+
def can_update?(requirements_to_unlock:)
|
|
32
|
+
return super unless multiple_locked_versions?
|
|
33
|
+
|
|
34
|
+
results, ignored_count = locked_version_results(false) do |checker|
|
|
35
|
+
checker.can_update?(requirements_to_unlock: requirements_to_unlock)
|
|
36
|
+
end
|
|
37
|
+
updatable = results.any?
|
|
38
|
+
raise Dependabot::AllVersionsIgnored if ignored_count.positive? && !updatable
|
|
39
|
+
|
|
40
|
+
updatable
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
sig do
|
|
44
|
+
override.params(requirements_to_unlock: T.nilable(Symbol))
|
|
45
|
+
.returns(T::Array[Dependabot::Dependency])
|
|
46
|
+
end
|
|
47
|
+
def updated_dependencies(requirements_to_unlock:)
|
|
48
|
+
return super unless multiple_locked_versions?
|
|
49
|
+
|
|
50
|
+
results, ignored_count = locked_version_results([]) do |checker|
|
|
51
|
+
checker.updated_dependencies(requirements_to_unlock: requirements_to_unlock)
|
|
52
|
+
end
|
|
53
|
+
updates = results.flatten
|
|
54
|
+
raise Dependabot::AllVersionsIgnored if ignored_count.positive? && updates.empty?
|
|
55
|
+
|
|
56
|
+
updates
|
|
57
|
+
end
|
|
58
|
+
|
|
21
59
|
sig { override.returns(T.nilable(T.any(String, Gem::Version))) }
|
|
22
60
|
def latest_version
|
|
23
61
|
return if path_dependency?
|
|
24
62
|
|
|
63
|
+
raise Dependabot::AllVersionsIgnored if multiple_locked_versions? && only_ignored_updates_remaining?
|
|
64
|
+
|
|
25
65
|
@latest_version = T.let(
|
|
26
66
|
if git_dependency?
|
|
27
67
|
latest_version_for_git_dependency
|
|
@@ -57,6 +97,8 @@ module Dependabot
|
|
|
57
97
|
|
|
58
98
|
sig { override.returns(T.nilable(Gem::Version)) }
|
|
59
99
|
def lowest_security_fix_version
|
|
100
|
+
raise Dependabot::AllVersionsIgnored if multiple_locked_versions? && only_ignored_updates_remaining?
|
|
101
|
+
|
|
60
102
|
latest_version_finder.lowest_security_fix_version
|
|
61
103
|
end
|
|
62
104
|
|
|
@@ -349,6 +391,113 @@ module Dependabot
|
|
|
349
391
|
dependency.source_type == "path"
|
|
350
392
|
end
|
|
351
393
|
|
|
394
|
+
sig { returns(T::Boolean) }
|
|
395
|
+
def multiple_locked_versions?
|
|
396
|
+
sources = locked_version_dependencies.map { |candidate| candidate.metadata[:cargo_package_source] }.uniq
|
|
397
|
+
!dependency.top_level? && locked_version_dependencies.length > 1 && sources.one?
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
|
401
|
+
def locked_version_dependencies
|
|
402
|
+
@locked_version_dependencies ||= T.let(
|
|
403
|
+
begin
|
|
404
|
+
all_versions = dependency.metadata[:all_versions]
|
|
405
|
+
candidates = all_versions.is_a?(Array) ? all_versions.grep(Dependabot::Dependency) : []
|
|
406
|
+
numeric_candidates = candidates.select do |candidate|
|
|
407
|
+
!candidate.top_level? && version_class.correct?(candidate.version)
|
|
408
|
+
end
|
|
409
|
+
numeric_candidates.uniq { |candidate| [candidate.version, candidate.metadata[:cargo_package_source]] }
|
|
410
|
+
end,
|
|
411
|
+
T.nilable(T::Array[Dependabot::Dependency])
|
|
412
|
+
)
|
|
413
|
+
end
|
|
414
|
+
|
|
415
|
+
sig { returns(T::Array[Dependabot::Cargo::UpdateChecker]) }
|
|
416
|
+
def locked_version_checkers
|
|
417
|
+
@locked_version_checkers ||= T.let(
|
|
418
|
+
locked_version_dependencies_to_check.map do |locked_dependency|
|
|
419
|
+
self.class.new(
|
|
420
|
+
dependency: locked_dependency,
|
|
421
|
+
dependency_files: dependency_files,
|
|
422
|
+
repo_contents_path: repo_contents_path,
|
|
423
|
+
credentials: credentials,
|
|
424
|
+
ignored_versions: ignored_versions,
|
|
425
|
+
security_advisories: security_advisories,
|
|
426
|
+
raise_on_ignored: raise_on_ignored,
|
|
427
|
+
requirements_update_strategy: requirements_update_strategy,
|
|
428
|
+
dependency_group: dependency_group,
|
|
429
|
+
update_cooldown: update_cooldown,
|
|
430
|
+
options: options
|
|
431
|
+
)
|
|
432
|
+
end,
|
|
433
|
+
T.nilable(T::Array[Dependabot::Cargo::UpdateChecker])
|
|
434
|
+
)
|
|
435
|
+
end
|
|
436
|
+
|
|
437
|
+
# The operations layer only rescues AllVersionsIgnored around its
|
|
438
|
+
# preflight probes (latest_version, and lowest_security_fix_version for
|
|
439
|
+
# security jobs); anywhere else the error halts the whole update run.
|
|
440
|
+
# These helpers let those probes raise when ignored lines leave nothing
|
|
441
|
+
# actionable, so a mixed ignored/at-ceiling dependency surfaces as a
|
|
442
|
+
# clean ignored skip, and the aggregate overrides below can never be
|
|
443
|
+
# the first place the error appears.
|
|
444
|
+
sig { returns(T::Boolean) }
|
|
445
|
+
def only_ignored_updates_remaining?
|
|
446
|
+
evaluate_locked_line_currency
|
|
447
|
+
T.must(@only_ignored_updates_remaining)
|
|
448
|
+
end
|
|
449
|
+
|
|
450
|
+
sig { returns(T::Boolean) }
|
|
451
|
+
def all_lines_current?
|
|
452
|
+
evaluate_locked_line_currency
|
|
453
|
+
T.must(@all_lines_current)
|
|
454
|
+
end
|
|
455
|
+
|
|
456
|
+
sig { void }
|
|
457
|
+
def evaluate_locked_line_currency
|
|
458
|
+
return if defined?(@all_lines_current)
|
|
459
|
+
|
|
460
|
+
results, ignored_count = locked_version_results(true) do |checker|
|
|
461
|
+
checker.up_to_date? || checker.latest_resolvable_version&.to_s == checker.dependency.version
|
|
462
|
+
end
|
|
463
|
+
@all_lines_current = T.let(results.all?, T.nilable(T::Boolean))
|
|
464
|
+
@only_ignored_updates_remaining = T.let(ignored_count.positive? && results.all?, T.nilable(T::Boolean))
|
|
465
|
+
end
|
|
466
|
+
|
|
467
|
+
# Evaluates the block for every locked-line checker, substituting
|
|
468
|
+
# ignored_result for lines whose updates are all ignored. Returns the
|
|
469
|
+
# results alongside the ignored-line count so callers can re-raise
|
|
470
|
+
# AllVersionsIgnored when ignored lines exist and no other line yields
|
|
471
|
+
# an actionable result.
|
|
472
|
+
sig do
|
|
473
|
+
type_parameters(:U)
|
|
474
|
+
.params(
|
|
475
|
+
ignored_result: T.type_parameter(:U),
|
|
476
|
+
_blk: T.proc.params(checker: Dependabot::Cargo::UpdateChecker).returns(T.type_parameter(:U))
|
|
477
|
+
).returns([T::Array[T.type_parameter(:U)], Integer])
|
|
478
|
+
end
|
|
479
|
+
def locked_version_results(ignored_result, &_blk)
|
|
480
|
+
ignored_count = 0
|
|
481
|
+
results = locked_version_checkers.map do |checker|
|
|
482
|
+
yield(checker)
|
|
483
|
+
rescue Dependabot::AllVersionsIgnored
|
|
484
|
+
ignored_count += 1
|
|
485
|
+
ignored_result
|
|
486
|
+
end
|
|
487
|
+
|
|
488
|
+
[results, ignored_count]
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
sig { returns(T::Array[Dependabot::Dependency]) }
|
|
492
|
+
def locked_version_dependencies_to_check
|
|
493
|
+
return locked_version_dependencies if security_advisories.empty?
|
|
494
|
+
|
|
495
|
+
locked_version_dependencies.select do |candidate|
|
|
496
|
+
version = version_class.new(T.must(candidate.version))
|
|
497
|
+
security_advisories.any? { |advisory| advisory.vulnerable?(version) }
|
|
498
|
+
end
|
|
499
|
+
end
|
|
500
|
+
|
|
352
501
|
sig { returns(GitCommitChecker) }
|
|
353
502
|
def git_commit_checker
|
|
354
503
|
@git_commit_checker = T.let(
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: dependabot-cargo
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.390.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.390.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.390.0
|
|
26
26
|
- !ruby/object:Gem::Dependency
|
|
27
27
|
name: debug
|
|
28
28
|
requirement: !ruby/object:Gem::Requirement
|
|
@@ -266,7 +266,7 @@ licenses:
|
|
|
266
266
|
- MIT
|
|
267
267
|
metadata:
|
|
268
268
|
bug_tracker_uri: https://github.com/dependabot/dependabot-core/issues
|
|
269
|
-
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.
|
|
269
|
+
changelog_uri: https://github.com/dependabot/dependabot-core/releases/tag/v0.390.0
|
|
270
270
|
rdoc_options: []
|
|
271
271
|
require_paths:
|
|
272
272
|
- lib
|