dependabot-bundler 0.122.1 → 0.123.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,9 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "dependabot/monkey_patches/bundler/definition_ruby_version_patch"
4
- require "dependabot/monkey_patches/bundler/definition_bundler_version_patch"
5
- require "dependabot/monkey_patches/bundler/git_source_patch"
6
-
7
3
  require "excon"
8
4
 
9
5
  require "dependabot/bundler/update_checker"
@@ -21,8 +17,6 @@ module Dependabot
21
17
  require_relative "shared_bundler_helpers"
22
18
  include SharedBundlerHelpers
23
19
 
24
- GEM_NOT_FOUND_ERROR_REGEX = /locked to (?<name>[^\s]+) \(/.freeze
25
-
26
20
  def initialize(dependency:, unprepared_dependency_files:,
27
21
  repo_contents_path: nil, credentials:, ignored_versions:,
28
22
  raise_on_ignored: false,
@@ -77,42 +71,47 @@ module Dependabot
77
71
  return latest_version_details unless gemfile
78
72
 
79
73
  SharedHelpers.with_git_configured(credentials: credentials) do
80
- in_a_temporary_bundler_context do
81
- dep = dependency_from_definition
82
-
83
- # If the dependency wasn't found in the definition, but *is*
84
- # included in a gemspec, it's because the Gemfile didn't import
85
- # the gemspec. This is unusual, but the correct behaviour if/when
86
- # it happens is to behave as if the repo was gemspec-only.
87
- if dep.nil? && dependency.requirements.any?
88
- next latest_version_details
89
- end
90
-
91
- # Otherwise, if the dependency wasn't found it's because it is a
92
- # subdependency that was removed when attempting to update it.
93
- next nil if dep.nil?
94
-
95
- # If the dependency is Bundler itself then we can't trust the
96
- # version that has been returned (it's the version Dependabot is
97
- # running on, rather than the true latest resolvable version).
98
- next nil if dep.name == "bundler"
99
-
100
- # If the old Gemfile index was used then it won't have checked
101
- # Ruby compatibility. Fix that by doing the check manually (and
102
- # saying no update is possible if the Ruby version is a mismatch)
103
- next nil if ruby_version_incompatible?(dep)
104
-
105
- details = { version: dep.version }
106
- if dep.source.instance_of?(::Bundler::Source::Git)
107
- details[:commit_sha] = dep.source.revision
74
+ # We do not want the helper to handle errors for us as there are
75
+ # some errors we want to handle specifically ourselves, including
76
+ # potentially retrying in the case of the Ruby version being locked
77
+ in_a_native_bundler_context(error_handling: false) do |tmp_dir|
78
+ details = SharedHelpers.run_helper_subprocess(
79
+ command: NativeHelpers.helper_path,
80
+ function: "resolve_version",
81
+ args: {
82
+ dependency_name: dependency.name,
83
+ dependency_requirements: dependency.requirements,
84
+ gemfile_name: gemfile.name,
85
+ lockfile_name: lockfile&.name,
86
+ using_bundler_2: using_bundler_2?,
87
+ dir: tmp_dir,
88
+ credentials: credentials
89
+ }
90
+ )
91
+
92
+ return latest_version_details if details == "latest"
93
+
94
+ if details
95
+ details.transform_keys!(&:to_sym)
96
+
97
+ # If the old Gemfile index was used then it won't have checked
98
+ # Ruby compatibility. Fix that by doing the check manually and
99
+ # saying no update is possible if the Ruby version is a
100
+ # mismatch
101
+ return nil if ruby_version_incompatible?(details)
102
+
103
+ details[:version] = Gem::Version.new(details[:version])
108
104
  end
109
105
  details
110
106
  end
111
107
  end
112
- rescue Dependabot::DependencyFileNotResolvable => e
108
+ rescue Dependabot::SharedHelpers::HelperSubprocessFailed => e
113
109
  return if error_due_to_restrictive_upper_bound?(e)
114
110
  return if circular_dependency_at_new_version?(e)
115
- raise unless ruby_lock_error?(e)
111
+
112
+ # If we are unable to handle the error ourselves, pass it on to the
113
+ # general bundler error handling.
114
+ handle_bundler_errors(e) unless ruby_lock_error?(e)
116
115
 
117
116
  @gemspec_ruby_unlocked = true
118
117
  regenerate_dependency_files_without_ruby_lock && retry
@@ -120,7 +119,9 @@ module Dependabot
120
119
  # rubocop:enable Metrics/PerceivedComplexity
121
120
 
122
121
  def circular_dependency_at_new_version?(error)
123
- return false unless error.message.include?("CyclicDependencyError")
122
+ unless error.error_class.include?("CyclicDependencyError")
123
+ return false
124
+ end
124
125
 
125
126
  error.message.include?("'#{dependency.name}'")
126
127
  end
@@ -155,71 +156,30 @@ module Dependabot
155
156
  ).prepared_dependency_files
156
157
  end
157
158
 
158
- # rubocop:disable Metrics/PerceivedComplexity
159
- def dependency_from_definition(unlock_subdependencies: true)
160
- dependencies_to_unlock = [dependency.name]
161
- dependencies_to_unlock += subdependencies if unlock_subdependencies
162
- begin
163
- definition = build_definition(dependencies_to_unlock)
164
- definition.resolve_remotely!
165
- rescue ::Bundler::GemNotFound => e
166
- unlock_yanked_gem(dependencies_to_unlock, e) && retry
167
- rescue ::Bundler::HTTPError => e
168
- # Retry network errors
169
- attempt ||= 1
170
- attempt += 1
171
- raise if attempt > 3 || !e.message.include?("Network error")
172
-
173
- retry
174
- end
175
-
176
- dep = definition.resolve.find { |d| d.name == dependency.name }
177
- return dep if dep
178
- return if dependency.requirements.any? || !unlock_subdependencies
179
-
180
- # If no definition was found and we're updating a sub-dependency,
181
- # try again but without unlocking any other sub-dependencies
182
- dependency_from_definition(unlock_subdependencies: false)
183
- end
184
-
185
- # rubocop:enable Metrics/PerceivedComplexity
186
-
187
- def unlock_yanked_gem(dependencies_to_unlock, error)
188
- raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
189
-
190
- gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX).
191
- named_captures["name"]
192
- raise if dependencies_to_unlock.include?(gem_name)
193
-
194
- dependencies_to_unlock << gem_name
195
- end
196
-
197
- def subdependencies
198
- # If there's no lockfile we don't need to worry about
199
- # subdependencies
200
- return [] unless lockfile
201
-
202
- all_deps = ::Bundler::LockfileParser.new(sanitized_lockfile_body).
203
- specs.map(&:name).map(&:to_s).uniq
204
- top_level = build_definition([]).dependencies.
205
- map(&:name).map(&:to_s)
206
-
207
- all_deps - top_level
159
+ def latest_version_details
160
+ @latest_version_details ||=
161
+ LatestVersionFinder.new(
162
+ dependency: dependency,
163
+ dependency_files: dependency_files,
164
+ repo_contents_path: repo_contents_path,
165
+ credentials: credentials,
166
+ ignored_versions: ignored_versions,
167
+ raise_on_ignored: @raise_on_ignored,
168
+ security_advisories: []
169
+ ).latest_version_details
208
170
  end
