dependabot-bundler 0.135.0 → 0.138.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 (34) hide show
  1. checksums.yaml +4 -4
  2. data/helpers/v1/.bundle/config +2 -0
  3. data/helpers/v1/.gitignore +9 -0
  4. data/helpers/v1/Gemfile +12 -0
  5. data/helpers/{build → v1/build} +6 -0
  6. data/helpers/{lib → v1/lib}/functions.rb +0 -0
  7. data/helpers/{lib → v1/lib}/functions/conflicting_dependency_resolver.rb +0 -0
  8. data/helpers/{lib → v1/lib}/functions/dependency_source.rb +0 -0
  9. data/helpers/{lib → v1/lib}/functions/file_parser.rb +0 -0
  10. data/helpers/{lib → v1/lib}/functions/force_updater.rb +0 -0
  11. data/helpers/{lib → v1/lib}/functions/lockfile_updater.rb +0 -0
  12. data/helpers/{lib → v1/lib}/functions/version_resolver.rb +0 -0
  13. data/helpers/{monkey_patches → v1/monkey_patches}/definition_bundler_version_patch.rb +0 -0
  14. data/helpers/{monkey_patches → v1/monkey_patches}/definition_ruby_version_patch.rb +0 -0
  15. data/helpers/{monkey_patches → v1/monkey_patches}/git_source_patch.rb +0 -0
  16. data/helpers/{run.rb → v1/run.rb} +0 -0
  17. data/helpers/v1/spec/functions/conflicting_dependency_resolver_spec.rb +133 -0
  18. data/helpers/v1/spec/functions/dependency_source_spec.rb +187 -0
  19. data/helpers/v1/spec/functions/file_parser_spec.rb +77 -0
  20. data/helpers/v1/spec/functions/version_resolver_spec.rb +97 -0
  21. data/helpers/v1/spec/native_spec_helper.rb +49 -0
  22. data/helpers/v1/spec/shared_contexts.rb +59 -0
  23. data/lib/dependabot/bundler/file_parser.rb +9 -4
  24. data/lib/dependabot/bundler/file_updater.rb +7 -2
  25. data/lib/dependabot/bundler/file_updater/gemspec_sanitizer.rb +26 -5
  26. data/lib/dependabot/bundler/file_updater/lockfile_updater.rb +7 -2
  27. data/lib/dependabot/bundler/helpers.rb +16 -0
  28. data/lib/dependabot/bundler/native_helpers.rb +29 -2
  29. data/lib/dependabot/bundler/update_checker/conflicting_dependency_resolver.rb +9 -2
  30. data/lib/dependabot/bundler/update_checker/force_updater.rb +7 -2
  31. data/lib/dependabot/bundler/update_checker/latest_version_finder/dependency_source.rb +18 -6
  32. data/lib/dependabot/bundler/update_checker/shared_bundler_helpers.rb +9 -4
  33. data/lib/dependabot/bundler/update_checker/version_resolver.rb +7 -2
  34. metadata +26 -16
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 967d2da1e215388ce5d73185bf92ab738f1f769a4d221c8ece0d6166f070ee80
4
- data.tar.gz: 8b3b76b611be774e676da7c7a46b5216cae7b54ce95d6816dd41a64a0a9623cc
3
+ metadata.gz: 84aea554fac0f0cc19cce07782f3b587fd66f05927cec2f07a9b606dff33b041
4
+ data.tar.gz: 0cf54804f99668071287e08e212641d49a267a94ed19d5d8d65c5aff081c263d
5
5
  SHA512:
6
- metadata.gz: b208788a96fcf1eb801073e2ae69e7132329facd9f0acded7a51a466515c5ff07baa255efccd5f5db2c29d7a37f075e098c9670a36908254db092fabf3b196dc
7
- data.tar.gz: 577e39faa093464f653d2f728e900ab06f3c4049bc90b2a2e7c7e9f6f0139914d041ef2f0a31df08cc1a7a7f33882986368eb9d6a85483cb04bc063a897aa024
6
+ metadata.gz: 208fd556fed167b644db2fe337ba6ecb6ce2d0392f85b06f78100656253d60594d9bfeae8b7fdaa8cd0556ee1c24ab8f7c9797fdf437879358fc7e740148db9a
7
+ data.tar.gz: 3884bca4a97c32b25dd796423dd95dfbf0adf3f318900aff052a6af78c710360411e532eada5cab7b07c59a71d99e2cff81c9b376955d4bc0d6fe56f155a0189
@@ -0,0 +1,2 @@
1
+ ---
2
+ BUNDLE_PATH: ".bundle"
@@ -0,0 +1,9 @@
1
+ /.bundle/*
2
+ !/.bundle/config
3
+ /.env
4
+ /tmp
5
+ /dependabot-*.gem
6
+ Gemfile.lock
7
+ spec/fixtures/projects/*/.bundle/
8
+ !spec/fixtures/projects/**/Gemfile.lock
9
+ !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
@@ -10,9 +10,15 @@ fi
10
10
 
11
11
  helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
12
12
  cp -r \
13
+ "$helpers_dir/.bundle" \
13
14
  "$helpers_dir/lib" \
14
15
  "$helpers_dir/monkey_patches" \
15
16
  "$helpers_dir/run.rb" \
17
+ "$helpers_dir/Gemfile" \
16
18
  "$install_dir"
17
19
 
18
20
  cd "$install_dir"
