dependabot-common 0.236.0 → 0.238.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (32) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/clients/azure.rb +3 -3
  3. data/lib/dependabot/clients/codecommit.rb +1 -0
  4. data/lib/dependabot/config/file.rb +17 -6
  5. data/lib/dependabot/config/update_config.rb +23 -5
  6. data/lib/dependabot/dependency.rb +137 -27
  7. data/lib/dependabot/dependency_file.rb +84 -14
  8. data/lib/dependabot/dependency_group.rb +29 -5
  9. data/lib/dependabot/errors.rb +335 -13
  10. data/lib/dependabot/file_fetchers/base.rb +227 -93
  11. data/lib/dependabot/file_updaters/base.rb +1 -1
  12. data/lib/dependabot/git_commit_checker.rb +6 -0
  13. data/lib/dependabot/git_metadata_fetcher.rb +58 -20
  14. data/lib/dependabot/git_ref.rb +71 -0
  15. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +13 -6
  16. data/lib/dependabot/pull_request_creator/github.rb +11 -8
  17. data/lib/dependabot/pull_request_creator/message.rb +21 -2
  18. data/lib/dependabot/pull_request_creator/message_builder/link_and_mention_sanitizer.rb +37 -16
  19. data/lib/dependabot/pull_request_creator/message_builder/metadata_presenter.rb +4 -2
  20. data/lib/dependabot/pull_request_creator/message_builder.rb +54 -4
  21. data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +10 -4
  22. data/lib/dependabot/shared_helpers.rb +117 -33
  23. data/lib/dependabot/simple_instrumentor.rb +22 -3
  24. data/lib/dependabot/source.rb +65 -17
  25. data/lib/dependabot/update_checkers/version_filters.rb +12 -1
  26. data/lib/dependabot/utils.rb +21 -2
  27. data/lib/dependabot/workspace/base.rb +42 -7
  28. data/lib/dependabot/workspace/change_attempt.rb +31 -3
  29. data/lib/dependabot/workspace/git.rb +34 -4
  30. data/lib/dependabot/workspace.rb +16 -2
  31. data/lib/dependabot.rb +1 -1
  32. metadata +38 -9
@@ -1,7 +1,8 @@
1
- # typed: false
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "stringio"
5
+ require "sorbet-runtime"
5
6
  require "dependabot/config"
6
7
  require "dependabot/dependency_file"
7
8
  require "dependabot/source"
@@ -17,15 +18,33 @@ require "dependabot/shared_helpers"
17
18
  module Dependabot
18
19
  module FileFetchers
19
20
  class Base
20
- attr_reader :source, :credentials, :repo_contents_path, :options
21
+ extend T::Sig
22
+ extend T::Helpers
21
23
 
22
- CLIENT_NOT_FOUND_ERRORS = [
23
- Octokit::NotFound,
24
- Gitlab::Error::NotFound,
25
- Dependabot::Clients::Azure::NotFound,
26
- Dependabot::Clients::Bitbucket::NotFound,
27
- Dependabot::Clients::CodeCommit::NotFound
28
- ].freeze
24
+ abstract!
25
+
26
+ sig { returns(Dependabot::Source) }
27
+ attr_reader :source
28
+
29
+ sig { returns(T::Array[T::Hash[String, String]]) }
30
+ attr_reader :credentials
31
+
32
+ sig { returns(T.nilable(String)) }
33
+ attr_reader :repo_contents_path
34
+
35
+ sig { returns(T::Hash[String, String]) }
36
+ attr_reader :options
37
+
38
+ CLIENT_NOT_FOUND_ERRORS = T.let(
39
+ [
40
+ Octokit::NotFound,
41
+ Gitlab::Error::NotFound,
42
+ Dependabot::Clients::Azure::NotFound,
43
+ Dependabot::Clients::Bitbucket::NotFound,
44
+ Dependabot::Clients::CodeCommit::NotFound
45
+ ].freeze,
46
+ T::Array[T.class_of(StandardError)]
47
+ )
29
48
 
30
49
  GIT_SUBMODULE_INACCESSIBLE_ERROR =