209
171
 
210
- def ruby_version_incompatible?(dep)
211
- return false unless dep.source.is_a?(::Bundler::Source::Rubygems)
212
-
213
- fetcher = dep.source.fetchers.first.fetchers.first
214
-
172
+ def ruby_version_incompatible?(details)
215
173
  # It's only the old index we have a problem with
216
- return false unless fetcher.is_a?(::Bundler::Fetcher::Dependency)
174
+ unless details[:fetcher] == "Bundler::Fetcher::Dependency"
175
+ return false
176
+ end
217
177
 
218
178
  # If no Ruby version is specified, we don't have a problem
219
- return false unless ruby_version
179
+ return false unless details[:ruby_version]
220
180
 
221
181
  versions = Excon.get(
222
- "#{fetcher.fetch_uri}api/v1/versions/#{dependency.name}.json",
182
+ "https://rubygems.org/api/v1/versions/#{dependency.name}.json",
223
183
  idempotent: true,
224
184
  **SharedHelpers.excon_defaults
225
185
  )
@@ -230,53 +190,23 @@ module Dependabot
230
190
 
231
191
  ruby_requirement =
232
192
  JSON.parse(versions.body).
233
- find { |details| details["number"] == dep.version.to_s }&.
193
+ find { |version| version["number"] == details[:version] }&.
234
194
  fetch("ruby_version", nil)
