dependabot-bundler 0.138.0 → 0.138.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/v1/build +2 -1
  3. data/helpers/v1/run.rb +16 -0
  4. data/helpers/v2/.gitignore +8 -0
  5. data/helpers/v2/Gemfile +12 -0
  6. data/helpers/v2/build +25 -0
  7. data/helpers/v2/lib/functions.rb +195 -0
  8. data/helpers/v2/lib/functions/conflicting_dependency_resolver.rb +86 -0
  9. data/helpers/v2/lib/functions/dependency_source.rb +86 -0
  10. data/helpers/v2/lib/functions/file_parser.rb +106 -0
  11. data/helpers/v2/lib/functions/force_updater.rb +167 -0
  12. data/helpers/v2/lib/functions/lockfile_updater.rb +224 -0
  13. data/helpers/v2/lib/functions/version_resolver.rb +140 -0
  14. data/helpers/v2/monkey_patches/definition_bundler_version_patch.rb +15 -0
  15. data/helpers/v2/monkey_patches/definition_ruby_version_patch.rb +20 -0
  16. data/helpers/v2/monkey_patches/git_source_patch.rb +62 -0
  17. data/helpers/v2/run.rb +44 -0
  18. data/helpers/v2/spec/functions/conflicting_dependency_resolver_spec.rb +133 -0
  19. data/helpers/v2/spec/functions/dependency_source_spec.rb +185 -0
  20. data/helpers/v2/spec/functions/file_parser_spec.rb +142 -0
  21. data/helpers/v2/spec/functions/version_resolver_spec.rb +97 -0
  22. data/helpers/v2/spec/functions_spec.rb +25 -0
  23. data/helpers/v2/spec/native_spec_helper.rb +49 -0
  24. data/helpers/v2/spec/shared_contexts.rb +60 -0
  25. data/lib/dependabot/bundler/file_parser.rb +13 -1
  26. data/lib/dependabot/bundler/file_updater.rb +3 -2
  27. data/lib/dependabot/bundler/file_updater/lockfile_updater.rb +4 -3
  28. data/lib/dependabot/bundler/file_updater/requirement_replacer.rb +4 -2
  29. data/lib/dependabot/bundler/helpers.rb +15 -3
  30. data/lib/dependabot/bundler/native_helpers.rb +5 -0
  31. data/lib/dependabot/bundler/update_checker.rb +12 -6
  32. data/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb +5 -2
  33. data/lib/dependabot/bundler/update_checker/force_updater.rb +6 -3
  34. data/lib/dependabot/bundler/update_checker/latest_version_finder.rb +6 -3
  35. data/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb +5 -3
  36. data/lib/dependabot/bundler/update_checker/requirements_updater.rb +2 -2
  37. data/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb +3 -5
  38. data/lib/dependabot/bundler/update_checker/version_resolver.rb +8 -4
  39. metadata +25 -4
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 84aea554fac0f0cc19cce07782f3b587fd66f05927cec2f07a9b606dff33b041
4
- data.tar.gz: 0cf54804f99668071287e08e212641d49a267a94ed19d5d8d65c5aff081c263d
3
+ metadata.gz: a9808afb0b3a963f82ac68b0668b0bf8df45111751f6aab0c7b1e1011e50a496
4
+ data.tar.gz: 8537cad525a3f8e95faa2977bebac06e5bcf1ca5a8e49b0813bee266fbe078df
5
5
  SHA512:
6
- metadata.gz: 208fd556fed167b644db2fe337ba6ecb6ce2d0392f85b06f78100656253d60594d9bfeae8b7fdaa8cd0556ee1c24ab8f7c9797fdf437879358fc7e740148db9a
7
- data.tar.gz: 3884bca4a97c32b25dd796423dd95dfbf0adf3f318900aff052a6af78c710360411e532eada5cab7b07c59a71d99e2cff81c9b376955d4bc0d6fe56f155a0189
6
+ metadata.gz: 8e6581d20513493201c9694a2f04e1c00a4f0002d97dfcab4877095a92fd61d4da0c0e7f19f38ea60df415b418e1589fe555adb298e989a2519ea11ce3c61d38
7
+ data.tar.gz: 1507fe54b6b138493cb9904d894e70989df8ef1cadf3fe69003bd9eb46b1bd2e4bcf56b7cd146bcb0944fec6d1a75de11b5e884cd9997c2e3c30a62d0059fcbe
data/helpers/v1/build CHANGED
@@ -21,4 +21,5 @@ cd "$install_dir"
21
21
 
