dependabot-core 0.80.1 → 0.81.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,162 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dependabot/file_updaters/rust/cargo"
4
-
5
- module Dependabot
6
- module FileUpdaters
7
- module Rust
8
- class Cargo
9
- class ManifestUpdater
10
- def initialize(dependencies:, manifest:)
11
- @dependencies = dependencies
12
- @manifest = manifest
13
- end
14
-
15
- def updated_manifest_content
16
- dependencies.
17
- select { |dep| requirement_changed?(manifest, dep) }.
18
- reduce(manifest.content.dup) do |content, dep|
19
- updated_content = content
20
-
21
- updated_content = update_requirements(
22
- content: updated_content,
23
- filename: manifest.name,
24
- dependency: dep
25
- )
26
-
27
- updated_content = update_git_pin(
28
- content: updated_content,
29
- filename: manifest.name,
30
- dependency: dep
31
- )
32
-
33
- if content == updated_content
34
- raise "Expected content to change!"
35
- end
36
-
37
- updated_content
38
- end
39
- end
40
-
41
- private
42
-
43
- attr_reader :dependencies, :manifest
44
-
45
- def requirement_changed?(file, dependency)
46
- changed_requirements =
47
- dependency.requirements - dependency.previous_requirements
48
-
49
- changed_requirements.any? { |f| f[:file] == file.name }
50
- end
51
-
52
- def update_requirements(content:, filename:, dependency:)
53
- updated_content = content.dup
54
-
55
- # The UpdateChecker ensures the order of requirements is preserved
56
- # when updating, so we can zip them together in new/old pairs.
57
- reqs = dependency.requirements.
58
- zip(dependency.previous_requirements).
59
- reject { |new_req, old_req| new_req == old_req }
60
-
61
- # Loop through each changed requirement
62
- reqs.each do |new_req, old_req|
63
- raise "Bad req match" unless new_req[:file] == old_req[:file]
64
- next if new_req[:requirement] == old_req[:requirement]
65
- next unless new_req[:file] == filename
66
-
67
- updated_content = update_manifest_req(
68
- content: updated_content,
69
- dep: dependency,
70
- old_req: old_req.fetch(:requirement),
71
- new_req: new_req.fetch(:requirement)
72
- )
73
- end
74
-
75
- updated_content
76
- end
77
-
78
- def update_git_pin(content:, filename:, dependency:)
79
- updated_pin =
80
- dependency.requirements.
81
- find { |r| r[:file] == filename }&.
82
- dig(:source, :ref)
83
-
84
- old_pin =
85
- dependency.previous_requirements.
86
- find { |r| r[:file] == filename }&.
87
- dig(:source, :ref)
88
-
89
- return content unless old_pin
90
-
91
- update_manifest_pin(
92
- content: content,
93
- dep: dependency,
94
- old_pin: old_pin,
95
- new_pin: updated_pin
96
- )
97
- end
98
-
99
- def update_manifest_req(content:, dep:, old_req:, new_req:)
100
- simple_declaration = content.scan(declaration_regex(dep)).
101
- find { |m| m.include?(old_req) }
102
-
103
- if simple_declaration
104
- content.gsub(simple_declaration) do |line|
105
- line.gsub(old_req, new_req)
106
- end
107
- elsif content.match?(feature_declaration_version_regex(dep))
108
- content.gsub(feature_declaration_version_regex(dep)) do |part|
109
- line = content.match(feature_declaration_version_regex(dep)).
110
- named_captures.fetch("version_declaration")
111
- new_line = line.gsub(old_req, new_req)
112
- part.gsub(line, new_line)
113
- end
114
- else
115
- content
116
- end
117
- end
118
-
119
- def update_manifest_pin(content:, dep:, old_pin:, new_pin:)
120
- simple_declaration = content.scan(declaration_regex(dep)).
121
- find { |m| m.include?(old_pin) }
122
-
123
- if simple_declaration
124
- content.gsub(simple_declaration) do |line|
125
- line.gsub(old_pin, new_pin)
126
- end
127
- elsif content.match?(feature_declaration_pin_regex(dep))
128
- content.gsub(feature_declaration_pin_regex(dep)) do |part|
129
- line = content.match(feature_declaration_pin_regex(dep)).
130
- named_captures.fetch("pin_declaration")
131
- new_line = line.gsub(old_pin, new_pin)
132
- part.gsub(line, new_line)
133
- end
134
- else
135
- content
136
- end
137
- end
138
-
139
- def declaration_regex(dep)
140
- /(?:^|["'])#{Regexp.escape(dep.name)}["']?\s*=.*$/i
141
- end
142
-
143
- def feature_declaration_version_regex(dep)
144
- /
145
- #{Regexp.quote("dependencies.#{dep.name}]")}
146
- (?:(?!^\[).)+
147
- (?<version_declaration>version\s*=[^\[]*)$
148
- /mx
149
- end
150
-
151
- def feature_declaration_pin_regex(dep)
152
- /
153
- #{Regexp.quote("dependencies.#{dep.name}]")}
154
- (?:(?!^\[).)+
155
- (?<pin_declaration>(?:tag|rev)\s*=[^\[]*)$
156
- /mx
157
- end
158
- end
159
- end
160
- end
161
- end
162
- end
@@ -1,64 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "excon"
4
- require "dependabot/metadata_finders/base"
5
- require "dependabot/shared_helpers"
6
-
7
- module Dependabot
8
- module MetadataFinders
9
- module Rust
10
- class Cargo < Dependabot::MetadataFinders::Base
11
- SOURCE_KEYS = %w(repository homepage documentation).freeze
12
-
13
- private
14
-
15
- def look_up_source
16
- case new_source_type
17
- when "default" then find_source_from_crates_listing
18
- when "git" then find_source_from_git_url
19
- else raise "Unexpected source type: #{new_source_type}"
20
- end
21
- end
22
-
23
- def new_source_type
24
- sources =
25
- dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
26
-
27
- return "default" if sources.empty?
28
- raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
29
-
30
- sources.first[:type] || sources.first.fetch("type")
31
- end
32
-
33
- def find_source_from_crates_listing
34
- potential_source_urls =
35
- SOURCE_KEYS.
36
- map { |key| crates_listing.dig("crate", key) }.
37
- compact
38
-
39
- source_url = potential_source_urls.find { |url| Source.from_url(url) }
40
- Source.from_url(source_url)
41
- end
42
-
43
- def find_source_from_git_url
44
- info = dependency.requirements.map { |r| r[:source] }.compact.first
45
-
46
- url = info[:url] || info.fetch("url")
47
- Source.from_url(url)
48
- end
49
-
50
- def crates_listing
51
- return @crates_listing unless @crates_listing.nil?
52
-
53
- response = Excon.get(
54
- "https://crates.io/api/v1/crates/#{dependency.name}",
55
- idempotent: true,
56
- **SharedHelpers.excon_defaults
57
- )
58
-
59
- @crates_listing = JSON.parse(response.body)
60
- end
61
- end
62
- end
63
- end
64
- end
@@ -1,282 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "excon"
4
- require "dependabot/git_commit_checker"
5
- require "dependabot/update_checkers/base"
6
-
7
- module Dependabot
8
- module UpdateCheckers
9
- module Rust
10
- class Cargo < Dependabot::UpdateCheckers::Base
11
- require_relative "cargo/requirements_updater"
12
- require_relative "cargo/version_resolver"
13
- require_relative "cargo/file_preparer"
14
-
15
- def latest_version
16
- return if path_dependency?
17
-
18
- @latest_version =
19
- if git_dependency?
20
- latest_version_for_git_dependency
21
- elsif git_subdependency?
22
- # TODO: Dependabot can't update git sub-dependencies yet, because
23
- # they can't be passed to GitCommitChecker.
24
- nil
25
- else
26
- versions = available_versions
27
- versions.reject!(&:prerelease?) unless wants_prerelease?
28
- versions.reject! do |v|
29
- ignore_reqs.any? { |r| r.satisfied_by?(v) }
30
- end
31
- versions.max
32
- end
33
- end
34
-
35
- def latest_resolvable_version
36
- return if path_dependency?
37
-
38
- @latest_resolvable_version ||=
39
- if git_dependency?
40
- latest_resolvable_version_for_git_dependency
41
- elsif git_subdependency?
42
- # TODO: Dependabot can't update git sub-dependencies yet, because
43
- # they can't be passed to GitCommitChecker.
44
- nil
45
- else
46
- fetch_latest_resolvable_version(unlock_requirement: true)
47
- end
48
- end
49
-
50
- def latest_resolvable_version_with_no_unlock
51
- return if path_dependency?
52
-
53
- @latest_resolvable_version_with_no_unlock ||=
54
- if git_dependency?
55
- latest_resolvable_commit_with_unchanged_git_source
56
- else
57
- fetch_latest_resolvable_version(unlock_requirement: false)
58
- end
59
- end
60
-
61
- def updated_requirements
62
- RequirementsUpdater.new(
63
- requirements: dependency.requirements,
64
- updated_source: updated_source,
65
- latest_resolvable_version: latest_resolvable_version&.to_s,
66
- latest_version: latest_version&.to_s,
67
- library: library?,
68
- update_strategy: requirement_update_strategy
69
- ).updated_requirements
70
- end
71
-
72
- private
73
-
74
- def latest_version_resolvable_with_full_unlock?
75
- # Full unlock checks aren't implemented for Rust (yet)
76
- false
77
- end
78
-
79
- def updated_dependencies_after_full_unlock
80
- raise NotImplementedError
81
- end
82
-
83
- def library?
84
- # If it has a lockfile, treat it as an application. Otherwise treat it
85
- # as a library.
86
- dependency_files.none? { |f| f.name == "Cargo.lock" }
87
- end
88
-
89
- def requirement_update_strategy
90
- library? ? :bump_versions_if_necessary : :bump_versions
91
- end
92
-
93
- def latest_version_for_git_dependency
94
- latest_git_version_sha
95
- end
96
-
97
- def latest_git_version_sha
98
- # If the gem isn't pinned, the latest version is just the latest
99
- # commit for the specified branch.
100
- unless git_commit_checker.pinned?
101
- return git_commit_checker.head_commit_for_current_branch
102
- end
103
-
104
- # If the dependency is pinned to a tag that looks like a version then
105
- # we want to update that tag. The latest version will then be the SHA
106
- # of the latest tag that looks like a version.
107
- if git_commit_checker.pinned_ref_looks_like_version?
108
- latest_tag = git_commit_checker.local_tag_for_latest_version
109
- return latest_tag&.fetch(:commit_sha) || dependency.version
110
- end
111
-
112
- # If the dependency is pinned to a tag that doesn't look like a
113
- # version then there's nothing we can do.
114
- dependency.version
115
- end
116
-
117
- def latest_resolvable_version_for_git_dependency
118
- # If the gem isn't pinned, the latest version is just the latest
119
- # commit for the specified branch.
120
- unless git_commit_checker.pinned?
121
- return latest_resolvable_commit_with_unchanged_git_source
122
- end
123
-
124
- # If the dependency is pinned to a tag that looks like a version then
125
- # we want to update that tag. The latest version will then be the SHA
126
- # of the latest tag that looks like a version.
127
- if git_commit_checker.pinned_ref_looks_like_version? &&
128
- latest_git_tag_is_resolvable?
129
- new_tag = git_commit_checker.local_tag_for_latest_version
130
- return new_tag.fetch(:commit_sha)
131
- end
132
-
133
- # If the dependency is pinned then there's nothing we can do.
134
- dependency.version
135
- end
136
-
137
- def latest_git_tag_is_resolvable?
138
- return @git_tag_resolvable if @latest_git_tag_is_resolvable_checked
139
-
140
- @latest_git_tag_is_resolvable_checked = true
141
-
142
- return false if git_commit_checker.local_tag_for_latest_version.nil?
143
-
144
- replacement_tag = git_commit_checker.local_tag_for_latest_version
145
-
146
- prepared_files = FilePreparer.new(
147
- dependency_files: dependency_files,
148
- dependency: dependency,
149
- unlock_requirement: true,
150
- replacement_git_pin: replacement_tag.fetch(:tag)
151
- ).prepared_dependency_files
152
-
153
- VersionResolver.new(
154
- dependency: dependency,
155
- prepared_dependency_files: prepared_files,
156
- original_dependency_files: dependency_files,
157
- credentials: credentials
158
- ).latest_resolvable_version
159
- @git_tag_resolvable = true
160
- rescue SharedHelpers::HelperSubprocessFailed => error
161
- raise error unless error.message.include?("versions conflict")
162
-
163
- @git_tag_resolvable = false
164
- end
165
-
166
- def latest_resolvable_commit_with_unchanged_git_source
167
- fetch_latest_resolvable_version(unlock_requirement: false)
168
- rescue SharedHelpers::HelperSubprocessFailed => error
169
- # Resolution may fail, as Cargo updates straight to the tip of the
170
- # branch. Just return `nil` if it does (so no update).
171
- return if error.message.include?("versions conflict")
172
-
173
- raise error
174
- end
175
-
176
- def fetch_latest_resolvable_version(unlock_requirement:)
177
- prepared_files = FilePreparer.new(
178
- dependency_files: dependency_files,
179
- dependency: dependency,
180
- unlock_requirement: unlock_requirement,
181
- latest_allowable_version: latest_version
182
- ).prepared_dependency_files
183
-
184
- VersionResolver.new(
185
- dependency: dependency,
186
- prepared_dependency_files: prepared_files,
187
- original_dependency_files: dependency_files,
188
- credentials: credentials
189
- ).latest_resolvable_version
190
- end
191
-
192
- def updated_source
193
- # Never need to update source, unless a git_dependency
194
- return dependency_source_details unless git_dependency?
195
-
196
- # Update the git tag if updating a pinned version
197
- if git_commit_checker.pinned_ref_looks_like_version? &&
198
- latest_git_tag_is_resolvable?
199
- new_tag = git_commit_checker.local_tag_for_latest_version
200
- return dependency_source_details.merge(ref: new_tag.fetch(:tag))
201
- end
202
-
203
- # Otherwise return the original source
204
- dependency_source_details
205
- end
206
-
207
- def dependency_source_details
208
- sources =
209
- dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
210
-
211
- raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
212
-
213
- sources.first
214
- end
215
-
216
- def wants_prerelease?
217
- if dependency.version &&
218
- version_class.new(dependency.version).prerelease?
219
- return true
220
- end
221
-
222
- dependency.requirements.any? do |req|
223
- reqs = (req.fetch(:requirement) || "").split(",").map(&:strip)
224
- reqs.any? { |r| r.match?(/[A-Za-z]/) }
225
- end
226
- end
227
-
228
- def available_versions
229
- crates_listing.
230
- fetch("versions", []).
231
- reject { |v| v["yanked"] }.
232
- map { |v| version_class.new(v.fetch("num")) }
233
- end
234
-
235
- def git_dependency?
236
- git_commit_checker.git_dependency?
237
- end
238
-
239
- def git_subdependency?
240
- return false if dependency.top_level?
241
-
242
- !version_class.correct?(dependency.version)
243
- end
244
-
245
- def path_dependency?
246
- sources = dependency.requirements.
247
- map { |r| r.fetch(:source) }.uniq.compact
248
-
249
- raise "Multiple sources! #{sources.join(', ')}" if sources.count > 1
250
-
251
- sources.first&.fetch(:type) == "path"
252
- end
253
-
254
- def git_commit_checker
255
- @git_commit_checker ||=
256
- GitCommitChecker.new(
257
- dependency: dependency,
258
- credentials: credentials
259
- )
260
- end
261
-
262
- def crates_listing
263
- return @crates_listing unless @crates_listing.nil?
264
-
265
- response = Excon.get(
266
- "https://crates.io/api/v1/crates/#{dependency.name}",
267
- idempotent: true,
268
- **SharedHelpers.excon_defaults
269
- )
270
-
271
- @crates_listing = JSON.parse(response.body)
272
- rescue Excon::Error::Timeout
273
- retrying ||= false
274
- raise if retrying
275
-
276
- retrying = true
277
- sleep(rand(1.0..5.0)) && retry
278
- end
279
- end
280
- end
281
- end
282
- end