dependabot-common 0.212.0 → 0.213.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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