dependabot-common 0.239.0 → 0.241.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,8 +1,9 @@
1
- # typed: true
1
+ # typed: strict
2
2
  # frozen_string_literal: true
3
3
 
4
4
  require "excon"
5
5
  require "gitlab"
6
+ require "sorbet-runtime"
6
7
  require "dependabot/clients/github_with_retries"
7
8
  require "dependabot/clients/gitlab_with_retries"
8
9
  require "dependabot/clients/bitbucket_with_retries"
@@ -13,7 +14,10 @@ require "dependabot/source"
13
14
  require "dependabot/dependency"
14
15
  require "dependabot/git_metadata_fetcher"
15
16
  module Dependabot
17
+ # rubocop:disable Metrics/ClassLength
16
18
  class GitCommitChecker
19
+ extend T::Sig
20
+
17
21
  VERSION_REGEX = /
18
22
  (?<version>
19
23
  (?<=^v)[0-9]+(?:\-[a-z0-9]+)?
@@ -22,6 +26,17 @@ module Dependabot
22
26
  )$
23
27
  /ix
24
28
 
29
+ sig do
30
+ params(
31
+ dependency: Dependabot::Dependency,
32
+ credentials: T::Array[T::Hash[String, String]],
33
+ ignored_versions: T::Array[String],
34
+ raise_on_ignored: T::Boolean,
35
+ consider_version_branches_pinned: T::Boolean,
36
+ dependency_source_details: T.nilable(T::Hash[Symbol, String])
37
+ )
38
+ .void
39
+ end
25
40
  def initialize(dependency:, credentials:,
26
41
  ignored_versions: [], raise_on_ignored: false,
27
42
  consider_version_branches_pinned: false, dependency_source_details: nil)
@@ -33,58 +48,68 @@ module Dependabot
33
48
  @dependency_source_details = dependency_source_details
34
49
  end
35
50
 
51
+ sig { returns(T::Boolean) }
36
52
  def git_dependency?
37
53
  return false if dependency_source_details.nil?
38
54
 
39
- dependency_source_details.fetch(:type) == "git"
55
+ dependency_source_details&.fetch(:type) == "git"
40
56
  end
41
57
 
58
+ # rubocop:disable Metrics/PerceivedComplexity
59
+ sig { returns(T::Boolean) }
42
60
  def pinned?
43
61
  raise "Not a git dependency!" unless git_dependency?
44
62
 
45
- branch = dependency_source_details.fetch(:branch)
63
+ branch = dependency_source_details&.fetch(:branch)
46
64
 
47
65
  return false if ref.nil?
48
66
  return false if branch == ref
49
67
  return true if branch
50
- return true if dependency.version&.start_with?(ref)
68
+ return true if dependency.version&.start_with?(T.must(ref))
51
69
 
52
70
  # If the specified `ref` is actually a tag, we're pinned
