dependabot-common 0.215.0 → 0.216.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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/lib/dependabot/clients/azure.rb +69 -0
  3. data/lib/dependabot/clients/bitbucket.rb +3 -1
  4. data/lib/dependabot/clients/github_with_retries.rb +6 -0
  5. data/lib/dependabot/config/file_fetcher.rb +1 -1
  6. data/lib/dependabot/config/ignore_condition.rb +20 -13
  7. data/lib/dependabot/dependency.rb +63 -2
  8. data/lib/dependabot/dependency_file.rb +1 -1
  9. data/lib/dependabot/errors.rb +1 -1
  10. data/lib/dependabot/file_fetchers/base.rb +15 -4
  11. data/lib/dependabot/file_parsers/base/dependency_set.rb +19 -12
  12. data/lib/dependabot/file_parsers/base.rb +0 -2
  13. data/lib/dependabot/git_metadata_fetcher.rb +30 -25
  14. data/lib/dependabot/group_rule.rb +11 -0
  15. data/lib/dependabot/metadata_finders/README.md +1 -1
  16. data/lib/dependabot/metadata_finders/base/changelog_finder.rb +53 -11
  17. data/lib/dependabot/metadata_finders/base/commits_finder.rb +40 -3
  18. data/lib/dependabot/metadata_finders/base/release_finder.rb +1 -1
  19. data/lib/dependabot/pull_request_creator/branch_namer/group_rule_strategy.rb +28 -0
  20. data/lib/dependabot/pull_request_creator/branch_namer/solo_strategy.rb +208 -0
  21. data/lib/dependabot/pull_request_creator/branch_namer.rb +28 -179
  22. data/lib/dependabot/pull_request_creator/labeler.rb +5 -1
  23. data/lib/dependabot/pull_request_creator/message_builder/link_and_mention_sanitizer.rb +3 -1
  24. data/lib/dependabot/pull_request_creator/message_builder/metadata_presenter.rb +1 -1
  25. data/lib/dependabot/pull_request_creator/message_builder.rb +22 -81
  26. data/lib/dependabot/pull_request_creator.rb +1 -0
  27. data/lib/dependabot/security_advisory.rb +1 -1
  28. data/lib/dependabot/shared_helpers.rb +16 -3
  29. data/lib/dependabot/simple_instrumentor.rb +19 -0
  30. data/lib/dependabot/source.rb +7 -7
  31. data/lib/dependabot/version.rb +18 -1
  32. data/lib/dependabot.rb +1 -0
  33. metadata +46 -62
  34. data/lib/dependabot/notifications.rb +0 -18
  35. data/lib/rubygems_version_patch.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e671c74d96743b47570682e512064ebb84774d5b63ce1090278f34d0f6ea857d
4
- data.tar.gz: 32956c64580c84d9592253981318c2e442511988e21887f5c3bb5757d95adb2f
3
+ metadata.gz: 645d08a2a5cfd122e1ec0bb4302d7c4e80bd0348e0c0e52d28dc210d196b963b
4
+ data.tar.gz: 25d81216a7b48d60b332d241e06f3a7dcc384b06ea3f5fac1dcc02949da226dc
5
5
  SHA512:
6
- metadata.gz: 35725326642881096654be6bf9e0278555078763c4003a676fcea4ae55115ecda3358866398f39ca5e0626853070e177b827f90d3f63b4701db1092a3bacc948
7
- data.tar.gz: efbfb8bf7609d0ddae2e9294fedbcfd592b016c41cede8010760c8bb81d32ed005fafac040b98f9222310a92bec2c4ce9ec0c0dacfb459309ac85ea3e001538c
6
+ metadata.gz: 8ef726592da7c2ff04784322801d12902ab42613e53b084687acdbfb479982e60a5303cb6e7f249e5525547a7b7681af48c3afe9df16cfda435b771e5e3f84ad
7
+ data.tar.gz: ef3a1010b2dd5736c9595754b1eb1cf1cca4cd7eecdfae551b97a2d84c71952ccad43bedafab0f9b6cea07892afbff41b766d9d8941ae10bcd7eedb434970aa3
@@ -192,6 +192,32 @@ module Dependabot
192
192
  "/pullrequests?api-version=5.0", content.to_json)
193
193
  end
194
194
 
