dependabot-bundler 0.138.3 → 0.138.4

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: 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