dependabot-bundler 0.138.3 → 0.138.4

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 6fe43d8a687e3405df9f2704529fe54f9e79cdb75b6246d43a32e32e5a7b1928
4
- data.tar.gz: 67b1b3afd35f559613e57f6ef2a26ef9ee0c50910072b40c5d9ac412b830ed91
3
+ metadata.gz: c508c2f2ed7c44b477686d7385a08b08f9cc3b22821d4fd1a96f6228d7f7b7ae
4
+ data.tar.gz: 89ef7a905d30d6b46191760a893c119b8e45814e8fdd4a6fd6150327ecccf7f2
5
5
  SHA512:
6
- metadata.gz: 55bd706fffed2c9caa866237eea57fde3f91a83f465014865dacf2252094ad31e9098d0894c9b85da77f08e5ae0941f8a74ca1f1cfecec447775aa20541bcfae
7
- data.tar.gz: 563832a40283d4c9f29175b0cddc850ac88e461e65905e255f94234aee9c913690525016a2d12cbfdedb17f4d5c0873214863b324207277bbfc80bccee7c7f8f
6
+ metadata.gz: e91dae1d2fbed29ba408ab4bcc854662b3e36634d425732216e98bc92c881170b475325c0bca09e50803bdc9835cf28456aaf9c59d0620c6ba2c96920a9d3032
7
+ data.tar.gz: 28cba5afca8459dfc9c2e7e9ec64065ed1da409d0391c40847cf9c5353ea21f56da8b3228b0462209789abf08022b40e547e1677df694874d3c708f4bcf33c6c
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
 
data/helpers/v2/build CHANGED
@@ -11,6 +11,7 @@ fi
11
11
  helpers_dir="$(dirname "${BASH_SOURCE[0]}")"
12
12
  cp -r \
13
13
  "$helpers_dir/lib" \
14
+ "$helpers_dir/monkey_patches" \
14
15
  "$helpers_dir/run.rb" \
15
16
  "$helpers_dir/Gemfile" \
16
17
  "$install_dir"
@@ -1,59 +1,107 @@
1
- require "functions/file_parser"
1
+ # frozen_string_literal: true
2
+
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"
3
9
 
4
10
  module Functions
5
11
  class NotImplementedError < StandardError; end
6
12
 
7
13
  def self.parsed_gemfile(lockfile_name:, gemfile_name:, dir:)
8
14
  set_bundler_flags_and_credentials(dir: dir, credentials: [],
9
- using_bundler2: false)
15
+ using_bundler2: false)
10
16
  FileParser.new(lockfile_name: lockfile_name).
11
17
  parsed_gemfile(gemfile_name: gemfile_name)
12
18
  end
13
19
 
14
20
  def self.parsed_gemspec(lockfile_name:, gemspec_name:, dir:)
15
21
  set_bundler_flags_and_credentials(dir: dir, credentials: [],
16
- using_bundler2: false)
22
+ using_bundler2: false)
17
23
  FileParser.new(lockfile_name: lockfile_name).
18
24
  parsed_gemspec(gemspec_name: gemspec_name)
19
25
  end
20
26
 
21
27
  def self.vendor_cache_dir(dir:)
22
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
28
+ set_bundler_flags_and_credentials(dir: dir, credentials: [],
29
+ using_bundler2: false)
30
+ Bundler.app_cache
23
31
  end
24
32
 
25
33
  def self.update_lockfile(dir:, gemfile_name:, lockfile_name:, using_bundler2:,
26
34
  credentials:, dependencies:)
27
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
28
42
  end
29
43
 
30
44
  def self.force_update(dir:, dependency_name:, target_version:, gemfile_name:,
31
45
  lockfile_name:, using_bundler2:, credentials:,
32
46
  update_multiple_dependencies:)
33
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
34
56
  end
35
57
 
36
58
  def self.dependency_source_type(gemfile_name:, dependency_name:, dir:,
37
59
  credentials:)
38
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
39
67
  end
40
68
 
