dependabot-common 0.212.0 → 0.213.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5242196cd22b0092cbdaaf08f6d5ce3f4249d1eeccedada949822b0b14657e9c
4
- data.tar.gz: f47437aa525423bdbb507dfa5b0978863e41f3010682ed1c6f321d2ac67efea9
3
+ metadata.gz: 23fa5c7ea872ca0849f22018af9b0811ad9044f03a4e7d59aa023b3dd80bd4e6
4
+ data.tar.gz: cea778ebef75ccec5afcd3e5932af78d9711c51c4c864ea02d65930fce8ca4dc
5
5
  SHA512:
6
- metadata.gz: cad3f4c8848f45b07d7769bdf4a1b351e3cca4f921bb449cee629ddffc3c579df3b1dfc9343ecfdabd1192d1f1e207e82f8af0c8ee0f142af5856e5bee769d0e
7
- data.tar.gz: c564e966eba317b8b5e61bf4d82df255248ee4932711d85854e38e843ce0f26d0dc22a649cb7e2f475e01f2b0fd61dce160a46a5ecdc0cbe61d560fd5b004587
6
+ metadata.gz: b14ad55cbabd2a49bd35c7f8012f95972515eead80a71363353a8978286e9d756fd97da5442cb3013acdebfb1a77c7f8f7c450bac553a2d6f52b12687fcf2d43
7
+ data.tar.gz: 86cbba3afb724d1ee0b6c1fd0bb357a33c288263b3032e0ef029e099b5d7f7d8d5104526a8890d6233fdb8850b56ecd05cb0746bd8e6fe8fd84338db8caa28dd
@@ -144,7 +144,14 @@ module Dependabot
144
144
  end
145
145
  # rubocop:enable Metrics/ParameterLists
146
146
 
147
+ def current_user
148
+ base_url = "https://api.bitbucket.org/2.0/user?fields=uuid"
149
+ response = get(base_url)
150
+ JSON.parse(response.body).fetch("uuid")
151
+ end
152
+
147
153
  def default_reviewers(repo)
154
+ current_uuid = current_user
148
155
  path = "#{repo}/default-reviewers?pagelen=100&fields=values.uuid,next"
149
156
  reviewers_url = base_url + path
150
157
 
@@ -153,7 +160,7 @@ module Dependabot
153
160
  reviewer_data = []
154
161
 
155
162
  default_reviewers.each do |reviewer|
156
- reviewer_data.append({ uuid: reviewer.fetch("uuid") })
163
+ reviewer_data.append({ uuid: reviewer.fetch("uuid") }) unless current_uuid == reviewer.fetch("uuid")
157
164
  end
158
165
 
159
166
  reviewer_data
@@ -71,7 +71,7 @@ module Dependabot
71
71
  commit_message = cfg&.dig(:"commit-message") || {}
72
72
  Dependabot::Config::UpdateConfig::CommitMessageOptions.new(
73
73
  prefix: commit_message[:prefix],
74
- prefix_development: commit_message[:"prefix-development"],
74
+ prefix_development: commit_message[:"prefix-development"] || commit_message[:prefix],
75
75
  include: commit_message[:include]
76
76
  )
77
77
  end
@@ -37,11 +37,11 @@ module Dependabot
37
37
 
38
38
  attr_reader :name, :version, :requirements, :package_manager,
39
39
  :previous_version, :previous_requirements,
40
- :subdependency_metadata
40
+ :subdependency_metadata, :metadata
41
41
 
42
42
  def initialize(name:, requirements:, package_manager:, version: nil,
43
43
  previous_version: nil, previous_requirements: nil,
44
- subdependency_metadata: [], removed: false)
44
+ subdependency_metadata: [], removed: false, metadata: {})
45
45
  @name = name
46
46
  @version = version
47
47
  @requirements = requirements.map { |req| symbolize_keys(req) }
@@ -54,6 +54,7 @@ module Dependabot
54
54
  map { |h| symbolize_keys(h) }
55
55
  end
56
56
  @removed = removed
57
+ @metadata = symbolize_keys(metadata || {})
57
58
 
58
59
  check_values
59
60
  end
@@ -105,6 +106,22 @@ module Dependabot
105
106
  display_name_builder.call(name)
106
107
  end
107
108
 
