dependabot-common 0.95.1 → 0.95.2

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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot.rb +4 -0
  3. data/lib/dependabot/clients/bitbucket.rb +105 -0
  4. data/lib/dependabot/clients/github_with_retries.rb +121 -0
  5. data/lib/dependabot/clients/gitlab.rb +72 -0
  6. data/lib/dependabot/dependency.rb +115 -0
  7. data/lib/dependabot/dependency_file.rb +60 -0
  8. data/lib/dependabot/errors.rb +179 -0
  9. data/lib/dependabot/file_fetchers.rb +18 -0
  10. data/lib/dependabot/file_fetchers/README.md +65 -0
  11. data/lib/dependabot/file_fetchers/base.rb +368 -0
  12. data/lib/dependabot/file_parsers.rb +18 -0
  13. data/lib/dependabot/file_parsers/README.md +45 -0
  14. data/lib/dependabot/file_parsers/base.rb +31 -0
  15. data/lib/dependabot/file_parsers/base/dependency_set.rb +77 -0
  16. data/lib/dependabot/file_updaters.rb +18 -0
  17. data/lib/dependabot/file_updaters/README.md +58 -0
  18. data/lib/dependabot/file_updaters/base.rb +52 -0
  19. data/lib/dependabot/git_commit_checker.rb +412 -0
  20. data/lib/dependabot/metadata_finders.rb +18 -0
  21. data/lib/dependabot/metadata_finders/README.md +53 -0
  22. data/lib/dependabot/metadata_finders/base.rb +117 -0
  23. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +321 -0
  24. data/lib/dependabot/metadata_finders/base/changelog_pruner.rb +177 -0
  25. data/lib/dependabot/metadata_finders/base/commits_finder.rb +221 -0
  26. data/lib/dependabot/metadata_finders/base/release_finder.rb +255 -0
  27. data/lib/dependabot/pull_request_creator.rb +155 -0
  28. data/lib/dependabot/pull_request_creator/branch_namer.rb +170 -0
  29. data/lib/dependabot/pull_request_creator/commit_signer.rb +63 -0
  30. data/lib/dependabot/pull_request_creator/github.rb +277 -0
  31. data/lib/dependabot/pull_request_creator/gitlab.rb +162 -0
  32. data/lib/dependabot/pull_request_creator/labeler.rb +373 -0
  33. data/lib/dependabot/pull_request_creator/message_builder.rb +906 -0
  34. data/lib/dependabot/pull_request_updater.rb +43 -0
  35. data/lib/dependabot/pull_request_updater/github.rb +165 -0
  36. data/lib/dependabot/shared_helpers.rb +224 -0
  37. data/lib/dependabot/source.rb +120 -0
  38. data/lib/dependabot/update_checkers.rb +18 -0
  39. data/lib/dependabot/update_checkers/README.md +67 -0
  40. data/lib/dependabot/update_checkers/base.rb +220 -0
  41. data/lib/dependabot/utils.rb +33 -0
  42. data/lib/dependabot/version.rb +5 -0
  43. data/lib/rubygems_version_patch.rb +14 -0
  44. metadata +44 -2
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module FileParsers
5
+ @file_parsers = {}
6
+
7
+ def self.for_package_manager(package_manager)
8
+ file_parser = @file_parsers[package_manager]
9
+ return file_parser if file_parser
10
+
11
+ raise "Unsupported package_manager #{package_manager}"
12
+ end
13
+
14
+ def self.register(package_manager, file_parser)
15
+ @file_parsers[package_manager] = file_parser
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,45 @@
1
+ # File parsers
2
+
3
+ File parsers take a set of dependency files and extract a list of dependencies
4
+ for the project.
5
+
6
+ There is a `Dependabot::FileParsers` class for each language Dependabot
7
+ supports.
8
+
9
+ ## Public API
10
+
11
+ Each `Dependabot::FileParsers` class implements the following methods:
12
+
13
+ | Method | Description |
14
+ |---------------------|-----------------------------------------------------------------------------------------------|
15
+ | `#parse` | Returns an array of `Dependabot::Dependency` instances, representing the dependencies for the project. Each `Dependabot::Dependency` has a `name`, `version` and a `requirements` array |
16
+
17
+ An integration might look as follows:
18
+
19
+ ```ruby
20
+ require 'dependabot/file_parsers'
21
+
22
+ files = fetcher.files
23
+
24
+ parser_class = Dependabot::FileParsers::Ruby::Bundler
25
+ source = Dependabot::Source.new(provider: 'github', repo: "gocardless/business")
26
+ parser = parser_class.new(dependency_files: files, source: source)
27
+
28
+ dependencies = parser.parse
29
+
30
+ puts "Found the following dependencies: #{dependencies.map(&:name)}"
31
+ ```
32
+
33
+ ## Writing a file parser for a new language
34
+
35
+ All new file parsers should inherit from `Dependabot::FileParsers::Base` and
36
+ implement the following methods:
37
+
38
+ | Method | Description |
39
+ |-------------------------|-----------------------------------------------------------------------------------------------|
40
+ | `#parse` | See Public API section. |
41
+ | `#check_required_files` | Raise a runtime error unless an appropriate set of files is provided. Private. |
42
+
43
+ To ensure the above are implemented, you should include
44
+ `it_behaves_like "a dependency file parser"` in your specs for the new file
45
+ parser.
@@ -0,0 +1,31 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module FileParsers
5
+ class Base
6
+ attr_reader :dependency_files, :credentials, :source
7
+
8
+ def initialize(dependency_files:, source:, credentials: [])
9
+ @dependency_files = dependency_files
10
+ @credentials = credentials
11
+ @source = source
12
+
13
+ check_required_files
14
+ end
15
+
16
+ def parse
17
+ raise NotImplementedError
18
+ end
19
+
20
+ private
21
+
22
+ def check_required_files
23
+ raise NotImplementedError
24
+ end
25
+
26
+ def get_original_file(filename)
27
+ dependency_files.find { |f| f.name == filename }
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dependabot/dependency"
4
+ require "dependabot/file_parsers/base"
5
+ require "dependabot/utils"
6
+
7
+ module Dependabot
8
+ module FileParsers
9
+ class Base
10
+ class DependencySet
11
+ def initialize(dependencies = [])
12
+ unless dependencies.is_a?(Array) &&
13
+ dependencies.all? { |dep| dep.is_a?(Dependency) }
14
+ raise ArgumentError, "must be an array of Dependency objects"
15
+ end
16
+
17
+ @dependencies = dependencies
18
+ end
19
+
20
+ attr_reader :dependencies
21
+
22
+ def <<(dep)
23
+ unless dep.is_a?(Dependency)
24
+ raise ArgumentError, "must be a Dependency object"
25
+ end
26
+
27
+ existing_dependency = dependencies.find { |d| d.name == dep.name }
28
+
29
+ return self if existing_dependency&.to_h == dep.to_h
30
+
31
+ if existing_dependency
32
+ dependencies[dependencies.index(existing_dependency)] =
33
+ combined_dependency(existing_dependency, dep)
34
+ else
35
+ dependencies << dep
36
+ end
37
+
38
+ self
39
+ end
40
+
41
+ def +(other)
42
+ unless other.is_a?(DependencySet)
43
+ raise ArgumentError, "must be a DependencySet"
44
+ end
45
+
46
+ other.dependencies.each { |dep| self << dep }
47
+ self
48
+ end
49
+
50
+ private
51
+
52
+ def combined_dependency(old_dep, new_dep)
53
+ package_manager = old_dep.package_manager
54
+ v_cls = Utils.version_class_for_package_manager(package_manager)
55
+
56
+ # If we already have a requirement use the existing version
57
+ # (if present). Otherwise, use whatever the lowest version is
58
+ new_version =
59
+ if old_dep.requirements.any? then old_dep.version || new_dep.version
60
+ elsif !v_cls.correct?(new_dep.version) then old_dep.version
61
+ elsif !v_cls.correct?(old_dep.version) then new_dep.version
62
+ elsif v_cls.new(new_dep.version) > v_cls.new(old_dep.version)
63
+ old_dep.version
64
+ else new_dep.version
65
+ end
66
+
67
+ Dependency.new(
68
+ name: old_dep.name,
69
+ version: new_version,
70
+ requirements: (old_dep.requirements + new_dep.requirements).uniq,
71
+ package_manager: package_manager
72
+ )
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,18 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module FileUpdaters
5
+ @file_updaters = {}
6
+
7
+ def self.for_package_manager(package_manager)
8
+ file_updater = @file_updaters[package_manager]
9
+ return file_updater if file_updater
10
+
11
+ raise "Unsupported package_manager #{package_manager}"
12
+ end
13
+
14
+ def self.register(package_manager, file_updater)
15
+ @file_updaters[package_manager] = file_updater
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,58 @@
1
+ # File updaters
2
+
3
+ File updaters update a dependency file to use the latest version of a given
4
+ dependency. They rely on information provided to them by update checkers.
5
+
6
+ There is a `Dependabot::FileUpdaters` class for each language Dependabot
7
+ supports.
8
+
9
+ ## Public API
10
+
11
+ Each `Dependabot::FileUpdaters` class implements the following methods:
12
+
13
+ | Method | Description |
14
+ |------------------------------|-----------------------------------------------------------------------------------------------|
15
+ | `.updated_files_regex` | An array of regular expressions matching the names of the files this class updates. Intended to be used by integrators when checking whether a commit may cause merge-conflicts with a dependency update pull request. |
16
+ | `#updated_dependency_files` | Returns an array of updated `Dependabot::DependencyFile` instances, with their content updated to include the updated dependency. |
17
+
18
+ An integration might look as follows:
19
+
20
+ ```ruby
21
+ require 'dependabot/file_updaters'
22
+
23
+ unless update_checker.can_update?(requirements_to_update: :own)
24
+ raise "Dependency doesn't need update!"
25
+ end
26
+ dependencies = update_checker.updated_dependencies(requirements_to_update: :own)
27
+
28
+ file_updater_class = Dependabot::FileUpdaters::Ruby::Bundler
29
+ file_updater = file_updater_class.new(
30
+ dependencies: dependencies,
31
+ dependency_files: files,
32
+ credentials: [{
33
+ "type" => "git_source",
34
+ "host" => "github.com",
35
+ "username" => "x-access-token",
36
+ "password" => "token"
37
+ }]
38
+ )
39
+
40
+ file_updater.updated_dependency_files.each do |file|
41
+ puts "Updated #{file.name} with new content:\n\n#{file.content}"
42
+ end
43
+ ```
44
+
45
+ ## Writing a file updater for a new language
46
+
47
+ All new file updaters should inherit from `Dependabot::FileUpdaters::Base` and
48
+ implement the following methods:
49
+
50
+ | Method | Description |
51
+ |-----------------------------|-------------------------|
52
+ | `.updated_files_regex` | See Public API section. |
53
+ | `#updated_dependency_files` | See Public API section. |
54
+
55
+ To ensure the above are implemented, you should include
56
+ `it_behaves_like "a dependency file updater"` in your specs for the new file
57
+ updater.
58
+
@@ -0,0 +1,52 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module FileUpdaters
5
+ class Base
6
+ attr_reader :dependencies, :dependency_files, :credentials
7
+
8
+ def self.updated_files_regex
9
+ raise NotImplementedError
10
+ end
11
+
12
+ def initialize(dependencies:, dependency_files:, credentials:)
13
+ @dependencies = dependencies
14
+ @dependency_files = dependency_files
15
+ @credentials = credentials
16
+
17
+ check_required_files
18
+ end
19
+
20
+ def updated_dependency_files
21
+ raise NotImplementedError
22
+ end
23
+
24
+ private
25
+
26
+ def check_required_files
27
+ raise NotImplementedError
28
+ end
29
+
30
+ def get_original_file(filename)
31
+ dependency_files.find { |f| f.name == filename }
32
+ end
33
+
34
+ def file_changed?(file)
35
+ dependencies.any? { |dep| requirement_changed?(file, dep) }
36
+ end
37
+
38
+ def requirement_changed?(file, dependency)
39
+ changed_requirements =
40
+ dependency.requirements - dependency.previous_requirements
41
+
42
+ changed_requirements.any? { |f| f[:file] == file.name }
43
+ end
44
+
45
+ def updated_file(file:, content:)
46
+ updated_file = file.dup
47
+ updated_file.content = content
48
+ updated_file
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,412 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "excon"
4
+ require "gitlab"
5
+ require "dependabot/clients/github_with_retries"
6
+ require "dependabot/metadata_finders"
7
+ require "dependabot/errors"
8
+ require "dependabot/utils"
9
+ require "dependabot/source"
10
+
11
+ # rubocop:disable Metrics/ClassLength
12
+ module Dependabot
13
+ class GitCommitChecker
14
+ VERSION_REGEX = /(?<version>[0-9]+\.[0-9]+(?:\.[a-zA-Z0-9\-]+)*)$/.freeze
15
+ KNOWN_HOSTS = /github\.com|bitbucket\.org|gitlab.com/.freeze
16
+
17
+ def initialize(dependency:, credentials:, ignored_versions: [],
18
+ requirement_class: nil, version_class: nil)
19
+ @dependency = dependency
20
+ @credentials = credentials
21
+ @ignored_versions = ignored_versions
22
+ @requirement_class = requirement_class
23
+ @version_class = version_class
24
+ end
25
+
26
+ def git_dependency?
27
+ return false if dependency_source_details.nil?
28
+
29
+ dependency_source_details.fetch(:type) == "git"
30
+ end
31
+
32
+ def pinned?
33
+ raise "Not a git dependency!" unless git_dependency?
34
+
35
+ ref = dependency_source_details.fetch(:ref)
36
+ branch = dependency_source_details.fetch(:branch)
37
+
38
+ return false if ref.nil?
39
+ return false if branch == ref
40
+ return true if branch
41
+ return true if dependency.version&.start_with?(ref)
42
+
43
+ # Check the specified `ref` isn't actually a branch
44
+ !local_upload_pack.match?("refs/heads/#{ref}")
45
+ end
46
+
47
+ def pinned_ref_looks_like_version?
48
+ return false unless pinned?
49
+
50
+ dependency_source_details.fetch(:ref).match?(VERSION_REGEX)
51
+ end
52
+
53
+ def branch_or_ref_in_release?(version)
54
+ pinned_ref_in_release?(version) || branch_behind_release?(version)
55
+ end
56
+
57
+ def head_commit_for_current_branch
58
+ return dependency.version if pinned?
59
+
60
+ branch_ref = ref_or_branch ? "refs/heads/#{ref_or_branch}" : "HEAD"
61
+
62
+ # Remove the opening clause of the upload pack as this isn't always
63
+ # followed by a line break. When it isn't (e.g., with Bitbucket) it causes
64
+ # problems for our `sha_for_update_pack_line` logic
65
+ line = local_upload_pack.
66
+ gsub(/.*git-upload-pack/, "").
67
+ lines.find { |l| l.include?(" #{branch_ref}") }
68
+
69
+ return sha_for_update_pack_line(line) if line
70
+
71
+ raise Dependabot::GitDependencyReferenceNotFound, dependency.name
72
+ end
73
+
74
+ def local_tag_for_latest_version
75
+ tag =
76
+ local_tags.
77
+ select { |t| t.name.match?(VERSION_REGEX) }.
78
+ reject { |t| tag_included_in_ignore_reqs?(t) }.
79
+ reject { |t| tag_is_prerelease?(t) && !wants_prerelease? }.
80
+ max_by do |t|
81
+ version = t.name.match(VERSION_REGEX).named_captures.fetch("version")
82
+ version_class.new(version)
83
+ end
84
+
85
+ return unless tag
86
+
87
+ {
88
+ tag: tag.name,
89
+ commit_sha: tag.commit_sha,
90
+ tag_sha: tag.tag_sha
91
+ }
92
+ end
93
+
94
+ private
95
+
96
+ attr_reader :dependency, :credentials, :ignored_versions
97
+
98
+ def pinned_ref_in_release?(version)
99
+ raise "Not a git dependency!" unless git_dependency?
100
+
101
+ return false unless pinned?
102
+ return false if listing_source_url.nil?
103
+
104
+ tag = listing_tag_for_version(version.to_s)
105
+ return false unless tag
106
+
107
+ commit_included_in_tag?(
108
+ commit: dependency_source_details.fetch(:ref),
109
+ tag: tag,
110
+ allow_identical: true
111
+ )
112
+ end
113
+
114
+ def branch_behind_release?(version)
115
+ raise "Not a git dependency!" unless git_dependency?
116
+
117
+ return false if ref_or_branch.nil?
118
+ return false if listing_source_url.nil?
119
+
120
+ tag = listing_tag_for_version(version.to_s)
121
+ return false unless tag
122
+
123
+ # Check if behind, excluding the case where it's identical, because
124
+ # we normally wouldn't switch you from tracking master to a release.
125
+ commit_included_in_tag?(
126
+ commit: ref_or_branch,
127
+ tag: tag,
128
+ allow_identical: false
129
+ )
130
+ end
131
+
132
+ def local_upload_pack
133
+ @local_upload_pack ||=
134
+ fetch_upload_pack_for(dependency_source_details.fetch(:url))
135
+ end
136
+
137
+ def local_tags
138
+ return [] unless local_upload_pack
139
+
140
+ tags_for_upload_pack(local_upload_pack)
141
+ end
142
+
143
+ def tags_for_upload_pack(upload_pack)
144
+ peeled_lines = []
145
+ unpeeled_lines = []
146
+
147
+ upload_pack.lines.each do |line|
148
+ next unless line.split(" ").last.start_with?("refs/tags")
149
+
150
+ if line.strip.end_with?("^{}") then peeled_lines << line
151
+ else unpeeled_lines << line
152
+ end
153
+ end
154
+
155
+ unpeeled_lines.map do |line|
156
+ tag_name = line.split(" refs/tags/").last.strip
157
+ tag_sha = sha_for_update_pack_line(line)
158
+ peeled_line = peeled_lines.find do |pl|
159
+ pl.split(" refs/tags/").last.strip == "#{tag_name}^{}"
160
+ end
161
+
162
+ commit_sha =
163
+ peeled_line ? sha_for_update_pack_line(peeled_line) : tag_sha
164
+
165
+ if dependency_source_details&.fetch(:ref, nil)&.start_with?("tags/")
166
+ tag_name = "tags/#{tag_name}"
167
+ end
168
+
169
+ OpenStruct.new(name: tag_name, tag_sha: tag_sha, commit_sha: commit_sha)
170
+ end
171
+ end
172
+
173
+ # rubocop:disable Metrics/CyclomaticComplexity
174
+ # rubocop:disable Metrics/PerceivedComplexity
175
+ def fetch_upload_pack_for(uri)
176
+ response = Excon.get(
177
+ service_pack_uri(uri),
178
+ idempotent: true,
179
+ **SharedHelpers.excon_defaults
180
+ )
181
+
182
+ return response.body if response.status == 200
183
+ if response.status >= 500 && uri.match?(KNOWN_HOSTS)
184
+ raise "Server error at #{uri}: #{response.body}"
185
+ end
186
+
187
+ raise Dependabot::GitDependenciesNotReachable, [uri]
188
+ rescue Excon::Error::Socket, Excon::Error::Timeout
189
+ retry_count ||= 0
190
+ retry_count += 1
191
+
192
+ sleep(rand(0.9)) && retry if retry_count < 2 && uri.match?(KNOWN_HOSTS)
193
+ raise if uri.match?(KNOWN_HOSTS)
194
+
195
+ raise Dependabot::GitDependenciesNotReachable, [uri]
196
+ end
197
+ # rubocop:enable Metrics/CyclomaticComplexity
198
+ # rubocop:enable Metrics/PerceivedComplexity
199
+
200
+ def service_pack_uri(uri)
201
+ service_pack_uri = uri_with_auth(uri)
202
+ service_pack_uri = service_pack_uri.gsub(%r{/$}, "")
203
+ service_pack_uri += ".git" unless service_pack_uri.end_with?(".git")
204
+ service_pack_uri + "/info/refs?service=git-upload-pack"
205
+ end
206
+
207
+ def uri_with_auth(uri)
208
+ bare_uri =
209
+ if uri.include?("git@") then uri.split("git@").last.sub(":", "/")
210
+ else uri.sub(%r{.*?://}, "")
211
+ end
212
+ cred = credentials.select { |c| c["type"] == "git_source" }.
213
+ find { |c| bare_uri.start_with?(c["host"]) }
214
+
215
+ if bare_uri.match?(%r{[^/]+:[^/]+@})
216
+ # URI already has authentication details
217
+ "https://#{bare_uri}"
218
+ elsif cred
219
+ # URI doesn't have authentication details, but we have credentials
220
+ auth_string = "#{cred.fetch('username')}:#{cred.fetch('password')}"
221
+ "https://#{auth_string}@#{bare_uri}"
222
+ else
223
+ # No credentials, so just return the https URI
224
+ "https://#{bare_uri}"
225
+ end
226
+ end
227
+
228
+ def commit_included_in_tag?(tag:, commit:, allow_identical: false)
229
+ status =
230
+ case Source.from_url(listing_source_url)&.provider
231
+ when "github" then github_commit_comparison_status(tag, commit)
232
+ when "gitlab" then gitlab_commit_comparison_status(tag, commit)
233
+ when "bitbucket" then bitbucket_commit_comparison_status(tag, commit)
234
+ else raise "Unknown source"
235
+ end
236
+
237
+ return true if status == "behind"
238
+
239
+ allow_identical && status == "identical"
240
+ rescue Octokit::NotFound, Gitlab::Error::NotFound,
241
+ Octokit::InternalServerError
242
+ false
243
+ end
244
+
245
+ def github_commit_comparison_status(ref1, ref2)
246
+ client = Clients::GithubWithRetries.
247
+ for_github_dot_com(credentials: credentials)
248
+
249
+ client.compare(listing_source_repo, ref1, ref2).status
250
+ end
251
+
252
+ def gitlab_commit_comparison_status(ref1, ref2)
253
+ access_token = credentials.
254
+ select { |cred| cred["type"] == "git_source" }.
255
+ find { |cred| cred["host"] == "gitlab.com" }&.
256
+ fetch("token")
257
+
258
+ client = Gitlab.client(endpoint: "https://gitlab.com/api/v4",
259
+ private_token: access_token.to_s)
260
+
261
+ comparison = client.compare(listing_source_repo, ref1, ref2)
262
+
263
+ if comparison.commits.none? then "behind"
264
+ elsif comparison.compare_same_ref then "identical"
265
+ else "ahead"
266
+ end
267
+ end
268
+
269
+ def bitbucket_commit_comparison_status(ref1, ref2)
270
+ url = "https://api.bitbucket.org/2.0/repositories/"\
271
+ "#{listing_source_repo}/commits/?"\
272
+ "include=#{ref2}&exclude=#{ref1}"
273
+
274
+ response = Excon.get(url,
275
+ headers: bitbucket_auth_header,
276
+ idempotent: true,
277
+ **SharedHelpers.excon_defaults)
278
+
279
+ # Conservatively assume that ref2 is ahead in the equality case, of
280
+ # if we get an unexpected format (e.g., due to a 404)
281
+ if JSON.parse(response.body).fetch("values", ["x"]).none? then "behind"
282
+ else "ahead"
283
+ end
284
+ end
285
+
286
+ def bitbucket_auth_header
287
+ token = credentials.
288
+ select { |cred| cred["type"] == "git_source" }.
289
+ find { |cred| cred["host"] == "bitbucket.org" }&.
290
+ fetch("token")
291
+
292
+ if token.nil? then {}
293
+ elsif token.include?(":")
294
+ encoded_token = Base64.encode64(token).delete("\n")
295
+ { "Authorization" => "Basic #{encoded_token}" }
296
+ elsif Base64.decode64(token).ascii_only? &&
297
+ Base64.decode64(token).include?(":")
298
+ { "Authorization" => "Basic #{token.delete("\n")}" }
299
+ else
300
+ { "Authorization" => "Bearer #{token}" }
301
+ end
302
+ end
303
+
304
+ def dependency_source_details
305
+ sources =
306
+ dependency.requirements.map { |r| r.fetch(:source) }.uniq.compact
307
+
308
+ return sources.first if sources.count <= 1
309
+
310
+ # If there are multiple source types, or multiple source URLs, then it's
311
+ # unclear how we should proceed
312
+ if sources.map { |s| [s.fetch(:type), s.fetch(:url, nil)] }.uniq.count > 1
313
+ raise "Multiple sources! #{sources.join(', ')}"
314
+ end
315
+
316
+ # Otherwise it's reasonable to take the first source and use that. This
317
+ # will happen if we have multiple git sources with difference references
318
+ # specified. In that case it's fine to update them all.
319
+ sources.first
320
+ end
321
+
322
+ def ref_or_branch
323
+ dependency_source_details.fetch(:ref) ||
324
+ dependency_source_details.fetch(:branch)
325
+ end
326
+
327
+ def listing_source_url
328
+ @listing_source_url ||=
329
+ begin
330
+ # Remove the git source, so the metadata finder looks on the
331
+ # registry
332
+ candidate_dep = Dependency.new(
333
+ name: dependency.name,
334
+ version: dependency.version,
335
+ requirements: [],
336
+ package_manager: dependency.package_manager
337
+ )
338
+
339
+ MetadataFinders.
340
+ for_package_manager(dependency.package_manager).
341
+ new(dependency: candidate_dep, credentials: credentials).
342
+ source_url
343
+ end
344
+ end
345
+
346
+ def listing_source_repo
347
+ return unless listing_source_url
348
+
349
+ Source.from_url(listing_source_url)&.repo
350
+ end
351
+
352
+ def listing_tag_for_version(version)
353
+ listing_tags.
354
+ find { |t| t.name =~ /(?:[^0-9\.]|\A)#{Regexp.escape(version)}\z/ }&.
355
+ name
356
+ end
357
+
358
+ def listing_tags
359
+ return [] unless listing_upload_pack
360
+
361
+ tags_for_upload_pack(listing_upload_pack)
362
+ rescue GitDependenciesNotReachable
363
+ []
364
+ end
365
+
366
+ def listing_upload_pack
367
+ return unless listing_source_url
368
+
369
+ @listing_upload_pack ||= fetch_upload_pack_for(listing_source_url)
370
+ end
371
+
372
+ def ignore_reqs
373
+ ignored_versions.map { |req| requirement_class.new(req.split(",")) }
374
+ end
375
+
376
+ def wants_prerelease?
377
+ return false unless dependency_source_details&.fetch(:ref, nil)
378
+ return false unless pinned_ref_looks_like_version?
379
+
380
+ version = dependency_source_details.fetch(:ref).match(VERSION_REGEX).
381
+ named_captures.fetch("version")
382
+ version_class.new(version).prerelease?
383
+ end
384
+
385
+ def tag_included_in_ignore_reqs?(tag)
386
+ version = tag.name.match(VERSION_REGEX).named_captures.fetch("version")
387
+ ignore_reqs.any? { |r| r.satisfied_by?(version_class.new(version)) }
388
+ end
389
+
390
+ def tag_is_prerelease?(tag)
391
+ version = tag.name.match(VERSION_REGEX).named_captures.fetch("version")
392
+ version_class.new(version).prerelease?
393
+ end
394
+
395
+ def version_class
396
+ return @version_class if @version_class
397
+
398
+ Utils.version_class_for_package_manager(dependency.package_manager)
399
+ end
400
+
401
+ def requirement_class
402
+ return @requirement_class if @requirement_class
403
+
404
+ Utils.requirement_class_for_package_manager(dependency.package_manager)
405
+ end
406
+
407
+ def sha_for_update_pack_line(line)
408
+ line.split(" ").first.chars.last(40).join
409
+ end
410
+ end
411
+ end
412
+ # rubocop:enable Metrics/ClassLength