41
69
  def self.depencency_source_latest_git_version(gemfile_name:, dependency_name:,
42
70
  dir:, credentials:,
43
71
  dependency_source_url:,
44
72
  dependency_source_branch:)
45
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
+ )
46
82
  end
47
83
 
48
84
  def self.private_registry_versions(gemfile_name:, dependency_name:, dir:,
49
85
  credentials:)
50
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
51
93
  end
52
94
 
53
95
  def self.resolve_version(dependency_name:, dependency_requirements:,
54
96
  gemfile_name:, lockfile_name:, using_bundler2:,
55
97
  dir:, credentials:)
56
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
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
57
105
  end
58
106
 
59
107
  def self.jfrog_source(dir:, gemfile_name:, credentials:, using_bundler2:)
@@ -61,7 +109,26 @@ module Functions
61
109
  end
62
110
 
63
111
  def self.git_specs(dir:, gemfile_name:, credentials:, using_bundler2:)
64
- raise NotImplementedError, "Bundler 2 adapter does not yet implement #{__method__}"
112
+ set_bundler_flags_and_credentials(dir: dir, credentials: credentials,
113
+ using_bundler2: using_bundler2)
114
+
115
+ git_specs = Bundler::Definition.build(gemfile_name, nil, {}).dependencies.
116
+ select do |spec|
117
+ spec.source.is_a?(Bundler::Source::Git)
118
+ end
119
+ git_specs.map do |spec|
120
+ # Piggy-back off some private Bundler methods to configure the
121
+ # URI with auth details in the same way Bundler does.
122
+ git_proxy = spec.source.send(:git_proxy)
123
+ auth_uri = spec.source.uri.gsub("git://", "https://")
124
+ auth_uri = git_proxy.send(:configured_uri_for, auth_uri)
125
+ auth_uri += ".git" unless auth_uri.end_with?(".git")
126
+ auth_uri += "/info/refs?service=git-upload-pack"
127
+ {
128
+ uri: spec.source.uri,
129
+ auth_uri: auth_uri
130
+ }
131
+ end
65
132
  end
66
133
 