31
50
  /^fatal: unable to access '(?<url>.*)': The requested URL returned error: (?<code>\d+)$/
@@ -33,13 +52,11 @@ module Dependabot
33
52
  /^fatal: clone of '(?<url>.*)' into submodule path '.*' failed$/
34
53
  GIT_SUBMODULE_ERROR_REGEX = /(#{GIT_SUBMODULE_INACCESSIBLE_ERROR})|(#{GIT_SUBMODULE_CLONE_ERROR})/
35
54
 
36
- def self.required_files_in?(_filename_array)
37
- raise NotImplementedError
38
- end
55
+ sig { abstract.params(filenames: T::Array[String]).returns(T::Boolean) }
56
+ def self.required_files_in?(filenames); end
39
57
 
40
- def self.required_files_message
41
- raise NotImplementedError
42
- end
58
+ sig { abstract.returns(String) }
59
+ def self.required_files_message; end
43
60
 
44
61
  # Creates a new FileFetcher for retrieving `DependencyFile`s.
45
62
  #
@@ -52,38 +69,58 @@ module Dependabot
52
69
  # by repo_contents_path and still use an API trip.
53
70
  #
54
71
  # options supports custom feature enablement
72
+ sig do
73
+ params(
74
+ source: Dependabot::Source,
75
+ credentials: T::Array[T::Hash[String, String]],
76
+ repo_contents_path: T.nilable(String),
77
+ options: T::Hash[String, String]
78
+ )
79
+ .void
80
+ end
55
81
  def initialize(source:, credentials:, repo_contents_path: nil, options: {})
56
82
  @source = source
57
83
  @credentials = credentials
58
84
  @repo_contents_path = repo_contents_path
59
- @linked_paths = {}
60
- @submodules = []
85
+ @linked_paths = T.let({}, T::Hash[T.untyped, T.untyped])
86
+ @submodules = T.let([], T::Array[T.untyped])
61
87
  @options = options
62
88
  end
63
89
 
90
+ sig { returns(String) }
64
91
  def repo
65
92
  source.repo
66
93
  end
67
94
 
95
+ sig { returns(String) }
68
96
  def directory
69
97
  Pathname.new(source.directory || "/").cleanpath.to_path
70
98
  end
71
99
 
100
+ sig { returns(T.nilable(String)) }
72
101
  def target_branch
73
102
  source.branch
74
103
  end
75
104
 
105
+ sig { returns(T::Array[DependencyFile]) }
76
106
  def files
77
- @files ||= fetch_files
107
+ @files ||= T.let(
108
+ fetch_files.each { |f| f.job_directory = directory },
109
+ T.nilable(T::Array[DependencyFile])
110
+ )
78
111
  end
79
112
 
113
+ sig { abstract.returns(T::Array[DependencyFile]) }
114
+ def fetch_files; end
115
+
116
+ sig { returns(T.nilable(String)) }
80
117
  def commit
81
- return cloned_commit if cloned_commit
82
- return source.commit if source.commit
118
+ return T.must(cloned_commit) if cloned_commit
119
+ return T.must(source.commit) if source.commit
83
120
 
84
121
  branch = target_branch || default_branch_for_repo
85
122
 
86
- @commit ||= client_for_provider.fetch_commit(repo, branch)
123
+ @commit ||= T.let(T.unsafe(client_for_provider).fetch_commit(repo, branch), T.nilable(String))
87
124
  rescue *CLIENT_NOT_FOUND_ERRORS
88
125
  raise Dependabot::BranchNotFound, branch
89
126
  rescue Octokit::Conflict => e
@@ -91,9 +128,12 @@ module Dependabot
91
128
  end
92
129
 
93
130
  # Returns the path to the cloned repo
131
+ sig { returns(String) }
94
132
  def clone_repo_contents
95
- @clone_repo_contents ||=
96
- _clone_repo_contents(target_directory: repo_contents_path)
133
+ @clone_repo_contents ||= T.let(
134
+ _clone_repo_contents(target_directory: repo_contents_path),
135
+ T.nilable(String)
136
+ )
97
137
  rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
98
138
  if e.message.include?("fatal: Remote branch #{target_branch} not found in upstream origin")
99
139
  raise Dependabot::BranchNotFound, target_branch
@@ -104,16 +144,17 @@ module Dependabot
104
144
  raise Dependabot::RepoNotFound.new(source, e.message)
105
145
  end
106
146
 
107
- def ecosystem_versions
108
- nil
109
- end
147
+ sig { overridable.returns(T.nilable(T::Hash[Symbol, T.untyped])) }
148
+ def ecosystem_versions; end
110
149
 
111
150
  private
112
151
 
152
+ sig { params(name: String).returns(T.nilable(Dependabot::DependencyFile)) }
113
153
  def fetch_support_file(name)
114
154
  fetch_file_if_present(name)&.tap { |f| f.support_file = true }
115
155
  end
116
156
 
157
+ sig { params(filename: String, fetch_submodules: T::Boolean).returns(T.nilable(DependencyFile)) }
117
158
  def fetch_file_if_present(filename, fetch_submodules: false)
118
159
  unless repo_contents_path.nil?
119
160
  begin
@@ -137,6 +178,7 @@ module Dependabot
137
178
  nil
138
179
  end
139
180
 
181
+ sig { params(filename: T.any(Pathname, String)).returns(Dependabot::DependencyFile) }
140
182
  def load_cloned_file_if_present(filename)
141
183
  path = Pathname.new(File.join(directory, filename)).cleanpath.to_path
142
184
  repo_path = File.join(clone_repo_contents, path)
@@ -160,6 +202,14 @@ module Dependabot
160
202
  )
