dependabot-bundler 0.129.1 → 0.130.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: b239879862978b10704d5772fb74b3df2a77621b507f30918b9caba59fb48a4c
4
- data.tar.gz: e1e3f9b64a99e6740cdeb496450c6a04423987fa6196bd3dacf2a6004b8107a9
3
+ metadata.gz: a13db3fcb8629f8472eb78a6e9120a0fa557cb08c95f6f4009bbeab84c6c6e18
4
+ data.tar.gz: aeb964a11289fff3ee0c284f4af0425a8f0cc862de77609f80f246b8047a42de
5
5
  SHA512:
6
- metadata.gz: cc82c28e54933523085591ef557352224d0dcdf2d28c83b2de58d0df341cd6d5bf053e21e2d400a8184d34a5783e5bb2e2ecf530e3a5fb079538f76763e2fa5d
7
- data.tar.gz: 3b743693c65c964babe9a6df82ee56113aed699bcb7eef69c60ac88a650902855d0ed29ba27d21638b79715677422c4e68425998585c94b536125d89575bb2f4
6
+ metadata.gz: 698e4f197b91844a0eecc0ecf0357c63045c5f18f54ac7563f42ece3e98512d27b4d337781d50d7093e75f3fe3cc820229dc8ec4390527eff397966dcfa38451
7
+ data.tar.gz: 1117227e92345f6b68283aa540653a1825628436eb8d49377d6f7fbafe8d41e9699d9393b65803705e5327be1d8149030351066d18e979ffc1d5e19807d744ec
@@ -0,0 +1,18 @@
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
+ "$install_dir"
17
+
18
+ cd "$install_dir"
@@ -0,0 +1,193 @@
1
+ require "functions/file_parser"
2
+ require "functions/force_updater"
3
+ require "functions/lockfile_updater"
4
+ require "functions/dependency_source"
5
+ require "functions/version_resolver"
6
+ require "functions/conflicting_dependency_resolver"
7
+
8
+ module Functions
9
+ def self.parsed_gemfile(lockfile_name:, gemfile_name:, dir:)
10
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
11
+ using_bundler2: false)
12
+ FileParser.new(lockfile_name: lockfile_name).
13
+ parsed_gemfile(gemfile_name: gemfile_name)
14
+ end
15
+
16
+ def self.parsed_gemspec(lockfile_name:, gemspec_name:, dir:)
17
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
18
+ using_bundler2: false)
19
+ FileParser.new(lockfile_name: lockfile_name).
20
+ parsed_gemspec(gemspec_name: gemspec_name)
21
+ end
22
+
23
+ def self.vendor_cache_dir(dir:)
24
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
25
+ using_bundler2: false)
26
+ Bundler.app_cache
27
+ end
28
+
29
+ def self.update_lockfile(dir:, gemfile_name:, lockfile_name:, using_bundler2:,
30
+ credentials:, dependencies:)
31
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
32
+ using_bundler2: using_bundler2)
33
+ LockfileUpdater.new(
34
+ gemfile_name: gemfile_name,
35
+ lockfile_name: lockfile_name,
36
+ dependencies: dependencies
37
+ ).run
38
+ end
39
+
40
+ def self.force_update(dir:, dependency_name:, target_version:, gemfile_name:,
41
+ lockfile_name:, using_bundler2:, credentials:,
42
+ update_multiple_dependencies:)
43
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
44
+ using_bundler2: using_bundler2)
45
+ ForceUpdater.new(
46
+ dependency_name: dependency_name,
47
+ target_version: target_version,
48
+ gemfile_name: gemfile_name,
49
+ lockfile_name: lockfile_name,
50
+ update_multiple_dependencies: update_multiple_dependencies
51
+ ).run
52
+ end
53
+
54
+ def self.dependency_source_type(gemfile_name:, dependency_name:, dir:,
55
+ credentials:)
56
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
57
+ using_bundler2: false)
58
+
59
+ DependencySource.new(
60
+ gemfile_name: gemfile_name,
61
+ dependency_name: dependency_name
62
+ ).type
63
+ end
64
+
65
+ def self.depencency_source_latest_git_version(gemfile_name:, dependency_name:,
66
+ dir:, credentials:,
67
+ dependency_source_url:,
68
+ dependency_source_branch:)
69
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
70
+ using_bundler2: false)
71
+ DependencySource.new(
72
+ gemfile_name: gemfile_name,
73
+ dependency_name: dependency_name
74
+ ).latest_git_version(
75
+ dependency_source_url: dependency_source_url,
76
+ dependency_source_branch: dependency_source_branch
77
+ )
78
+ end
79
+
80
+ def self.private_registry_versions(gemfile_name:, dependency_name:, dir:,
81
+ credentials:)
82
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
83
+ using_bundler2: false)
84
+
85
+ DependencySource.new(
86
+ gemfile_name: gemfile_name,
87
+ dependency_name: dependency_name
88
+ ).private_registry_versions
89
+ end
90
+
91
+ def self.resolve_version(dependency_name:, dependency_requirements:,
92
+ gemfile_name:, lockfile_name:, using_bundler2:,
93
+ dir:, credentials:)
94
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
95
+ using_bundler2: using_bundler2)
96
+ VersionResolver.new(
97
+ dependency_name: dependency_name,
98
+ dependency_requirements: dependency_requirements,
99
+ gemfile_name: gemfile_name,
100
+ lockfile_name: lockfile_name
101
+ ).version_details
102
+ end
103
+
104
+ def self.jfrog_source(dir:, gemfile_name:, credentials:, using_bundler2:)
105
+ # Set flags and credentials
106
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
107
+ using_bundler2: using_bundler2)
108
+
109
+ Bundler::Definition.build(gemfile_name, nil, {}).
110
+ send(:sources).
111
+ rubygems_remotes.
112
+ find { |uri| uri.host.include?("jfrog") }&.
113
+ host
114
+ end
115
+
116
+ def self.git_specs(dir:, gemfile_name:, credentials:, using_bundler2:)
117
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
118
+ using_bundler2: using_bundler2)
119
+
120
+ git_specs = Bundler::Definition.build(gemfile_name, nil, {}).dependencies.
121
+ select do |spec|
122
+ spec.source.is_a?(Bundler::Source::Git)
123
+ end
124
+ git_specs.map do |spec|
125
+ # Piggy-back off some private Bundler methods to configure the
126
+ # URI with auth details in the same way Bundler does.
127
+ git_proxy = spec.source.send(:git_proxy)
128
+ auth_uri = spec.source.uri.gsub("git://", "https://")
129
+ auth_uri = git_proxy.send(:configured_uri_for, auth_uri)
130
+ auth_uri += ".git" unless auth_uri.end_with?(".git")
131
+ auth_uri += "/info/refs?service=git-upload-pack"
132
+ {
133
+ uri: spec.source.uri,
134
+ auth_uri: auth_uri
135
+ }
136
+ end
137
+ end
138
+
139
+ def self.set_bundler_flags_and_credentials(dir:, credentials:,
140
+ using_bundler2:)
141
+ dir = dir ? Pathname.new(dir) : dir
142
+ Bundler.instance_variable_set(:@root, dir)
143
+
144
+ # Remove installed gems from the default Rubygems index
145
+ Gem::Specification.all =
146
+ Gem::Specification.send(:default_stubs, "*.gemspec")
147
+
148
+ # Set auth details
149
+ relevant_credentials(credentials).each do |cred|
150
+ token = cred["token"] ||
151
+ "#{cred['username']}:#{cred['password']}"
152
+
153
+ Bundler.settings.set_command_option(
154
+ cred.fetch("host"),
155
+ token.gsub("@", "%40F").gsub("?", "%3F")
156
+ )
157
+ end
158
+
159
+ # Use HTTPS for GitHub if lockfile was generated by Bundler 2
160
+ if using_bundler2
161
+ Bundler.settings.set_command_option("forget_cli_options", "true")
162
+ Bundler.settings.set_command_option("github.https", "true")
163
+ end
164
+ end
165
+
166
+ def self.relevant_credentials(credentials)
167
+ [
168
+ *git_source_credentials(credentials),
169
+ *private_registry_credentials(credentials)
170
+ ].select { |cred| cred["password"] || cred["token"] }
171
+ end
172
+
173
+ def self.private_registry_credentials(credentials)
174
+ credentials.
175
+ select { |cred| cred["type"] == "rubygems_server" }
176
+ end
177
+
178
+ def self.git_source_credentials(credentials)
179
+ credentials.
180
+ select { |cred| cred["type"] == "git_source" }
181
+ end
182
+
183
+ def self.conflicting_dependencies(dir:, dependency_name:, target_version:,
184
+ lockfile_name:, using_bundler2:, credentials:)
185
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
186
+ using_bundler2: using_bundler2)
187
+ ConflictingDependencyResolver.new(
188
+ dependency_name: dependency_name,
189
+ target_version: target_version,
190
+ lockfile_name: lockfile_name
191
+ ).conflicting_dependencies
192
+ end
193
+ 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
@@ -0,0 +1,105 @@
1
+ module Functions
2
+ class FileParser
3
+ def initialize(lockfile_name:)
4
+ @lockfile_name = lockfile_name
5
+ end
6
+
7
+ attr_reader :lockfile_name
8
+
9
+ def parsed_gemfile(gemfile_name:)
10
+ Bundler::Definition.build(gemfile_name, nil, {}).
11
+ dependencies.select(&:current_platform?).
12
+ reject { |dep| dep.source.is_a?(Bundler::Source::Gemspec) }.
13
+ map(&method(:serialize_bundler_dependency))
14
+ end
15
+
16
+ def parsed_gemspec(gemspec_name:)
17
+ Bundler.load_gemspec_uncached(gemspec_name).
18
+ dependencies.
19
+ map(&method(:serialize_bundler_dependency))
20
+ end
21
+
22
+ private
23
+
24
+ def lockfile
25
+ return @lockfile if defined?(@lockfile)
26
+
27
+ @lockfile =
28
+ begin
29
+ return unless lockfile_name && File.exist?(lockfile_name)
30
+
31
+ File.read(lockfile_name)
32
+ end
33
+ end
34
+
35
+ def parsed_lockfile
36
+ return unless lockfile
37
+
38
+ @parsed_lockfile ||= Bundler::LockfileParser.new(lockfile)
39
+ end
40
+
41
+ def source_from_lockfile(dependency_name)
42
+ parsed_lockfile&.specs.find { |s| s.name == dependency_name }&.source
43
+ end
44
+
45
+ def source_for(dependency)
46
+ source = dependency.source
47
+ if lockfile && default_rubygems?(source)
48
+ # If there's a lockfile and the Gemfile doesn't have anything
49
+ # interesting to say about the source, check that.
50
+ source = source_from_lockfile(dependency.name)
51
+ end
52
+ raise "Bad source: #{source}" unless sources.include?(source.class)
53
+
54
+ return nil if default_rubygems?(source)
55
+
56
+ details = { type: source.class.name.split("::").last.downcase }
57
+ if source.is_a?(Bundler::Source::Git)
58
+ details.merge!(git_source_details(source))
59
+ end
60
+ if source.is_a?(Bundler::Source::Rubygems)
61
+ details[:url] = source.remotes.first.to_s
62
+ end
63
+ details
64
+ end
65
+
66
+ def git_source_details(source)
67
+ {
68
+ url: source.uri,
69
+ branch: source.branch || "master",
70
+ ref: source.ref
71
+ }
72
+ end
73
+
74
+ def default_rubygems?(source)
75
+ return true if source.nil?
76
+ return false unless source.is_a?(Bundler::Source::Rubygems)
77
+
78
+ source.remotes.any? { |r| r.to_s.include?("rubygems.org") }
79
+ end
80
+
81
+ def serialize_bundler_dependency(dependency)
82
+ {
83
+ name: dependency.name,
84
+ requirement: dependency.requirement,
85
+ groups: dependency.groups,
86
+ source: source_for(dependency),
87
+ type: dependency.type
88
+ }
89
+ end
90
+
91
+ # Can't be a constant because some of these don't exist in bundler
92
+ # 1.15, which used to cause issues on Heroku (causing exception on boot).
93
+ # TODO: Check if this will be an issue with multiple bundler versions
94
+ def sources
95
+ [
96
+ NilClass,
97
+ Bundler::Source::Rubygems,
98
+ Bundler::Source::Git,
99
+ Bundler::Source::Path,
100
+ Bundler::Source::Gemspec,
101
+ Bundler::Source::Metadata
102
+ ]
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,167 @@
1
+ module Functions
2
+ class ForceUpdater
3
+ class TransitiveDependencyError < StandardError; end
4
+
5
+ def initialize(dependency_name:, target_version:, gemfile_name:,
6
+ lockfile_name:, update_multiple_dependencies:)
7
+ @dependency_name = dependency_name
8
+ @target_version = target_version
9
+ @gemfile_name = gemfile_name
10
+ @lockfile_name = lockfile_name
11
+ @update_multiple_dependencies = update_multiple_dependencies
12
+ end
13
+
14
+ def run
15
+ # Only allow upgrades. Otherwise it's unlikely that this
16
+ # resolution will be found by the FileUpdater
17
+ Bundler.settings.set_command_option(
18
+ "only_update_to_newer_versions",
19
+ true
20
+ )
21
+
22
+ dependencies_to_unlock = []
23
+
24
+ begin
25
+ definition = build_definition(dependencies_to_unlock: dependencies_to_unlock)
26
+ definition.resolve_remotely!
27
+ specs = definition.resolve
28
+ updates = [{ name: dependency_name }] +
29
+ dependencies_to_unlock.map { |dep| { name: dep.name } }
30
+ specs = specs.map do |dep|
31
+ {
32
+ name: dep.name,
33
+ version: dep.version
34
+ }
35
+ end
36
+ [updates, specs]
37
+ rescue Bundler::VersionConflict => e
38
+ raise unless update_multiple_dependencies?
39
+
40
+ # TODO: Not sure this won't unlock way too many things...
41
+ new_dependencies_to_unlock =
42
+ new_dependencies_to_unlock_from(
43
+ error: e,
44
+ already_unlocked: dependencies_to_unlock
45
+ )
46
+
47
+ raise if new_dependencies_to_unlock.none?
48
+
49
+ dependencies_to_unlock += new_dependencies_to_unlock
50
+ retry
51
+ end
52
+ end
53
+
54
+ private
55
+
56
+ attr_reader :dependency_name, :target_version, :gemfile_name,
57
+ :lockfile_name, :credentials,
58
+ :update_multiple_dependencies
59
+ alias update_multiple_dependencies? update_multiple_dependencies
60
+
61
+ def new_dependencies_to_unlock_from(error:, already_unlocked:)
62
+ potentials_deps =
63
+ relevant_conflicts(error, already_unlocked).
64
+ flat_map(&:requirement_trees).
65
+ reject do |tree|
66
+ # If the final requirement wasn't specific, it can't be binding
67
+ next true if tree.last.requirement == Gem::Requirement.new(">= 0")
68
+
69
+ # If the conflict wasn't for the dependency we're updating then
70
+ # we don't have enough info to reject it
71
+ next false unless tree.last.name == dependency_name
72
+
73
+ # If the final requirement *was* for the dependency we're updating
74
+ # then we can ignore the tree if it permits the target version
75
+ tree.last.requirement.satisfied_by?(
76
+ Gem::Version.new(target_version)
77
+ )
78
+ end.map(&:first)
79
+
80
+ potentials_deps.
81
+ reject { |dep| already_unlocked.map(&:name).include?(dep.name) }.
82
+ reject { |dep| [dependency_name, "ruby\0"].include?(dep.name) }.
83
+ uniq
84
+ end
85
+
86
+ def relevant_conflicts(error, dependencies_being_unlocked)
87
+ names = [*dependencies_being_unlocked.map(&:name), dependency_name]
88
+
89
+ # For a conflict to be relevant to the updates we're making it must be
90
+ # 1) caused by a new requirement introduced by our unlocking, or
91
+ # 2) caused by an old requirement that prohibits the update.
92
+ # Hence, we look at the beginning and end of the requirement trees
93
+ error.cause.conflicts.values.
94
+ select do |conflict|
95
+ conflict.requirement_trees.any? do |t|
96
+ names.include?(t.last.name) || names.include?(t.first.name)
97
+ end
98
+ end
99
+ end
100
+
101
+ def build_definition(dependencies_to_unlock:)
102
+ gems_to_unlock = dependencies_to_unlock.map(&:name) + [dependency_name]
103
+ definition = Bundler::Definition.build(
104
+ gemfile_name,
105
+ lockfile_name,
106
+ gems: gems_to_unlock + subdependencies,
107
+ lock_shared_dependencies: true
108
+ )
109
+
110
+ # Remove the Gemfile / gemspec requirements on the gems we're
111
+ # unlocking (i.e., completely unlock them)
112
+ gems_to_unlock.each do |gem_name|
113
+ unlock_gem(definition: definition, gem_name: gem_name)
114
+ end
115
+
116
+ dep = definition.dependencies.
117
+ find { |d| d.name == dependency_name }
118
+
119
+ # If the dependency is not found in the Gemfile it means this is a
120
+ # transitive dependency that we can't force update.
121
+ raise TransitiveDependencyError unless dep
122
+
123
+ # Set the requirement for the gem we're forcing an update of
124
+ new_req = Gem::Requirement.create("= #{target_version}")
125
+ dep.instance_variable_set(:@requirement, new_req)
126
+ dep.source = nil if dep.source.is_a?(Bundler::Source::Git)
127
+
128
+ definition
129
+ end
130
+
131
+ def lockfile
132
+ return @lockfile if defined?(@lockfile)
133
+
134
+ @lockfile =
135
+ begin
136
+ return unless lockfile_name && File.exist?(lockfile_name)
137
+
138
+ File.read(lockfile_name)
139
+ end
140
+ end
141
+
142
+ def subdependencies
143
+ # If there's no lockfile we don't need to worry about
144
+ # subdependencies
145
+ return [] unless lockfile
146
+
147
+ all_deps = Bundler::LockfileParser.new(lockfile).
148
+ specs.map(&:name).map(&:to_s)
149
+ top_level = Bundler::Definition.
150
+ build(gemfile_name, lockfile_name, {}).
151
+ dependencies.map(&:name).map(&:to_s)
152
+
153
+ all_deps - top_level
154
+ end
155
+
156
+ def unlock_gem(definition:, gem_name:)
157
+ dep = definition.dependencies.find { |d| d.name == gem_name }
158
+ version = definition.locked_gems.specs.
159
+ find { |d| d.name == gem_name }.version
160
+
161
+ dep&.instance_variable_set(
162
+ :@requirement,
163
+ Gem::Requirement.create(">= #{version}")
164
+ )
165
+ end
166
+ end
167
+ end
@@ -0,0 +1,224 @@
1
+ module Functions
2
+ class LockfileUpdater
3
+ RETRYABLE_ERRORS = [Bundler::HTTPError].freeze
4
+ GEM_NOT_FOUND_ERROR_REGEX =
5
+ /
6
+ locked\sto\s(?<name>[^\s]+)\s\(|
7
+ not\sfind\s(?<name>[^\s]+)-\d|
8
+ has\s(?<name>[^\s]+)\slocked\sat
9
+ /x.freeze
10
+
11
+ def initialize(gemfile_name:, lockfile_name:, dependencies:)
12
+ @gemfile_name = gemfile_name
13
+ @lockfile_name = lockfile_name
14
+ @dependencies = dependencies
15
+ end
16
+
17
+ def run
18
+ generate_lockfile
19
+ end
20
+
21
+ private
22
+
23
+ attr_reader :gemfile_name, :lockfile_name, :dependencies
24
+
25
+ def generate_lockfile
26
+ dependencies_to_unlock = dependencies.map { |d| d.fetch("name") }
27
+
28
+ begin
29
+ definition = build_definition(dependencies_to_unlock)
30
+
31
+ old_reqs = lock_deps_being_updated_to_exact_versions(definition)
32
+
33
+ definition.resolve_remotely!
34
+
35
+ old_reqs.each do |dep_name, old_req|
36
+ d_dep = definition.dependencies.find { |d| d.name == dep_name }
37
+ if old_req == :none then definition.dependencies.delete(d_dep)
38
+ else
39
+ d_dep.instance_variable_set(:@requirement, old_req)
40
+ end
41
+ end
42
+
43
+ cache_vendored_gems(definition) if Bundler.app_cache.exist?
44
+
45
+ definition.to_lock
46
+ rescue Bundler::GemNotFound => e
47
+ unlock_yanked_gem(dependencies_to_unlock, e) && retry
48
+ rescue Bundler::VersionConflict => e
49
+ unlock_blocking_subdeps(dependencies_to_unlock, e) && retry
50
+ rescue *RETRYABLE_ERRORS
51
+ raise if @retrying
52
+
53
+ @retrying = true
54
+ sleep(rand(1.0..5.0))
55
+ retry
56
+ end
57
+ end
58
+
59
+ def cache_vendored_gems(definition)
60
+ # Dependencies that have been unlocked for the update (including
61
+ # sub-dependencies)
62
+ unlocked_gems = definition.instance_variable_get(:@unlock).
63
+ fetch(:gems).reject { |gem| __keep_on_prune?(gem) }
64
+ bundler_opts = {
65
+ cache_all: true,
66
+ cache_all_platforms: true,
67
+ no_prune: true
68
+ }
69
+
70
+ Bundler.settings.temporary(**bundler_opts) do
71
+ # Fetch and cache gems on all platforms without pruning
72
+ Bundler::Runtime.new(nil, definition).cache
73
+
74
+ # Only prune unlocked gems (the original implementation is in
75
+ # Bundler::Runtime)
76
+ cache_path = Bundler.app_cache
77
+ resolve = definition.resolve
78
+ prune_gem_cache(resolve, cache_path, unlocked_gems)
79
+ prune_git_and_path_cache(resolve, cache_path)
80
+ end
81
+ end
82
+
83
+ # This is not officially supported and may be removed without notice.
84
+ def __keep_on_prune?(spec_name)
85
+ unless (specs = Bundler.settings[:persistent_gems_after_clean])
86
+ return false
87
+ end
88
+
89
+ specs.include?(spec_name)
90
+ end
91
+
92
+ # Copied from Bundler::Runtime: Modified to only prune gems that have
93
+ # been unlocked
94
+ def prune_gem_cache(resolve, cache_path, unlocked_gems)
95
+ cached_gems = Dir["#{cache_path}/*.gem"]
96
+
97
+ outdated_gems = cached_gems.reject do |path|
98
+ spec = Bundler.rubygems.spec_from_gem path
99
+
100
+ !unlocked_gems.include?(spec.name) || resolve.any? do |s|
101
+ s.name == spec.name && s.version == spec.version &&
102
+ !s.source.is_a?(Bundler::Source::Git)
103
+ end
104
+ end
105
+
106
+ return unless outdated_gems.any?
107
+
108
+ outdated_gems.each do |path|
109
+ File.delete(path)
110
+ end
111
+ end
112
+
113
+ # Copied from Bundler::Runtime
114
+ def prune_git_and_path_cache(resolve, cache_path)
115
+ cached_git_and_path = Dir["#{cache_path}/*/.bundlecache"]
116
+
117
+ outdated_git_and_path = cached_git_and_path.reject do |path|
118
+ name = File.basename(File.dirname(path))
119
+
120
+ resolve.any? do |s|
121
+ s.source.respond_to?(:app_cache_dirname) &&
122
+ s.source.app_cache_dirname == name
123
+ end
124
+ end
125
+
126
+ return unless outdated_git_and_path.any?
127
+
128
+ outdated_git_and_path.each do |path|
129
+ path = File.dirname(path)
130
+ FileUtils.rm_rf(path)
131
+ end
132
+ end
133
+
134
+ def unlock_yanked_gem(dependencies_to_unlock, error)
135
+ raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
136
+
137
+ gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX).
138
+ named_captures["name"]
139
+ raise if dependencies_to_unlock.include?(gem_name)
140
+
141
+ dependencies_to_unlock << gem_name
142
+ end
143
+
144
+ # rubocop:disable Metrics/PerceivedComplexity
145
+ def unlock_blocking_subdeps(dependencies_to_unlock, error)
146
+ all_deps = Bundler::LockfileParser.new(lockfile).
147
+ specs.map(&:name).map(&:to_s)
148
+ top_level = build_definition([]).dependencies.
149
+ map(&:name).map(&:to_s)
150
+ allowed_new_unlocks = all_deps - top_level - dependencies_to_unlock
151
+
152
+ raise if allowed_new_unlocks.none?
153
+
154
+ # Unlock any sub-dependencies that Bundler reports caused the
155
+ # conflict
156
+ potentials_deps =
157
+ error.cause.conflicts.values.
158
+ flat_map(&:requirement_trees).
159
+ map do |tree|
160
+ tree.find { |req| allowed_new_unlocks.include?(req.name) }
161
+ end.compact.map(&:name)
162
+
163
+ # If there are specific dependencies we can unlock, unlock them
164
+ if potentials_deps.any?
165
+ return dependencies_to_unlock.append(*potentials_deps)
166
+ end
167
+
168
+ # Fall back to unlocking *all* sub-dependencies. This is required
169
+ # because Bundler's VersionConflict objects don't include enough
170
+ # information to chart the full path through all conflicts unwound
171
+ dependencies_to_unlock.append(*allowed_new_unlocks)
172
+ end
173
+ # rubocop:enable Metrics/PerceivedComplexity
174
+
175
+ def build_definition(dependencies_to_unlock)
176
+ defn = Bundler::Definition.build(
177
+ gemfile_name,
178
+ lockfile_name,
179
+ gems: dependencies_to_unlock
180
+ )
181
+
182
+ # Bundler unlocks the sub-dependencies of gems it is passed even
183
+ # if those sub-deps are top-level dependencies. We only want true
184
+ # subdeps unlocked, like they were in the UpdateChecker, so we
185
+ # mutate the unlocked gems array.
186
+ unlocked = defn.instance_variable_get(:@unlock).fetch(:gems)
187
+ must_not_unlock = defn.dependencies.map(&:name).map(&:to_s) -
188
+ dependencies_to_unlock
189
+ unlocked.reject! { |n| must_not_unlock.include?(n) }
190
+
191
+ defn
192
+ end
193
+
194
+ def lock_deps_being_updated_to_exact_versions(definition)
195
+ dependencies.each_with_object({}) do |dep, old_reqs|
196
+ defn_dep = definition.dependencies.find do |d|
197
+ d.name == dep.fetch("name")
198
+ end
199
+
200
+ if defn_dep.nil?
201
+ definition.dependencies <<
202
+ Bundler::Dependency.new(dep.fetch("name"), dep.fetch("version"))
203
+ old_reqs[dep.fetch("name")] = :none
204
+ elsif git_dependency?(dep) &&
205
+ defn_dep.source.is_a?(Bundler::Source::Git)
206
+ defn_dep.source.unlock!
207
+ elsif Gem::Version.correct?(dep.fetch("version"))
208
+ new_req = Gem::Requirement.create("= #{dep.fetch("version")}")
209
+ old_reqs[dep.fetch("name")] = defn_dep.requirement
210
+ defn_dep.instance_variable_set(:@requirement, new_req)
211
+ end
212
+ end
213
+ end
214
+
215
+ def git_dependency?(dep)
216
+ sources = dep.fetch("requirements").map { |r| r.fetch("source") }
217
+ sources.all? { |s| s&.fetch("type", nil) == "git" }
218
+ end
219
+
220
+ def lockfile
221
+ @lockfile ||= File.read(lockfile_name)
222
+ end
223
+ end
224
+ end
@@ -0,0 +1,140 @@
1
+ module Functions
2
+ class VersionResolver
3
+ GEM_NOT_FOUND_ERROR_REGEX = /locked to (?<name>[^\s]+) \(/.freeze
4
+
5
+ attr_reader :dependency_name, :dependency_requirements,
6
+ :gemfile_name, :lockfile_name
7
+
8
+ def initialize(dependency_name:, dependency_requirements:,
9
+ gemfile_name:, lockfile_name:)
10
+ @dependency_name = dependency_name
11
+ @dependency_requirements = dependency_requirements
12
+ @gemfile_name = gemfile_name
13
+ @lockfile_name = lockfile_name
14
+ end
15
+
16
+ def version_details
17
+ dep = dependency_from_definition
18
+
19
+ # If the dependency wasn't found in the definition, but *is*
20
+ # included in a gemspec, it's because the Gemfile didn't import
21
+ # the gemspec. This is unusual, but the correct behaviour if/when
22
+ # it happens is to behave as if the repo was gemspec-only.
23
+ if dep.nil? && dependency_requirements.any?
24
+ return "latest"
25
+ end
26
+
27
+ # Otherwise, if the dependency wasn't found it's because it is a
28
+ # subdependency that was removed when attempting to update it.
29
+ return nil if dep.nil?
30
+
31
+ # If the dependency is Bundler itself then we can't trust the
32
+ # version that has been returned (it's the version Dependabot is
33
+ # running on, rather than the true latest resolvable version).
34
+ return nil if dep.name == "bundler"
35
+
36
+ details = {
37
+ version: dep.version,
38
+ ruby_version: ruby_version,
39
+ fetcher: fetcher_class(dep)
40
+ }
41
+ if dep.source.instance_of?(::Bundler::Source::Git)
42
+ details[:commit_sha] = dep.source.revision
43
+ end
44
+ details
45
+ end
46
+
47
+ private
48
+
49
+ # rubocop:disable Metrics/PerceivedComplexity
50
+ def dependency_from_definition(unlock_subdependencies: true)
51
+ dependencies_to_unlock = [dependency_name]
52
+ dependencies_to_unlock += subdependencies if unlock_subdependencies
53
+ begin
54
+ definition = build_definition(dependencies_to_unlock)
55
+ definition.resolve_remotely!
56
+ rescue ::Bundler::GemNotFound => e
57
+ unlock_yanked_gem(dependencies_to_unlock, e) && retry
58
+ rescue ::Bundler::HTTPError => e
59
+ # Retry network errors
60
+ # Note: in_a_native_bundler_context will also retry `Bundler::HTTPError` errors
61
+ # up to three times meaning we'll end up retrying this error up to six times
62
+ # TODO: Could we get rid of this retry logic and only rely on
63
+ # SharedBundlerHelpers.in_a_native_bundler_context
64
+ attempt ||= 1
65
+ attempt += 1
66
+ raise if attempt > 3 || !e.message.include?("Network error")
67
+
68
+ retry
69
+ end
70
+
71
+ dep = definition.resolve.find { |d| d.name == dependency_name }
72
+ return dep if dep
73
+ return if dependency_requirements.any? || !unlock_subdependencies
74
+
75
+ # If no definition was found and we're updating a sub-dependency,
76
+ # try again but without unlocking any other sub-dependencies
77
+ dependency_from_definition(unlock_subdependencies: false)
78
+ end
79
+ # rubocop:enable Metrics/PerceivedComplexity
80
+
81
+ def subdependencies
82
+ # If there's no lockfile we don't need to worry about
83
+ # subdependencies
84
+ return [] unless lockfile
85
+
86
+ all_deps = ::Bundler::LockfileParser.new(lockfile).
87
+ specs.map(&:name).map(&:to_s).uniq
88
+ top_level = build_definition([]).dependencies.
89
+ map(&:name).map(&:to_s)
90
+
91
+ all_deps - top_level
92
+ end
93
+
94
+ def build_definition(dependencies_to_unlock)
95
+ # Note: we lock shared dependencies to avoid any top-level
96
+ # dependencies getting unlocked (which would happen if they were
97
+ # also subdependencies of the dependency being unlocked)
98
+ ::Bundler::Definition.build(
99
+ gemfile_name,
100
+ lockfile_name,
101
+ gems: dependencies_to_unlock,
102
+ lock_shared_dependencies: true
103
+ )
104
+ end
105
+
106
+ def unlock_yanked_gem(dependencies_to_unlock, error)
107
+ raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
108
+
109
+ gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX).
110
+ named_captures["name"]
111
+ raise if dependencies_to_unlock.include?(gem_name)
112
+
113
+ dependencies_to_unlock << gem_name
114
+ end
115
+
116
+ def lockfile
117
+ return @lockfile if defined?(@lockfile)
118
+
119
+ @lockfile =
120
+ begin
121
+ return unless lockfile_name
122
+ return unless File.exist?(lockfile_name)
123
+
124
+ File.read(lockfile_name)
125
+ end
126
+ end
127
+
128
+ def fetcher_class(dep)
129
+ return unless dep.source.is_a?(::Bundler::Source::Rubygems)
130
+
131
+ dep.source.fetchers.first.fetchers.first.class.to_s
132
+ end
133
+
134
+ def ruby_version
135
+ return nil unless gemfile_name
136
+
137
+ @ruby_version ||= build_definition([]).ruby_version&.gem_version
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,15 @@
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 BundlerDefinitionBundlerVersionPatch
8
+ def expanded_dependencies
9
+ @expanded_dependencies ||=
10
+ expand_dependencies(dependencies + metadata_dependencies, @remote).
11
+ reject { |d| d.name == "bundler" }
12
+ end
13
+ end
14
+
15
+ Bundler::Definition.prepend(BundlerDefinitionBundlerVersionPatch)
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/definition"
4
+
5
+ module BundlerDefinitionRubyVersionPatch
6
+ def index
7
+ @index ||= super.tap do
8
+ if ruby_version
9
+ requested_version = ruby_version.to_gem_version_with_patchlevel
10
+ sources.metadata_source.specs <<
11
+ Gem::Specification.new("ruby\0", requested_version)
12
+ end
13
+
14
+ sources.metadata_source.specs <<
15
+ Gem::Specification.new("ruby\0", "2.5.3p105")
16
+ end
17
+ end
18
+ end
19
+
20
+ Bundler::Definition.prepend(BundlerDefinitionRubyVersionPatch)
@@ -0,0 +1,63 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/source"
4
+
5
+ module Bundler
6
+ class Source
7
+ class Git
8
+ class GitProxy
9
+ private
10
+
11
+ # Bundler allows ssh authentication when talking to GitHub but there's
12
+ # no way for Dependabot to do so (it doesn't have any ssh keys).
13
+ # Instead, we convert all `git@github.com:` URLs to use HTTPS.
14
+ def configured_uri_for(uri)
15
+ uri = uri.gsub(%r{git@(.*?):/?}, 'https://\1/')
16
+ if uri.match?(/https?:/)
17
+ remote = URI(uri)
18
+ config_auth =
19
+ Bundler.settings[remote.to_s] || Bundler.settings[remote.host]
20
+ remote.userinfo ||= config_auth
21
+ remote.to_s
22
+ else
23
+ uri
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
30
+
31
+ module Bundler
32
+ class Source
33
+ class Git < Path
34
+ private
35
+
36
+ def serialize_gemspecs_in(destination)
37
+ original_load_paths = $LOAD_PATH.dup
38
+ reduced_load_paths = original_load_paths.
39
+ reject { |p| p.include?("/gems/") }
40
+
41
+ $LOAD_PATH.shift until $LOAD_PATH.empty?
42
+ reduced_load_paths.each { |p| $LOAD_PATH << p }
43
+
44
+ if destination.relative?
45
+ destination = destination.expand_path(Bundler.root)
46
+ end
47
+ Dir["#{destination}/#{@glob}"].each do |spec_path|
48
+ # Evaluate gemspecs and cache the result. Gemspecs
49
+ # in git might require git or other dependencies.
50
+ # The gemspecs we cache should already be evaluated.
51
+ spec = Bundler.load_gemspec(spec_path)
52
+ next unless spec
53
+
54
+ Bundler.rubygems.set_installed_by_version(spec)
55
+ Bundler.rubygems.validate(spec)
56
+ File.open(spec_path, "wb") { |file| file.write(spec.to_ruby) }
57
+ end
58
+ $LOAD_PATH.shift until $LOAD_PATH.empty?
59
+ original_load_paths.each { |p| $LOAD_PATH << p }
60
+ end
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,30 @@
1
+ require "bundler"
2
+ require "json"
3
+
4
+ $LOAD_PATH.unshift(File.expand_path("./lib", __dir__))
5
+ $LOAD_PATH.unshift(File.expand_path("./monkey_patches", __dir__))
6
+
7
+ # Bundler monkey patches
8
+ require "definition_ruby_version_patch"
9
+ require "definition_bundler_version_patch"
10
+ require "git_source_patch"
11
+
12
+ require "functions"
13
+
14
+ def output(obj)
15
+ print JSON.dump(obj)
16
+ end
17
+
18
+ begin
19
+ request = JSON.parse($stdin.read)
20
+
21
+ function = request["function"]
22
+ args = request["args"].transform_keys(&:to_sym)
23
+
24
+ output({ result: Functions.send(function, **args) })
25
+ rescue => error
26
+ output(
27
+ { error: error.message, error_class: error.class, trace: error.backtrace }
28
+ )
29
+ exit(1)
30
+ end
@@ -7,8 +7,11 @@ module Dependabot
7
7
  module Bundler