67
134
  def self.set_bundler_flags_and_credentials(dir:, credentials:,
@@ -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,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
+ # frozen_string_literal: true
2
+
3
+ module Functions
4
+ class LockfileUpdater
5
+ RETRYABLE_ERRORS = [Bundler::HTTPError].freeze
6
+ GEM_NOT_FOUND_ERROR_REGEX =
7
+ /
8
+ locked\sto\s(?<name>[^\s]+)\s\(|
9
+ not\sfind\s(?<name>[^\s]+)-\d|
10
+ has\s(?<name>[^\s]+)\slocked\sat
11
+ /x.freeze
12
+
13
+ def initialize(gemfile_name:, lockfile_name:, dependencies:)
14
+ @gemfile_name = gemfile_name
15
+ @lockfile_name = lockfile_name
16
+ @dependencies = dependencies
17
+ end
18
+
19
+ def run
20
+ generate_lockfile
21
+ end
22
+
23
+ private
24
+
25
+ attr_reader :gemfile_name, :lockfile_name, :dependencies
26
+
27
+ def generate_lockfile
28
+ dependencies_to_unlock = dependencies.map { |d| d.fetch("name") }
29
+
30
+ begin
31
+ definition = build_definition(dependencies_to_unlock)
32
+
33
+ old_reqs = lock_deps_being_updated_to_exact_versions(definition)
34
+
35
+ definition.resolve_remotely!
36
+
37
+ old_reqs.each do |dep_name, old_req|
38
+ d_dep = definition.dependencies.find { |d| d.name == dep_name }
39
+ if old_req == :none then definition.dependencies.delete(d_dep)
40
+ else
41
+ d_dep.instance_variable_set(:@requirement, old_req)
42
+ end
43
+ end
44
+
45
+ cache_vendored_gems(definition) if Bundler.app_cache.exist?
46
+
47
+ definition.to_lock
48
+ rescue Bundler::GemNotFound => e
49
+ unlock_yanked_gem(dependencies_to_unlock, e) && retry
50
+ rescue Bundler::VersionConflict => e
51
+ unlock_blocking_subdeps(dependencies_to_unlock, e) && retry
52
+ rescue *RETRYABLE_ERRORS
53
+ raise if @retrying
54
+
55
+ @retrying = true
56
+ sleep(rand(1.0..5.0))
57
+ retry
58
+ end
59
+ end
60
+
61
+ def cache_vendored_gems(definition)
62
+ # Dependencies that have been unlocked for the update (including
63
+ # sub-dependencies)
64
+ unlocked_gems = definition.instance_variable_get(:@unlock).
65
+ fetch(:gems).reject { |gem| __keep_on_prune?(gem) }
66
+ bundler_opts = {
67
+ cache_all: true,
68
+ cache_all_platforms: true,
69
+ no_prune: true
70
+ }
71
+
72
+ Bundler.settings.temporary(**bundler_opts) do
73
+ # Fetch and cache gems on all platforms without pruning
74
+ Bundler::Runtime.new(nil, definition).cache
75
+
76
+ # Only prune unlocked gems (the original implementation is in
77
+ # Bundler::Runtime)
78
+ cache_path = Bundler.app_cache
79
+ resolve = definition.resolve
80
+ prune_gem_cache(resolve, cache_path, unlocked_gems)
81
+ prune_git_and_path_cache(resolve, cache_path)
82
+ end
83
+ end
84
+
85
+ # This is not officially supported and may be removed without notice.
86
+ def __keep_on_prune?(spec_name)
87
+ unless (specs = Bundler.settings[:persistent_gems_after_clean])
88
+ return false
89
+ end
90
+
91
+ specs.include?(spec_name)
92
+ end
93
+
94
+ # Copied from Bundler::Runtime: Modified to only prune gems that have
95
+ # been unlocked
96
+ def prune_gem_cache(resolve, cache_path, unlocked_gems)
97
+ cached_gems = Dir["#{cache_path}/*.gem"]
98
+
99
+ outdated_gems = cached_gems.reject do |path|
100
+ spec = Bundler.rubygems.spec_from_gem path
101
+
102
+ !unlocked_gems.include?(spec.name) || resolve.any? do |s|
103
+ s.name == spec.name && s.version == spec.version &&
104
+ !s.source.is_a?(Bundler::Source::Git)
105
+ end
106
+ end
107
+
108
+ return unless outdated_gems.any?
109
+
110
+ outdated_gems.each do |path|
111
+ File.delete(path)
112
+ end
113
+ end
114
+
115
+ # Copied from Bundler::Runtime
116
+ def prune_git_and_path_cache(resolve, cache_path)
117
+ cached_git_and_path = Dir["#{cache_path}/*/.bundlecache"]
118
+
119
+ outdated_git_and_path = cached_git_and_path.reject do |path|
120
+ name = File.basename(File.dirname(path))
121
+
122
+ resolve.any? do |s|
123
+ s.source.respond_to?(:app_cache_dirname) &&
124
+ s.source.app_cache_dirname == name
125
+ end
126
+ end
127
+
128
+ return unless outdated_git_and_path.any?
129
+
130
+ outdated_git_and_path.each do |path|
131
+ path = File.dirname(path)
132
+ FileUtils.rm_rf(path)
133
+ end
134
+ end
135
+
136
+ def unlock_yanked_gem(dependencies_to_unlock, error)
137
+ raise unless error.message.match?(GEM_NOT_FOUND_ERROR_REGEX)
138
+
139
+ gem_name = error.message.match(GEM_NOT_FOUND_ERROR_REGEX).
140
+ named_captures["name"]
141
+ raise if dependencies_to_unlock.include?(gem_name)
142
+
143
+ dependencies_to_unlock << gem_name
144
+ end
145
+
146
+ # rubocop:disable Metrics/PerceivedComplexity
147
+ def unlock_blocking_subdeps(dependencies_to_unlock, error)
148
+ all_deps = Bundler::LockfileParser.new(lockfile).
149
+ specs.map(&:name).map(&:to_s)
150
+ top_level = build_definition([]).dependencies.
151
+ map(&:name).map(&:to_s)
152
+ allowed_new_unlocks = all_deps - top_level - dependencies_to_unlock
153
+
154
+ raise if allowed_new_unlocks.none?
155
+
156
+ # Unlock any sub-dependencies that Bundler reports caused the
157
+ # conflict
158
+ potentials_deps =
159
+ error.cause.conflicts.values.
160
+ flat_map(&:requirement_trees).
161
+ map do |tree|
162
+ tree.find { |req| allowed_new_unlocks.include?(req.name) }
163
+ end.compact.map(&:name)
164
+
165
+ # If there are specific dependencies we can unlock, unlock them
166
+ return dependencies_to_unlock.append(*potentials_deps) if potentials_deps.any?
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
@@ -8,11 +8,11 @@ module BundlerDefinitionRubyVersionPatch
8
8
  if ruby_version
9
9
  requested_version = ruby_version.to_gem_version_with_patchlevel
10
10
  sources.metadata_source.specs <<
11
- Gem::Specification.new("ruby\0", requested_version)
11
+ Gem::Specification.new("Ruby\0", requested_version)
12
12
  end
13
13
 
14
14
  sources.metadata_source.specs <<
15
- Gem::Specification.new("ruby\0", "2.5.3p105")
15
+ Gem::Specification.new("Ruby\0", "2.5.3p105")
16
16
  end
17
17
  end
18
18
  end
data/helpers/v2/run.rb CHANGED
@@ -2,7 +2,7 @@ require "bundler"
2
2
  require "json"
3
3
 
4
4
  $LOAD_PATH.unshift(File.expand_path("./lib", __dir__))
5
- $LOAD_PATH.unshift(File.expand_path("../v1/monkey_patches", __dir__))
5
+ $LOAD_PATH.unshift(File.expand_path("./monkey_patches", __dir__))
6
6
 
7
7
  # Bundler monkey patches
8
8
  require "definition_ruby_version_patch"
@@ -0,0 +1,185 @@
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::BadAuthenticationError
76
+ expect { private_registry_versions }.
77
+ to raise_error do |error|
78
+ expect(error).to be_a(error_class)
79
+ expect(error.message).to include("Bad username or password for")
80
+ end
81
+ end
82
+ end
83
+
84
+ context "that we have bad authentication details for" do
85
+ before do
86
+ stub_request(:get, registry_url + "versions").
87
+ with(basic_auth: ["SECRET_CODES", ""]).
88
+ to_return(status: 403)
89
+ stub_request(:get, registry_url + "api/v1/dependencies").
90
+ with(basic_auth: ["SECRET_CODES", ""]).
91
+ to_return(status: 403)
92
+ stub_request(:get, registry_url + "specs.4.8.gz").
93
+ with(basic_auth: ["SECRET_CODES", ""]).
94
+ to_return(status: 403)
95
+ end
96
+
97
+ it "blows up with a useful error" do
98
+ error_class = Bundler::Fetcher::BadAuthenticationError
99
+ expect { private_registry_versions }.
100
+ to raise_error do |error|
101
+ expect(error).to be_a(error_class)
102
+ expect(error.message).to include("Bad username or password for")
103
+ end
104
+ end
105
+ end
106
+
107
+ context "that bad-requested, but was a private repo" do
108
+ before do
109
+ stub_request(:get, registry_url + "versions").
110
+ with(basic_auth: ["SECRET_CODES", ""]).
111
+ to_return(status: 400)
112
+ stub_request(:get, registry_url + "api/v1/dependencies").
113
+ with(basic_auth: ["SECRET_CODES", ""]).
114
+ to_return(status: 400)
115
+ stub_request(:get, registry_url + "specs.4.8.gz").
116
+ with(basic_auth: ["SECRET_CODES", ""]).
117
+ to_return(status: 400)
118
+ end
119
+
120
+ it "blows up with a useful error" do
121
+ expect { private_registry_versions }.
122
+ to raise_error do |error|
123
+ expect(error).to be_a(Bundler::HTTPError)
124
+ expect(error.message).
125
+ to include("Could not fetch specs from")
126
+ end
127
+ end
128
+ end
129
+
130
+ context "that doesn't have details of the gem" do
131
+ before do
132
+ stub_request(:get, gemfury_business_url).
133
+ with(basic_auth: ["SECRET_CODES", ""]).
134
+ to_return(status: 404)
135
+
136
+ # Stub indexes to return details of other gems (but not this one)
137
+ stub_request(:get, registry_url + "specs.4.8.gz").
138
+ to_return(
139
+ status: 200,
140
+ body: fixture("ruby", "contribsys_old_index_response")
141
+ )
142
+ stub_request(:get, registry_url + "prerelease_specs.4.8.gz").
143
+ to_return(
144
+ status: 200,
145
+ body: fixture("ruby", "contribsys_old_index_prerelease_response")
146
+ )
147
+ end
148
+
149
+ it { is_expected.to be_empty }
150
+ end
151
+
152
+ context "that only implements the old Bundler index format..." do
153
+ let(:project_name) { "sidekiq_pro" }
154
+ let(:dependency_name) { "sidekiq-pro" }
155
+ let(:registry_url) { "https://gems.contribsys.com/" }
156
+
157
+ before do
158
+ stub_request(:get, registry_url + "versions").
159
+ with(basic_auth: %w(username password)).
160
+ to_return(status: 404)
161
+ stub_request(:get, registry_url + "api/v1/dependencies").
162
+ with(basic_auth: %w(username password)).
163
+ to_return(status: 404)
164
+ stub_request(:get, registry_url + "specs.4.8.gz").
165
+ with(basic_auth: %w(username password)).
166
+ to_return(
167
+ status: 200,
168
+ body: fixture("ruby", "contribsys_old_index_response")
169
+ )
170
+ stub_request(:get, registry_url + "prerelease_specs.4.8.gz").
171
+ with(basic_auth: %w(username password)).
172
+ to_return(
173
+ status: 200,
174
+ body: fixture("ruby", "contribsys_old_index_prerelease_response")
175
+ )
176
+ end
177
+
178
+ it "returns all versions from the private source" do
179
+ expect(private_registry_versions.length).to eql(70)
180
+ expect(private_registry_versions.min).to eql(Gem::Version.new("1.0.0"))
181
+ expect(private_registry_versions.max).to eql(Gem::Version.new("3.5.2"))
182
+ end
183
+ end
184
+ end
185
+ 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
@@ -5,18 +5,7 @@ require "native_spec_helper"
5
5
  RSpec.describe Functions do
6
6
  # Verify v1 method signatures are exist, but raise as NYI
7
7
  {
8
- vendor_cache_dir: [ :dir ],
9
- update_lockfile: [ :dir, :gemfile_name, :lockfile_name, :using_bundler2, :credentials, :dependencies ],
10
- force_update: [ :dir, :dependency_name, :target_version, :gemfile_name, :lockfile_name, :using_bundler2,
11
- :credentials, :update_multiple_dependencies ],
12
- dependency_source_type: [ :gemfile_name, :dependency_name, :dir, :credentials ],
13
- depencency_source_latest_git_version: [ :gemfile_name, :dependency_name, :dir, :credentials, :dependency_source_url,
14
- :dependency_source_branch ],
15
- private_registry_versions: [:gemfile_name, :dependency_name, :dir, :credentials ],
16
- resolve_version: [:dependency_name, :dependency_requirements, :gemfile_name, :lockfile_name, :using_bundler2,
17
- :dir, :credentials],
18
- jfrog_source: [:dir, :gemfile_name, :credentials, :using_bundler2],
19
- git_specs: [:dir, :gemfile_name, :credentials, :using_bundler2],
8
+ jfrog_source: %i(dir gemfile_name credentials using_bundler2)
20
9
  }.each do |function, kwargs|
21
10
  describe "::#{function}" do
22
11
  let(:args) do
@@ -167,6 +167,8 @@ module Dependabot
167
167
  req_string.include?(" ")
168
168
  end
169
169
 
170
+ EQUALITY_OPERATOR = /(?<![<>!])=/.freeze
171
+
170
172
  def use_equality_operator?(requirement_nodes)
171
173
  return true if requirement_nodes.none?
172
174
 
@@ -178,7 +180,7 @@ module Dependabot
178
180
  requirement_nodes.first.children.first.loc.expression.source
179
181
  end
180
182
 
181
- req_string.match?(/(?<![<>])=/)
183
+ req_string.match?(EQUALITY_OPERATOR)
182
184
  end
183
185
 
184
186
  def new_requirement_string(quote_characters:,
@@ -203,7 +205,7 @@ module Dependabot
203
205
  # Gem::Requirement serializes exact matches as a string starting
204
206
  # with `=`. We may need to remove that equality operator if it
205
207
  # wasn't used originally.
206
- tmp_req = tmp_req.gsub(/(?<![<>])=/, "") unless use_equality_operator
208
+ tmp_req = tmp_req.gsub(EQUALITY_OPERATOR, "") unless use_equality_operator
207
209
 
208
210
  tmp_req.strip
209
211
  end
@@ -188,7 +188,7 @@ module Dependabot
188
188
  req
189
189
  end
190
190
  when "<", "<=" then [update_greatest_version(req, latest_version)]
191
- when "~>" then convert_twidle_to_range(req, latest_version)
191
+ when "~>" then convert_twiddle_to_range(req, latest_version)
192
192
  when "!=" then []
193
193
  when ">", ">=" then raise UnfixableRequirement
194
194
  else raise "Unexpected operation for requirement: #{op}"
@@ -214,7 +214,7 @@ module Dependabot
214
214
  end
215
215
  end
216
216
 
217
- def convert_twidle_to_range(requirement, version_to_be_permitted)
217
+ def convert_twiddle_to_range(requirement, version_to_be_permitted)
218
218
  version = requirement.requirements.first.last
219
219
  version = version.release if version.prerelease?
220
220
 
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.138.3
4
+ version: 0.138.4
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-24 00:00:00.000000000 Z
11
+ date: 2021-03-25 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.138.3
19
+ version: 0.138.4
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.138.3
26
+ version: 0.138.4
27
27
  - !ruby/object:Gem::Dependency
28
28
  name: byebug
29
29
  requirement: !ruby/object:Gem::Requirement
@@ -211,13 +211,19 @@ files:
211
211
  - helpers/v2/build
212
212
  - helpers/v2/lib/functions.rb
213
213
  - helpers/v2/lib/functions/conflicting_dependency_resolver.rb
214
+ - helpers/v2/lib/functions/dependency_source.rb
214
215
  - helpers/v2/lib/functions/file_parser.rb
216
+ - helpers/v2/lib/functions/force_updater.rb
217
+ - helpers/v2/lib/functions/lockfile_updater.rb
218
+ - helpers/v2/lib/functions/version_resolver.rb
215
219
  - helpers/v2/monkey_patches/definition_bundler_version_patch.rb
216
220
  - helpers/v2/monkey_patches/definition_ruby_version_patch.rb
217
221
  - helpers/v2/monkey_patches/git_source_patch.rb
218
222
  - helpers/v2/run.rb
219
223
  - helpers/v2/spec/functions/conflicting_dependency_resolver_spec.rb
224
+ - helpers/v2/spec/functions/dependency_source_spec.rb
220
225
  - helpers/v2/spec/functions/file_parser_spec.rb
226
+ - helpers/v2/spec/functions/version_resolver_spec.rb
221
227
  - helpers/v2/spec/functions_spec.rb
222
228
  - helpers/v2/spec/native_spec_helper.rb
223
229
  - helpers/v2/spec/shared_contexts.rb