21
+
22
+ # NOTE: Sets `BUNDLED WITH` to match the installed v1 version in Gemfile.lock
23
+ # forcing native helpers to run with the same version
24
+ BUNDLER_VERSION=1 bundle install --without test
File without changes
File without changes
File without changes
File without changes
@@ -0,0 +1,133 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "native_spec_helper"
4
+ require "shared_contexts"
5
+
6
+ RSpec.describe Functions::ConflictingDependencyResolver do
7
+ include_context "in a temporary bundler directory"
8
+
9
+ let(:conflicting_dependency_resolver) do
10
+ described_class.new(
11
+ dependency_name: dependency_name,
12
+ target_version: target_version,
13
+ lockfile_name: "Gemfile.lock"
14
+ )
15
+ end
16
+
17
+ let(:dependency_name) { "dummy-pkg-a" }
18
+ let(:target_version) { "2.0.0" }
19
+
20
+ let(:project_name) { "blocked_by_subdep" }
21
+
22
+ describe "#conflicting_dependencies" do
23
+ subject(:conflicting_dependencies) do
24
+ in_tmp_folder { conflicting_dependency_resolver.conflicting_dependencies }
25
+ end
26
+
27
+ it "returns a list of dependencies that block the update" do
28
+ expect(conflicting_dependencies).to eq(
29
+ [{
30
+ "explanation" => "dummy-pkg-b (1.0.0) requires dummy-pkg-a (< 2.0.0)",
31
+ "name" => "dummy-pkg-b",
32
+ "version" => "1.0.0",
33
+ "requirement" => "< 2.0.0"
34
+ }]
35
+ )
36
+ end
37
+
38
+ context "for nested transitive dependencies" do
39
+ let(:project_name) { "transitive_blocking" }
40
+ let(:dependency_name) { "activesupport" }
41
+ let(:target_version) { "6.0.0" }
42
+
43
+ it "returns a list of dependencies that block the update" do
44
+ expect(conflicting_dependencies).to match_array(
45
+ [
46
+ {
47
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0)",
48
+ "name" => "rails",
49
+ "requirement" => "= 5.2.0",
50
+ "version" => "5.2.0"
51
+ },
52
+ {
53
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via actionpack (5.2.0)",
54
+ "name" => "actionpack",
55
+ "version" => "5.2.0",
56
+ "requirement" => "= 5.2.0"
57
+ },
58
+ {
59
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via actionview (5.2.0)",
60
+ "name" => "actionview",
61
+ "version" => "5.2.0",
62
+ "requirement" => "= 5.2.0"
63
+ },
64
+ {
65
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via activejob (5.2.0)",
66
+ "name" => "activejob",
67
+ "version" => "5.2.0",
68
+ "requirement" => "= 5.2.0"
69
+ },
70
+ {
71
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via activemodel (5.2.0)",
72
+ "name" => "activemodel",
73
+ "version" => "5.2.0",
74
+ "requirement" => "= 5.2.0"
75
+ },
76
+ {
77
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via activerecord (5.2.0)",
78
+ "name" => "activerecord",
79
+ "version" => "5.2.0",
80
+ "requirement" => "= 5.2.0"
81
+ },
82
+ {
83
+ "explanation" => "rails (5.2.0) requires activesupport (= 5.2.0) via railties (5.2.0)",
84
+ "name" => "railties",
85
+ "version" => "5.2.0",
86
+ "requirement" => "= 5.2.0"
87
+ }
88
+ ]
89
+ )
90
+ end
91
+ end
92
+
93
+ context "with multiple blocking dependencies" do
94
+ let(:dependency_name) { "activesupport" }
95
+ let(:current_version) { "5.0.0" }
96
+ let(:target_version) { "6.0.0" }
97
+ let(:project_name) { "multiple_blocking" }
98
+
99
+ it "returns all of the blocking dependencies" do
100
+ expect(conflicting_dependencies).to match_array(
101
+ [
102
+ {
103
+ "explanation" => "actionmailer (5.0.0) requires activesupport (= 5.0.0) via actionpack (5.0.0)",
104
+ "name" => "actionpack",
105
+ "version" => "5.0.0",
106
+ "requirement" => "= 5.0.0"
107
+ },
108
+ {
109
+ "explanation" => "actionview (5.0.0) requires activesupport (= 5.0.0)",
110
+ "name" => "actionview",
111
+ "version" => "5.0.0",
112
+ "requirement" => "= 5.0.0"
113
+ },
114
+ {
115
+ "explanation" => "actionmailer (5.0.0) requires activesupport (= 5.0.0) via activejob (5.0.0)",
116
+ "name" => "activejob",
117
+ "version" => "5.0.0",
118
+ "requirement" => "= 5.0.0"
119
+ }
120
+ ]
121
+ )
122
+ end
123
+ end
124
+
125
+ context "without any blocking dependencies" do
126
+ let(:target_version) { "1.0.0" }
127
+
128
+ it "returns an empty list" do
129
+ expect(conflicting_dependencies).to eq([])
130
+ end
131
+ end
132
+ end
133
+ end
@@ -0,0 +1,187 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "native_spec_helper"
4
+ require "shared_contexts"
5
+
6
+ RSpec.describe Functions::DependencySource do
7
+ include_context "in a temporary bundler directory"
8
+
9
+ let(:dependency_source) do
10
+ described_class.new(
11
+ gemfile_name: "Gemfile",
12
+ dependency_name: dependency_name
13
+ )
14
+ end
15
+
16
+ let(:dependency_name) { "business" }
17
+
18
+ let(:project_name) { "specified_source_no_lockfile" }
19
+ let(:registry_url) { "https://repo.fury.io/greysteil/" }
20
+ let(:gemfury_business_url) do
21
+ "https://repo.fury.io/greysteil/api/v1/dependencies?gems=business"
22
+ end
23
+
24
+ before do
25
+ stub_request(:get, registry_url + "versions").
26
+ with(basic_auth: ["SECRET_CODES", ""]).
27
+ to_return(status: 404)
28
+ stub_request(:get, registry_url + "api/v1/dependencies").
29
+ with(basic_auth: ["SECRET_CODES", ""]).
30
+ to_return(status: 200)
31
+ stub_request(:get, gemfury_business_url).
32
+ with(basic_auth: ["SECRET_CODES", ""]).
33
+ to_return(status: 200, body: fixture("ruby", "gemfury_response"))
34
+ end
35
+
36
+ describe "#private_registry_versions" do
37
+ subject(:private_registry_versions) do
38
+ in_tmp_folder { dependency_source.private_registry_versions }
39
+ end
40
+
41
+ it "returns all versions from the private source" do
42
+ is_expected.to eq([
43
+ Gem::Version.new("1.5.0"),
44
+ Gem::Version.new("1.9.0"),
45
+ Gem::Version.new("1.10.0.beta")
46
+ ])
47
+ end
48
+
49
+ context "specified as the default source" do
50
+ let(:project_name) { "specified_default_source_no_lockfile" }
51
+
52
+ it "returns all versions from the private source" do
53
+ is_expected.to eq([
54
+ Gem::Version.new("1.5.0"),
55
+ Gem::Version.new("1.9.0"),
56
+ Gem::Version.new("1.10.0.beta")
57
+ ])
58
+ end
59
+ end
60
+
61
+ context "that we don't have authentication details for" do
62
+ before do
63
+ stub_request(:get, registry_url + "versions").
64
+ with(basic_auth: ["SECRET_CODES", ""]).
65
+ to_return(status: 401)
66
+ stub_request(:get, registry_url + "api/v1/dependencies").
67
+ with(basic_auth: ["SECRET_CODES", ""]).
68
+ to_return(status: 401)
69
+ stub_request(:get, registry_url + "specs.4.8.gz").
70
+ with(basic_auth: ["SECRET_CODES", ""]).
71
+ to_return(status: 401)
72
+ end
73
+
74
+ it "blows up with a useful error" do
75
+ error_class = Bundler::Fetcher::AuthenticationRequiredError
76
+ error_message = "Authentication is required for repo.fury.io"
77
+ expect { private_registry_versions }.
78
+ to raise_error do |error|
79
+ expect(error).to be_a(error_class)
80
+ expect(error.message).to include(error_message)
81
+ end
82
+ end
83
+ end
84
+
85
+ context "that we have bad authentication details for" do
86
+ before do
87
+ stub_request(:get, registry_url + "versions").
88
+ with(basic_auth: ["SECRET_CODES", ""]).
89
+ to_return(status: 403)
90
+ stub_request(:get, registry_url + "api/v1/dependencies").
91
+ with(basic_auth: ["SECRET_CODES", ""]).
92
+ to_return(status: 403)
93
+ stub_request(:get, registry_url + "specs.4.8.gz").
94
+ with(basic_auth: ["SECRET_CODES", ""]).
95
+ to_return(status: 403)
96
+ end
97
+
98
+ it "blows up with a useful error" do
99
+ error_class = Bundler::Fetcher::BadAuthenticationError
100
+ expect { private_registry_versions }.
101
+ to raise_error do |error|
102
+ expect(error).to be_a(error_class)
103
+ expect(error.message).
104
+ to include("Bad username or password for")
105
+ end
106
+ end
107
+ end
108
+
109
+ context "that bad-requested, but was a private repo" do
110
+ before do
111
+ stub_request(:get, registry_url + "versions").
112
+ with(basic_auth: ["SECRET_CODES", ""]).
113
+ to_return(status: 400)
114
+ stub_request(:get, registry_url + "api/v1/dependencies").
115
+ with(basic_auth: ["SECRET_CODES", ""]).
116
+ to_return(status: 400)
117
+ stub_request(:get, registry_url + "specs.4.8.gz").
118
+ with(basic_auth: ["SECRET_CODES", ""]).
119
+ to_return(status: 400)
120
+ end
121
+
122
+ it "blows up with a useful error" do
123
+ expect { private_registry_versions }.
124
+ to raise_error do |error|
125
+ expect(error).to be_a(Bundler::HTTPError)
126
+ expect(error.message).
127
+ to include("Could not fetch specs from")
128
+ end
129
+ end
130
+ end
131
+
132
+ context "that doesn't have details of the gem" do
133
+ before do
134
+ stub_request(:get, gemfury_business_url).
135
+ with(basic_auth: ["SECRET_CODES", ""]).
136
+ to_return(status: 404)
137
+
138
+ # Stub indexes to return details of other gems (but not this one)
139
+ stub_request(:get, registry_url + "specs.4.8.gz").
140
+ to_return(
141
+ status: 200,
142
+ body: fixture("ruby", "contribsys_old_index_response")
143
+ )
144
+ stub_request(:get, registry_url + "prerelease_specs.4.8.gz").
145
+ to_return(
146
+ status: 200,
147
+ body: fixture("ruby", "contribsys_old_index_prerelease_response")
148
+ )
149
+ end
150
+
151
+ it { is_expected.to be_empty }
152
+ end
153
+
154
+ context "that only implements the old Bundler index format..." do
155
+ let(:project_name) { "sidekiq_pro" }
156
+ let(:dependency_name) { "sidekiq-pro" }
157
+ let(:registry_url) { "https://gems.contribsys.com/" }
158
+
159
+ before do
160
+ stub_request(:get, registry_url + "versions").
161
+ with(basic_auth: %w(username password)).
162
+ to_return(status: 404)
163
+ stub_request(:get, registry_url + "api/v1/dependencies").
164
+ with(basic_auth: %w(username password)).
165
+ to_return(status: 404)
166
+ stub_request(:get, registry_url + "specs.4.8.gz").
167
+ with(basic_auth: %w(username password)).
168
+ to_return(
169
+ status: 200,
170
+ body: fixture("ruby", "contribsys_old_index_response")
171
+ )
172
+ stub_request(:get, registry_url + "prerelease_specs.4.8.gz").
173
+ with(basic_auth: %w(username password)).
174
+ to_return(
175
+ status: 200,
176
+ body: fixture("ruby", "contribsys_old_index_prerelease_response")
177
+ )
178
+ end
179
+
180
+ it "returns all versions from the private source" do
181
+ expect(private_registry_versions.length).to eql(70)
182
+ expect(private_registry_versions.min).to eql(Gem::Version.new("1.0.0"))
183
+ expect(private_registry_versions.max).to eql(Gem::Version.new("3.5.2"))
184
+ end
185
+ end
186
+ end
187
+ end
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "native_spec_helper"
4
+ require "shared_contexts"
5
+
6
+ RSpec.describe Functions::FileParser do
7
+ include_context "in a temporary bundler directory"
8
+
9
+ let(:dependency_source) do
10
+ described_class.new(
11
+ lockfile_name: "Gemfile.lock"
12
+ )
13
+ end
14
+
15
+ let(:project_name) { "gemfile" }
16
+
17
+ describe "#parsed_gemfile" do
18
+ subject(:parsed_gemfile) do
19
+ in_tmp_folder do
20
+ dependency_source.parsed_gemfile(gemfile_name: "Gemfile")
21
+ end
22
+ end
23
+
24
+ it "parses gemfile" do
25
+ parsed_gemfile = [
26
+ {
27
+ groups: [:default],
28
+ name: "business",
29
+ requirement: Gem::Requirement.new("~> 1.4.0"),
30
+ source: nil,
31
+ type: :runtime
32
+ },
33
+ {
34
+ groups: [:default],
35
+ name: "statesman",
36
+ requirement: Gem::Requirement.new("~> 1.2.0"),
37
+ source: nil,
38
+ type: :runtime
39
+ }
40
+ ]
41
+ is_expected.to eq(parsed_gemfile)
42
+ end
43
+ end
44
+
45
+ describe "#parsed_gemspec" do
46
+ let!(:gemspec_fixture) do
47
+ fixture("ruby", "gemspecs", "exact")
48
+ end
49
+
50
+ subject(:parsed_gemspec) do
51
+ in_tmp_folder do |tmp_path|
52
+ File.write(File.join(tmp_path, "test.gemspec"), gemspec_fixture)
53
+ dependency_source.parsed_gemspec(gemspec_name: "test.gemspec")
54
+ end
55
+ end
56
+
57
+ it "parses gemspec" do
58
+ parsed_gemspec = [
59
+ {
60
+ groups: nil,
61
+ name: "business",
62
+ requirement: Gem::Requirement.new("= 1.0.0"),
63
+ source: nil,
64
+ type: :runtime
65
+ },
66
+ {
67
+ groups: nil,
68
+ name: "statesman",
69
+ requirement: Gem::Requirement.new("= 1.0.0"),
70
+ source: nil,
71
+ type: :runtime
72
+ }
73
+ ]
74
+ is_expected.to eq(parsed_gemspec)
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,97 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "native_spec_helper"
4
+ require "shared_contexts"
5
+
6
+ RSpec.describe Functions::VersionResolver do
7
+ include_context "in a temporary bundler directory"
8
+ include_context "stub rubygems compact index"
9
+
10
+ let(:version_resolver) do
11
+ described_class.new(
12
+ dependency_name: dependency_name,
13
+ dependency_requirements: dependency_requirements,
14
+ gemfile_name: "Gemfile",
15
+ lockfile_name: "Gemfile.lock"
16
+ )
17
+ end
18
+
19
+ let(:dependency_name) { "business" }
20
+ let(:dependency_requirements) do
21
+ [{
22
+ file: "Gemfile",
23
+ requirement: requirement_string,
24
+ groups: [],
25
+ source: source
26
+ }]
27
+ end
28
+ let(:source) { nil }
29
+
30
+ let(:rubygems_url) { "https://index.rubygems.org/api/v1/" }
31
+ let(:old_index_url) { rubygems_url + "dependencies" }
32
+
33
+ describe "#version_details" do
34
+ subject do
35
+ in_tmp_folder { version_resolver.version_details }
36
+ end
37
+
38
+ let(:project_name) { "gemfile" }
39
+ let(:requirement_string) { " >= 0" }
40
+
41
+ its([:version]) { is_expected.to eq(Gem::Version.new("1.4.0")) }
42
+ its([:fetcher]) { is_expected.to eq("Bundler::Fetcher::CompactIndex") }
43
+
44
+ context "with a private gemserver source" do
45
+ include_context "stub rubygems compact index"
46
+
47
+ let(:project_name) { "specified_source" }
48
+ let(:requirement_string) { ">= 0" }
49
+
50
+ before do
51
+ gemfury_url = "https://repo.fury.io/greysteil/"
52
+ gemfury_deps_url = gemfury_url + "api/v1/dependencies"
53
+
54
+ stub_request(:get, gemfury_url + "versions").
55
+ to_return(status: 200, body: fixture("ruby", "gemfury-index"))
56
+ stub_request(:get, gemfury_url + "info/business").to_return(status: 404)
57
+ stub_request(:get, gemfury_deps_url).to_return(status: 200)
58
+ stub_request(:get, gemfury_deps_url + "?gems=business,statesman").
59
+ to_return(status: 200, body: fixture("ruby", "gemfury_response"))
60
+ stub_request(:get, gemfury_deps_url + "?gems=business").
61
+ to_return(status: 200, body: fixture("ruby", "gemfury_response"))
62
+ stub_request(:get, gemfury_deps_url + "?gems=statesman").
63
+ to_return(status: 200, body: fixture("ruby", "gemfury_response"))
64
+ end
65
+
66
+ its([:version]) { is_expected.to eq(Gem::Version.new("1.9.0")) }
67
+ its([:fetcher]) { is_expected.to eq("Bundler::Fetcher::Dependency") }
68
+ end
69
+
70
+ context "with a git source" do
71
+ let(:project_name) { "git_source" }
72
+
73
+ its([:version]) { is_expected.to eq(Gem::Version.new("1.6.0")) }
74
+ its([:fetcher]) { is_expected.to be_nil }
75
+ end
76
+
77
+ context "when Bundler's compact index is down" do
78
+ before do
79
+ stub_request(:get, "https://index.rubygems.org/versions").
80
+ to_return(status: 500, body: "We'll be back soon")
81
+ stub_request(:get, "https://index.rubygems.org/info/public_suffix").
82
+ to_return(status: 500, body: "We'll be back soon")
83
+ stub_request(:get, old_index_url).to_return(status: 200)
84
+ stub_request(:get, old_index_url + "?gems=business,statesman").
85
+ to_return(
86
+ status: 200,
87
+ body: fixture("ruby",
88
+ "rubygems_responses",
89
+ "dependencies-default-gemfile")
90
+ )
91
+ end
92
+
93
+ its([:version]) { is_expected.to eq(Gem::Version.new("1.4.0")) }
94
+ its([:fetcher]) { is_expected.to eq("Bundler::Fetcher::Dependency") }
95
+ end
96
+ end
97
+ end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rspec/its"
4
+ require "webmock/rspec"
5
+ require "byebug"
6
+
7
+ $LOAD_PATH.unshift(File.expand_path("../lib", __dir__))
8
+ $LOAD_PATH.unshift(File.expand_path("../monkey_patches", __dir__))
9
+
10
+ # Bundler monkey patches
11
+ require "definition_ruby_version_patch"
12
+ require "definition_bundler_version_patch"
13
+ require "git_source_patch"
14
+
15
+ require "functions"
16
+
17
+ RSpec.configure do |config|
18
+ config.color = true
19
+ config.order = :rand
20
+ config.mock_with(:rspec) { |mocks| mocks.verify_partial_doubles = true }
21
+ config.raise_errors_for_deprecations!
22
+ end
23
+
24
+ # Duplicated in lib/dependabot/bundler/file_updater/lockfile_updater.rb
25
+ # TODO: Stop sanitizing the lockfile once we have bundler 2 installed
26
+ LOCKFILE_ENDING = /(?<ending>\s*(?:RUBY VERSION|BUNDLED WITH).*)/m.freeze
27
+
28
+ def project_dependency_files(project)
29
+ project_path = File.expand_path(File.join("../../spec/fixtures/projects/bundler1", project))
30
+ Dir.chdir(project_path) do
31
+ # NOTE: Include dotfiles (e.g. .npmrc)
32
+ files = Dir.glob("**/*", File::FNM_DOTMATCH)
33
+ files = files.select { |f| File.file?(f) }
34
+ files.map do |filename|
35
+ content = File.read(filename)
36
+ if filename == "Gemfile.lock"
37
+ content = content.gsub(LOCKFILE_ENDING, "")
38
+ end
39
+ {
40
+ name: filename,
41
+ content: content
42
+ }
43
+ end
44
+ end
45
+ end
46
+
47
+ def fixture(*name)
48
+ File.read(File.join("../../spec/fixtures", File.join(*name)))
49
+ end
@@ -0,0 +1,59 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/compact_index_client"
4
+ require "bundler/compact_index_client/updater"
5
+
6
+ TMP_DIR_PATH = File.expand_path("../tmp", __dir__)
7
+
8
+ RSpec.shared_context "in a temporary bundler directory" do
9
+ let(:project_name) { "gemfile" }
10
+
11
+ let(:tmp_path) do
12
+ Dir.mkdir(TMP_DIR_PATH) unless Dir.exist?(TMP_DIR_PATH)
13
+ dir = Dir.mktmpdir("native_helper_spec_", TMP_DIR_PATH)
14
+ Pathname.new(dir).expand_path
15
+ end
16
+
17
+ before do
18
+ project_dependency_files(project_name).each do |file|
19
+ File.write(File.join(tmp_path, file[:name]), file[:content])
20
+ end
21
+ end
22
+
23
+ def in_tmp_folder(&block)
24
+ Dir.chdir(tmp_path, &block)
25
+ end
26
+ end
27
+
28
+ RSpec.shared_context "without caching rubygems" do
29
+ before do
30
+ # Stub Bundler to stop it using a cached versions of Rubygems
31
+ allow_any_instance_of(Bundler::CompactIndexClient::Updater).
32
+ to receive(:etag_for).and_return("")
33
+ end
34
+ end
35
+
36
+ RSpec.shared_context "stub rubygems compact index" do
37
+ include_context "without caching rubygems"
38
+
39
+ before do
40
+ # Stub the Rubygems index
41
+ stub_request(:get, "https://index.rubygems.org/versions").
42
+ to_return(
43
+ status: 200,
44
+ body: fixture("ruby", "rubygems_responses", "index")
45
+ )
46
+
47
+ # Stub the Rubygems response for each dependency we have a fixture for
48
+ fixtures =
49
+ Dir[File.join("../../spec", "fixtures", "ruby", "rubygems_responses", "info-*")]
50
+ fixtures.each do |path|
51
+ dep_name = path.split("/").last.gsub("info-", "")
52
+ stub_request(:get, "https://index.rubygems.org/info/#{dep_name}").
53
+ to_return(
54
+ status: 200,
55
+ body: fixture("ruby", "rubygems_responses", "info-#{dep_name}")
56
+ )
57
+ end
58
+ end
59
+ end
@@ -5,6 +5,7 @@ require "dependabot/file_parsers"
5
5
  require "dependabot/file_parsers/base"