161
203
  end
162
204
 
205
+ sig do
206
+ params(
207
+ filename: T.any(Pathname, String),
208
+ type: String,
209
+ fetch_submodules: T::Boolean
210
+ )
211
+ .returns(Dependabot::DependencyFile)
212
+ end
163
213
  def fetch_file_from_host(filename, type: "file", fetch_submodules: false)
164
214
  return load_cloned_file_if_present(filename) unless repo_contents_path.nil?
165
215
 
@@ -169,7 +219,7 @@ module Dependabot
169
219
 
170
220
  linked_path = symlinked_subpath(clean_path)
171
221
  type = "symlink" if linked_path
172
- symlink_target = clean_path.sub(linked_path, @linked_paths.dig(linked_path, :path)) if type == "symlink"
222
+ symlink_target = clean_path.sub(T.must(linked_path), @linked_paths.dig(linked_path, :path)) if type == "symlink"
173
223
 
174
224
  DependencyFile.new(
175
225
  name: Pathname.new(filename).cleanpath.to_path,
@@ -183,65 +233,87 @@ module Dependabot
183
233
  end
184
234
 
185
235
  # Finds the first subpath in path that is a symlink
236
+ sig { params(path: String).returns(T.nilable(String)) }
186
237
  def symlinked_subpath(path)
187
238
  subpaths(path).find { |subpath| @linked_paths.key?(subpath) }
188
239
  end
189
240
 
241
+ sig { params(path: String).returns(T::Boolean) }
190
242
  def in_submodule?(path)
191
243
  subpaths(path.delete_prefix("/")).any? { |subpath| @submodules.include?(subpath) }
192
244
  end
193
245
 
194
246
  # Given a "foo/bar/baz" path, returns ["foo", "foo/bar", "foo/bar/baz"]
247
+ sig { params(path: String).returns(T::Array[String]) }
195
248
  def subpaths(path)
196
249
  components = path.split("/")
197
- components.map { |component| components[0..components.index(component)].join("/") }
250
+ components.map { |component| T.must(components[0..components.index(component)]).join("/") }
198
251
  end
199
252
 
253
+ sig do
254
+ params(
255
+ dir: T.any(Pathname, String),
256
+ ignore_base_directory: T::Boolean,
257
+ raise_errors: T::Boolean,
258
+ fetch_submodules: T::Boolean
259
+ )
260
+ .returns(T::Array[T.untyped])
261
+ end
200
262
  def repo_contents(dir: ".", ignore_base_directory: false,
201
263
  raise_errors: true, fetch_submodules: false)
202
264
  dir = File.join(directory, dir) unless ignore_base_directory
203
265
  path = Pathname.new(dir).cleanpath.to_path.gsub(%r{^/*}, "")
204
266
 
205
- @repo_contents ||= {}
206
- @repo_contents[dir] ||= if repo_contents_path
207
- _cloned_repo_contents(path)
208
- else
209
- _fetch_repo_contents(path, raise_errors: raise_errors,
210
- fetch_submodules: fetch_submodules)
211
- end
267
+ @repo_contents ||= T.let({}, T.nilable(T::Hash[String, T::Array[T.untyped]]))
268
+ @repo_contents[dir.to_s] ||= if repo_contents_path
269
+ _cloned_repo_contents(path)
270
+ else
271
+ _fetch_repo_contents(path, raise_errors: raise_errors,
272
+ fetch_submodules: fetch_submodules)
273
+ end
212
274
  end
213
275
 
276
+ sig { returns(T.nilable(String)) }
214
277
  def cloned_commit
215
278
  return if repo_contents_path.nil? || !File.directory?(File.join(repo_contents_path, ".git"))
216
279
 
217
280
  SharedHelpers.with_git_configured(credentials: credentials) do
218
- Dir.chdir(repo_contents_path) do
219
- return SharedHelpers.run_shell_command("git rev-parse HEAD")&.strip
281
+ Dir.chdir(T.must(repo_contents_path)) do
282
+ return SharedHelpers.run_shell_command("git rev-parse HEAD").strip
220
283
  end
221
284
  end
222
285
  end
223
286
 
287
+ sig { returns(String) }
224
288
  def default_branch_for_repo
225
- @default_branch_for_repo ||= client_for_provider
226
- .fetch_default_branch(repo)
289
+ @default_branch_for_repo ||= T.let(T.unsafe(client_for_provider).fetch_default_branch(repo), T.nilable(String))
227
290
  rescue *CLIENT_NOT_FOUND_ERRORS
228
291
  raise Dependabot::RepoNotFound, source
229
292
  end
230
293
 
294
+ sig do
295
+ params(
296
+ repo: String,
297
+ path: String,
298
+ commit: String,
299
+ github_response: Sawyer::Resource
300
+ )
301
+ .returns(T.nilable(T::Hash[String, T.untyped]))
302
+ end
231
303
  def update_linked_paths(repo, path, commit, github_response)
232
- case github_response.type
304
+ case T.unsafe(github_response).type
233
305
  when "submodule"
234
- sub_source = Source.from_url(github_response.submodule_git_url)
306
+ sub_source = Source.from_url(T.unsafe(github_response).submodule_git_url)
235
307
  return unless sub_source
236
308
 
237
309
  @linked_paths[path] = {
238
310
  repo: sub_source.repo,
239
311
  provider: sub_source.provider,
240
- commit: github_response.sha,
312
+ commit: T.unsafe(github_response).sha,
241
313
  path: "/"
242
314
  }
243
315
  when "symlink"
244
- updated_path = File.join(File.dirname(path), github_response.target)
316
+ updated_path = File.join(File.dirname(path), T.unsafe(github_response).target)
245
317
  @linked_paths[path] = {
246
318
  repo: repo,
247
319
  provider: "github",
@@ -251,10 +323,22 @@ module Dependabot
251
323
  end
252
324
  end
253
325
 
326
+ sig { returns(T::Boolean) }
254
327
  def recurse_submodules_when_cloning?
255
328
  false
256
329
  end
257
330
 
331
+ sig do
332
+ returns(
333
+ T.any(
334
+ Dependabot::Clients::GithubWithRetries,
335
+ Dependabot::Clients::GitlabWithRetries,
336
+ Dependabot::Clients::Azure,
337
+ Dependabot::Clients::BitbucketWithRetries,
338
+ Dependabot::Clients::CodeCommit
339
+ )
340
+ )
341
+ end
258
342
  def client_for_provider
259
343
  case source.provider
260
344
  when "github" then github_client
@@ -266,46 +350,75 @@ module Dependabot
266
350
  end
267
351
  end
268
352
 
353
+ sig { returns(Dependabot::Clients::GithubWithRetries) }
269
354
  def github_client
270
355
  @github_client ||=
271
- Dependabot::Clients::GithubWithRetries.for_source(
272
- source: source,
273
- credentials: credentials
356
+ T.let(
357
+ Dependabot::Clients::GithubWithRetries.for_source(
358
+ source: source,
359
+ credentials: credentials
360
+ ),
361
+ T.nilable(Dependabot::Clients::GithubWithRetries)
274
362
  )
275
363
  end
276
364
 
365
+ sig { returns(Dependabot::Clients::GitlabWithRetries) }
277
366
  def gitlab_client
278
367
  @gitlab_client ||=
279
- Dependabot::Clients::GitlabWithRetries.for_source(
280
- source: source,
281
- credentials: credentials
368
+ T.let(
369
+ Dependabot::Clients::GitlabWithRetries.for_source(
370
+ source: source,
371
+ credentials: credentials
372
+ ),
373
+ T.nilable(Dependabot::Clients::GitlabWithRetries)
282
374
  )
283
375
  end
284
376
 
377
+ sig { returns(Dependabot::Clients::Azure) }
285
378
  def azure_client
286
379
  @azure_client ||=
287
- Dependabot::Clients::Azure
288
- .for_source(source: source, credentials: credentials)
380
+ T.let(
381
+ Dependabot::Clients::Azure.for_source(
382
+ source: source,
383
+ credentials: credentials
384
+ ),
385
+ T.nilable(Dependabot::Clients::Azure)
386
+ )
289
387
  end
290
388
 
389
+ sig { returns(Dependabot::Clients::BitbucketWithRetries) }
291
390
  def bitbucket_client
292
391
  # TODO: When self-hosted Bitbucket is supported this should use
293
392
  # `Bitbucket.for_source`
294
393
  @bitbucket_client ||=
295
- Dependabot::Clients::BitbucketWithRetries
296
- .for_bitbucket_dot_org(credentials: credentials)
394
+ T.let(
395
+ Dependabot::Clients::BitbucketWithRetries.for_bitbucket_dot_org(
396
+ credentials: credentials
397
+ ),
398
+ T.nilable(Dependabot::Clients::BitbucketWithRetries)
399
+ )
297
400
  end
298
401
 
402
+ sig { returns(Dependabot::Clients::CodeCommit) }
299
403
  def codecommit_client
300
404
  @codecommit_client ||=
301
- Dependabot::Clients::CodeCommit
302
- .for_source(source: source, credentials: credentials)
405
+ T.let(
406
+ Dependabot::Clients::CodeCommit.for_source(
407
+ source: source,
408
+ credentials: credentials
409
+ ),
410
+ T.nilable(Dependabot::Clients::CodeCommit)
411
+ )
303
412
  end
304
413
 
305
414
  #################################################
306
415
  # INTERNAL METHODS (not for use by sub-classes) #
307
416
  #################################################
308
417
 
418
+ sig do
419
+ params(path: String, fetch_submodules: T::Boolean, raise_errors: T::Boolean)
420
+ .returns(T::Array[OpenStruct])
421
+ end
309
422
  def _fetch_repo_contents(path, fetch_submodules: false,
310
423
  raise_errors: true)
311
424
  path = path.gsub(" ", "%20")
@@ -337,6 +450,10 @@ module Dependabot
337
450
  retry
338
451
  end
339
452
 
453
+ sig do
454
+ params(provider: String, repo: String, path: String, commit: String)
455
+ .returns(T::Array[OpenStruct])
456
+ end
340
457
  def _fetch_repo_contents_fully_specified(provider, repo, path, commit)
341
458
  case provider
342
459
  when "github"
@@ -353,9 +470,10 @@ module Dependabot
353
470
  end
354
471
  end
355
472
 
473
+ sig { params(repo: String, path: String, commit: String).returns(T::Array[OpenStruct]) }
356
474
  def _github_repo_contents(repo, path, commit)
357
475
  path = path.gsub(" ", "%20")
358
- github_response = github_client.contents(repo, path: path, ref: commit)
476
+ github_response = T.unsafe(github_client).contents(repo, path: path, ref: commit)
359
477
 
360
478
  if github_response.respond_to?(:type)
361
479
  update_linked_paths(repo, path, commit, github_response)
@@ -365,6 +483,7 @@ module Dependabot
365
483
  github_response.map { |f| _build_github_file_struct(f) }
366
484
  end
367
485
 
486
+ sig { params(relative_path: String).returns(T::Array[OpenStruct]) }
368
487
  def _cloned_repo_contents(relative_path)
369
488
  repo_path = File.join(clone_repo_contents, relative_path)
370
489
  return [] unless Dir.exist?(repo_path)
@@ -390,37 +509,40 @@ module Dependabot
390
509
  end
391
510
  end
392
511
 
512
+ sig { params(file: Sawyer::Resource).returns(OpenStruct) }
393
513
  def _build_github_file_struct(file)
394
514
  OpenStruct.new(
395
- name: file.name,
396
- path: file.path,
397
- type: file.type,
398
- sha: file.sha,
399
- size: file.size
515
+ name: T.unsafe(file).name,
516
+ path: T.unsafe(file).path,
517
+ type: T.unsafe(file).type,
518
+ sha: T.unsafe(file).sha,
519
+ size: T.unsafe(file).size
400
520
  )
401
521
  end
402
522
 
523
+ sig { params(repo: String, path: String, commit: String).returns(T::Array[OpenStruct]) }
403
524
  def _gitlab_repo_contents(repo, path, commit)
404
- gitlab_client
405
- .repo_tree(repo, path: path, ref: commit, per_page: 100)
406
- .map do |file|
407
- # GitLab API essentially returns the output from `git ls-tree`
408
- type = case file.type
409
- when "blob" then "file"
410
- when "tree" then "dir"
411
- when "commit" then "submodule"
412
- else file.fetch("type")
413
- end
414
-
415
- OpenStruct.new(
416
- name: file.name,
417
- path: file.path,
418
- type: type,
419
- size: 0 # GitLab doesn't return file size
420
- )
421
- end
525
+ T.unsafe(gitlab_client)
526
+ .repo_tree(repo, path: path, ref: commit, per_page: 100)
527
+ .map do |file|
528
+ # GitLab API essentially returns the output from `git ls-tree`
529
+ type = case file.type
530
+ when "blob" then "file"
531
+ when "tree" then "dir"
532
+ when "commit" then "submodule"
533
+ else file.fetch("type")
534
+ end
535
+
536
+ OpenStruct.new(
537
+ name: file.name,
538
+ path: file.path,
539
+ type: type,
540
+ size: 0 # GitLab doesn't return file size
541
+ )
542
+ end
422
543
  end
423
544
 
545
+ sig { params(path: String, commit: String).returns(T::Array[OpenStruct]) }
424
546
  def _azure_repo_contents(path, commit)
425
547
  response = azure_client.fetch_repo_contents(commit, path)
426
548
 
@@ -440,12 +562,14 @@ module Dependabot
440
562
  end
441
563
  end
442
564
 
565
+ sig { params(repo: String, path: String, commit: String).returns(T::Array[OpenStruct]) }
443
566
  def _bitbucket_repo_contents(repo, path, commit)
444
- response = bitbucket_client.fetch_repo_contents(
445
- repo,
446
- commit,
447
- path
448
- )
567
+ response = T.unsafe(bitbucket_client)
568
+ .fetch_repo_contents(
569
+ repo,
570
+ commit,
571
+ path
572
+ )
449
573
 
450
574
  response.map do |file|
451
575
  type = case file.fetch("type")
@@ -463,6 +587,7 @@ module Dependabot
463
587
  end
464
588
  end
465
589
 
590
+ sig { params(repo: String, path: String, commit: String).returns(T::Array[OpenStruct]) }
466
591
  def _codecommit_repo_contents(repo, path, commit)
467
592
  response = codecommit_client.fetch_repo_contents(
468
593
  repo,
@@ -480,11 +605,12 @@ module Dependabot
480
605
  end
481
606
  end
482
607
 
608
+ sig { params(path: String, fetch_submodules: T::Boolean).returns(T::Hash[Symbol, T.untyped]) }
483
609
  def _full_specification_for(path, fetch_submodules:)
484
610
  if fetch_submodules && _linked_dir_for(path)
485
611
  linked_dir_details = @linked_paths[_linked_dir_for(path)]
486
612
  sub_path =
487
- path.gsub(%r{^#{Regexp.quote(_linked_dir_for(path))}(/|$)}, "")
613
+ path.gsub(%r{^#{Regexp.quote(T.must(_linked_dir_for(path)))}(/|$)}, "")
488
614
  new_path =
489
615
  Pathname.new(File.join(linked_dir_details.fetch(:path), sub_path))
490
616
  .cleanpath.to_path
@@ -505,6 +631,7 @@ module Dependabot
505
631
  end
506
632
  end
507
633
 
634
+ sig { params(path: String, fetch_submodules: T::Boolean).returns(String) }
508
635
  def _fetch_file_content(path, fetch_submodules: false)
509
636
  path = path.gsub(%r{^/*}, "")
510
637
 
@@ -525,17 +652,18 @@ module Dependabot
525
652
  retry
526
653
  end
527
654
 
655
+ sig { params(provider: String, repo: String, path: String, commit: String).returns(String) }
528
656
  def _fetch_file_content_fully_specified(provider, repo, path, commit)
529
657
  case provider
530
658
  when "github"
531
659
  _fetch_file_content_from_github(path, repo, commit)
532
660
  when "gitlab"
533
- tmp = gitlab_client.get_file(repo, path, commit).content
661
+ tmp = T.unsafe(gitlab_client).get_file(repo, path, commit).content
534
662
  decode_binary_string(tmp)
535
663
  when "azure"
536
664
  azure_client.fetch_file_contents(commit, path)
537
665
  when "bitbucket"
538
- bitbucket_client.fetch_file_contents(repo, commit, path)
666
+ T.unsafe(bitbucket_client).fetch_file_contents(repo, commit, path)
539
667
  when "codecommit"
540
668
  codecommit_client.fetch_file_contents(repo, commit, path)
541
669
  else raise "Unsupported provider '#{source.provider}'."
@@ -543,8 +671,9 @@ module Dependabot
543
671
  end
544
672
 
545
673
  # rubocop:disable Metrics/AbcSize
674
+ sig { params(path: String, repo: String, commit: String).returns(String) }
546
675
  def _fetch_file_content_from_github(path, repo, commit)
547
- tmp = github_client.contents(repo, path: path, ref: commit)
676
+ tmp = T.unsafe(github_client).contents(repo, path: path, ref: commit)
548
677
 
549
678
  raise Octokit::NotFound if tmp.is_a?(Array)
550
679
 
@@ -555,7 +684,7 @@ module Dependabot
555
684
  commit: commit,
556
685
  path: Pathname.new(tmp.target).cleanpath.to_path
557
686
  }
558
- tmp = github_client.contents(
687
+ tmp = T.unsafe(github_client).contents(
559
688
  repo,
560
689
  path: Pathname.new(tmp.target).cleanpath.to_path,
561
690
  ref: commit
@@ -565,7 +694,7 @@ module Dependabot
565
694
  if tmp.content == ""
566
695
  # The file may have exceeded the 1MB limit
567
696
  # see https://github.blog/changelog/2022-05-03-increased-file-size-limit-when-retrieving-file-contents-via-rest-api/
568
- github_client.contents(repo, path: path, ref: commit, accept: "application/vnd.github.v3.raw")
697
+ T.unsafe(github_client).contents(repo, path: path, ref: commit, accept: "application/vnd.github.v3.raw")
569
698
  else
570
699
  decode_binary_string(tmp.content)
571
700
  end
@@ -579,7 +708,7 @@ module Dependabot
579
708
  file_details = repo_contents(dir: dir).find { |f| f.name == basename }
580
709
  raise unless file_details
581
710
 
582
- tmp = github_client.blob(repo, file_details.sha)
711
+ tmp = T.unsafe(github_client).blob(repo, file_details.sha)
583
712
  return tmp.content if tmp.encoding == "utf-8"
584
713
 
585
714
  decode_binary_string(tmp.content)
@@ -589,6 +718,7 @@ module Dependabot
589
718
  # Update the @linked_paths hash by exploiting a side-effect of
590
719
  # recursively calling `repo_contents` for each directory up the tree
591
720
  # until a submodule or symlink is found
721
+ sig { params(path: String).returns(T.nilable(T::Array[T.untyped])) }
592
722
  def _find_linked_dirs(path)
593
723
  path = Pathname.new(path).cleanpath.to_path.gsub(%r{^/*}, "")
594
724
  dir = File.dirname(path)
@@ -603,6 +733,7 @@ module Dependabot
603
733
  )
604
734
  end
605
735
 
736
+ sig { params(path: String).returns(T.nilable(String)) }
606
737
  def _linked_dir_for(path)
607
738
  linked_dirs = @linked_paths.keys
608
739
  linked_dirs
@@ -614,6 +745,7 @@ module Dependabot
614
745
  # rubocop:disable Metrics/MethodLength
615
746
  # rubocop:disable Metrics/PerceivedComplexity
616
747
  # rubocop:disable Metrics/BlockLength
748
+ sig { params(target_directory: T.nilable(String)).returns(String) }
617
749
  def _clone_repo_contents(target_directory:)
618
750
  SharedHelpers.with_git_configured(credentials: credentials) do
619
751
  path = target_directory || File.join("tmp", source.repo)
@@ -645,7 +777,7 @@ module Dependabot
645
777
  raise unless e.message.match(GIT_SUBMODULE_ERROR_REGEX) && e.message.downcase.include?("submodule")
646
778
 
647
779
  submodule_cloning_failed = true
648
- match = e.message.match(GIT_SUBMODULE_ERROR_REGEX)
780
+ match = T.must(e.message.match(GIT_SUBMODULE_ERROR_REGEX))
649
781
  url = match.named_captures["url"]
650
782
  code = match.named_captures["code"]
651
783
 
@@ -688,11 +820,13 @@ module Dependabot
688
820
  # rubocop:enable Metrics/PerceivedComplexity
689
821
  # rubocop:enable Metrics/BlockLength
690
822
 
823
+ sig { params(str: String).returns(String) }
691
824
  def decode_binary_string(str)
692
825
  bom = (+"\xEF\xBB\xBF").force_encoding(Encoding::BINARY)
693
826
  Base64.decode64(str).delete_prefix(bom).force_encoding("UTF-8").encode
694
827
  end
695
828
 
829
+ sig { params(path: String).returns(T::Array[String]) }
696
830
  def find_submodules(path)
697
831
  SharedHelpers.run_shell_command(
698
832
  <<~CMD
@@ -702,7 +836,7 @@ module Dependabot
702
836
  info = line.split
703
837
 
704
838
  type = info.first
705
- path = info.last
839
+ path = T.must(info.last)
706
840
 
707
841
  next path if type == DependencyFile::Mode::SUBMODULE
708
842
  end
@@ -73,7 +73,7 @@ module Dependabot
73
73
 
74
74
  sig { params(file: Dependabot::DependencyFile, dependency: Dependabot::Dependency).returns(T::Boolean) }
75
75
  def requirement_changed?(file, dependency)
76
- changed_requirements = dependency.requirements - dependency.previous_requirements
76
+ changed_requirements = dependency.requirements - T.must(dependency.previous_requirements)
77
77
 
78
78
  changed_requirements.any? { |f| f[:file] == file.name }
79
79
  end
@@ -153,6 +153,12 @@ module Dependabot
153
153
  @local_tag_for_pinned_sha = most_specific_version_tag_for_sha(ref) if pinned_ref_looks_like_commit_sha?
154
154
  end
155
155
 
156
+ def version_for_pinned_sha
157
+ return unless local_tag_for_pinned_sha && version_class.correct?(local_tag_for_pinned_sha)
158
+
159
+ version_class.new(local_tag_for_pinned_sha)
160
+ end
161
+
156
162
  def git_repo_reachable?
157
163
  local_upload_pack
158
164
  true