22
22
  # NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock
23
23
  # forcing native helpers to run with the same version
24
- BUNDLER_VERSION=1 bundle install --without test
24
+ BUNDLER_VERSION=1 bundle config set --local without "test"
25
+ BUNDLER_VERSION=1 bundle install
data/helpers/v1/run.rb CHANGED
@@ -1,3 +1,5 @@
1
+ # frozen_string_literal: true
2
+
1
3
  require "bundler"
2
4
  require "json"
3
5
 
@@ -11,11 +13,25 @@ require "git_source_patch"
11
13
 
12
14
  require "functions"
13
15
 
16
+ MAX_BUNDLER_VERSION="2.0.0"
17
+
18
+ def validate_bundler_version!
19
+ return true if correct_bundler_version?
20
+
21
+ raise StandardError, "Called with Bundler '#{Bundler::VERSION}', expected < '#{MAX_BUNDLER_VERSION}'"
22
+ end
23
+
24
+ def correct_bundler_version?
25
+ Gem::Version.new(Bundler::VERSION) < Gem::Version.new(MAX_BUNDLER_VERSION)
26
+ end
27
+
14
28
  def output(obj)
15
29
  print JSON.dump(obj)
16
30
  end
17
31
 
18
32
  begin
33
+ validate_bundler_version!
34
+
19
35
  request = JSON.parse($stdin.read)
20
36
 
21
37
  function = request["function"]
