dependabot-core 0.94.13 → 0.95.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. checksums.yaml +4 -4
  2. metadata +13 -337
  3. data/CHANGELOG.md +0 -7079
  4. data/LICENSE +0 -39
  5. data/README.md +0 -114
  6. data/helpers/test/run.rb +0 -18
  7. data/helpers/utils/git-credential-store-immutable +0 -10
  8. data/lib/dependabot/clients/bitbucket.rb +0 -105
  9. data/lib/dependabot/clients/github_with_retries.rb +0 -121
  10. data/lib/dependabot/clients/gitlab.rb +0 -72
  11. data/lib/dependabot/dependency.rb +0 -115
  12. data/lib/dependabot/dependency_file.rb +0 -60
  13. data/lib/dependabot/errors.rb +0 -179
  14. data/lib/dependabot/file_fetchers/README.md +0 -65
  15. data/lib/dependabot/file_fetchers/base.rb +0 -368
  16. data/lib/dependabot/file_fetchers.rb +0 -18
  17. data/lib/dependabot/file_parsers/README.md +0 -45
  18. data/lib/dependabot/file_parsers/base/dependency_set.rb +0 -77
  19. data/lib/dependabot/file_parsers/base.rb +0 -31
  20. data/lib/dependabot/file_parsers.rb +0 -18
  21. data/lib/dependabot/file_updaters/README.md +0 -58
  22. data/lib/dependabot/file_updaters/base.rb +0 -52
  23. data/lib/dependabot/file_updaters.rb +0 -18
  24. data/lib/dependabot/git_commit_checker.rb +0 -412
  25. data/lib/dependabot/metadata_finders/README.md +0 -53
  26. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +0 -321
  27. data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +0 -177
  28. data/lib/dependabot/metadata_finders/base/commits_finder.rb +0 -221
  29. data/lib/dependabot/metadata_finders/base/release_finder.rb +0 -255
  30. data/lib/dependabot/metadata_finders/base.rb +0 -117
  31. data/lib/dependabot/metadata_finders.rb +0 -18
  32. data/lib/dependabot/pull_request_creator/branch_namer.rb +0 -170
  33. data/lib/dependabot/pull_request_creator/commit_signer.rb +0 -63
  34. data/lib/dependabot/pull_request_creator/github.rb +0 -277
  35. data/lib/dependabot/pull_request_creator/gitlab.rb +0 -136
  36. data/lib/dependabot/pull_request_creator/labeler.rb +0 -373
  37. data/lib/dependabot/pull_request_creator/message_builder.rb +0 -906
  38. data/lib/dependabot/pull_request_creator.rb +0 -153
  39. data/lib/dependabot/pull_request_updater/github.rb +0 -165
  40. data/lib/dependabot/pull_request_updater.rb +0 -43
  41. data/lib/dependabot/shared_helpers.rb +0 -224
  42. data/lib/dependabot/source.rb +0 -120
  43. data/lib/dependabot/update_checkers/README.md +0 -67
  44. data/lib/dependabot/update_checkers/base.rb +0 -220
  45. data/lib/dependabot/update_checkers.rb +0 -18
  46. data/lib/dependabot/utils.rb +0 -33
  47. data/lib/dependabot/version.rb +0 -5
  48. data/lib/dependabot.rb +0 -4
  49. data/lib/rubygems_version_patch.rb +0 -14