8
8
  class FileUpdater
9
9
  class RubyRequirementSetter
10
- RUBY_VERSIONS =
11
- %w(1.8.7 1.9.3 2.0.0 2.1.10 2.2.10 2.3.8 2.4.7 2.5.6 2.6.4).freeze
10
+ class RubyVersionNotFound < StandardError; end
11
+
12
+ RUBY_VERSIONS = %w(
13
+ 1.8.7 1.9.3 2.0.0 2.1.10 2.2.10 2.3.8 2.4.10 2.5.8 2.6.6 2.7.2 3.0.0
14
+ ).freeze
12
15
 
13
16
  attr_reader :gemspec
14
17
 
@@ -53,7 +56,7 @@ module Dependabot
53
56
  map { |v| Gem::Version.new(v) }.sort.
54
57
  find { |v| requirement.satisfied_by?(v) }
55
58
 
56
- raise "Couldn't find Ruby version!" unless ruby_version
59
+ raise RubyVersionNotFound unless ruby_version
57
60
 
58
61
  ruby_version
59
62
  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.129.1
4
+ version: 0.130.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Dependabot
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2020-12-21 00:00:00.000000000 Z
11
+ date: 2021-01-13 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.129.1
19
+ version: 0.130.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.129.1
26
+ version: 0.130.0
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -100,28 +100,28 @@ dependencies:
100
100
  requirements:
101
101
  - - "~>"
102
102
  - !ruby/object:Gem::Version
103
- version: 1.6.0
103
+ version: 1.8.0
104
104
  type: :development
105
105
  prerelease: false
106
106
  version_requirements: !ruby/object:Gem::Requirement
107
107
  requirements:
108
108
  - - "~>"
109
109
  - !ruby/object:Gem::Version
110
- version: 1.6.0
110
+ version: 1.8.0
111
111
  - !ruby/object:Gem::Dependency
112
112
  name: simplecov
113
113
  requirement: !ruby/object:Gem::Requirement
114
114
  requirements:
115
115
  - - "~>"
116
116
  - !ruby/object:Gem::Version
117
- version: 0.20.0
117
+ version: 0.21.0
118
118
  type: :development
119
119
  prerelease: false
120
120
  version_requirements: !ruby/object:Gem::Requirement
121
121
  requirements:
122
122
  - - "~>"
123
123
  - !ruby/object:Gem::Version
124
- version: 0.20.0
124
+ version: 0.21.0
125
125
  - !ruby/object:Gem::Dependency
126
126
  name: simplecov-console
127
127
  requirement: !ruby/object:Gem::Requirement
@@ -171,6 +171,18 @@ executables: []
171
171
  extensions: []
172
172
  extra_rdoc_files: []
173
173
  files:
174
+ - helpers/build
175
+ - helpers/lib/functions.rb
176
+ - helpers/lib/functions/conflicting_dependency_resolver.rb
177
+ - helpers/lib/functions/dependency_source.rb
178
+ - helpers/lib/functions/file_parser.rb
179
+ - helpers/lib/functions/force_updater.rb
180
+ - helpers/lib/functions/lockfile_updater.rb
181
+ - helpers/lib/functions/version_resolver.rb
182
+ - helpers/monkey_patches/definition_bundler_version_patch.rb
183
+ - helpers/monkey_patches/definition_ruby_version_patch.rb
184
+ - helpers/monkey_patches/git_source_patch.rb
185
+ - helpers/run.rb
174
186
  - lib/dependabot/bundler.rb
175
187
  - lib/dependabot/bundler/file_fetcher.rb
176
188
  - lib/dependabot/bundler/file_fetcher/child_gemfile_finder.rb