@@ -0,0 +1,8 @@
1
+ /.bundle
2
+ /.env
3
+ /tmp
4
+ /dependabot-*.gem
5
+ Gemfile.lock
6
+ spec/fixtures/projects/*/.bundle/
7
+ !spec/fixtures/projects/**/Gemfile.lock
8
+ !spec/fixtures/projects/**/vendor
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ source "https://rubygems.org"
4
+
5
+ # NOTE: Used to run native helper specs
6
+ group :test do
7
+ gem "byebug", "11.1.3"
8
+ gem "rspec", "3.10.0"
9
+ gem "rspec-its", "1.3.0"
10
+ gem "vcr", "6.0.0"
11
+ gem "webmock", "3.12.1"
12
+ end
data/helpers/v2/build ADDED
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+
3
+ set -e
4
+
5
+ install_dir=$1
6
+ if [ -z "$install_dir" ]; then
7
+ echo "usage: $0 INSTALL_DIR"
8
+ exit 1
9
+ fi
10
+
11
+ helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
12
+ cp -r \
13
+ "$helpers_dir/lib" \
14
+ "$helpers_dir/monkey_patches" \
15
+ "$helpers_dir/run.rb" \
16
+ "$helpers_dir/Gemfile" \
17
+ "$install_dir"
18
+
19
+ cd "$install_dir"
20
+
21
+ # NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock
22
+ # forcing specs and native helpers to run with the same version
23
+ BUNDLER_VERSION=2 bundle config set --local path ".bundle"
24
+ BUNDLER_VERSION=2 bundle config set --local without "test"
25
+ BUNDLER_VERSION=2 bundle install
@@ -0,0 +1,195 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "functions/conflicting_dependency_resolver"
4
+ require "functions/dependency_source"
5
+ require "functions/file_parser"
6
+ require "functions/force_updater"
7
+ require "functions/lockfile_updater"
8
+ require "functions/version_resolver"
9
+
10
+ module Functions
11
+ class NotImplementedError < StandardError; end
12
+
13
+ def self.parsed_gemfile(lockfile_name:, gemfile_name:, dir:)
14
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
15
+ using_bundler2: false)
16
+ FileParser.new(lockfile_name: lockfile_name).
17
+ parsed_gemfile(gemfile_name: gemfile_name)
18
+ end
19
+
20
+ def self.parsed_gemspec(lockfile_name:, gemspec_name:, dir:)
21
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
22
+ using_bundler2: false)
23
+ FileParser.new(lockfile_name: lockfile_name).
24
+ parsed_gemspec(gemspec_name: gemspec_name)
25
+ end
26
+
27
+ def self.vendor_cache_dir(dir:)
28
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
29
+ using_bundler2: false)
30
+ Bundler.app_cache
31
+ end
32
+
33
+ def self.update_lockfile(dir:, gemfile_name:, lockfile_name:, using_bundler2:,
34
+ credentials:, dependencies:)
35
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
36
+ using_bundler2: using_bundler2)
37
+ LockfileUpdater.new(
38
+ gemfile_name: gemfile_name,
39
+ lockfile_name: lockfile_name,
40
+ dependencies: dependencies
41
+ ).run
42
+ end
43
+
44
+ def self.force_update(dir:, dependency_name:, target_version:, gemfile_name:,
45
+ lockfile_name:, using_bundler2:, credentials:,
46
+ update_multiple_dependencies:)
47
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
48
+ using_bundler2: using_bundler2)
49
+ ForceUpdater.new(
50
+ dependency_name: dependency_name,
51
+ target_version: target_version,
52
+ gemfile_name: gemfile_name,
53
+ lockfile_name: lockfile_name,
54
+ update_multiple_dependencies: update_multiple_dependencies
55
+ ).run
56
+ end
57
+
58
+ def self.dependency_source_type(gemfile_name:, dependency_name:, dir:,
59
+ credentials:)
60
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
61
+ using_bundler2: false)
62
+
63
+ DependencySource.new(
64
+ gemfile_name: gemfile_name,
65
+ dependency_name: dependency_name
66
+ ).type
67
+ end
68
+
69
+ def self.depencency_source_latest_git_version(gemfile_name:, dependency_name:,
70
+ dir:, credentials:,
71
+ dependency_source_url:,
72
+ dependency_source_branch:)
73
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
74
+ using_bundler2: false)
75
+ DependencySource.new(
76
+ gemfile_name: gemfile_name,
77
+ dependency_name: dependency_name
78
+ ).latest_git_version(
79
+ dependency_source_url: dependency_source_url,
80
+ dependency_source_branch: dependency_source_branch
81
+ )
82
+ end
83
+
84
+ def self.private_registry_versions(gemfile_name:, dependency_name:, dir:,
85
+ credentials:)
86
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
87
+ using_bundler2: false)
88
+
89
+ DependencySource.new(
90
+ gemfile_name: gemfile_name,
91
+ dependency_name: dependency_name
92
+ ).private_registry_versions
93
+ end
94
+
95
+ def self.resolve_version(dependency_name:, dependency_requirements:,
96
+ gemfile_name:, lockfile_name:, using_bundler2:,
97
+ dir:, credentials:)
98
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials, using_bundler2: using_bundler2)
99
+ VersionResolver.new(
100
+ dependency_name: dependency_name,
101
+ dependency_requirements: dependency_requirements,
102
+ gemfile_name: gemfile_name,
103
+ lockfile_name: lockfile_name
104
+ ).version_details
105
+ end
106
+
107
+ def self.jfrog_source(dir:, gemfile_name:, credentials:, using_bundler2:)
108
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials, using_bundler2: using_bundler2)
109
+
110
+ Bundler::Definition.build(gemfile_name, nil, {}).
111
+ send(:sources).
112
+ rubygems_remotes.
113
+ find { |uri| uri.host.include?("jfrog") }&.
114
+ host
115
+ end
116
+
117
+ def self.git_specs(dir:, gemfile_name:, credentials:, using_bundler2:)
118
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
119
+ using_bundler2: using_bundler2)
120
+
121
+ git_specs = Bundler::Definition.build(gemfile_name, nil, {}).dependencies.
122
+ select do |spec|
123
+ spec.source.is_a?(Bundler::Source::Git)
124
+ end
125
+ git_specs.map do |spec|
126
+ # Piggy-back off some private Bundler methods to configure the
127
+ # URI with auth details in the same way Bundler does.
128
+ git_proxy = spec.source.send(:git_proxy)
129
+ auth_uri = spec.source.uri.gsub("git://", "https://")
130
+ auth_uri = git_proxy.send(:configured_uri_for, auth_uri)
131
+ auth_uri += ".git" unless auth_uri.end_with?(".git")
132
+ auth_uri += "/info/refs?service=git-upload-pack"
133
+ {
134
+ uri: spec.source.uri,
135
+ auth_uri: auth_uri
136
+ }
137
+ end
138
+ end
139
+
140
+ def self.set_bundler_flags_and_credentials(dir:, credentials:,
141
+ using_bundler2:)
142
+ dir = dir ? Pathname.new(dir) : dir
143
+ Bundler.instance_variable_set(:@root, dir)
144
+
145
+ # Remove installed gems from the default Rubygems index
146
+ Gem::Specification.all =
147
+ Gem::Specification.send(:default_stubs, "*.gemspec")
148
+
149
+ # Set auth details
150
+ relevant_credentials(credentials).each do |cred|
151
+ token = cred["token"] ||
152
+ "#{cred['username']}:#{cred['password']}"
153
+
154
+ Bundler.settings.set_command_option(
155
+ cred.fetch("host"),
156
+ token.gsub("@", "%40F").gsub("?", "%3F")
157
+ )
158
+ end
159
+
160
+ # NOTE: Prevent bundler from printing resolution information
161
+ Bundler.ui = Bundler::UI::Silent.new
162
+
163
+ # Use HTTPS for GitHub if lockfile
164
+ Bundler.settings.set_command_option("forget_cli_options", "true")
165
+ Bundler.settings.set_command_option("github.https", "true")
166
+ end
167
+
168
+ def self.relevant_credentials(credentials)
169
+ [
170
+ *git_source_credentials(credentials),
171
+ *private_registry_credentials(credentials)
172
+ ].select { |cred| cred["password"] || cred["token"] }
173
+ end
174
+
175
+ def self.private_registry_credentials(credentials)
176
+ credentials.
177
+ select { |cred| cred["type"] == "rubygems_server" }
178
+ end
179
+
180
+ def self.git_source_credentials(credentials)
181
+ credentials.
182
+ select { |cred| cred["type"] == "git_source" }
183
+ end
184
+
185
+ def self.conflicting_dependencies(dir:, dependency_name:, target_version:,
186
+ lockfile_name:, using_bundler2:, credentials:)
187
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
188
+ using_bundler2: using_bundler2)
189
+ ConflictingDependencyResolver.new(
190
+ dependency_name: dependency_name,
191
+ target_version: target_version,
192
+ lockfile_name: lockfile_name
193
+ ).conflicting_dependencies
194
+ end
195
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Functions
4
+ class ConflictingDependencyResolver
5
+ def initialize(dependency_name:, target_version:, lockfile_name:)
6
+ @dependency_name = dependency_name
7
+ @target_version = target_version
8
+ @lockfile_name = lockfile_name
9
+ end
10
+
11
+ # Finds any dependencies in the lockfile that have a subdependency on the
12
+ # given dependency that does not satisfly the target_version.
13
+ # @return [Array<Hash{String => String}]
14
+ # * explanation [String] a sentence explaining the conflict
15
+ # * name [String] the blocking dependencies name
16
+ # * version [String] the version of the blocking dependency
17
+ # * requirement [String] the requirement on the target_dependency
18
+ def conflicting_dependencies
19
+ Bundler.settings.set_command_option("only_update_to_newer_versions", true)
20
+
21
+ parent_specs.flat_map do |parent_spec|
22
+ top_level_specs_for(parent_spec).map do |top_level|
23
+ dependency = parent_spec.dependencies.find { |bd| bd.name == dependency_name }
24
+ {
25
+ "explanation" => explanation(parent_spec, dependency, top_level),
26
+ "name" => parent_spec.name,
27
+ "version" => parent_spec.version.to_s,
28
+ "requirement" => dependency.requirement.to_s
29
+ }
30
+ end
31
+ end
32
+ end
33
+
34
+ private
35
+
36
+ attr_reader :dependency_name, :target_version, :lockfile_name
37
+
38
+ def parent_specs
39
+ version = Gem::Version.new(target_version)
40
+ parsed_lockfile.specs.filter do |spec|
41
+ spec.dependencies.any? do |dep|
42
+ dep.name == dependency_name &&
43
+ !dep.requirement.satisfied_by?(version)
44
+ end
45
+ end
46
+ end
47
+
48
+ def top_level_specs_for(parent_spec)
49
+ return [parent_spec] if top_level?(parent_spec)
50
+
51
+ parsed_lockfile.specs.filter do |spec|
52
+ spec.dependencies.any? do |dep|
53
+ dep.name == parent_spec.name && top_level?(spec)
54
+ end
55
+ end
56
+ end
57
+
58
+ def top_level?(spec)
59
+ parsed_lockfile.dependencies.key?(spec.name)
60
+ end
61
+
62
+ def explanation(spec, dependency, top_level)
63
+ if spec.name == top_level.name
64
+ "#{spec.name} (#{spec.version}) requires #{dependency_name} (#{dependency.requirement})"
65
+ else
66
+ "#{top_level.name} (#{top_level.version}) requires #{dependency_name} "\
67
+ "(#{dependency.requirement}) via #{spec.name} (#{spec.version})"
68
+ end
69
+ end
70
+
71
+ def parsed_lockfile
72
+ @parsed_lockfile ||= Bundler::LockfileParser.new(lockfile)
73
+ end
74
+
75
+ def lockfile
76
+ return @lockfile if defined?(@lockfile)
77
+
78
+ @lockfile =
79
+ begin
80
+ return unless lockfile_name && File.exist?(lockfile_name)
81
+
82
+ File.read(lockfile_name)
83
+ end
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,86 @@
1
+ module Functions
2
+ class DependencySource
3
+ attr_reader :gemfile_name, :dependency_name
4
+
5
+ RUBYGEMS = "rubygems"
6
+ PRIVATE_REGISTRY = "private"
7
+ GIT = "git"
8
+ OTHER = "other"
9
+
10
+ def initialize(gemfile_name:, dependency_name:)
11
+ @gemfile_name = gemfile_name
12
+ @dependency_name = dependency_name
13
+ end
14
+
15
+ def type
16
+ bundler_source = specified_source || default_source
17
+ type_of(bundler_source)
18
+ end
19
+
20
+ def latest_git_version(dependency_source_url:, dependency_source_branch:)
21
+ source = Bundler::Source::Git.new(
22
+ "uri" => dependency_source_url,
23
+ "branch" => dependency_source_branch,
24
+ "name" => dependency_name,
25
+ "submodules" => true
26
+ )
27
+
28
+ # Tell Bundler we're fine with fetching the source remotely
29
+ source.instance_variable_set(:@allow_remote, true)
30
+
31
+ spec = source.specs.first
32
+ { version: spec.version, commit_sha: spec.source.revision }
33
+ end
34
+
35
+ def private_registry_versions
36
+ bundler_source = specified_source || default_source
37
+
38
+ bundler_source.
39
+ fetchers.flat_map do |fetcher|
40
+ fetcher.
41
+ specs_with_retry([dependency_name], bundler_source).
42
+ search_all(dependency_name)
43
+ end.
44
+ map(&:version)
45
+ end
46
+
47
+ private
48
+
49
+ def type_of(bundler_source)
50
+ case bundler_source
51
+ when Bundler::Source::Rubygems
52
+ remote = bundler_source.remotes.first
53
+ if remote.nil? || remote.to_s == "https://rubygems.org/"
54
+ RUBYGEMS
55
+ else
56
+ PRIVATE_REGISTRY
57
+ end
58
+ when Bundler::Source::Git
59
+ GIT
60
+ else
61
+ OTHER
62
+ end
63
+ end
64
+
65
+ def specified_source
66
+ return @specified_source if defined? @specified_source
67
+
68
+ @specified_source = definition.dependencies.
69
+ find { |dep| dep.name == dependency_name }&.source
70
+ end
71
+
72
+ def default_source
73
+ definition.send(:sources).default_source
74
+ end
75
+
76
+ def definition
77
+ @definition ||= Bundler::Definition.build(gemfile_name, nil, {})
78
+ end
79
+
80
+ def serialize_bundler_source(source)
81
+ {
82
+ type: source.class.to_s
83
+ }
84
+ end
85
+ end
86
+ end