235
195
 
236
196
  # Give the benefit of the doubt if we can't find the version's
237
197
  # required Ruby version.
238
198
  return false unless ruby_requirement
239
199
 
240
- ruby_requirement = Requirement.new(ruby_requirement)
200
+ ruby_requirement = Gem::Requirement.new(ruby_requirement)
201
+ current_ruby_version = Gem::Version.new(details[:ruby_version])
241
202
 
242
- !ruby_requirement.satisfied_by?(ruby_version)
203
+ !ruby_requirement.satisfied_by?(current_ruby_version)
243
204
  rescue JSON::ParserError, Excon::Error::Socket, Excon::Error::Timeout
244
205
  # Give the benefit of the doubt if something goes wrong fetching
245
206
  # version details (could be that it's a private index, etc.)
246
207
  false
247
208
  end
248
209
 
249
- def build_definition(dependencies_to_unlock)
250
- # Note: we lock shared dependencies to avoid any top-level
251
- # dependencies getting unlocked (which would happen if they were
252
- # also subdependencies of the dependency being unlocked)
253
- ::Bundler::Definition.build(
254
- gemfile.name,
255
- lockfile&.name,
256
- gems: dependencies_to_unlock,
257
- lock_shared_dependencies: true
258
- )
259
- end
260
-
261
- def ruby_version
262
- return nil unless gemfile
263
-
264
- @ruby_version ||= build_definition([]).ruby_version&.gem_version
265
- end
266
-
267
- def latest_version_details
268
- @latest_version_details ||=
269
- LatestVersionFinder.new(
270
- dependency: dependency,
271
- dependency_files: dependency_files,
272
- repo_contents_path: repo_contents_path,
273
- credentials: credentials,
274
- ignored_versions: ignored_versions,
275
- raise_on_ignored: @raise_on_ignored,
276
- security_advisories: []
277
- ).latest_version_details
278
- end
279
-
280
210
  def gemfile
281
211
  dependency_files.find { |f| f.name == "Gemfile" } ||
282
212
  dependency_files.find { |f| f.name == "gems.rb" }
@@ -287,9 +217,10 @@ module Dependabot
287
217
  dependency_files.find { |f| f.name == "gems.locked" }
288
218
  end
289
219
 
290
- def sanitized_lockfile_body
291
- re = FileUpdater::LockfileUpdater::LOCKFILE_ENDING
292
- lockfile.content.gsub(re, "")
220
+ def using_bundler_2?
221
+ return unless lockfile
222
+
223
+ lockfile.content.match?(/BUNDLED WITH\s+2/m)
293
224
  end
294
225
  end
295
226
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.122.1
4
+ version: 0.123.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.122.1
19
+ version: 0.123.0
20
20
  type: :runtime
21
21
  prerelease: false
22
22
  version_requirements: !ruby/object:Gem::Requirement
23
23
  requirements:
24
24
  - - '='
25
25
  - !ruby/object:Gem::Version
26
- version: 0.122.1
26
+ version: 0.123.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -191,6 +191,7 @@ files:
191
191
  - lib/dependabot/bundler/file_updater/requirement_replacer.rb
192
192
  - lib/dependabot/bundler/file_updater/ruby_requirement_setter.rb
193
193
  - lib/dependabot/bundler/metadata_finder.rb