53
- return true if local_upload_pack.match?(%r{ refs/tags/#{ref}$})
71
+ return true if local_upload_pack&.match?(%r{ refs/tags/#{ref}$})
54
72
 
55
73
  # Assume we're pinned unless the specified `ref` is actually a branch
56
- return true unless local_upload_pack.match?(%r{ refs/heads/#{ref}$})
74
+ return true unless local_upload_pack&.match?(%r{ refs/heads/#{ref}$})
57
75
 
58
76
  # TODO: Research whether considering branches that look like versions pinned makes sense for all ecosystems
59
- @consider_version_branches_pinned && version_tag?(ref)
77
+ @consider_version_branches_pinned && version_tag?(T.must(ref))
60
78
  end
79
+ # rubocop:enable Metrics/PerceivedComplexity
61
80
 
81
+ sig { returns(T::Boolean) }
62
82
  def pinned_ref_looks_like_version?
63
83
  return false unless pinned?
64
84
 
65
- version_tag?(ref)
85
+ version_tag?(T.must(ref))
66
86
  end
67
87
 
88
+ sig { returns(T::Boolean) }
68
89
  def pinned_ref_looks_like_commit_sha?
69
- return false unless ref && ref_looks_like_commit_sha?(ref)
90
+ return false unless ref && ref_looks_like_commit_sha?(T.must(ref))
70
91
 
71
92
  return false unless pinned?
72
93
 
73
- local_repo_git_metadata_fetcher.head_commit_for_ref(ref).nil?
94
+ local_repo_git_metadata_fetcher.head_commit_for_ref(T.must(ref)).nil?
74
95
  end
75
96
 
97
+ sig { returns(T.nilable(String)) }
76
98
  def head_commit_for_pinned_ref
77
- local_repo_git_metadata_fetcher.head_commit_for_ref_sha(ref)
99
+ local_repo_git_metadata_fetcher.head_commit_for_ref_sha(T.must(ref))
78
100
  end
79
101
 
102
+ sig { params(ref: String).returns(T::Boolean) }
80
103
  def ref_looks_like_commit_sha?(ref)
81
104
  ref.match?(/^[0-9a-f]{6,40}$/)
82
105
  end
83
106
 
107
+ sig { params(version: T.any(String, Gem::Version)).returns(T::Boolean) }
84
108
  def branch_or_ref_in_release?(version)
85
109
  pinned_ref_in_release?(version) || branch_behind_release?(version)
86
110
  end
87
111
 
112
+ sig { returns(T.nilable(String)) }
88
113
  def head_commit_for_current_branch
89
114
  ref = ref_or_branch || "HEAD"
90
115
 
@@ -94,42 +119,51 @@ module Dependabot
94
119
  raise Dependabot::GitDependencyReferenceNotFound, dependency.name
95
120
  end
96
121
 
122
+ sig { params(name: String).returns(T.nilable(String)) }
97
123
  def head_commit_for_local_branch(name)
98
124
  local_repo_git_metadata_fetcher.head_commit_for_ref(name)
99
125
  end
100
126
 
127
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
101
128
  def local_ref_for_latest_version_matching_existing_precision
102
129
  allowed_refs = local_tag_for_pinned_sha ? allowed_version_tags : allowed_version_refs
103
130
 
104
131
  max_local_tag_for_current_precision(allowed_refs)
105
132
  end
106
133
 
134
+ sig { returns(T.nilable(T::Hash[Symbol, T.untyped])) }
107
135
  def local_tag_for_latest_version
108
136
  max_local_tag(allowed_version_tags)
109
137
  end
110
138
 
139
+ sig { returns(T::Array[T.nilable(T::Hash[Symbol, T.untyped])]) }
111
140
  def local_tags_for_allowed_versions_matching_existing_precision
112
141
  select_matching_existing_precision(allowed_version_tags).map { |t| to_local_tag(t) }
113
142
  end
114
143
 
144
+ sig { returns(T::Array[T.nilable(T::Hash[Symbol, T.untyped])]) }
115
145
  def local_tags_for_allowed_versions
116
146
  allowed_version_tags.map { |t| to_local_tag(t) }
117
147
  end
118
148
 
149
+ sig { returns(T::Array[Dependabot::GitRef]) }
119
150
  def allowed_version_tags
120
151
  allowed_versions(local_tags)
121
152
  end
122
153
 
154
+ sig { returns(T::Array[Dependabot::GitRef]) }
123
155
  def allowed_version_refs
124
156
  allowed_versions(local_refs)
125
157
  end
126
158
 
159
+ sig { returns(T.nilable(Gem::Version)) }
127
160
  def current_version
128
- return unless dependency.version && version_tag?(dependency.version)
161
+ return unless dependency.version && version_tag?(T.must(dependency.version))
129
162
 
130
- version_from_ref(dependency.version)
163
+ version_from_ref(T.must(dependency.version))
131
164
  end
132
165
 
166
+ sig { params(tags: T::Array[Dependabot::GitRef]).returns(T::Array[T.any(Dependabot::GitRef, Gem::Version)]) }
133
167
  def filter_lower_versions(tags)
134
168
  return tags unless current_version
135
169
 
@@ -142,23 +176,30 @@ module Dependabot
142
176
  end
143
177
  end
144
178
 
179
+ sig { returns(T.nilable(String)) }
145
180
  def most_specific_tag_equivalent_to_pinned_ref
146
- commit_sha = head_commit_for_local_branch(ref)
181
+ commit_sha = head_commit_for_local_branch(T.must(ref))
147
182
  most_specific_version_tag_for_sha(commit_sha)
148
183
  end
149
184
 
185
+ sig { returns(T.nilable(String)) }
150
186
  def local_tag_for_pinned_sha
151
- return @local_tag_for_pinned_sha if defined?(@local_tag_for_pinned_sha)
187
+ return unless pinned_ref_looks_like_commit_sha?
152
188
 
153
- @local_tag_for_pinned_sha = most_specific_version_tag_for_sha(ref) if pinned_ref_looks_like_commit_sha?
189
+ @local_tag_for_pinned_sha = T.let(
190
+ most_specific_version_tag_for_sha(ref),
191
+ T.nilable(String)
192
+ )
154
193
  end
155
194
 
195
+ sig { returns(T.nilable(Gem::Version)) }
156
196
  def version_for_pinned_sha
157
197
  return unless local_tag_for_pinned_sha && version_class.correct?(local_tag_for_pinned_sha)
158
198
 
159
199
  version_class.new(local_tag_for_pinned_sha)
160
200
  end
161
201
 
202
+ sig { returns(T::Boolean) }
162
203
  def git_repo_reachable?
163
204
  local_upload_pack
164
205
  true
@@ -166,26 +207,37 @@ module Dependabot
166
207
  false
167
208
  end
168
209
 
210
+ sig { returns(T.nilable(T::Hash[T.any(Symbol, String), T.untyped])) }
169
211
  def dependency_source_details
170
212
  @dependency_source_details || dependency.source_details(allowed_types: ["git"])
171
213
  end
172
214
 
215
+ sig { params(commit_sha: T.nilable(String)).returns(T.nilable(String)) }
173
216
  def most_specific_version_tag_for_sha(commit_sha)
174
217
  tags = local_tags.select { |t| t.commit_sha == commit_sha && version_class.correct?(t.name) }
175
218
  .sort_by { |t| version_class.new(t.name) }
176
219
  return if tags.empty?
177
220
 
178
- tags[-1].name
221
+ tags[-1]&.name
179
222
  end
180
223
 
181
224
  private
182
225
 
183
- attr_reader :dependency, :credentials, :ignored_versions
226
+ sig { returns(Dependabot::Dependency) }
227
+ attr_reader :dependency
184
228
 
229
+ sig { returns(T::Array[T::Hash[String, String]]) }
230
+ attr_reader :credentials
231
+
232
+ sig { returns(T::Array[String]) }
233
+ attr_reader :ignored_versions
234
+
235
+ sig { params(tags: T::Array[Dependabot::GitRef]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
185
236
  def max_local_tag_for_current_precision(tags)
186
237
  max_local_tag(select_matching_existing_precision(tags))
187
238
  end
188
239
 
240
+ sig { params(tags: T::Array[Dependabot::GitRef]).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
189
241
  def max_local_tag(tags)
190
242
  max_version_tag = tags.max_by { |t| version_from_tag(t) }
191
243
 
@@ -193,16 +245,19 @@ module Dependabot
193
245
  end
194
246
 
195
247
  # Find the latest version with the same precision as the pinned version.
248
+ sig { params(tags: T::Array[Dependabot::GitRef]).returns(T::Array[Dependabot::GitRef]) }
196
249
  def select_matching_existing_precision(tags)
197
- current_precision = precision(dependency.version)
250
+ current_precision = precision(T.must(dependency.version))
198
251
 
199
252
  tags.select { |tag| precision(scan_version(tag.name)) == current_precision }
200
253
  end
201
254
 
255
+ sig { params(version: String).returns(Integer) }
202
256
  def precision(version)
203
257
  version.split(".").length
204
258
  end
205
259
 
260
+ sig { params(local_tags: T::Array[Dependabot::GitRef]).returns(T::Array[Dependabot::GitRef]) }
206
261
  def allowed_versions(local_tags)
207
262
  tags =
208
263
  local_tags
@@ -217,6 +272,7 @@ module Dependabot
217
272
  .reject { |t| tag_is_prerelease?(t) && !wants_prerelease? }
218
273
  end
219
274
 
275
+ sig { params(version: T.any(String, Gem::Version)).returns(T::Boolean) }
220
276
  def pinned_ref_in_release?(version)
221
277
  raise "Not a git dependency!" unless git_dependency?
222
278
 
@@ -227,12 +283,13 @@ module Dependabot
227
283
  return false unless tag
228
284
 
229
285
  commit_included_in_tag?(
230
- commit: ref,
286
+ commit: T.must(ref),
231
287
  tag: tag,
232
288
  allow_identical: true
233
289
  )
234
290
  end
235
291
 
292
+ sig { params(version: T.any(String, Gem::Version)).returns(T::Boolean) }
236
293
  def branch_behind_release?(version)
237
294
  raise "Not a git dependency!" unless git_dependency?
238
295
 
@@ -245,24 +302,28 @@ module Dependabot
245
302
  # Check if behind, excluding the case where it's identical, because
246
303
  # we normally wouldn't switch you from tracking master to a release.
247
304
  commit_included_in_tag?(
248
- commit: ref_or_branch,
305
+ commit: T.must(ref_or_branch),
249
306
  tag: tag,
250
307
  allow_identical: false
251
308
  )
252
309
  end
253
310
 
311
+ sig { returns(T.nilable(String)) }
254
312
  def local_upload_pack
255
313
  local_repo_git_metadata_fetcher.upload_pack
256
314
  end
257
315
 
316
+ sig { returns(T::Array[Dependabot::GitRef]) }
258
317
  def local_refs
259
318
  handle_tag_prefix(local_repo_git_metadata_fetcher.refs_for_upload_pack)
260
319
  end
261
320
 
321
+ sig { returns(T::Array[Dependabot::GitRef]) }
262
322
  def local_tags
263
323
  handle_tag_prefix(local_repo_git_metadata_fetcher.tags_for_upload_pack)
264
324
  end
265
325
 
326
+ sig { params(tags: T::Array[Dependabot::GitRef]).returns(T::Array[Dependabot::GitRef]) }
266
327
  def handle_tag_prefix(tags)
267
328
  if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
268
329
  tags = tags.map do |tag|
@@ -273,6 +334,14 @@ module Dependabot
273
334
  tags
274
335
  end
275
336
 
337
+ sig do
338
+ params(
339
+ tag: String,
340
+ commit: String,
341
+ allow_identical: T::Boolean
342
+ )
343
+ .returns(T::Boolean)
344
+ end
276
345
  def commit_included_in_tag?(tag:, commit:, allow_identical: false)
277
346
  status =
278
347
  case Source.from_url(listing_source_url)&.provider
@@ -292,6 +361,7 @@ module Dependabot
292
361
  false
293
362
  end
294
363
 
364
+ sig { params(ref1: String, ref2: String).returns(String) }
295
365
  def github_commit_comparison_status(ref1, ref2)
296
366
  client = Clients::GithubWithRetries
297
367
  .for_github_dot_com(credentials: credentials)
@@ -299,6 +369,7 @@ module Dependabot
299
369
  client.compare(listing_source_repo, ref1, ref2).status
300
370
  end
301
371
 
372
+ sig { params(ref1: String, ref2: String).returns(String) }
302
373
  def gitlab_commit_comparison_status(ref1, ref2)
303
374
  client = Clients::GitlabWithRetries
304
375
  .for_gitlab_dot_com(credentials: credentials)
@@ -312,6 +383,7 @@ module Dependabot
312
383
  end
313
384
  end
314
385
 
386
+ sig { params(ref1: String, ref2: String).returns(String) }
315
387
  def bitbucket_commit_comparison_status(ref1, ref2)
316
388
  url = "https://api.bitbucket.org/2.0/repositories/" \
317
389
  "#{listing_source_repo}/commits/?" \
@@ -330,33 +402,39 @@ module Dependabot
330
402
  end
331
403
  end
332
404
 
405
+ sig { returns(T.nilable(String)) }
333
406
  def ref_or_branch
334
- ref || dependency_source_details.fetch(:branch)
407
+ ref || dependency_source_details&.fetch(:branch)
335
408
  end
336
409
 
410
+ sig { returns(T.nilable(String)) }
337
411
  def ref
338
- dependency_source_details.fetch(:ref)
412
+ dependency_source_details&.fetch(:ref)
339
413
  end
340
414
 
415
+ sig { params(tag: String).returns(T::Boolean) }
341
416
  def version_tag?(tag)
342
417
  tag.match?(VERSION_REGEX)
343
418
  end
344
419
 
420
+ sig { params(tag: String).returns(T::Boolean) }
345
421
  def matches_existing_prefix?(tag)
346
422
  return true unless ref_or_branch
347
423
 
348
- if version_tag?(ref_or_branch)
349
- same_prefix?(ref_or_branch, tag)
424
+ if version_tag?(T.must(ref_or_branch))
425
+ same_prefix?(T.must(ref_or_branch), tag)
350
426
  else
351
- local_tag_for_pinned_sha.nil? || same_prefix?(local_tag_for_pinned_sha, tag)
427
+ local_tag_for_pinned_sha.nil? || same_prefix?(T.must(local_tag_for_pinned_sha), tag)
352
428
  end
353
429
  end
354
430
 
431
+ sig { params(tag: String, other_tag: String).returns(T::Boolean) }
355
432
  def same_prefix?(tag, other_tag)
356
433
  tag.gsub(VERSION_REGEX, "").gsub(/v$/i, "") ==
357
434
  other_tag.gsub(VERSION_REGEX, "").gsub(/v$/i, "")
358
435
  end
359
436
 
437
+ sig { params(tag: T.nilable(Dependabot::GitRef)).returns(T.nilable(T::Hash[Symbol, T.untyped])) }
360
438
  def to_local_tag(tag)
361
439
  return unless tag
362
440
 
@@ -369,8 +447,9 @@ module Dependabot
369
447
  }
370
448
  end
371
449
 
450
+ sig { returns(T.nilable(String)) }
372
451
  def listing_source_url
373
- @listing_source_url ||=
452
+ @listing_source_url ||= T.let(
374
453
  begin
375
454
  # Remove the git source, so the metadata finder looks on the
376
455
  # registry
@@ -385,100 +464,133 @@ module Dependabot
385
464
  .for_package_manager(dependency.package_manager)
386
465
  .new(dependency: candidate_dep, credentials: credentials)
387
466
  .source_url
388
- end
467
+ end,
468
+ T.nilable(String)
469
+ )
389
470
  end
390
471
 
472
+ sig { returns(T.nilable(String)) }
391
473
  def listing_source_repo
392
474
  return unless listing_source_url
393
475
 
394
476
  Source.from_url(listing_source_url)&.repo
395
477
  end
396
478
 
479
+ sig { params(version: String).returns(T.nilable(String)) }
397
480
  def listing_tag_for_version(version)
398
481
  listing_tags
399
482
  .find { |t| t.name =~ /(?:[^0-9\.]|\A)#{Regexp.escape(version)}\z/ }
400
483
  &.name
401
484
  end
402
485
 
486
+ sig { returns(T::Array[Dependabot::GitRef]) }
403
487
  def listing_tags
404
488
  return [] unless listing_source_url
405
489
 
406
- @listing_tags ||= begin
407
- tags = listing_repo_git_metadata_fetcher.tags
490
+ @listing_tags ||= T.let(
491
+ begin
492
+ tags = listing_repo_git_metadata_fetcher.tags
408
493
 
409
- if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
410
- tags = tags.map do |tag|
411
- tag.dup.tap { |t| t.name = "tags/#{tag.name}" }
494
+ if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
495
+ tags = tags.map do |tag|
496
+ tag.dup.tap { |t| t.name = "tags/#{tag.name}" }
497
+ end
412
498
  end
413
- end
414
499
 
415
- tags
416
- rescue GitDependenciesNotReachable
417
- []
418
- end
500
+ tags
501
+ rescue GitDependenciesNotReachable
502
+ []
503
+ end,
504
+ T.nilable(T::Array[Dependabot::GitRef])
505
+ )
419
506
  end
420
507
 
508
+ sig { returns(T.nilable(String)) }
421
509
  def listing_upload_pack
422
510
  return unless listing_source_url
423
511
 
424
512
  listing_repo_git_metadata_fetcher.upload_pack
425
513
  end
426
514
 
515
+ sig { returns(T::Array[Dependabot::Requirement]) }
427
516
  def ignore_requirements
428
517
  ignored_versions.flat_map { |req| requirement_class.requirements_array(req) }
429
518
  end
430
519
 
520
+ sig { returns(T::Boolean) }
431
521
  def wants_prerelease?
432
522
  return false unless dependency_source_details&.fetch(:ref, nil)
433
523
  return false unless pinned_ref_looks_like_version?
434
524
 
435
- version = version_from_ref(ref)
525
+ version = version_from_ref(T.must(ref))
436
526
  version.prerelease?
437
527
  end
438
528
 
529
+ sig { params(tag: Dependabot::GitRef).returns(T::Boolean) }
439
530
  def tag_included_in_ignore_requirements?(tag)
440
531
  version = version_from_tag(tag)
441
532
  ignore_requirements.any? { |r| r.satisfied_by?(version) }
442
533
  end
443
534
 
535
+ sig { params(tag: Dependabot::GitRef).returns(T::Boolean) }
444
536
  def tag_is_prerelease?(tag)
445
537
  version_from_tag(tag).prerelease?
446
538
  end
447
539
 
540
+ sig { params(tag: Dependabot::GitRef).returns(Gem::Version) }
448
541
  def version_from_tag(tag)
449
542
  version_from_ref(tag.name)
450
543
  end
451
544
 
545
+ sig { params(name: String).returns(Gem::Version) }
452
546
  def version_from_ref(name)
453
547
  version_class.new(scan_version(name))
454
548
  end
455
549
 
550
+ sig { params(name: String).returns(String) }
456
551
  def scan_version(name)
457
- name.match(VERSION_REGEX).named_captures.fetch("version")
552
+ T.must(T.must(name.match(VERSION_REGEX)).named_captures.fetch("version"))
458
553
  end
459
554
 
555
+ sig { returns(T.class_of(Gem::Version)) }
460
556
  def version_class
461
- @version_class ||= dependency.version_class
557
+ @version_class ||= T.let(
558
+ dependency.version_class,
559
+ T.nilable(T.class_of(Gem::Version))
560
+ )
462
561
  end
463
562
 
563
+ sig { returns(T.class_of(Dependabot::Requirement)) }
464
564
  def requirement_class
465
- @requirement_class ||= dependency.requirement_class
565
+ @requirement_class ||= T.let(
566
+ dependency.requirement_class,
567
+ T.nilable(T.class_of(Dependabot::Requirement))
568
+ )
466
569
  end
467
570
 
571
+ sig { returns(Dependabot::GitMetadataFetcher) }
468
572
  def local_repo_git_metadata_fetcher
469
573
  @local_repo_git_metadata_fetcher ||=
470
- GitMetadataFetcher.new(
471
- url: dependency_source_details.fetch(:url),
472
- credentials: credentials
574
+ T.let(
575
+ GitMetadataFetcher.new(
576
+ url: dependency_source_details&.fetch(:url),
577
+ credentials: credentials
578
+ ),
579
+ T.nilable(Dependabot::GitMetadataFetcher)
473
580
  )
474
581
  end
475
582
 
583
+ sig { returns(Dependabot::GitMetadataFetcher) }
476
584
  def listing_repo_git_metadata_fetcher
477
585
  @listing_repo_git_metadata_fetcher ||=
478
- GitMetadataFetcher.new(
479
- url: listing_source_url,
480
- credentials: credentials
586
+ T.let(
587
+ GitMetadataFetcher.new(
588
+ url: T.must(listing_source_url),
589
+ credentials: credentials
590
+ ),
591
+ T.nilable(Dependabot::GitMetadataFetcher)
481
592
  )
482
593
  end
483
594
  end
595
+ # rubocop:enable Metrics/ClassLength
484
596
  end