@@ -1,906 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "pathname"
4
- require "dependabot/clients/github_with_retries"
5
- require "dependabot/clients/gitlab"
6
- require "dependabot/metadata_finders"
7
- require "dependabot/pull_request_creator"
8
-
9
- # rubocop:disable Metrics/ClassLength
10
- module Dependabot
11
- class PullRequestCreator
12
- class MessageBuilder
13
- ANGULAR_PREFIXES = %w(build chore ci docs feat fix perf refactor style
14
- test).freeze
15
- ESLINT_PREFIXES = %w(Breaking Build Chore Docs Fix New Update
16
- Upgrade).freeze
17
- GITMOJI_PREFIXES = %w(art zap fire bug ambulance sparkles memo rocket
18
- lipstick tada white_check_mark lock apple penguin
19
- checkered_flag robot green_apple bookmark
20
- rotating_light construction green_heart arrow_down
21
- arrow_up pushpin construction_worker
22
- chart_with_upwards_trend recycle heavy_minus_sign
23
- whale heavy_plus_sign wrench globe_with_meridians
24
- pencil2 hankey rewind twisted_rightwards_arrows
25
- package alien truck page_facing_up boom bento
26
- ok_hand wheelchair bulb beers speech_balloon
27
- card_file_box loud_sound mute busts_in_silhouette
28
- children_crossing building_construction iphone
29
- clown_face egg see_no_evil camera_flash).freeze
30
- ISSUE_TAG_REGEX =
31
- /(?<=[\s(\\]|^)(?<tag>(?:\#|GH-)\d+)(?=[^A-Za-z0-9\-]|$)/.freeze
32
- GITHUB_REF_REGEX = %r{github\.com/[^/\s]+/[^/\s]+/(?:issue|pull)}.freeze
33
-
34
- attr_reader :source, :dependencies, :files, :credentials,
35
- :pr_message_footer, :author_details, :vulnerabilities_fixed
36
-
37
- def initialize(source:, dependencies:, files:, credentials:,
38
- pr_message_footer: nil, author_details: nil,
39
- vulnerabilities_fixed: {})
40
- @dependencies = dependencies
41
- @files = files
42
- @source = source
43
- @credentials = credentials
44
- @pr_message_footer = pr_message_footer
45
- @author_details = author_details
46
- @vulnerabilities_fixed = vulnerabilities_fixed
47
- end
48
-
49
- def pr_name
50
- return library_pr_name if library?
51
-
52
- application_pr_name
53
- end
54
-
55
- def pr_message
56
- commit_message_intro + metadata_cascades + prefixed_pr_message_footer
57
- end
58
-
59
- def commit_message
60
- message = commit_subject + "\n\n"
61
- message += commit_message_intro
62
- message += metadata_links
63
- message += "\n\n" + signoff_message if signoff_message
64
- message
65
- end
66
-
67
- private
68
-
69
- def commit_subject
70
- subject = pr_name.gsub("⬆️", ":arrow_up:").gsub("🔒", ":lock:")
71
- return subject unless subject.length > 72
72
-
73
- subject = subject.gsub(/ from [^\s]*? to [^\s]*/, "")
74
- return subject unless subject.length > 72
75
-
76
- subject.split(" in ").first
77
- end
78
-
79
- def commit_message_intro
80
- return requirement_commit_message_intro if library?
81
-
82
- version_commit_message_intro
83
- end
84
-
85
- def prefixed_pr_message_footer
86
- return "" unless pr_message_footer
87
-
88
- "\n\n#{pr_message_footer}"
89
- end
90
-
91
- def signoff_message
92
- return unless author_details.is_a?(Hash)
93
- return unless author_details[:name] && author_details[:email]
94
-
95
- "Signed-off-by: #{author_details[:name]} <#{author_details[:email]}>"
96
- end
97
-
98
- def library_pr_name
99
- pr_name = pr_name_prefix
100
-
101
- pr_name +=
102
- if dependencies.count == 1
103
- "#{dependencies.first.display_name} requirement "\
104
- "from #{old_library_requirement(dependencies.first)} "\
105
- "to #{new_library_requirement(dependencies.first)}"
106
- else
107
- names = dependencies.map(&:name)
108
- "requirements for #{names[0..-2].join(', ')} and #{names[-1]}"
109
- end
110
-
111
- return pr_name if files.first.directory == "/"
112
-
113
- pr_name + " in #{files.first.directory}"
114
- end
115
-
116
- # rubocop:disable Metrics/AbcSize
117
- def application_pr_name
118
- pr_name = pr_name_prefix
119
-
120
- pr_name +=
121
- if dependencies.count == 1
122
- dependency = dependencies.first
123
- "#{dependency.display_name} from #{previous_version(dependency)} "\
124
- "to #{new_version(dependency)}"
125
- elsif updating_a_property?
126
- dependency = dependencies.first
127
- "#{property_name} from #{previous_version(dependency)} "\
128
- "to #{new_version(dependency)}"
129
- elsif updating_a_dependency_set?
130
- dependency = dependencies.first
131
- "#{dependency_set.fetch(:group)} dependency set "\
132
- "from #{previous_version(dependency)} "\
133
- "to #{new_version(dependency)}"
134
- else
135
- names = dependencies.map(&:name)
136
- "#{names[0..-2].join(', ')} and #{names[-1]}"
137
- end
138
-
139
- return pr_name if files.first.directory == "/"
140
-
141
- pr_name + " in #{files.first.directory}"
142
- end
143
- # rubocop:enable Metrics/AbcSize
144
-
145
- def pr_name_prefix
146
- prefix = commit_prefix.to_s
147
- prefix += security_prefix if includes_security_fixes?
148
- prefix + pr_name_first_word
149
- end
150
-
151
- def commit_prefix
152
- # If there is a previous Dependabot commit, and it used a known style,
153
- # use that as our model for subsequent commits
154
- case last_dependabot_commit_style
155
- when :gitmoji then "⬆️ "
156
- when :contentional_prefix then "#{last_dependabot_commit_prefix}: "
157
- when :contentional_prefix_with_scope
158
- scope = dependencies.any?(&:production?) ? "deps" : "deps-dev"
159
- "#{last_dependabot_commit_prefix}(#{scope}): "
160
- else
161
- # Otherwise we need to detect the user's preferred style from the
162
- # existing commits on their repo
163
- build_commit_prefix_from_previous_commits
164
- end
165
- end
166
-
167
- def security_prefix
168
- return "🔒 " if commit_prefix == "⬆️ "
169
-
170
- capitalize_first_word? ? "[Security] " : "[security] "
171
- end
172
-
173
- def pr_name_first_word
174
- first_word = library? ? "update " : "bump "
175
- capitalize_first_word? ? first_word.capitalize : first_word
176
- end
177
-
178
- def capitalize_first_word?
179
- case last_dependabot_commit_style
180
- when :gitmoji then true
181
- when :contentional_prefix, :contentional_prefix_with_scope
182
- last_dependabot_commit_message.match?(/: (\[Security\] )?(B|U)/)
183
- else
184
- if using_angular_commit_messages? || using_eslint_commit_messages?
185
- prefixes = ANGULAR_PREFIXES + ESLINT_PREFIXES
186
- semantic_msgs = recent_commit_messages.select do |message|
187
- prefixes.any? { |pre| message.match?(/#{pre}[:(]/i) }
188
- end
189
-
190
- return true if semantic_msgs.all? { |m| m.match?(/:\s+\[?[A-Z]/) }
191
- return false if semantic_msgs.all? { |m| m.match?(/:\s+\[?[a-z]/) }
192
- end
193
-
194
- !commit_prefix&.match(/^[a-z]/)
195
- end
196
- end
197
-
198
- def build_commit_prefix_from_previous_commits
199
- if using_angular_commit_messages?
200
- scope = dependencies.any?(&:production?) ? "deps" : "deps-dev"
201
- "#{angular_commit_prefix}(#{scope}): "
202
- elsif using_eslint_commit_messages?
203
- # https://eslint.org/docs/developer-guide/contributing/pull-requests
204
- "Upgrade: "
205
- elsif using_gitmoji_commit_messages?
206
- "⬆️ "
207
- end
208
- end
209
-
210
- def last_dependabot_commit_style
211
- return unless (msg = last_dependabot_commit_message)
212
-
213
- return :gitmoji if msg.start_with?("⬆️")
214
- return :contentional_prefix if msg.match?(/^(chore|build|upgrade):/i)
215
- return unless msg.match?(/^(chore|build|upgrade)\(/i)
216
-
217
- :contentional_prefix_with_scope
218
- end
219
-
220
- def last_dependabot_commit_prefix
221
- last_dependabot_commit_message&.split(/[:(]/)&.first
222
- end
223
-
224
- def requirement_commit_message_intro
225
- msg = "Updates the requirements on "
226
-
227
- msg +=
228
- if dependencies.count == 1
229
- "#{dependency_links.first} "
230
- else
231
- "#{dependency_links[0..-2].join(', ')} and #{dependency_links[-1]} "
232
- end
233
-
234
- msg + "to permit the latest version."
235
- end
236
-
237
- # rubocop:disable Metrics/CyclomaticComplexity
238
- # rubocop:disable Metrics/PerceivedComplexity
239
- def version_commit_message_intro
240
- if dependencies.count > 1 && updating_a_property?
241
- return multidependency_property_intro
242
- end
243
-
244
- if dependencies.count > 1 && updating_a_dependency_set?
245
- return dependency_set_intro
246
- end
247
-
248
- return multidependency_intro if dependencies.count > 1
249
-
250
- dependency = dependencies.first
251
- msg = "Bumps #{dependency_links.first} "\
252
- "from #{previous_version(dependency)} "\
253
- "to #{new_version(dependency)}."
254
-
255
- if switching_from_ref_to_release?(dependency)
256
- msg += " This release includes the previously tagged commit."
257
- end
258
-
259
- if vulnerabilities_fixed[dependency.name]&.any?
260
- msg += " **This update includes security fixes.**"
261
- end
262
-
263
- msg
264
- end
265
- # rubocop:enable Metrics/CyclomaticComplexity
266
- # rubocop:enable Metrics/PerceivedComplexity
267
-
268
- def multidependency_property_intro
269
- dependency = dependencies.first
270
-
271
- "Bumps `#{property_name}` "\
272
- "from #{previous_version(dependency)} "\
273
- "to #{new_version(dependency)}."
274
- end
275
-
276
- def dependency_set_intro
277
- dependency = dependencies.first
278
-
279
- "Bumps `#{dependency_set.fetch(:group)}` "\
280
- "dependency set from #{previous_version(dependency)} "\
281
- "to #{new_version(dependency)}."
282
- end
283
-
284
- def multidependency_intro
285
- "Bumps #{dependency_links[0..-2].join(', ')} "\
286
- "and #{dependency_links[-1]}. These "\
287
- "dependencies needed to be updated together."
288
- end
289
-
290
- def updating_a_property?
291
- dependencies.first.
292
- requirements.
293
- any? { |r| r.dig(:metadata, :property_name) }
294
- end
295
-
296
- def updating_a_dependency_set?
297
- dependencies.first.
298
- requirements.
299
- any? { |r| r.dig(:metadata, :dependency_set) }
300
- end
301
-
302
- def property_name
303
- @property_name ||= dependencies.first.requirements.
304
- find { |r| r.dig(:metadata, :property_name) }&.
305
- dig(:metadata, :property_name)
306
-
307
- raise "No property name!" unless @property_name
308
-
309
- @property_name
310
- end
311
-
312
- def dependency_set
313
- @dependency_set ||= dependencies.first.requirements.
314
- find { |r| r.dig(:metadata, :dependency_set) }&.
315
- dig(:metadata, :dependency_set)
316
-
317
- raise "No dependency set!" unless @dependency_set
318
-
319
- @dependency_set
320
- end
321
-
322
- def dependency_links
323
- dependencies.map do |dependency|
324
- if source_url(dependency)
325
- "[#{dependency.display_name}](#{source_url(dependency)})"
326
- elsif homepage_url(dependency)
327
- "[#{dependency.display_name}](#{homepage_url(dependency)})"
328
- else
329
- dependency.display_name
330
- end
331
- end
332
- end
333
-
334
- def metadata_links
335
- if dependencies.count == 1
336
- return metadata_links_for_dep(dependencies.first)
337
- end
338
-
339
- dependencies.map do |dep|
340
- "\n\nUpdates `#{dep.display_name}` from #{previous_version(dep)} to "\
341
- "#{new_version(dep)}"\
342
- "#{metadata_links_for_dep(dep)}"
343
- end.join
344
- end
345
-
346
- def metadata_links_for_dep(dep)
347
- msg = ""
348
- msg += "\n- [Release notes](#{releases_url(dep)})" if releases_url(dep)
349
- msg += "\n- [Changelog](#{changelog_url(dep)})" if changelog_url(dep)
350
- msg += "\n- [Upgrade guide](#{upgrade_url(dep)})" if upgrade_url(dep)
351
- msg += "\n- [Commits](#{commits_url(dep)})" if commits_url(dep)
352
- msg
353
- end
354
-
355
- def metadata_cascades
356
- if dependencies.count == 1
357
- return metadata_cascades_for_dep(dependencies.first)
358
- end
359
-
360
- dependencies.map do |dep|
361
- msg = "\n\nUpdates `#{dep.display_name}` from "\
362
- "#{previous_version(dep)} to #{new_version(dep)}"
363
- if vulnerabilities_fixed[dep.name]&.any?
364
- msg += ". **This update includes security fixes.**"
365
- end
366
- msg + metadata_cascades_for_dep(dep)
367
- end.join
368
- end
369
-
370
- def metadata_cascades_for_dep(dep)
371
- msg = ""
372
- msg += vulnerabilities_cascade(dep)
373
- msg += release_cascade(dep)
374
- msg += changelog_cascade(dep)
375
- msg += upgrade_guide_cascade(dep)
376
- msg += commits_cascade(dep)
377
- msg += maintainer_changes_cascade(dep)
378
- msg += "\n<br />" unless msg == ""
379
- sanitize_links_and_mentions(msg)
380
- end
381
-
382
- def vulnerabilities_cascade(dep)
383
- fixed_vulns = vulnerabilities_fixed[dep.name]
384
- return "" unless fixed_vulns&.any?
385
-
386
- msg = "\n<details>\n<summary>Vulnerabilities fixed</summary>\n\n"
387
- fixed_vulns.each { |v| msg += serialized_vulnerability_details(v) }
388
- msg += "</details>"
389
- sanitize_template_tags(msg)
390
- end
391
-
392
- def release_cascade(dep)
393
- return "" unless releases_text(dep) && releases_url(dep)
394
-
395
- msg = "\n<details>\n<summary>Release notes</summary>\n\n"
396
- msg += "*Sourced from [#{dep.display_name}'s releases]"\
397
- "(#{releases_url(dep)}).*\n\n"
398
- msg +=
399
- begin
400
- release_note_lines = releases_text(dep).split("\n").first(50)
401
- release_note_lines = release_note_lines.map { |line| "> #{line}\n" }
402
- if release_note_lines.count == 50
403
- release_note_lines << truncated_line
404
- end
405
- release_note_lines.join
406
- end
407
- msg += "</details>"
408
- msg = link_issues(text: msg, dependency: dep)
409
- msg = fix_relative_links(
410
- text: msg,
411
- base_url: source_url(dep) + "/blob/HEAD/"
412
- )
413
- sanitize_template_tags(msg)
414
- end
415
-
416
- def changelog_cascade(dep)
417
- return "" unless changelog_url(dep) && changelog_text(dep)
418
-
419
- msg = "\n<details>\n<summary>Changelog</summary>\n\n"
420
- msg += "*Sourced from "\
421
- "[#{dep.display_name}'s changelog](#{changelog_url(dep)}).*\n\n"
422
- msg +=
423
- begin
424
- changelog_lines = changelog_text(dep).split("\n").first(50)
425
- changelog_lines = changelog_lines.map { |line| "> #{line}\n" }
426
- changelog_lines << truncated_line if changelog_lines.count == 50
427
- changelog_lines.join
428
- end
429
- msg += "</details>"
430
- msg = link_issues(text: msg, dependency: dep)
431
- msg = fix_relative_links(text: msg, base_url: changelog_url(dep))
432
- sanitize_template_tags(msg)
433
- end
434
-
435
- def upgrade_guide_cascade(dep)
436
- return "" unless upgrade_url(dep) && upgrade_text(dep)
437
-
438
- msg = "\n<details>\n<summary>Upgrade guide</summary>\n\n"
439
- msg += "*Sourced from "\
440
- "[#{dep.display_name}'s upgrade guide]"\
441
- "(#{upgrade_url(dep)}).*\n\n"
442
- msg +=
443
- begin
444
- upgrade_lines = upgrade_text(dep).split("\n").first(50)
445
- upgrade_lines = upgrade_lines.map { |line| "> #{line}\n" }
446
- upgrade_lines << truncated_line if upgrade_lines.count == 50
447
- upgrade_lines.join
448
- end
449
- msg += "</details>"
450
- msg = link_issues(text: msg, dependency: dep)
451
- msg = fix_relative_links(text: msg, base_url: upgrade_url(dep))
452
- sanitize_template_tags(msg)
453
- end
454
-
455
- def commits_cascade(dep)
456
- return "" unless commits_url(dep) && commits(dep)
457
-
458
- msg = "\n<details>\n<summary>Commits</summary>\n\n"
459
-
460
- commits(dep).reverse.first(10).each do |commit|
461
- title = commit[:message].strip.split("\n").first
462
- title = title.slice(0..76) + "..." if title && title.length > 80
463
- sha = commit[:sha][0, 7]
464
- msg += "- [`#{sha}`](#{commit[:html_url]}) #{title}\n"
465
- end
466
-
467
- msg +=
468
- if commits(dep).count > 10
469
- "- Additional commits viewable in "\
470
- "[compare view](#{commits_url(dep)})\n"
471
- else
472
- "- See full diff in [compare view](#{commits_url(dep)})\n"
473
- end
474
-
475
- msg += "</details>"
476
- msg = link_issues(text: msg, dependency: dep)
477
- sanitize_template_tags(msg)
478
- end
479
-
480
- def maintainer_changes_cascade(dep)
481
- return "" unless maintainer_changes(dep)
482
-
483
- msg = "\n<details>\n<summary>Maintainer changes</summary>\n\n"
484
- msg += maintainer_changes(dep)
485
- msg + "\n</details>"
486
- end
487
-
488
- def serialized_vulnerability_details(details)
489
- msg = vulnerability_source_line(details)
490
-
491
- if details["title"]
492
- msg += "> **#{details['title'].lines.map(&:strip).join(' ')}**\n"
493
- end
494
-
495
- if (description = details["description"])
496
- description.strip.lines.first(20).each { |line| msg += "> #{line}" }
497
- msg += truncated_line if description.strip.lines.count > 20
498
- end
499
-
500
- msg += "\n" unless msg.end_with?("\n")
501
- msg += "> \n"
502
- msg += vulnerability_version_range_lines(details)
503
- msg + "\n"
504
- end
505
-
506
- def vulnerability_source_line(details)
507
- if details["source_url"] && details["source_name"]
508
- "*Sourced from [#{details['source_name']}]"\
509
- "(#{details['source_url']}).*\n\n"
510
- elsif details["source_name"]
511
- "*Sourced from #{details['source_name']}.*\n\n"
512
- else
513
- ""
514
- end
515
- end
516
-
517
- def vulnerability_version_range_lines(details)
518
- msg = ""
519
- %w(patched_versions unaffected_versions affected_versions).each do |tp|
520
- type = tp.split("_").first.capitalize
521
- next unless details[tp]
522
-
523
- versions_string = details[tp].any? ? details[tp].join("; ") : "none"
524
- versions_string = versions_string.gsub(/(?<!\\)~/, '\~')
525
- msg += "> #{type} versions: #{versions_string}\n"
526
- end
527
- msg
528
- end
529
-
530
- def truncated_line
531
- # Tables can spill out of truncated details, so we close them
532
- "></tr></table> ... (truncated)\n"
533
- end
534
-
535
- def releases_url(dependency)
536
- metadata_finder(dependency).releases_url
537
- end
538
-
539
- def releases_text(dependency)
540
- metadata_finder(dependency).releases_text
541
- end
542
-
543
- def changelog_url(dependency)
544
- metadata_finder(dependency).changelog_url
545
- end
546
-
547
- def changelog_text(dependency)
548
- metadata_finder(dependency).changelog_text
549
- end
550
-
551
- def upgrade_url(dependency)
552
- metadata_finder(dependency).upgrade_guide_url
553
- end
554
-
555
- def upgrade_text(dependency)
556
- metadata_finder(dependency).upgrade_guide_text
557
- end
558
-
559
- def commits_url(dependency)
560
- metadata_finder(dependency).commits_url
561
- end
562
-
563
- def commits(dependency)
564
- metadata_finder(dependency).commits
565
- end
566
-
567
- def maintainer_changes(dependency)
568
- metadata_finder(dependency).maintainer_changes
569
- end
570
-
571
- def source_url(dependency)
572
- metadata_finder(dependency).source_url
573
- end
574
-
575
- def homepage_url(dependency)
576
- metadata_finder(dependency).homepage_url
577
- end
578
-
579
- def metadata_finder(dependency)
580
- @metadata_finder ||= {}
581
- @metadata_finder[dependency.name] ||=
582
- MetadataFinders.
583
- for_package_manager(dependency.package_manager).
584
- new(dependency: dependency, credentials: credentials)
585
- end
586
-
587
- def previous_version(dependency)
588
- if dependency.previous_version.match?(/^[0-9a-f]{40}$/)
589
- return previous_ref(dependency) if ref_changed?(dependency)
590
-
591
- "`#{dependency.previous_version[0..6]}`"
592
- elsif dependency.version == dependency.previous_version &&
593
- package_manager == "docker"
594
- digest =
595
- dependency.previous_requirements.
596
- map { |r| r.dig(:source, "digest") || r.dig(:source, :digest) }.
597
- compact.first
598
- "`#{digest.split(':').last[0..6]}`"
599
- else
600
- dependency.previous_version
601
- end
602
- end
603
-
604
- def new_version(dependency)
605
- if dependency.version.match?(/^[0-9a-f]{40}$/)
606
- return new_ref(dependency) if ref_changed?(dependency)
607
-
608
- "`#{dependency.version[0..6]}`"
609
- elsif dependency.version == dependency.previous_version &&
610
- package_manager == "docker"
611
- digest =
612
- dependency.requirements.
613
- map { |r| r.dig(:source, "digest") || r.dig(:source, :digest) }.
614
- compact.first
615
- "`#{digest.split(':').last[0..6]}`"
616
- else
617
- dependency.version
618
- end
619
- end
620
-
621
- def previous_ref(dependency)
622
- dependency.previous_requirements.map do |r|
623
- r.dig(:source, "ref") || r.dig(:source, :ref)
624
- end.compact.first
625
- end
626
-
627
- def new_ref(dependency)
628
- dependency.requirements.map do |r|
629
- r.dig(:source, "ref") || r.dig(:source, :ref)
630
- end.compact.first
631
- end
632
-
633
- def old_library_requirement(dependency)
634
- old_reqs =
635
- dependency.previous_requirements - dependency.requirements
636
-
637
- gemspec =
638
- old_reqs.find { |r| r[:file].match?(%r{^[^/]*\.gemspec$}) }
639
- return gemspec.fetch(:requirement) if gemspec
640
-
641
- req = old_reqs.first.fetch(:requirement)
642
- return req if req
643
- return previous_ref(dependency) if ref_changed?(dependency)
644
-
645
- raise "No previous requirement!"
646
- end
647
-
648
- def new_library_requirement(dependency)
649
- updated_reqs =
650
- dependency.requirements - dependency.previous_requirements
651
-
652
- gemspec =
653
- updated_reqs.find { |r| r[:file].match?(%r{^[^/]*\.gemspec$}) }
654
- return gemspec.fetch(:requirement) if gemspec
655
-
656
- req = updated_reqs.first.fetch(:requirement)
657
- return req if req
658
- return new_ref(dependency) if ref_changed?(dependency)
659
-
660
- raise "No new requirement!"
661
- end
662
-
663
- def link_issues(text:, dependency:)
664
- text.gsub(ISSUE_TAG_REGEX) do |mention|
665
- number = mention.tr("#", "").gsub("GH-", "")
666
- "[#{mention}](#{source_url(dependency)}/issues/#{number})"
667
- end
668
- end
669
-
670
- def fix_relative_links(text:, base_url:)
671
- text.gsub(/\[.*?\]\([^)]+\)/) do |link|
672
- next link if link.include?("://")
673
-
674
- relative_path = link.match(/\((.*?)\)/).captures.last
675
- base = base_url.split("://").last.gsub(%r{[^/]*$}, "")
676
- path = File.join(base, relative_path)
677
- absolute_path =
678
- base_url.sub(
679
- %r{(?<=://).*$},
680
- Pathname.new(path).cleanpath.to_s
681
- )
682
- link.gsub(relative_path, absolute_path)
683
- end
684
- end
685
-
686
- def sanitize_links_and_mentions(text)
687
- text = text.gsub(%r{(?<![A-Za-z0-9\-])@[A-Za-z0-9\-/]+}) do |mention|
688
- next mention if mention.include?("/")
689
-
690
- "[**#{mention.tr('@', '')}**]"\
691
- "(https://github.com/#{mention.tr('@', '')})"
692
- end
693
-
694
- text.gsub(GITHUB_REF_REGEX) do |ref|
695
- ref.gsub("github.com", "github-redirect.dependabot.com")
696
- end
697
- end
698
-
699
- def sanitize_template_tags(text)
700
- text.gsub(/\<.*?\>/) do |tag|
701
- tag_contents = tag.match(/\<(.*?)\>/).captures.first.strip
702
-
703
- # Unclosed calls to template overflow out of the blockquote block,
704
- # wrecking the rest of our PRs. Other tags don't share this problem.
705
- next "\\#{tag}" if tag_contents.start_with?("template")
706
-
707
- tag
708
- end
709
- end
710
-
711
- def ref_changed?(dependency)
712
- return false unless previous_ref(dependency)
713
-
714
- previous_ref(dependency) != new_ref(dependency)
715
- end
716
-
717
- def library?
718
- return true if files.map(&:name).any? { |nm| nm.end_with?(".gemspec") }
719
-
720
- dependencies.none?(&:appears_in_lockfile?)
721
- end
722
-
723
- def switching_from_ref_to_release?(dependency)
724
- return false unless dependency.previous_version.match?(/^[0-9a-f]{40}$/)
725
-
726
- Gem::Version.correct?(dependency.version)
727
- end
728
-
729
- def includes_security_fixes?
730
- vulnerabilities_fixed.values.flatten.any?
731
- end
732
-
733
- def using_angular_commit_messages?
734
- return false if recent_commit_messages.none?
735
-
736
- angular_messages = recent_commit_messages.select do |message|
737
- ANGULAR_PREFIXES.any? { |pre| message.match?(/#{pre}[:(]/i) }
738
- end
739
-
740
- # Definitely not using Angular commits if < 30% match angular commits
741
- if angular_messages.count.to_f / recent_commit_messages.count < 0.3
742
- return false
743
- end
744
-
745
- eslint_only_pres = ESLINT_PREFIXES.map(&:downcase) - ANGULAR_PREFIXES
746
- angular_only_pres = ANGULAR_PREFIXES - ESLINT_PREFIXES.map(&:downcase)
747
-
748
- uses_eslint_only_pres =
749
- recent_commit_messages.
750
- any? { |m| eslint_only_pres.any? { |pre| m.match?(/#{pre}[:(]/i) } }
751
-
752
- uses_angular_only_pres =
753
- recent_commit_messages.
754
- any? { |m| angular_only_pres.any? { |pre| m.match?(/#{pre}[:(]/i) } }
755
-
756
- # If using any angular-only prefixes, return true
757
- # (i.e., we assume Angular over ESLint when both are present)
758
- return true if uses_angular_only_pres
759
- return false if uses_eslint_only_pres
760
-
761
- true
762
- end
763
-
764
- def using_eslint_commit_messages?
765
- return false if recent_commit_messages.none?
766
-
767
- semantic_messages = recent_commit_messages.select do |message|
768
- ESLINT_PREFIXES.any? { |pre| message.start_with?(/#{pre}[:(]/) }
769
- end
770
-
771
- semantic_messages.count.to_f / recent_commit_messages.count > 0.3
772
- end
773
-
774
- def angular_commit_prefix
775
- raise "Not using angular commits!" unless using_angular_commit_messages?
776
-
777
- recent_commits_using_chore =
778
- recent_commit_messages.
779
- any? { |msg| msg.start_with?("chore", "Chore") }
780
-
781
- recent_commits_using_build =
782
- recent_commit_messages.
783
- any? { |msg| msg.start_with?("build", "Build") }
784
-
785
- commit_prefix =
786
- if recent_commits_using_chore && !recent_commits_using_build
787
- "chore"
788
- else
789
- "build"
790
- end
791
-
792
- if capitalize_angular_commit_prefix?
793
- commit_prefix = commit_prefix.capitalize
794
- end
795
-
796
- commit_prefix
797
- end
798
-
799
- def capitalize_angular_commit_prefix?
800
- semantic_messages = recent_commit_messages.select do |message|
801
- ANGULAR_PREFIXES.any? { |pre| message.match?(/#{pre}[:(]/i) }
802
- end
803
-
804
- if semantic_messages.none?
805
- return last_dependabot_commit_message&.match?(/^A-Z/)
806
- end
807
-
808
- capitalized_msgs = semantic_messages.select { |m| m.match?(/^[A-Z]/) }
809
- capitalized_msgs.count.to_f / semantic_messages.count > 0.5
810
- end
811
-
812
- def using_gitmoji_commit_messages?
813
- return false if recent_commit_messages.none?
814
-
815
- gitmoji_messages = recent_commit_messages.select do |message|
816
- GITMOJI_PREFIXES.any? { |pre| message.match?(/:#{pre}:/i) }
817
- end
818
-
819
- gitmoji_messages.count.to_f / recent_commit_messages.count > 0.3
820
- end
821
-
822
- def recent_commit_messages
823
- case source.provider
824
- when "github" then recent_github_commit_messages
825
- when "gitlab" then recent_gitlab_commit_messages
826
- else raise "Unsupported provider: #{source.provider}"
827
- end
828
- end
829
-
830
- def recent_github_commit_messages
831
- @recent_github_commit_messages ||=
832
- github_client_for_source.commits(source.repo)
833
-
834
- @recent_github_commit_messages.
835
- reject { |c| c.author&.type == "Bot" }.
836
- reject { |c| c.commit&.message&.start_with?("Merge") }.
837
- map(&:commit).
838
- map(&:message).
839
- compact.
840
- map(&:strip)
841
- end
842
-
843
- def recent_gitlab_commit_messages
844
- @recent_gitlab_commit_messages ||=
845
- gitlab_client_for_source.commits(source.repo)
846
-
847
- @recent_gitlab_commit_messages.
848
- reject { |c| c.author_email == "support@dependabot.com" }.
849
- reject { |c| c.message&.start_with?("merge !") }.
850
- map(&:message).
851
- compact.
852
- map(&:strip)
853
- end
854
-
855
- def last_dependabot_commit_message
856
- case source.provider
857
- when "github" then last_github_dependabot_commit_message
858
- when "gitlab" then last_gitlab_dependabot_commit_message
859
- else raise "Unsupported provider: #{source.provider}"
860
- end
861
- end
862
-
863
- def last_github_dependabot_commit_message
864
- @recent_github_commit_messages ||=
865
- github_client_for_source.commits(source.repo)
866
-
867
- @recent_github_commit_messages.
868
- reject { |c| c.commit&.message&.start_with?("Merge") }.
869
- find { |c| c.commit.author&.name == "dependabot[bot]" }&.
870
- commit&.
871
- message&.
872
- strip
873
- end
874
-
875
- def last_gitlab_dependabot_commit_message
876
- @recent_gitlab_commit_messages ||=
877
- gitlab_client_for_source.commits(source.repo)
878
-
879
- @recent_gitlab_commit_messages.
880
- find { |c| c.author_email == "support@dependabot.com" }&.
881
- message&.
882
- strip
883
- end
884
-
885
- def github_client_for_source
886
- @github_client_for_source ||=
887
- Dependabot::Clients::GithubWithRetries.for_source(
888
- source: source,
889
- credentials: credentials
890
- )
891
- end
892
-
893
- def gitlab_client_for_source
894
- @gitlab_client_for_source ||= Dependabot::Clients::Gitlab.for_source(
895
- source: source,
896
- credentials: credentials
897
- )
898
- end
899
-
900
- def package_manager
901
- @package_manager ||= dependencies.first.package_manager
902
- end
903
- end
904
- end
905
- end
906
- # rubocop:enable Metrics/ClassLength