195
+ def autocomplete_pull_request(pull_request_id, auto_complete_set_by, merge_commit_message,
196
+ delete_source_branch = true, squash_merge = true, merge_strategy = "squash",
197
+ trans_work_items = true, ignore_config_ids = [])
198
+
199
+ content = {
200
+ autoCompleteSetBy: {
201
+ id: auto_complete_set_by
202
+ },
203
+ completionOptions: {
204
+ mergeCommitMessage: merge_commit_message,
205
+ deleteSourceBranch: delete_source_branch,
206
+ squashMerge: squash_merge,
207
+ mergeStrategy: merge_strategy,
208
+ transitionWorkItems: trans_work_items,
209
+ autoCompleteIgnoreConfigIds: ignore_config_ids
210
+ }
211
+ }
212
+
213
+ response = patch(source.api_endpoint +
214
+ source.organization + "/" + source.project +
215
+ "/_apis/git/repositories/" + source.unscoped_repo +
216
+ "/pullrequests/" + pull_request_id.to_s + "?api-version=5.1", content.to_json)
217
+
218
+ JSON.parse(response.body)
219
+ end
220
+
195
221
  def pull_request(pull_request_id)
196
222
  response = get(source.api_endpoint +
197
223
  source.organization + "/" + source.project +
@@ -217,6 +243,18 @@ module Dependabot
217
243
  end
218
244
  # rubocop:enable Metrics/ParameterLists
219
245
 
246
+ def compare(previous_tag, new_tag, type)
247
+ response = get(source.api_endpoint +
248
+ source.organization + "/" + source.project +
249
+ "/_apis/git/repositories/" + source.unscoped_repo +
250
+ "/commits?searchCriteria.itemVersion.versionType=#{type}" \
251
+ "&searchCriteria.itemVersion.version=#{previous_tag}" \
252
+ "&searchCriteria.compareVersion.versionType=#{type}" \
253
+ "&searchCriteria.compareVersion.version=#{new_tag}")
254
+
255
+ JSON.parse(response.body).fetch("value")
256
+ end
257
+
220
258
  def get(url)
221
259
  response = nil
222
260
 
@@ -279,6 +317,37 @@ module Dependabot
279
317
  response
280
318
  end
281
319
 
320
+ def patch(url, json)
321
+ response = nil
322
+
323
+ retry_connection_failures do
324
+ response = Excon.patch(
325
+ url,
326
+ body: json,
327
+ user: credentials&.fetch("username", nil),
328
+ password: credentials&.fetch("password", nil),
329
+ idempotent: true,
330
+ **SharedHelpers.excon_defaults(
331
+ headers: auth_header.merge(
332
+ {
333
+ "Content-Type" => "application/json"
334
+ }
335
+ )
336
+ )
337
+ )
338
+
339
+ raise InternalServerError if response.status == 500
340
+ raise BadGateway if response.status == 502
341
+ raise ServiceNotAvailable if response.status == 503
342
+ end
343
+
344
+ raise Unauthorized if response.status == 401
345
+ raise Forbidden if response.status == 403
346
+ raise NotFound if response.status == 404
347
+
348
+ response
349
+ end
350
+
282
351
  private
283
352
 
284
353
  def retry_connection_failures
@@ -171,6 +171,8 @@ module Dependabot
171
171
  base_url = "https://api.bitbucket.org/2.0/user?fields=uuid"
172
172
  response = get(base_url)
173
173
  JSON.parse(response.body).fetch("uuid")
174
+ rescue Unauthorized
175
+ [nil]
174
176
  end
175
177
 
176
178
  def default_reviewers(repo)
@@ -205,7 +207,7 @@ module Dependabot
205
207
 
206
208
  def get(url)
207
209
  response = Excon.get(
208
- url,
210
+ URI::DEFAULT_PARSER.escape(url),
209
211
  user: credentials&.fetch("username", nil),
210
212
  password: credentials&.fetch("password", nil),
211
213
  # Setting to false to prevent Excon retries, use BitbucketWithRetries for retries.
@@ -89,6 +89,12 @@ module Dependabot
89
89
  access_tokens << nil if access_tokens.empty?
90
90
  access_tokens.uniq!
91
91
 
92
+ # Explicitly set the proxy if one is set in the environment
93
+ # as Faraday's find_proxy is very slow.
94
+ Octokit.configure do |c|
95
+ c.proxy = ENV["HTTPS_PROXY"] if ENV["HTTPS_PROXY"]
96
+ end
97
+
92
98
  Octokit.middleware = Faraday::RackBuilder.new do |builder|
93
99
  builder.use Faraday::Retry::Middleware, exceptions: RETRYABLE_ERRORS, max: max_retries || 3
94
100
 
@@ -40,7 +40,7 @@ module Dependabot
40
40
  end
41
41
 
42
42
  unless self.class.required_files_in?(fetched_files.map(&:name))
43
- raise Dependabot::DependencyFileNotFound, self.class.required_files_message
43
+ raise Dependabot::DependencyFileNotFound.new(nil, self.class.required_files_message)
44
44
  end
45
45
 
46
46
  fetched_files
@@ -32,16 +32,19 @@ module Dependabot
32
32
  end
33
33
 
34
34
  def versions_by_type(dependency)
35
- return [] unless dependency.version
35
+ version = correct_version_for(dependency)
36
+ return [] unless version
37
+
38
+ semver = version.to_semver
36
39
 
37
40
  transformed_update_types.flat_map do |t|
38
41
  case t
39
42
  when PATCH_VERSION_TYPE
40
- ignore_patch(dependency.version)
43
+ ignore_patch(semver)
41
44
  when MINOR_VERSION_TYPE
42
- ignore_minor(dependency.version)
45
+ ignore_minor(semver)
43
46
  when MAJOR_VERSION_TYPE
44
- ignore_major(dependency.version)
47
+ ignore_major(semver)
45
48
  else
46
49
  []
47
50
  end
@@ -49,8 +52,6 @@ module Dependabot
49
52
  end
50
53
 
51
54
  def ignore_patch(version)
52
- return [] unless rubygems_compatible?(version)
53
-
54
55
  parts = version.split(".")
55
56
  version_parts = parts.fill(0, parts.length...2)
56
57
  upper_parts = version_parts.first(1) + [version_parts[1].to_i + 1]
@@ -61,8 +62,6 @@ module Dependabot
61
62
  end
62
63
 
63
64
  def ignore_minor(version)
64
- return [] unless rubygems_compatible?(version)
65
-
66
65
  parts = version.split(".")
67
66
  version_parts = parts.fill(0, parts.length...2)
68
67
  lower_parts = version_parts.first(1) + [version_parts[1].to_i + 1] + ["a"]
@@ -74,8 +73,6 @@ module Dependabot
74
73
  end
75
74
 
76
75
  def ignore_major(version)
77
- return [] unless rubygems_compatible?(version)
78
-
79
76
  version_parts = version.split(".")
80
77
  lower_parts = [version_parts[0].to_i + 1] + ["a"]
81
78
  lower_bound = ">= #{lower_parts.join('.')}"
@@ -83,10 +80,20 @@ module Dependabot
83
80
  [lower_bound]
84
81
  end
85
82
 
86
- def rubygems_compatible?(version)
87
- return false if version.nil? || version.empty?
83
+ def correct_version_for(dependency)
84
+ version = dependency.version
85
+ return if version.nil? || version.empty?
86
+
87
+ version_class = version_class_for(dependency.package_manager)
88
+ return unless version_class.correct?(version)
89
+
90
+ version_class.new(version)
91
+ end
88
92
 
89
- Gem::Version.correct?(version)
93
+ def version_class_for(package_manager)
94
+ Utils.version_class_for_package_manager(package_manager)
95
+ rescue StandardError
96
+ Dependabot::Version
90
97
  end
91
98
  end
92
99
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "rubygems_version_patch"
3
+ require "dependabot/version"
4
4
 
5
5
  module Dependabot
6
6
  class Dependency
@@ -110,6 +110,67 @@ module Dependabot
110
110
  display_name_builder.call(name)
111
111
  end
112
112
 
113
+ def humanized_previous_version
114
+ # If we don't have a previous version, we *may* still be able to figure
115
+ # one out if a ref was provided and has been changed (in which case the
116
+ # previous ref was essentially the version).
117
+ if previous_version.nil?
118
+ return ref_changed? ? previous_ref : nil
119
+ end
120
+
121
+ if previous_version.match?(/^[0-9a-f]{40}/)
122
+ return previous_ref if ref_changed? && previous_ref
123
+
124
+ "`#{previous_version[0..6]}`"
125
+ elsif version == previous_version &&
126
+ package_manager == "docker"
127
+ digest = docker_digest_from_reqs(previous_requirements)
128
+ "`#{digest.split(':').last[0..6]}`"
129
+ else
130
+ previous_version
131
+ end
132
+ end
133
+
134
+ def humanized_version
135
+ return if removed?
136
+
137
+ if version.match?(/^[0-9a-f]{40}/)
138
+ return new_ref if ref_changed? && new_ref
139
+
140
+ "`#{version[0..6]}`"
141
+ elsif version == previous_version &&
142
+ package_manager == "docker"
143
+ digest = docker_digest_from_reqs(requirements)
144
+ "`#{digest.split(':').last[0..6]}`"
145
+ else
146
+ version
147
+ end
148
+ end
149
+
150
+ def docker_digest_from_reqs(requirements)
151
+ requirements.
152
+ filter_map { |r| r.dig(:source, "digest") || r.dig(:source, :digest) }.
153
+ first
154
+ end
155
+
156
+ def previous_ref
157
+ previous_refs = previous_requirements.filter_map do |r|
158
+ r.dig(:source, "ref") || r.dig(:source, :ref)
159
+ end.uniq
160
+ return previous_refs.first if previous_refs.count == 1
161
+ end
162
+
163
+ def new_ref
164
+ new_refs = requirements.filter_map do |r|
165
+ r.dig(:source, "ref") || r.dig(:source, :ref)
166
+ end.uniq
167
+ return new_refs.first if new_refs.count == 1
168
+ end
169
+
170
+ def ref_changed?
171
+ previous_ref != new_ref
172
+ end
173
+
113
174
  # Returns all detected versions of the dependency. Only ecosystems that
114
175
  # support this feature will return more than the current version.
115
176
  def all_versions
@@ -135,7 +196,7 @@ module Dependabot
135
196
  end
136
197
 
137
198
  def eql?(other)
138
- self.==(other)
199
+ self == other
139
200
  end
140
201
 
141
202
  private
@@ -87,7 +87,7 @@ module Dependabot
87
87
  end
88
88
 
89
89
  def eql?(other)
90
- self.==(other)
90
+ self == other
91
91
  end
92
92
 
93
93
  def support_file?
@@ -80,7 +80,7 @@ module Dependabot
80
80
 
81
81
  def initialize(file_path, msg = nil)
82
82
  @file_path = file_path
83
- super(msg)
83
+ super("#{file_path} not found" || msg)
84
84
  end
85
85
 
86
86
  def file_name
@@ -95,11 +95,17 @@ module Dependabot
95
95
  rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
96
96
  if e.message.include?("fatal: Remote branch #{target_branch} not found in upstream origin")
97
97
  raise Dependabot::BranchNotFound, target_branch
98
+ elsif e.message.include?("No space left on device")
99
+ raise Dependabot::OutOfDisk
98
100
  end
99
101
 
100
102
  raise Dependabot::RepoNotFound, source
101
103
  end
102
104
 
105
+ def package_manager_version
106
+ nil
107
+ end
108
+
103
109
  private
104
110
 
105
111
  def fetch_file_if_present(filename, fetch_submodules: false)
@@ -498,7 +504,7 @@ module Dependabot
498
504
  _fetch_file_content_from_github(path, repo, commit)
499
505
  when "gitlab"
500
506
  tmp = gitlab_client.get_file(repo, path, commit).content
501
- Base64.decode64(tmp).force_encoding("UTF-8").encode
507
+ decode_binary_string(tmp)
502
508
  when "azure"
503
509
  azure_client.fetch_file_contents(commit, path)
504
510
  when "bitbucket"
@@ -534,7 +540,7 @@ module Dependabot
534
540
  # see https://github.blog/changelog/2022-05-03-increased-file-size-limit-when-retrieving-file-contents-via-rest-api/
535
541
  github_client.contents(repo, path: path, ref: commit, accept: "application/vnd.github.v3.raw")
536
542
  else
537
- Base64.decode64(tmp.content).force_encoding("UTF-8").encode
543
+ decode_binary_string(tmp.content)
538
544
  end
539
545
  rescue Octokit::Forbidden => e
540
546
  raise unless e.message.include?("too_large")
@@ -549,7 +555,7 @@ module Dependabot
549
555
  tmp = github_client.blob(repo, file_details.sha)
550
556
  return tmp.content if tmp.encoding == "utf-8"
551
557
 
552
- Base64.decode64(tmp.content).force_encoding("UTF-8").encode
558
+ decode_binary_string(tmp.content)
553
559
  end
554
560
  # rubocop:enable Metrics/AbcSize
555
561
 
@@ -607,7 +613,7 @@ module Dependabot
607
613
  CMD
608
614
  )
609
615
  rescue SharedHelpers::HelperSubprocessFailed => e
610
- raise unless GIT_SUBMODULE_ERROR_REGEX && e.message.downcase.include?("submodule")
616
+ raise unless e.message.match(GIT_SUBMODULE_ERROR_REGEX) && e.message.downcase.include?("submodule")
611
617
 
612
618
  submodule_cloning_failed = true
613
619
  match = e.message.match(GIT_SUBMODULE_ERROR_REGEX)
@@ -652,6 +658,11 @@ module Dependabot
652
658
  # rubocop:enable Metrics/MethodLength
653
659
  # rubocop:enable Metrics/PerceivedComplexity
654
660
  # rubocop:enable Metrics/BlockLength
661
+
662
+ def decode_binary_string(str)
663
+ bom = (+"\xEF\xBB\xBF").force_encoding(Encoding::BINARY)
664
+ Base64.decode64(str).delete_prefix(bom).force_encoding("UTF-8").encode
665
+ end
655
666
  end
656
667
  end
657
668
  end
@@ -117,19 +117,10 @@ module Dependabot
117
117
 
118
118
  # Produces a new dependency by merging the attributes of `old_dep` with those of
119
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.
120
+ # The version of the combined dependency is determined by the
121
+ # `#combined_version` method below.
121
122
  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
123
+ version = combined_version(old_dep, new_dep)
133
124
  requirements = (old_dep.requirements + new_dep.requirements).uniq
134
125
  subdependency_metadata = (
135
126
  (old_dep.subdependency_metadata || []) +
@@ -145,6 +136,22 @@ module Dependabot
145
136
  )
146
137
  end
147
138
 
139
+ def combined_version(old_dep, new_dep)
140
+ if old_dep.version.nil? ^ new_dep.version.nil?
141
+ [old_dep, new_dep].find(&:version).version
142
+ elsif old_dep.top_level? ^ new_dep.top_level? # Prefer a direct dependency over a transitive one
143
+ [old_dep, new_dep].find(&:top_level?).version
144
+ elsif !version_class.correct?(new_dep.version)
145
+ old_dep.version
146
+ elsif !version_class.correct?(old_dep.version)
147
+ new_dep.version
148
+ elsif version_class.new(new_dep.version) > version_class.new(old_dep.version)
149
+ old_dep.version
150
+ else
151
+ new_dep.version
152
+ end
153
+ end
154
+
148
155
  def version_class
149
156
  @version_class ||= Utils.version_class_for_package_manager(@combined.package_manager)
150
157
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dependabot/notifications"
4
-
5
3
  module Dependabot
6
4
  module FileParsers
7
5
  class Base
@@ -106,7 +106,7 @@ module Dependabot
106
106
 
107
107
  def fetch_raw_upload_pack_with_git_for(uri)
108
108
  service_pack_uri = uri
109
- service_pack_uri += ".git" unless service_pack_uri.end_with?(".git")
109
+ service_pack_uri += ".git" unless service_pack_uri.end_with?(".git") || skip_git_suffix(uri)
110
110
 
111
111
  env = { "PATH" => ENV.fetch("PATH", nil) }
112
112
  command = "git ls-remote #{service_pack_uri}"
@@ -158,40 +158,45 @@ module Dependabot
158
158
  def service_pack_uri(uri)
159
159
  service_pack_uri = uri_with_auth(uri)
160
160
  service_pack_uri = service_pack_uri.gsub(%r{/$}, "")
161
- service_pack_uri += ".git" unless service_pack_uri.end_with?(".git")
161
+ service_pack_uri += ".git" unless service_pack_uri.end_with?(".git") || skip_git_suffix(uri)
162
162
  service_pack_uri + "/info/refs?service=git-upload-pack"
163
163
  end
164
164
 
165
+ def skip_git_suffix(uri)
166
+ # TODO: Unlike the other providers (GitHub, GitLab, BitBucket), as of 2023-01-18 Azure DevOps does not support the
167
+ # ".git" suffix. It will return a 404.
168
+ # So skip adding ".git" if looks like an ADO URI.
169
+ # There's no access to the source object here, so have to check the URI instead.
170
+ # Even if we had the current source object, the URI may be for a dependency hosted elsewhere.
171
+ # Unfortunately as a consequence, urls pointing to Azure DevOps Server will not work.
172
+ # Only alternative is to remove the addition of ".git" suffix since the other providers
173
+ # (GitHub, GitLab, BitBucket) work with or without the suffix.
174
+ # That change has other ramifications, so it'd be better if Azure started supporting ".git"
175
+ # like all the other providers.
176
+ uri = "https://#{uri.split('git@').last.sub(%r{:/?}, '/')}" if uri.start_with?("git@")
177
+ uri = URI(uri)
178
+ hostname = uri.hostname.to_s
179
+ hostname == "dev.azure.com" || hostname.end_with?(".visualstudio.com")
180
+ end
181
+
182
+ # Add in username and password if present in credentials.
183
+ # Credentials are never present for production Dependabot.
165
184
  def uri_with_auth(uri)
166
- bare_uri =
167
- if uri.include?("git@") then uri.split("git@").last.sub(%r{:/?}, "/")
168
- else
169
- uri.sub(%r{.*?://}, "")
170
- end
185
+ # Handle SCP-style git URIs
186
+ uri = "https://#{uri.split('git@').last.sub(%r{:/?}, '/')}" if uri.start_with?("git@")
187
+ uri = URI(uri)
171
188
  cred = credentials.select { |c| c["type"] == "git_source" }.
172
- find { |c| bare_uri.start_with?(c["host"]) }
189
+ find { |c| uri.host == c["host"] }
173
190
 
174
- scheme = scheme_for_uri(uri)
191
+ uri.scheme = "https" if uri.scheme != "http"
175
192
 
176
- if bare_uri.match?(%r{[^/]+:[^/]+@})
177
- # URI already has authentication details
178
- "#{scheme}://#{bare_uri}"
179
- elsif cred&.fetch("username", nil) && cred&.fetch("password", nil)
193
+ if !uri.password && cred&.fetch("username", nil) && cred&.fetch("password", nil)
180
194
  # URI doesn't have authentication details, but we have credentials
181
- auth_string = "#{cred.fetch('username')}:#{cred.fetch('password')}"
182
- "#{scheme}://#{auth_string}@#{bare_uri}"
183
- else
184
- # No credentials, so just return the http(s) URI
185
- "#{scheme}://#{bare_uri}"
195
+ uri.user = URI.encode_www_form_component(cred["username"])
196
+ uri.password = URI.encode_www_form_component(cred["password"])
186
197
  end
187
- end
188
198
 
189
- def scheme_for_uri(uri)
190
- if uri.match?(%r{^http://})
191
- "http"
192
- else
193
- "https"
194
- end
199
+ uri.to_s
195
200
  end
196
201
 
197
202
  def sha_for_update_pack_line(line)
@@ -0,0 +1,11 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ class GroupRule
5
+ attr_reader :name
6
+
7
+ def initialize(name)
8
+ @name = name
9
+ end
10
+ end
11
+ end
@@ -45,7 +45,7 @@ and implement the following methods:
45
45
 
46
46
  | Method | Description |
47
47
  |------------------------|-------------------------|
48
- | `#look_up_source` | Private method that returns a `Dependabot::Source` object. Generally the source details are extracted from a source code URL provided by the language's dependency registry, but sometimes it's already know from parsing the dependency file. |
48
+ | `#look_up_source` | Private method that returns a `Dependabot::Source` object. Generally the source details are extracted from a source code URL provided by the language's dependency registry, but sometimes it's already available from parsing the dependency file. |
49
49
 
50
50
  To ensure the above are implemented, you should include
51
51
  `it_behaves_like "a dependency metadata finder"` in your specs for the new
@@ -82,11 +82,12 @@ module Dependabot
82
82
  return unless suggested_changelog_url
83
83
 
84
84
  # TODO: Support other providers
85
- source = Source.from_url(suggested_changelog_url)
86
- return unless source&.provider == "github"
85
+ suggested_source = Source.from_url(suggested_changelog_url)
86
+ return unless suggested_source&.provider == "github"
87
87
 
88
- opts = { path: source.directory, ref: source.branch }.compact
89
- tmp_files = github_client.contents(source.repo, opts)
88
+ opts = { path: suggested_source.directory, ref: suggested_source.branch }.compact
89
+ suggested_source_client = github_client_for_source(suggested_source)
90
+ tmp_files = suggested_source_client.contents(suggested_source.repo, opts)
90
91
 
91
92
  filename = suggested_changelog_url.split("/").last.split("#").first
92
93
  @changelog_from_suggested_url =
@@ -161,12 +162,13 @@ module Dependabot
161
162
  @file_text ||= {}
162
163
 
163
164
  unless @file_text.key?(file.download_url)
164
- provider = Source.from_url(file.html_url).provider
165
+ file_source = Source.from_url(file.html_url)
165
166
  @file_text[file.download_url] =
166
- case provider
167
- when "github" then fetch_github_file(file)
167
+ case file_source.provider
168
+ when "github" then fetch_github_file(file_source, file)
168
169
  when "gitlab" then fetch_gitlab_file(file)
169
170
  when "bitbucket" then fetch_bitbucket_file(file)
171
+ when "azure" then fetch_azure_file(file)
170
172
  when "codecommit" then nil # TODO: git file from codecommit
171
173
  else raise "Unsupported provider '#{provider}'"
172
174
  end
@@ -177,9 +179,9 @@ module Dependabot
177
179
  @file_text[file.download_url].sub(/\n*\z/, "")
178
180
  end
179
181
 
180
- def fetch_github_file(file)
182
+ def fetch_github_file(file_source, file)
181
183
  # Hitting the download URL directly causes encoding problems
182
- raw_content = github_client.get(file.url).content
184
+ raw_content = github_client_for_source(file_source).get(file.url).content
183
185
  Base64.decode64(raw_content).force_encoding("UTF-8").encode
184
186
  end
185
187
 
@@ -196,6 +198,11 @@ module Dependabot
196
198
  force_encoding("UTF-8").encode
197
199
  end
198
200
 
201
+ def fetch_azure_file(file)
202
+ azure_client.get(file.download_url).body.
203
+ force_encoding("UTF-8").encode
204
+ end
205
+
199
206
  def upgrade_guide
200
207
  return unless source
201
208
 
@@ -220,7 +227,7 @@ module Dependabot
220
227
  when "github" then fetch_github_file_list(ref)
221
228
  when "bitbucket" then fetch_bitbucket_file_list
222
229
  when "gitlab" then fetch_gitlab_file_list
223
- when "azure" then [] # TODO: Fetch files from Azure
230
+ when "azure" then fetch_azure_file_list
224
231
  when "codecommit" then [] # TODO: Fetch Files from Codecommit
225
232
  else raise "Unexpected repo provider '#{source.provider}'"
226
233
  end
@@ -292,6 +299,29 @@ module Dependabot
292
299
  []
293
300
  end
294
301
 
302
+ def fetch_azure_file_list
303
+ azure_client.fetch_repo_contents.map do |entry|
304
+ type = case entry.fetch("gitObjectType")
305
+ when "blob" then "file"
306
+ when "tree" then "dir"
307
+ else entry.fetch("gitObjectType")
308
+ end
309
+
310
+ OpenStruct.new(
311
+ name: File.basename(entry.fetch("relativePath")),
312
+ type: type,
313
+ size: entry.fetch("size"),
314
+ path: entry.fetch("relativePath"),
315
+ html_url: "#{source.url}?path=/#{entry.fetch('relativePath')}",
316
+ download_url: entry.fetch("url")
317
+ )
318
+ end
319
+ rescue Dependabot::Clients::Azure::NotFound,
320
+ Dependabot::Clients::Azure::Unauthorized,
321
+ Dependabot::Clients::Azure::Forbidden
322
+ []
323
+ end
324
+
295
325
  def new_version
296
326
  return @new_version if defined?(@new_version)
297
327
 
@@ -346,7 +376,19 @@ module Dependabot
346
376
 
347
377
  def github_client
348
378
  @github_client ||= Dependabot::Clients::GithubWithRetries.
349
- for_github_dot_com(credentials: credentials)
379
+ for_source(source: source, credentials: credentials)
380
+ end
381
+
382
+ def azure_client
383
+ @azure_client ||= Dependabot::Clients::Azure.
384
+ for_source(source: source, credentials: credentials)
385
+ end
386
+
387
+ def github_client_for_source(client_source)
388
+ return github_client if client_source == source
389
+
390
+ Dependabot::Clients::GithubWithRetries.
391
+ for_source(source: client_source, credentials: credentials)
350
392
  end
351
393
 
352
394
  def bitbucket_client