194
+ - lib/dependabot/bundler/native_helpers.rb
194
195
  - lib/dependabot/bundler/requirement.rb
195
196
  - lib/dependabot/bundler/update_checker.rb
196
197
  - lib/dependabot/bundler/update_checker/file_preparer.rb
@@ -201,9 +202,6 @@ files:
201
202
  - lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb
202
203
  - lib/dependabot/bundler/update_checker/version_resolver.rb
203
204
  - lib/dependabot/bundler/version.rb
204
- - lib/dependabot/monkey_patches/bundler/definition_bundler_version_patch.rb
205
- - lib/dependabot/monkey_patches/bundler/definition_ruby_version_patch.rb
206
- - lib/dependabot/monkey_patches/bundler/git_source_patch.rb
207
205
  homepage: https://github.com/dependabot/dependabot-core
208
206
  licenses:
209
207
  - Nonstandard
@@ -1,15 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "bundler/definition"
4
-
5
- # Ignore the Bundler version specified in the Gemfile (since the only Bundler
6
- # version available to us is the one we're using).
7
- module Bundler
8
- class Definition
9
- def expanded_dependencies
10
- @expanded_dependencies ||=
11
- expand_dependencies(dependencies + metadata_dependencies, @remote).
12
- reject { |d| d.name == "bundler" }
13
- end
14
- end
15
- end
@@ -1,17 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module BundlerDefinitionRubyVersionPatch
4
- def index
5
- @index ||= super.tap do
6
- if ruby_version
7
- requested_version = ruby_version.to_gem_version_with_patchlevel
8
- sources.metadata_source.specs <<
9
- Gem::Specification.new("ruby\0", requested_version)
10
- end
11
-
12
- sources.metadata_source.specs <<
13
- Gem::Specification.new("ruby\0", "2.5.3p105")
14
- end
15
- end
16
- end
17
- Bundler::Definition.prepend(BundlerDefinitionRubyVersionPatch)
@@ -1,61 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Bundler
4
- class Source
5
- class Git
6
- class GitProxy
7
- private
8
-
9
- # Bundler allows ssh authentication when talking to GitHub but there's
10
- # no way for Dependabot to do so (it doesn't have any ssh keys).
11
- # Instead, we convert all `git@github.com:` URLs to use HTTPS.
12
- def configured_uri_for(uri)
13
- uri = uri.gsub(%r{git@(.*?):/?}, 'https://\1/')
14
- if uri.match?(/https?:/)
15
- remote = URI(uri)
16
- config_auth =
17
- Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
18
- remote.userinfo ||= config_auth
19
- remote.to_s
20
- else
21
- uri
22
- end
23
- end
24
- end
25
- end
26
- end
27
- end
28
-
29
- module Bundler
30
- class Source
31
- class Git < Path
32
- private
33
-
34
- def serialize_gemspecs_in(destination)
35
- original_load_paths = $LOAD_PATH.dup
36
- reduced_load_paths = original_load_paths.
37
- reject { |p| p.include?("/gems/") }
38
-
39
- $LOAD_PATH.shift until $LOAD_PATH.empty?
40
- reduced_load_paths.each { |p| $LOAD_PATH << p }
41
-
42
- if destination.relative?
43
- destination = destination.expand_path(Bundler.root)
44
- end
45
- Dir["#{destination}/#{@glob}"].each do |spec_path|
46
- # Evaluate gemspecs and cache the result. Gemspecs
47
- # in git might require git or other dependencies.
48
- # The gemspecs we cache should already be evaluated.
49
- spec = Bundler.load_gemspec(spec_path)
50
- next unless spec
51
-
52
- Bundler.rubygems.set_installed_by_version(spec)
53
- Bundler.rubygems.validate(spec)
54
- File.open(spec_path, "wb") { |file| file.write(spec.to_ruby) }
55
- end
56
- $LOAD_PATH.shift until $LOAD_PATH.empty?
57
- original_load_paths.each { |p| $LOAD_PATH << p }
58
- end
59
- end
60
- end
61
- end