6
6
  require "dependabot/bundler/file_updater/lockfile_updater"
7
7
  require "dependabot/bundler/native_helpers"
8
+ require "dependabot/bundler/helpers"
8
9
  require "dependabot/bundler/version"
9
10
  require "dependabot/shared_helpers"
10
11
  require "dependabot/errors"
@@ -129,8 +130,8 @@ module Dependabot
129
130
  repo_contents_path) do
130
131
  write_temporary_dependency_files
131
132
 
132
- SharedHelpers.run_helper_subprocess(
133
- command: NativeHelpers.helper_path,
133
+ NativeHelpers.run_bundler_subprocess(
134
+ bundler_version: bundler_version,
134
135
  function: "parsed_gemfile",
135
136
  args: {
136
137
  gemfile_name: gemfile.name,
@@ -159,8 +160,8 @@ module Dependabot
159
160
  repo_contents_path) do
160
161
  write_temporary_dependency_files
161
162
 
162
- SharedHelpers.run_helper_subprocess(
163
- command: NativeHelpers.helper_path,
163
+ NativeHelpers.run_bundler_subprocess(
164
+ bundler_version: bundler_version,
164
165
  function: "parsed_gemspec",
165
166
  args: {
166
167
  gemspec_name: file.name,
@@ -298,6 +299,10 @@ module Dependabot
298
299
  select { |f| f.name.end_with?(".rb") }.
299
300
  reject { |f| f.name == "gems.rb" }
300
301
  end
302
+
303
+ def bundler_version
304
+ @bundler_version ||= Helpers.bundler_version(lockfile)
305
+ end
301
306
  end
302
307
  end
303
308
  end
@@ -3,6 +3,7 @@
3
3
  require "dependabot/file_updaters"
4
4
  require "dependabot/file_updaters/base"
5
5
  require "dependabot/bundler/native_helpers"
6
+ require "dependabot/bundler/helpers"
6
7
  require "dependabot/file_updaters/vendor_updater"
7
8
 
8
9
  module Dependabot
@@ -75,8 +76,8 @@ module Dependabot
75
76
  return @vendor_cache_dir if defined?(@vendor_cache_dir)
76
77
 
77
78
  @vendor_cache_dir =
78
- SharedHelpers.run_helper_subprocess(
79
- command: NativeHelpers.helper_path,
79
+ NativeHelpers.run_bundler_subprocess(
80
+ bundler_version: bundler_version,
80
81
  function: "vendor_cache_dir",
81
82
  args: {
82
83
  dir: repo_contents_path
@@ -159,6 +160,10 @@ module Dependabot
159
160
  select { |file| file.name.end_with?(".gemspec") }.
160
161
  reject(&:support_file?)
161
162
  end
163
+
164
+ def bundler_version
165
+ @bundler_version ||= Helpers.bundler_version(lockfile)
166
+ end
162
167
  end
163
168
  end
164
169
  end
@@ -234,11 +234,8 @@ module Dependabot
234
234
  def remove_unnecessary_assignments(node)
235
235
  return unless node.is_a?(Parser::AST::Node)
236
236
 
237
- if unnecessary_assignment?(node) &&
238
- node.children.last&.location.respond_to?(:heredoc_end)
239
- range_to_remove = node.loc.expression.join(
240
- node.children.last.location.heredoc_end
241
- )
237
+ if unnecessary_assignment?(node) && node_includes_heredoc?(node)
238
+ range_to_remove = node.loc.expression.join(find_heredoc_end_range(node))
242
239
  return replace(range_to_remove, '"sanitized"')
243
240
  elsif unnecessary_assignment?(node)
244
241
  return replace(node.loc.expression, '"sanitized"')
@@ -249,6 +246,30 @@ module Dependabot
249
246
  end
250
247
  end
251
248
 
249
+ def node_includes_heredoc?(node)
250
+ find_heredoc_end_range(node)
251
+ end
252
+
253
+ # Performs a depth-first search for the first heredoc in the given
254
+ # Parser::AST::Node.
255
+ #
256
+ # Returns a Parser::Source::Range identifying the location of the end
257
+ # of the heredoc, or nil if no heredoc was found.
258
+ def find_heredoc_end_range(node)
259
+ return unless node.is_a?(Parser::AST::Node)
260
+
261
+ node.children.each do |child|
262
+ next unless child.is_a?(Parser::AST::Node)
263
+
264
+ return child.location.heredoc_end if child.location.respond_to?(:heredoc_end)
265
+
266
+ range = find_heredoc_end_range(child)
267
+ return range if range
268
+ end
269
+
270
+ nil
271
+ end
272
+
252
273
  def unnecessary_assignment?(node)
253
274
  return false unless node.is_a?(Parser::AST::Node)
254
275
  return false unless node.children.first.is_a?(Parser::AST::Node)
@@ -6,6 +6,7 @@ require "dependabot/shared_helpers"
6
6
  require "dependabot/errors"
7
7
  require "dependabot/bundler/file_updater"
8
8
  require "dependabot/bundler/native_helpers"
9
+ require "dependabot/bundler/helpers"
9
10
 
10
11
  module Dependabot
11
12
  module Bundler
@@ -64,8 +65,8 @@ module Dependabot
64
65
  ) do |tmp_dir|
65
66
  write_temporary_dependency_files
66
67
 
67
- SharedHelpers.run_helper_subprocess(
68
- command: NativeHelpers.helper_path,
68
+ NativeHelpers.run_bundler_subprocess(
69
+ bundler_version: bundler_version,
69
70
  function: "update_lockfile",
70
71
  args: {
71
72
  gemfile_name: gemfile.name,
@@ -301,6 +302,10 @@ module Dependabot
301
302
 
302
303
  lockfile.content.match?(/BUNDLED WITH\s+2/m)
303
304
  end
305
+
306
+ def bundler_version
307
+ @bundler_version ||= Helpers.bundler_version(lockfile)
308
+ end
304
309
  end
305
310
  end
306
311
  end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Dependabot
4
+ module Bundler
5
+ module Helpers
6
+ V1 = "1"
7
+ V2 = "2"
8
+
9
+ # TODO: Add support for bundler v2
10
+ # return "v2" if lockfile.content.match?(/BUNDLED WITH\s+2/m)
11
+ def self.bundler_version(_lockfile)
12
+ V1
13
+ end
14
+ end
15
+ end
16
+ end
@@ -1,10 +1,37 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "bundler"
4
+ require "dependabot/shared_helpers"
5
+
3
6
  module Dependabot
4
7
  module Bundler
5
8
  module NativeHelpers
6
- def self.helper_path
7
- "bundle exec ruby #{File.join(native_helpers_root, 'run.rb')}"
9
+ def self.run_bundler_subprocess(function:, args:, bundler_version:)
10
+ # Run helper suprocess with all bundler-related ENV variables removed
11
+ ::Bundler.with_original_env do
12
+ SharedHelpers.run_helper_subprocess(
13
+ command: helper_path(bundler_version: bundler_version),
14
+ function: function,
15
+ args: args,
16
+ env: {
17
+ # Bundler will pick the matching installed major version
18
+ "BUNDLER_VERSION" => bundler_version,
19
+ "BUNDLE_GEMFILE" => File.join(versioned_helper_path(bundler_version: bundler_version), "Gemfile"),
20
+ "BUNDLE_PATH" => File.join(versioned_helper_path(bundler_version: bundler_version), ".bundle"),
21
+ # Prevent the GEM_HOME from being set to a folder owned by root
22
+ "GEM_HOME" => File.join(versioned_helper_path(bundler_version: bundler_version), ".bundle")
23
+ }
24
+ )
25
+ end
26
+ end
27
+
28
+ def self.versioned_helper_path(bundler_version:)
29
+ native_helper_version = "v#{bundler_version}"
30
+ File.join(native_helpers_root, native_helper_version)
31
+ end
32
+
33
+ def self.helper_path(bundler_version:)
34
+ "ruby #{File.join(versioned_helper_path(bundler_version: bundler_version), 'run.rb')}"
8
35
  end
9
36
 
10
37
  def self.native_helpers_root
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "dependabot/bundler/update_checker"
4
4
  require "dependabot/bundler/native_helpers"
5
+ require "dependabot/bundler/helpers"
5
6
  require "dependabot/shared_helpers"
6
7
 
7
8
  module Dependabot
@@ -28,8 +29,8 @@ module Dependabot
28
29
  # * requirement [String] the requirement on the target_dependency
29
30
  def conflicting_dependencies(dependency:, target_version:)
30
31
  in_a_native_bundler_context(error_handling: false) do |tmp_dir|
31
- SharedHelpers.run_helper_subprocess(
32
- command: NativeHelpers.helper_path,
32
+ NativeHelpers.run_bundler_subprocess(
33
+ bundler_version: bundler_version,
33
34
  function: "conflicting_dependencies",
34
35
  args: {
35
36
  dir: tmp_dir,
@@ -42,6 +43,12 @@ module Dependabot
42
43
  )
43
44
  end
44
45
  end
46
+
47
+ private
48
+
49
+ def bundler_version
50
+ @bundler_version ||= Helpers.bundler_version(lockfile)
51
+ end
45
52
  end
46
53
  end
47
54
  end
@@ -3,6 +3,7 @@
3
3
  require "dependabot/bundler/file_parser"
4
4
  require "dependabot/bundler/file_updater/lockfile_updater"
5
5
  require "dependabot/bundler/native_helpers"
6
+ require "dependabot/bundler/helpers"
6
7
  require "dependabot/bundler/update_checker"
7
8
  require "dependabot/bundler/update_checker/requirements_updater"
8
9
  require "dependabot/errors"
@@ -43,8 +44,8 @@ module Dependabot
43
44
 
44
45
  def force_update
45
46
  in_a_native_bundler_context(error_handling: false) do |tmp_dir|
46
- updated_deps, specs = SharedHelpers.run_helper_subprocess(
47
- command: NativeHelpers.helper_path,
47
+ updated_deps, specs = NativeHelpers.run_bundler_subprocess(
48
+ bundler_version: bundler_version,
48
49
  function: "force_update",
49
50
  args: {
50
51
  dir: tmp_dir,
@@ -146,6 +147,10 @@ module Dependabot
146
147
 
147
148
  lockfile.content.match?(/BUNDLED WITH\s+2/m)
148
149
  end
150
+
151
+ def bundler_version
152
+ @bundler_version ||= Helpers.bundler_version(lockfile)
153
+ end
149
154
  end
150
155
  end
151
156
  end
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "dependabot/bundler/native_helpers"
4
+ require "dependabot/bundler/helpers"
5
+
3
6
  module Dependabot
4
7
  module Bundler
5
8
  class UpdateChecker
@@ -53,8 +56,8 @@ module Dependabot
53
56
 
54
57
  SharedHelpers.with_git_configured(credentials: credentials) do
55
58
  in_a_native_bundler_context do |tmp_dir|
56
- SharedHelpers.run_helper_subprocess(
57
- command: NativeHelpers.helper_path,
59
+ NativeHelpers.run_bundler_subprocess(
60
+ bundler_version: bundler_version,
58
61
  function: "depencency_source_latest_git_version",
59
62
  args: {
60
63
  dir: tmp_dir,
@@ -98,8 +101,8 @@ module Dependabot
98
101
  def private_registry_versions
99
102
  @private_registry_versions ||=
100
103
  in_a_native_bundler_context do |tmp_dir|
101
- SharedHelpers.run_helper_subprocess(
102
- command: NativeHelpers.helper_path,
104
+ NativeHelpers.run_bundler_subprocess(
105
+ bundler_version: bundler_version,
103
106
  function: "private_registry_versions",
104
107
  args: {
105
108
  dir: tmp_dir,
@@ -118,8 +121,8 @@ module Dependabot
118
121
  return @source_type = RUBYGEMS unless gemfile
119
122
 
120
123
  @source_type = in_a_native_bundler_context do |tmp_dir|
121
- SharedHelpers.run_helper_subprocess(
122
- command: NativeHelpers.helper_path,
124
+ NativeHelpers.run_bundler_subprocess(
125
+ bundler_version: bundler_version,
123
126
  function: "dependency_source_type",
124
127
  args: {
125
128
  dir: tmp_dir,
@@ -135,6 +138,15 @@ module Dependabot
135
138
  dependency_files.find { |f| f.name == "Gemfile" } ||
136
139
  dependency_files.find { |f| f.name == "gems.rb" }
137
140
  end
141
+
142
+ def lockfile
143
+ dependency_files.find { |f| f.name == "Gemfile.lock" } ||
144
+ dependency_files.find { |f| f.name == "gems.locked" }
145
+ end
146
+
147
+ def bundler_version
148
+ @bundler_version ||= Helpers.bundler_version(lockfile)
149
+ end
138
150
  end
139
151
  end
140
152
  end
@@ -4,6 +4,7 @@ require "excon"
4
4
 
5
5
  require "dependabot/bundler/update_checker"
6
6
  require "dependabot/bundler/native_helpers"
7
+ require "dependabot/bundler/helpers"
7
8
  require "dependabot/shared_helpers"
8
9
  require "dependabot/errors"
9
10
 
@@ -163,8 +164,8 @@ module Dependabot
163
164
 
164
165
  def inaccessible_git_dependencies
165
166
  in_a_native_bundler_context(error_handling: false) do |tmp_dir|
166
- git_specs = SharedHelpers.run_helper_subprocess(
167
- command: NativeHelpers.helper_path,
167
+ git_specs = NativeHelpers.run_bundler_subprocess(
168
+ bundler_version: bundler_version,
168
169
  function: "git_specs",
169
170
  args: {
170
171
  dir: tmp_dir,
@@ -187,8 +188,8 @@ module Dependabot
187
188
 
188
189
  def jfrog_source
189
190
  in_a_native_bundler_context(error_handling: false) do |dir|
190
- SharedHelpers.run_helper_subprocess(
191
- command: NativeHelpers.helper_path,
191
+ NativeHelpers.run_bundler_subprocess(
192
+ bundler_version: bundler_version,
192
193
  function: "jfrog_source",
193
194
  args: {
194
195
  dir: dir,
@@ -236,6 +237,10 @@ module Dependabot
236
237
 
237
238
  lockfile.content.match?(/BUNDLED WITH\s+2/m)
238
239
  end
240
+
241
+ def bundler_version
242
+ @bundler_version ||= Helpers.bundler_version(lockfile)
243
+ end
239
244
  end
240
245
  end
241
246
  end
@@ -2,6 +2,7 @@
2
2
 
3
3
  require "excon"
4
4
 
5
+ require "dependabot/bundler/helpers"
5
6
  require "dependabot/bundler/update_checker"
6
7
  require "dependabot/bundler/file_updater/lockfile_updater"
7
8
  require "dependabot/bundler/requirement"
@@ -75,8 +76,8 @@ module Dependabot
75
76
  # some errors we want to handle specifically ourselves, including
76
77
  # potentially retrying in the case of the Ruby version being locked
77
78
  in_a_native_bundler_context(error_handling: false) do |tmp_dir|
78
- details = SharedHelpers.run_helper_subprocess(
79
- command: NativeHelpers.helper_path,
79
+ details = NativeHelpers.run_bundler_subprocess(
80
+ bundler_version: bundler_version,
80
81
  function: "resolve_version",
81
82
  args: {
82
83
  dependency_name: dependency.name,
@@ -218,6 +219,10 @@ module Dependabot
218
219
 
219
220
  lockfile.content.match?(/BUNDLED WITH\s+2/m)
220
221
  end
222
+
223
+ def bundler_version
224
+ @bundler_version ||= Helpers.bundler_version(lockfile)
225
+ end
221
226
  end
222
227
  end
223
228
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dependabot-bundler
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.135.0
4
+ version: 0.138.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2021-03-05 00:00:00.000000000 Z
11
+ date: 2021-03-17 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: dependabot-common
@@ -16,14 +16,14 @@ dependencies:
16
16
  requirements:
17
17
  - - '='
18
18
  - !ruby/object:Gem::Version
19
- version: 0.135.0
19
+ version: 0.138.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.135.0
26
+ version: 0.138.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -185,18 +185,27 @@ executables: []
185
185
  extensions: []
186
186
  extra_rdoc_files: []
187
187
  files:
188
- - helpers/build
189
- - helpers/lib/functions.rb
190
- - helpers/lib/functions/conflicting_dependency_resolver.rb
191
- - helpers/lib/functions/dependency_source.rb
192
- - helpers/lib/functions/file_parser.rb
193
- - helpers/lib/functions/force_updater.rb
194
- - helpers/lib/functions/lockfile_updater.rb
195
- - helpers/lib/functions/version_resolver.rb
196
- - helpers/monkey_patches/definition_bundler_version_patch.rb
197
- - helpers/monkey_patches/definition_ruby_version_patch.rb
198
- - helpers/monkey_patches/git_source_patch.rb
199
- - helpers/run.rb
188
+ - helpers/v1/.bundle/config
189
+ - helpers/v1/.gitignore
190
+ - helpers/v1/Gemfile
191
+ - helpers/v1/build
192
+ - helpers/v1/lib/functions.rb
193
+ - helpers/v1/lib/functions/conflicting_dependency_resolver.rb
194
+ - helpers/v1/lib/functions/dependency_source.rb
195
+ - helpers/v1/lib/functions/file_parser.rb
196
+ - helpers/v1/lib/functions/force_updater.rb
197
+ - helpers/v1/lib/functions/lockfile_updater.rb
198
+ - helpers/v1/lib/functions/version_resolver.rb
199
+ - helpers/v1/monkey_patches/definition_bundler_version_patch.rb
200
+ - helpers/v1/monkey_patches/definition_ruby_version_patch.rb
201
+ - helpers/v1/monkey_patches/git_source_patch.rb
202
+ - helpers/v1/run.rb
203
+ - helpers/v1/spec/functions/conflicting_dependency_resolver_spec.rb
204
+ - helpers/v1/spec/functions/dependency_source_spec.rb
205
+ - helpers/v1/spec/functions/file_parser_spec.rb
206
+ - helpers/v1/spec/functions/version_resolver_spec.rb
207
+ - helpers/v1/spec/native_spec_helper.rb
208
+ - helpers/v1/spec/shared_contexts.rb
200
209
  - lib/dependabot/bundler.rb
201
210
  - lib/dependabot/bundler/file_fetcher.rb
202
211
  - lib/dependabot/bundler/file_fetcher/child_gemfile_finder.rb
@@ -216,6 +225,7 @@ files:
216
225
  - lib/dependabot/bundler/file_updater/lockfile_updater.rb
217
226
  - lib/dependabot/bundler/file_updater/requirement_replacer.rb
218
227
  - lib/dependabot/bundler/file_updater/ruby_requirement_setter.rb
228
+ - lib/dependabot/bundler/helpers.rb
219
229
  - lib/dependabot/bundler/metadata_finder.rb
220
230
  - lib/dependabot/bundler/native_helpers.rb
221
231
  - lib/dependabot/bundler/requirement.rb