dependabot-common 0.212.0 → 0.214.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/dependabot/clients/azure.rb +10 -1
- data/lib/dependabot/clients/bitbucket.rb +49 -14
- data/lib/dependabot/clients/github_with_retries.rb +15 -19
- data/lib/dependabot/config/file.rb +1 -1
- data/lib/dependabot/dependency.rb +27 -2
- data/lib/dependabot/dependency_file.rb +11 -3
- data/lib/dependabot/errors.rb +3 -3
- data/lib/dependabot/experiments.rb +19 -0
- data/lib/dependabot/file_fetchers/base.rb +164 -80
- data/lib/dependabot/file_parsers/base/dependency_set.rb +106 -41
- data/lib/dependabot/git_commit_checker.rb +138 -91
- data/lib/dependabot/git_metadata_fetcher.rb +22 -18
- data/lib/dependabot/pull_request_creator/azure.rb +6 -2
- data/lib/dependabot/pull_request_creator/branch_namer.rb +15 -4
- data/lib/dependabot/pull_request_creator/github.rb +1 -1
- data/lib/dependabot/pull_request_creator/labeler.rb +6 -6
- data/lib/dependabot/pull_request_creator/message_builder/issue_linker.rb +5 -5
- data/lib/dependabot/pull_request_creator/message_builder/link_and_mention_sanitizer.rb +33 -5
- data/lib/dependabot/pull_request_creator/message_builder/metadata_presenter.rb +1 -3
- data/lib/dependabot/pull_request_creator/message_builder.rb +78 -6
- data/lib/dependabot/pull_request_creator/pr_name_prefixer.rb +3 -2
- data/lib/dependabot/pull_request_creator.rb +8 -3
- data/lib/dependabot/pull_request_updater/azure.rb +1 -1
- data/lib/dependabot/pull_request_updater/github.rb +15 -12
- data/lib/dependabot/pull_request_updater.rb +2 -1
- data/lib/dependabot/source.rb +9 -9
- data/lib/dependabot/update_checkers/base.rb +13 -6
- data/lib/dependabot/version.rb +1 -1
- metadata +36 -57
@@ -1,5 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
+
require "stringio"
|
3
4
|
require "dependabot/config"
|
4
5
|
require "dependabot/dependency_file"
|
5
6
|
require "dependabot/source"
|
@@ -25,6 +26,12 @@ module Dependabot
|
|
25
26
|
Dependabot::Clients::CodeCommit::NotFound
|
26
27
|
].freeze
|
27
28
|
|
29
|
+
GIT_SUBMODULE_INACCESSIBLE_ERROR =
|
30
|
+
/^fatal: unable to access '(?<url>.*)': The requested URL returned error: (?<code>\d+)$/
|
31
|
+
GIT_SUBMODULE_CLONE_ERROR =
|
32
|
+
/^fatal: clone of '(?<url>.*)' into submodule path '.*' failed$/
|
33
|
+
GIT_SUBMODULE_ERROR_REGEX = /(#{GIT_SUBMODULE_INACCESSIBLE_ERROR})|(#{GIT_SUBMODULE_CLONE_ERROR})/
|
34
|
+
|
28
35
|
def self.required_files_in?(_filename_array)
|
29
36
|
raise NotImplementedError
|
30
37
|
end
|
@@ -69,6 +76,7 @@ module Dependabot
|
|
69
76
|
end
|
70
77
|
|
71
78
|
def commit
|
79
|
+
return cloned_commit if cloned_commit
|
72
80
|
return source.commit if source.commit
|
73
81
|
|
74
82
|
branch = target_branch || default_branch_for_repo
|
@@ -84,7 +92,11 @@ module Dependabot
|
|
84
92
|
def clone_repo_contents
|
85
93
|
@clone_repo_contents ||=
|
86
94
|
_clone_repo_contents(target_directory: repo_contents_path)
|
87
|
-
rescue Dependabot::SharedHelpers::HelperSubprocessFailed
|
95
|
+
rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
|
96
|
+
if e.message.include?("fatal: Remote branch #{target_branch} not found in upstream origin")
|
97
|
+
raise Dependabot::BranchNotFound, target_branch
|
98
|
+
end
|
99
|
+
|
88
100
|
raise Dependabot::RepoNotFound, source
|
89
101
|
end
|
90
102
|
|
@@ -168,6 +180,97 @@ module Dependabot
|
|
168
180
|
end
|
169
181
|
end
|
170
182
|
|
183
|
+
def cloned_commit
|
184
|
+
return if repo_contents_path.nil? || !File.directory?(File.join(repo_contents_path, ".git"))
|
185
|
+
|
186
|
+
SharedHelpers.with_git_configured(credentials: credentials) do
|
187
|
+
Dir.chdir(repo_contents_path) do
|
188
|
+
return SharedHelpers.run_shell_command("git rev-parse HEAD")&.strip
|
189
|
+
end
|
190
|
+
end
|
191
|
+
end
|
192
|
+
|
193
|
+
def default_branch_for_repo
|
194
|
+
@default_branch_for_repo ||= client_for_provider.
|
195
|
+
fetch_default_branch(repo)
|
196
|
+
rescue *CLIENT_NOT_FOUND_ERRORS
|
197
|
+
raise Dependabot::RepoNotFound, source
|
198
|
+
end
|
199
|
+
|
200
|
+
def update_linked_paths(repo, path, commit, github_response)
|
201
|
+
case github_response.type
|
202
|
+
when "submodule"
|
203
|
+
sub_source = Source.from_url(github_response.submodule_git_url)
|
204
|
+
return unless sub_source
|
205
|
+
|
206
|
+
@linked_paths[path] = {
|
207
|
+
repo: sub_source.repo,
|
208
|
+
provider: sub_source.provider,
|
209
|
+
commit: github_response.sha,
|
210
|
+
path: "/"
|
211
|
+
}
|
212
|
+
when "symlink"
|
213
|
+
updated_path = File.join(File.dirname(path), github_response.target)
|
214
|
+
@linked_paths[path] = {
|
215
|
+
repo: repo,
|
216
|
+
provider: "github",
|
217
|
+
commit: commit,
|
218
|
+
path: Pathname.new(updated_path).cleanpath.to_path
|
219
|
+
}
|
220
|
+
end
|
221
|
+
end
|
222
|
+
|
223
|
+
def recurse_submodules_when_cloning?
|
224
|
+
false
|
225
|
+
end
|
226
|
+
|
227
|
+
def client_for_provider
|
228
|
+
case source.provider
|
229
|
+
when "github" then github_client
|
230
|
+
when "gitlab" then gitlab_client
|
231
|
+
when "azure" then azure_client
|
232
|
+
when "bitbucket" then bitbucket_client
|
233
|
+
when "codecommit" then codecommit_client
|
234
|
+
else raise "Unsupported provider '#{source.provider}'."
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
def github_client
|
239
|
+
@github_client ||=
|
240
|
+
Dependabot::Clients::GithubWithRetries.for_source(
|
241
|
+
source: source,
|
242
|
+
credentials: credentials
|
243
|
+
)
|
244
|
+
end
|
245
|
+
|
246
|
+
def gitlab_client
|
247
|
+
@gitlab_client ||=
|
248
|
+
Dependabot::Clients::GitlabWithRetries.for_source(
|
249
|
+
source: source,
|
250
|
+
credentials: credentials
|
251
|
+
)
|
252
|
+
end
|
253
|
+
|
254
|
+
def azure_client
|
255
|
+
@azure_client ||=
|
256
|
+
Dependabot::Clients::Azure.
|
257
|
+
for_source(source: source, credentials: credentials)
|
258
|
+
end
|
259
|
+
|
260
|
+
def bitbucket_client
|
261
|
+
# TODO: When self-hosted Bitbucket is supported this should use
|
262
|
+
# `Bitbucket.for_source`
|
263
|
+
@bitbucket_client ||=
|
264
|
+
Dependabot::Clients::BitbucketWithRetries.
|
265
|
+
for_bitbucket_dot_org(credentials: credentials)
|
266
|
+
end
|
267
|
+
|
268
|
+
def codecommit_client
|
269
|
+
@codecommit_client ||=
|
270
|
+
Dependabot::Clients::CodeCommit.
|
271
|
+
for_source(source: source, credentials: credentials)
|
272
|
+
end
|
273
|
+
|
171
274
|
#################################################
|
172
275
|
# INTERNAL METHODS (not for use by sub-classes) #
|
173
276
|
#################################################
|
@@ -254,29 +357,6 @@ module Dependabot
|
|
254
357
|
end
|
255
358
|
end
|
256
359
|
|
257
|
-
def update_linked_paths(repo, path, commit, github_response)
|
258
|
-
case github_response.type
|
259
|
-
when "submodule"
|
260
|
-
sub_source = Source.from_url(github_response.submodule_git_url)
|
261
|
-
return unless sub_source
|
262
|
-
|
263
|
-
@linked_paths[path] = {
|
264
|
-
repo: sub_source.repo,
|
265
|
-
provider: sub_source.provider,
|
266
|
-
commit: github_response.sha,
|
267
|
-
path: "/"
|
268
|
-
}
|
269
|
-
when "symlink"
|
270
|
-
updated_path = File.join(File.dirname(path), github_response.target)
|
271
|
-
@linked_paths[path] = {
|
272
|
-
repo: repo,
|
273
|
-
provider: "github",
|
274
|
-
commit: commit,
|
275
|
-
path: Pathname.new(updated_path).cleanpath.to_path
|
276
|
-
}
|
277
|
-
end
|
278
|
-
end
|
279
|
-
|
280
360
|
def _build_github_file_struct(file)
|
281
361
|
OpenStruct.new(
|
282
362
|
name: file.name,
|
@@ -473,13 +553,6 @@ module Dependabot
|
|
473
553
|
end
|
474
554
|
# rubocop:enable Metrics/AbcSize
|
475
555
|
|
476
|
-
def default_branch_for_repo
|
477
|
-
@default_branch_for_repo ||= client_for_provider.
|
478
|
-
fetch_default_branch(repo)
|
479
|
-
rescue *CLIENT_NOT_FOUND_ERRORS
|
480
|
-
raise Dependabot::RepoNotFound, source
|
481
|
-
end
|
482
|
-
|
483
556
|
# Update the @linked_paths hash by exploiting a side-effect of
|
484
557
|
# recursively calling `repo_contents` for each directory up the tree
|
485
558
|
# until a submodule or symlink is found
|
@@ -504,6 +577,10 @@ module Dependabot
|
|
504
577
|
max_by(&:length)
|
505
578
|
end
|
506
579
|
|
580
|
+
# rubocop:disable Metrics/AbcSize
|
581
|
+
# rubocop:disable Metrics/MethodLength
|
582
|
+
# rubocop:disable Metrics/PerceivedComplexity
|
583
|
+
# rubocop:disable Metrics/BlockLength
|
507
584
|
def _clone_repo_contents(target_directory:)
|
508
585
|
SharedHelpers.with_git_configured(credentials: credentials) do
|
509
586
|
path = target_directory || File.join("tmp", source.repo)
|
@@ -512,62 +589,69 @@ module Dependabot
|
|
512
589
|
return path if Dir.exist?(File.join(path, ".git"))
|
513
590
|
|
514
591
|
FileUtils.mkdir_p(path)
|
515
|
-
br_opt = " --branch #{source.branch} --single-branch" if source.branch
|
516
|
-
SharedHelpers.run_shell_command(
|
517
|
-
<<~CMD
|
518
|
-
git clone --no-tags --no-recurse-submodules --depth 1#{br_opt} #{source.url} #{path}
|
519
|
-
CMD
|
520
|
-
)
|
521
|
-
path
|
522
|
-
end
|
523
|
-
end
|
524
592
|
|
525
|
-
|
526
|
-
|
527
|
-
|
528
|
-
|
529
|
-
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
end
|
534
|
-
end
|
593
|
+
clone_options = StringIO.new
|
594
|
+
clone_options << "--no-tags --depth 1"
|
595
|
+
clone_options << if recurse_submodules_when_cloning?
|
596
|
+
" --recurse-submodules --shallow-submodules"
|
597
|
+
else
|
598
|
+
" --no-recurse-submodules"
|
599
|
+
end
|
600
|
+
clone_options << " --branch #{source.branch} --single-branch" if source.branch
|
535
601
|
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
541
|
-
|
542
|
-
|
602
|
+
submodule_cloning_failed = false
|
603
|
+
begin
|
604
|
+
SharedHelpers.run_shell_command(
|
605
|
+
<<~CMD
|
606
|
+
git clone #{clone_options.string} #{source.url} #{path}
|
607
|
+
CMD
|
608
|
+
)
|
609
|
+
rescue SharedHelpers::HelperSubprocessFailed => e
|
610
|
+
raise unless GIT_SUBMODULE_ERROR_REGEX && e.message.downcase.include?("submodule")
|
543
611
|
|
544
|
-
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
credentials: credentials
|
549
|
-
)
|
550
|
-
end
|
612
|
+
submodule_cloning_failed = true
|
613
|
+
match = e.message.match(GIT_SUBMODULE_ERROR_REGEX)
|
614
|
+
url = match.named_captures["url"]
|
615
|
+
code = match.named_captures["code"]
|
551
616
|
|
552
|
-
|
553
|
-
|
554
|
-
|
555
|
-
|
556
|
-
end
|
617
|
+
# Submodules might be in the repo but unrelated to dependencies,
|
618
|
+
# so ignoring this error to try the update anyway since the base repo exists.
|
619
|
+
Dependabot.logger.error("Cloning of submodule failed: #{url} error: #{code || 'unknown'}")
|
620
|
+
end
|
557
621
|
|
558
|
-
|
559
|
-
|
560
|
-
|
561
|
-
|
562
|
-
|
563
|
-
|
564
|
-
|
622
|
+
if source.commit
|
623
|
+
# This code will only be called for testing. Production will never pass a commit
|
624
|
+
# since Dependabot always wants to use the latest commit on a branch.
|
625
|
+
Dir.chdir(path) do
|
626
|
+
fetch_options = StringIO.new
|
627
|
+
fetch_options << "--depth 1"
|
628
|
+
fetch_options << if recurse_submodules_when_cloning? && !submodule_cloning_failed
|
629
|
+
" --recurse-submodules=on-demand"
|
630
|
+
else
|
631
|
+
" --no-recurse-submodules"
|
632
|
+
end
|
633
|
+
# Need to fetch the commit due to the --depth 1 above.
|
634
|
+
SharedHelpers.run_shell_command("git fetch #{fetch_options.string} origin #{source.commit}")
|
635
|
+
|
636
|
+
reset_options = StringIO.new
|
637
|
+
reset_options << "--hard"
|
638
|
+
reset_options << if recurse_submodules_when_cloning? && !submodule_cloning_failed
|
639
|
+
" --recurse-submodules"
|
640
|
+
else
|
641
|
+
" --no-recurse-submodules"
|
642
|
+
end
|
643
|
+
# Set HEAD to this commit so later calls so git reset HEAD will work.
|
644
|
+
SharedHelpers.run_shell_command("git reset #{reset_options.string} #{source.commit}")
|
645
|
+
end
|
646
|
+
end
|
565
647
|
|
566
|
-
|
567
|
-
|
568
|
-
Dependabot::Clients::CodeCommit.
|
569
|
-
for_source(source: source, credentials: credentials)
|
648
|
+
path
|
649
|
+
end
|
570
650
|
end
|
651
|
+
# rubocop:enable Metrics/AbcSize
|
652
|
+
# rubocop:enable Metrics/MethodLength
|
653
|
+
# rubocop:enable Metrics/PerceivedComplexity
|
654
|
+
# rubocop:enable Metrics/BlockLength
|
571
655
|
end
|
572
656
|
end
|
573
657
|
end
|
@@ -14,34 +14,42 @@ module Dependabot
|
|
14
14
|
raise ArgumentError, "must be an array of Dependency objects"
|
15
15
|
end
|
16
16
|
|
17
|
-
@dependencies = dependencies
|
18
17
|
@case_sensitive = case_sensitive
|
18
|
+
@dependencies = Hash.new { |hsh, key| hsh[key] = DependencySlot.new }
|
19
|
+
dependencies.each { |dep| self << dep }
|
19
20
|
end
|
20
21
|
|
21
|
-
|
22
|
+
def dependencies
|
23
|
+
@dependencies.values.filter_map(&:combined)
|
24
|
+
end
|
22
25
|
|
23
26
|
def <<(dep)
|
24
27
|
raise ArgumentError, "must be a Dependency object" unless dep.is_a?(Dependency)
|
25
28
|
|
26
|
-
|
29
|
+
@dependencies[key_for_dependency(dep)] << dep
|
30
|
+
self
|
31
|
+
end
|
27
32
|
|
28
|
-
|
33
|
+
def +(other)
|
34
|
+
raise ArgumentError, "must be a DependencySet" unless other.is_a?(DependencySet)
|
29
35
|
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
dependencies << dep
|
36
|
+
other_names = other.dependencies.map(&:name)
|
37
|
+
other_names.each do |name|
|
38
|
+
all_versions = other.all_versions_for_name(name)
|
39
|
+
all_versions.each { |dep| self << dep }
|
35
40
|
end
|
36
41
|
|
37
42
|
self
|
38
43
|
end
|
39
44
|
|
40
|
-
def
|
41
|
-
|
45
|
+
def all_versions_for_name(name)
|
46
|
+
key = key_for_name(name)
|
47
|
+
@dependencies.key?(key) ? @dependencies[key].all_versions : []
|
48
|
+
end
|
42
49
|
|
43
|
-
|
44
|
-
|
50
|
+
def dependency_for_name(name)
|
51
|
+
key = key_for_name(name)
|
52
|
+
@dependencies.key?(key) ? @dependencies[key].combined : nil
|
45
53
|
end
|
46
54
|
|
47
55
|
private
|
@@ -50,41 +58,98 @@ module Dependabot
|
|
50
58
|
@case_sensitive
|
51
59
|
end
|
52
60
|
|
53
|
-
def
|
54
|
-
|
61
|
+
def key_for_name(name)
|
62
|
+
case_sensitive? ? name : name.downcase
|
63
|
+
end
|
55
64
|
|
56
|
-
|
65
|
+
def key_for_dependency(dep)
|
66
|
+
key_for_name(dep.name)
|
57
67
|
end
|
58
68
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
69
|
+
# There can only be one entry per dependency name in a `DependencySet`. Each entry
|
70
|
+
# is assigned a `DependencySlot`.
|
71
|
+
#
|
72
|
+
# In some ecosystems (like `npm_and_yarn`), however, multiple versions of a
|
73
|
+
# dependency may be encountered and added to the set. The `DependencySlot` retains
|
74
|
+
# all added versions and presents a single unified dependency for the entry
|
75
|
+
# that combines the attributes of these versions.
|
76
|
+
#
|
77
|
+
# The combined dependency is accessible via `DependencySet#dependencies` or
|
78
|
+
# `DependencySet#dependency_for_name`. The list of individual versions of the
|
79
|
+
# dependency is accessible via `DependencySet#all_versions_for_name`.
|
80
|
+
class DependencySlot
|
81
|
+
attr_reader :all_versions, :combined
|
82
|
+
|
83
|
+
def initialize
|
84
|
+
@all_versions = []
|
85
|
+
@combined = nil
|
86
|
+
end
|
87
|
+
|
88
|
+
def <<(dep)
|
89
|
+
return self if @all_versions.include?(dep)
|
90
|
+
|
91
|
+
@combined = if @combined
|
92
|
+
combined_dependency(@combined, dep)
|
93
|
+
else
|
94
|
+
Dependency.new(
|
95
|
+
name: dep.name,
|
96
|
+
version: dep.version,
|
97
|
+
requirements: dep.requirements,
|
98
|
+
package_manager: dep.package_manager,
|
99
|
+
subdependency_metadata: dep.subdependency_metadata
|
100
|
+
)
|
101
|
+
end
|
102
|
+
|
103
|
+
index_of_same_version =
|
104
|
+
@all_versions.find_index { |other| other.version == dep.version }
|
105
|
+
|
106
|
+
if index_of_same_version.nil?
|
107
|
+
@all_versions << dep
|
71
108
|
else
|
72
|
-
|
109
|
+
same_version = @all_versions[index_of_same_version]
|
110
|
+
@all_versions[index_of_same_version] = combined_dependency(same_version, dep)
|
73
111
|
end
|
74
112
|
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
113
|
+
self
|
114
|
+
end
|
115
|
+
|
116
|
+
private
|
117
|
+
|
118
|
+
# Produces a new dependency by merging the attributes of `old_dep` with those of
|
119
|
+
# `new_dep`. Requirements and subdependency metadata will be combined and deduped.
|
120
|
+
# The version of the combined dependency is determined by the logic below.
|
121
|
+
def combined_dependency(old_dep, new_dep)
|
122
|
+
version = if old_dep.top_level? # Prefer a direct dependency over a transitive one
|
123
|
+
old_dep.version || new_dep.version
|
124
|
+
elsif !version_class.correct?(new_dep.version)
|
125
|
+
old_dep.version
|
126
|
+
elsif !version_class.correct?(old_dep.version)
|
127
|
+
new_dep.version
|
128
|
+
elsif version_class.new(new_dep.version) > version_class.new(old_dep.version)
|
129
|
+
old_dep.version
|
130
|
+
else
|
131
|
+
new_dep.version
|
132
|
+
end
|
133
|
+
requirements = (old_dep.requirements + new_dep.requirements).uniq
|
134
|
+
subdependency_metadata = (
|
135
|
+
(old_dep.subdependency_metadata || []) +
|
136
|
+
(new_dep.subdependency_metadata || [])
|
137
|
+
).uniq
|
138
|
+
|
139
|
+
Dependency.new(
|
140
|
+
name: old_dep.name,
|
141
|
+
version: version,
|
142
|
+
requirements: requirements,
|
143
|
+
package_manager: old_dep.package_manager,
|
144
|
+
subdependency_metadata: subdependency_metadata
|
145
|
+
)
|
146
|
+
end
|
147
|
+
|
148
|
+
def version_class
|
149
|
+
@version_class ||= Utils.version_class_for_package_manager(@combined.package_manager)
|
150
|
+
end
|
87
151
|
end
|
152
|
+
private_constant :DependencySlot
|
88
153
|
end
|
89
154
|
end
|
90
155
|
end
|