109
+ # Returns all detected versions of the dependency. Only ecosystems that
110
+ # support this feature will return more than the current version.
111
+ def all_versions
112
+ all_versions = metadata[:all_versions]
113
+ return [version].compact unless all_versions
114
+
115
+ all_versions.filter_map(&:version)
116
+ end
117
+
118
+ # This dependency is being indirectly updated by an update to another
119
+ # dependency. We don't need to try and update it ourselves but want to
120
+ # surface it to the user in the PR.
121
+ def informational_only?
122
+ metadata[:information_only]
123
+ end
124
+
108
125
  def ==(other)
109
126
  other.instance_of?(self.class) && to_h == other.to_h
110
127
  end
@@ -4,9 +4,9 @@ require "dependabot/utils"
4
4
 
5
5
  module Dependabot
6
6
  class DependabotError < StandardError
7
- BASIC_AUTH_REGEX = %r{://(?<auth>[^:]*:[^@%\s]+(@|%40))}.freeze
7
+ BASIC_AUTH_REGEX = %r{://(?<auth>[^:]*:[^@%\s]+(@|%40))}
8
8
  # Remove any path segment from fury.io sources
9
- FURY_IO_PATH_REGEX = %r{fury\.io/(?<path>.+)}.freeze
9
+ FURY_IO_PATH_REGEX = %r{fury\.io/(?<path>.+)}
10
10
 
11
11
  def initialize(message = nil)
12
12
  super(sanitize_message(message))
@@ -18,7 +18,7 @@ module Dependabot
18
18
  return message unless message.is_a?(String)
19
19
 
20
20
  path_regex =
21
- Regexp.escape(Utils::BUMP_TMP_DIR_PATH) + "\/" +
21
+ Regexp.escape(Utils::BUMP_TMP_DIR_PATH) + "\\/" +
22
22
  Regexp.escape(Utils::BUMP_TMP_FILE_PREFIX) + "[a-zA-Z0-9-]*"
23
23
 
24
24
  message = message.gsub(/#{path_regex}/, "dependabot_tmp_dir").strip
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module Experiments
5
+ @experiments = {}
6
+
7
+ def self.reset!
8
+ @experiments = {}
9
+ end
10
+
11
+ def self.register(name, value)
12
+ @experiments[name.to_sym] = value
13
+ end
14
+
15
+ def self.enabled?(name)
16
+ !!@experiments[name.to_sym]
17
+ end
18
+ end
19
+ end
@@ -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"
@@ -69,6 +70,7 @@ module Dependabot
69
70
  end
70
71
 
71
72
  def commit
73
+ return cloned_commit if cloned_commit
72
74
  return source.commit if source.commit
73
75
 
74
76
  branch = target_branch || default_branch_for_repo
@@ -84,7 +86,11 @@ module Dependabot
84
86
  def clone_repo_contents
85
87
  @clone_repo_contents ||=
86
88
  _clone_repo_contents(target_directory: repo_contents_path)
87
- rescue Dependabot::SharedHelpers::HelperSubprocessFailed
89
+ rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
90
+ if e.message.include?("fatal: Remote branch #{target_branch} not found in upstream origin")
91
+ raise Dependabot::BranchNotFound, target_branch
92
+ end
93
+
88
94
  raise Dependabot::RepoNotFound, source
89
95
  end
90
96
 
@@ -168,6 +174,97 @@ module Dependabot
168
174
  end
169
175
  end
170
176
 
177
+ def cloned_commit
178
+ return if repo_contents_path.nil? || !File.directory?(File.join(repo_contents_path, ".git"))
179
+
180
+ SharedHelpers.with_git_configured(credentials: credentials) do
181
+ Dir.chdir(repo_contents_path) do
182
+ return SharedHelpers.run_shell_command("git rev-parse HEAD")&.strip
183
+ end
184
+ end
185
+ end
186
+
187
+ def default_branch_for_repo
188
+ @default_branch_for_repo ||= client_for_provider.
189
+ fetch_default_branch(repo)
190
+ rescue *CLIENT_NOT_FOUND_ERRORS
191
+ raise Dependabot::RepoNotFound, source
192
+ end
193
+
194
+ def update_linked_paths(repo, path, commit, github_response)
195
+ case github_response.type
196
+ when "submodule"
197
+ sub_source = Source.from_url(github_response.submodule_git_url)
198
+ return unless sub_source
199
+
200
+ @linked_paths[path] = {
201
+ repo: sub_source.repo,
202
+ provider: sub_source.provider,
203
+ commit: github_response.sha,
204
+ path: "/"
205
+ }
206
+ when "symlink"
207
+ updated_path = File.join(File.dirname(path), github_response.target)
208
+ @linked_paths[path] = {
209
+ repo: repo,
210
+ provider: "github",
211
+ commit: commit,
212
+ path: Pathname.new(updated_path).cleanpath.to_path
213
+ }
214
+ end
215
+ end
216
+
217
+ def recurse_submodules_when_cloning?
218
+ false
219
+ end
220
+
221
+ def client_for_provider
222
+ case source.provider
223
+ when "github" then github_client
224
+ when "gitlab" then gitlab_client
225
+ when "azure" then azure_client
226
+ when "bitbucket" then bitbucket_client
227
+ when "codecommit" then codecommit_client
228
+ else raise "Unsupported provider '#{source.provider}'."
229
+ end
230
+ end
231
+
232
+ def github_client
233
+ @github_client ||=
234
+ Dependabot::Clients::GithubWithRetries.for_source(
235
+ source: source,
236
+ credentials: credentials
237
+ )
238
+ end
239
+
240
+ def gitlab_client
241
+ @gitlab_client ||=
242
+ Dependabot::Clients::GitlabWithRetries.for_source(
243
+ source: source,
244
+ credentials: credentials
245
+ )
246
+ end
247
+
248
+ def azure_client
249
+ @azure_client ||=
250
+ Dependabot::Clients::Azure.
251
+ for_source(source: source, credentials: credentials)
252
+ end
253
+
254
+ def bitbucket_client
255
+ # TODO: When self-hosted Bitbucket is supported this should use
256
+ # `Bitbucket.for_source`
257
+ @bitbucket_client ||=
258
+ Dependabot::Clients::BitbucketWithRetries.
259
+ for_bitbucket_dot_org(credentials: credentials)
260
+ end
261
+
262
+ def codecommit_client
263
+ @codecommit_client ||=
264
+ Dependabot::Clients::CodeCommit.
265
+ for_source(source: source, credentials: credentials)
266
+ end
267
+
171
268
  #################################################
172
269
  # INTERNAL METHODS (not for use by sub-classes) #
173
270
  #################################################
@@ -254,29 +351,6 @@ module Dependabot
254
351
  end
255
352
  end
256
353
 
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
354
  def _build_github_file_struct(file)
281
355
  OpenStruct.new(
282
356
  name: file.name,
@@ -473,13 +547,6 @@ module Dependabot
473
547
  end
474
548
  # rubocop:enable Metrics/AbcSize
475
549
 
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
550
  # Update the @linked_paths hash by exploiting a side-effect of
484
551
  # recursively calling `repo_contents` for each directory up the tree
485
552
  # until a submodule or symlink is found
@@ -504,6 +571,10 @@ module Dependabot
504
571
  max_by(&:length)
505
572
  end
506
573
 
574
+ # rubocop:disable Metrics/AbcSize
575
+ # rubocop:disable Metrics/MethodLength
576
+ # rubocop:disable Metrics/PerceivedComplexity
577
+ # rubocop:disable Metrics/BlockLength
507
578
  def _clone_repo_contents(target_directory:)
508
579
  SharedHelpers.with_git_configured(credentials: credentials) do
509
580
  path = target_directory || File.join("tmp", source.repo)
@@ -512,62 +583,54 @@ module Dependabot
512
583
  return path if Dir.exist?(File.join(path, ".git"))
513
584
 
514
585
  FileUtils.mkdir_p(path)
515
- br_opt = " --branch #{source.branch} --single-branch" if source.branch
586
+
587
+ clone_options = StringIO.new
588
+ clone_options << "--no-tags --depth 1"
589
+ clone_options << if recurse_submodules_when_cloning?
590
+ " --recurse-submodules --shallow-submodules"
591
+ else
592
+ " --no-recurse-submodules"
593
+ end
594
+ clone_options << " --branch #{source.branch} --single-branch" if source.branch
516
595
  SharedHelpers.run_shell_command(
517
596
  <<~CMD
518
- git clone --no-tags --no-recurse-submodules --depth 1#{br_opt} #{source.url} #{path}
597
+ git clone #{clone_options.string} #{source.url} #{path}
519
598
  CMD
520
599
  )
521
- path
522
- end
523
- end
524
600
 
525
- def client_for_provider
526
- case source.provider
527
- when "github" then github_client
528
- when "gitlab" then gitlab_client
529
- when "azure" then azure_client
530
- when "bitbucket" then bitbucket_client
531
- when "codecommit" then codecommit_client
532
- else raise "Unsupported provider '#{source.provider}'."
533
- end
534
- end
535
-
536
- def github_client
537
- @github_client ||=
538
- Dependabot::Clients::GithubWithRetries.for_source(
539
- source: source,
540
- credentials: credentials
541
- )
542
- end
543
-
544
- def gitlab_client
545
- @gitlab_client ||=
546
- Dependabot::Clients::GitlabWithRetries.for_source(
547
- source: source,
548
- credentials: credentials
549
- )
550
- end
551
-
552
- def azure_client
553
- @azure_client ||=
554
- Dependabot::Clients::Azure.
555
- for_source(source: source, credentials: credentials)
556
- end
557
-
558
- def bitbucket_client
559
- # TODO: When self-hosted Bitbucket is supported this should use
560
- # `Bitbucket.for_source`
561
- @bitbucket_client ||=
562
- Dependabot::Clients::BitbucketWithRetries.
563
- for_bitbucket_dot_org(credentials: credentials)
564
- end
601
+ if source.commit
602
+ # This code will only be called for testing. Production will never pass a commit
603
+ # since Dependabot always wants to use the latest commit on a branch.
604
+ Dir.chdir(path) do
605
+ fetch_options = StringIO.new
606
+ fetch_options << "--depth 1"
607
+ fetch_options << if recurse_submodules_when_cloning?
608
+ " --recurse-submodules=on-demand"
609
+ else
610
+ " --no-recurse-submodules"
611
+ end
612
+ # Need to fetch the commit due to the --depth 1 above.
613
+ SharedHelpers.run_shell_command("git fetch #{fetch_options.string} origin #{source.commit}")
614
+
615
+ reset_options = StringIO.new
616
+ reset_options << "--hard"
617
+ reset_options << if recurse_submodules_when_cloning?
618
+ " --recurse-submodules"
619
+ else
620
+ " --no-recurse-submodules"
621
+ end
622
+ # Set HEAD to this commit so later calls so git reset HEAD will work.
623
+ SharedHelpers.run_shell_command("git reset #{reset_options.string} #{source.commit}")
624
+ end
625
+ end
565
626
 
566
- def codecommit_client
567
- @codecommit_client ||=
568
- Dependabot::Clients::CodeCommit.
569
- for_source(source: source, credentials: credentials)
627
+ path
628
+ end
570
629
  end
630
+ # rubocop:enable Metrics/AbcSize
631
+ # rubocop:enable Metrics/MethodLength
632
+ # rubocop:enable Metrics/PerceivedComplexity
633
+ # rubocop:enable Metrics/BlockLength
571
634
  end
572
635
  end
573
636
  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
- attr_reader :dependencies
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
- existing_dependency = dependency_for_name(dep.name)
29
+ @dependencies[key_for_dependency(dep)] << dep
30
+ self
31
+ end
27
32
 
28
- return self if existing_dependency&.to_h == dep.to_h
33
+ def +(other)
34
+ raise ArgumentError, "must be a DependencySet" unless other.is_a?(DependencySet)
29
35
 
30
- if existing_dependency
31
- dependencies[dependencies.index(existing_dependency)] =
32
- combined_dependency(existing_dependency, dep)
33
- else
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 +(other)
41
- raise ArgumentError, "must be a DependencySet" unless other.is_a?(DependencySet)
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
- other.dependencies.each { |dep| self << dep }
44
- self
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 dependency_for_name(name)
54
- return dependencies.find { |d| d.name == name } if case_sensitive?
61
+ def key_for_name(name)
62
+ case_sensitive? ? name : name.downcase
63
+ end
55
64
 
56
- dependencies.find { |d| d.name&.downcase == name&.downcase }
65
+ def key_for_dependency(dep)
66
+ key_for_name(dep.name)
57
67
  end
58
68
 
59
- def combined_dependency(old_dep, new_dep)
60
- package_manager = old_dep.package_manager
61
- v_cls = Utils.version_class_for_package_manager(package_manager)
62
-
63
- # If we already have a requirement use the existing version
64
- # (if present). Otherwise, use whatever the lowest version is
65
- new_version =
66
- if old_dep.requirements.any? then old_dep.version || new_dep.version
67
- elsif !v_cls.correct?(new_dep.version) then old_dep.version
68
- elsif !v_cls.correct?(old_dep.version) then new_dep.version
69
- elsif v_cls.new(new_dep.version) > v_cls.new(old_dep.version)
70
- old_dep.version
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
- new_dep.version
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
- subdependency_metadata = (
76
- (old_dep.subdependency_metadata || []) +
77
- (new_dep.subdependency_metadata || [])
78
- ).uniq
79
-
80
- Dependency.new(
81
- name: old_dep.name,
82
- version: new_version,
83
- requirements: (old_dep.requirements + new_dep.requirements).uniq,
84
- package_manager: package_manager,
85
- subdependency_metadata: subdependency_metadata
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
@@ -19,7 +19,7 @@ module Dependabot
19
19
  |
20
20
  [0-9]+\.[0-9]+(?:\.[a-z0-9\-]+)*
21
21
  )$
22
- /ix.freeze
22
+ /ix
23
23
 
24
24
  def initialize(dependency:, credentials:,
25
25
  ignored_versions: [], raise_on_ignored: false,
@@ -49,8 +49,14 @@ module Dependabot
49
49
  return true if branch
50
50
  return true if dependency.version&.start_with?(ref)
51
51
 
52
- # Check the specified `ref` isn't actually a branch
53
- !local_upload_pack.match?(%r{ refs/heads/#{ref}$})
52
+ # If the specified `ref` is actually a tag, we're pinned
53
+ return true if local_upload_pack.match?(%r{ refs/tags/#{ref}$})
54
+
55
+ # If the specified `ref` is actually a branch, we're NOT pinned
56
+ return false if local_upload_pack.match?(%r{ refs/heads/#{ref}$})
57
+
58
+ # Otherwise, assume we're pinned
59
+ true
54
60
  end
55
61
 
56
62
  def pinned_ref_looks_like_version?
@@ -61,6 +67,10 @@ module Dependabot
61
67
 
62
68
  def pinned_ref_looks_like_commit_sha?
63
69
  ref = dependency_source_details.fetch(:ref)
70
+ ref_looks_like_commit_sha?(ref)
71
+ end
72
+
73
+ def ref_looks_like_commit_sha?(ref)
64
74
  return false unless ref&.match?(/^[0-9a-f]{6,40}$/)
65
75
 
66
76
  return false unless pinned?
@@ -365,17 +375,19 @@ module Dependabot
365
375
  def listing_tags
366
376
  return [] unless listing_source_url
367
377
 
368
- tags = listing_repo_git_metadata_fetcher.tags
378
+ @listing_tags ||= begin
379
+ tags = listing_repo_git_metadata_fetcher.tags
369
380
 
370
- if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
371
- tags = tags.map do |tag|
372
- tag.dup.tap { |t| t.name = "tags/#{tag.name}" }
381
+ if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
382
+ tags = tags.map do |tag|
383
+ tag.dup.tap { |t| t.name = "tags/#{tag.name}" }
384
+ end
373
385
  end
374
- end
375
386
 
376
- tags
377
- rescue GitDependenciesNotReachable
378
- []
387
+ tags
388
+ rescue GitDependenciesNotReachable
389
+ []
390
+ end
379
391
  end
380
392
 
381
393
  def listing_upload_pack
@@ -6,7 +6,7 @@ require "dependabot/errors"
6
6
 
7
7
  module Dependabot
8
8
  class GitMetadataFetcher
9
- KNOWN_HOSTS = /github\.com|bitbucket\.org|gitlab.com/i.freeze
9
+ KNOWN_HOSTS = /github\.com|bitbucket\.org|gitlab.com/i
10
10
 
11
11
  def initialize(url:, credentials:)
12
12
  @url = url