rubygems-update 3.6.6 → 3.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +26 -0
  3. data/Manifest.txt +2 -1
  4. data/README.md +1 -1
  5. data/bundler/CHANGELOG.md +37 -6
  6. data/bundler/lib/bundler/build_metadata.rb +2 -2
  7. data/bundler/lib/bundler/checksum.rb +21 -11
  8. data/bundler/lib/bundler/compact_index_client/cache.rb +1 -1
  9. data/bundler/lib/bundler/compact_index_client/parser.rb +1 -1
  10. data/bundler/lib/bundler/definition.rb +90 -65
  11. data/bundler/lib/bundler/dsl.rb +2 -3
  12. data/bundler/lib/bundler/friendly_errors.rb +1 -1
  13. data/bundler/lib/bundler/installer.rb +1 -1
  14. data/bundler/lib/bundler/lazy_specification.rb +9 -1
  15. data/bundler/lib/bundler/lockfile_parser.rb +8 -5
  16. data/bundler/lib/bundler/plugin/api/source.rb +1 -1
  17. data/bundler/lib/bundler/plugin/installer/path.rb +8 -0
  18. data/bundler/lib/bundler/plugin.rb +1 -1
  19. data/bundler/lib/bundler/resolver/candidate.rb +1 -1
  20. data/bundler/lib/bundler/resolver/strategy.rb +40 -0
  21. data/bundler/lib/bundler/resolver.rb +11 -22
  22. data/bundler/lib/bundler/rubygems_ext.rb +15 -0
  23. data/bundler/lib/bundler/runtime.rb +8 -5
  24. data/bundler/lib/bundler/source/gemspec.rb +1 -4
  25. data/bundler/lib/bundler/source/git/git_proxy.rb +8 -3
  26. data/bundler/lib/bundler/source/path.rb +2 -2
  27. data/bundler/lib/bundler/source_list.rb +29 -11
  28. data/bundler/lib/bundler/spec_set.rb +27 -10
  29. data/bundler/lib/bundler/vendor/connection_pool/lib/connection_pool/timed_stack.rb +53 -3
  30. data/bundler/lib/bundler/vendor/connection_pool/lib/connection_pool/version.rb +1 -1
  31. data/bundler/lib/bundler/vendor/connection_pool/lib/connection_pool.rb +11 -0
  32. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/basic_package_source.rb +4 -24
  33. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/strategy.rb +42 -0
  34. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/version_range.rb +20 -8
  35. data/bundler/lib/bundler/vendor/pub_grub/lib/pub_grub/version_solver.rb +17 -29
  36. data/bundler/lib/bundler/version.rb +1 -1
  37. data/doc/rubygems/CONTRIBUTING.md +0 -4
  38. data/lib/rubygems/commands/exec_command.rb +15 -6
  39. data/lib/rubygems/defaults.rb +1 -1
  40. data/lib/rubygems/resolver/api_set/gem_parser.rb +2 -5
  41. data/lib/rubygems/specification.rb +5 -5
  42. data/lib/rubygems/version.rb +22 -4
  43. data/lib/rubygems.rb +10 -7
  44. data/rubygems-update.gemspec +1 -1
  45. metadata +11 -10
  46. data/bundler/lib/bundler/compact_index_client/gem_parser.rb +0 -32
@@ -195,7 +195,7 @@ module Bundler
195
195
  @sources[name]
196
196
  end
197
197
 
198
- # @param [Hash] The options that are present in the lock file
198
+ # @param [Hash] The options that are present in the lockfile
199
199
  # @return [API::Source] the instance of the class that handles the source
200
200
  # type passed in locked_opts
201
201
  def from_lock(locked_opts)
@@ -17,7 +17,7 @@ module Bundler
17
17
  # Some candidates may also keep some information explicitly about the
18
18
  # package they refer to. These candidates are referred to as "canonical" and
19
19
  # are used when materializing resolution results back into RubyGems
20
- # specifications that can be installed, written to lock files, and so on.
20
+ # specifications that can be installed, written to lockfiles, and so on.
21
21
  #
22
22
  class Candidate
23
23
  include Comparable
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Bundler
4
+ class Resolver
5
+ class Strategy
6
+ def initialize(source)
7
+ @source = source
8
+ end
9
+
10
+ def next_package_and_version(unsatisfied)
11
+ package, range = next_term_to_try_from(unsatisfied)
12
+
13
+ [package, most_preferred_version_of(package, range).first]
14
+ end
15
+
16
+ private
17
+
18
+ def next_term_to_try_from(unsatisfied)
19
+ unsatisfied.min_by do |package, range|
20
+ matching_versions = @source.versions_for(package, range)
21
+ higher_versions = @source.versions_for(package, range.upper_invert)
22
+
23
+ [matching_versions.count <= 1 ? 0 : 1, higher_versions.count]
24
+ end
25
+ end
26
+
27
+ def most_preferred_version_of(package, range)
28
+ versions = @source.versions_for(package, range)
29
+
30
+ # Conditional avoids (among other things) calling
31
+ # sort_versions_by_preferred with the root package
32
+ if versions.size > 1
33
+ @source.sort_versions_by_preferred(package, versions)
34
+ else
35
+ versions
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -12,6 +12,7 @@ module Bundler
12
12
  require_relative "resolver/candidate"
13
13
  require_relative "resolver/incompatibility"
14
14
  require_relative "resolver/root"
15
+ require_relative "resolver/strategy"
15
16
 
16
17
  include GemHelpers
17
18
 
@@ -78,7 +79,7 @@ module Bundler
78
79
  end
79
80
 
80
81
  def solve_versions(root:, logger:)
81
- solver = PubGrub::VersionSolver.new(source: self, root: root, logger: logger)
82
+ solver = PubGrub::VersionSolver.new(source: self, root: root, strategy: Strategy.new(self), logger: logger)
82
83
  result = solver.solve
83
84
  resolved_specs = result.flat_map {|package, version| version.to_specs(package, @most_specific_locked_platform) }
84
85
  SpecSet.new(resolved_specs).specs_with_additional_variants_from(@base.locked_specs)
@@ -167,15 +168,7 @@ module Bundler
167
168
  end
168
169
 
169
170
  def versions_for(package, range=VersionRange.any)
170
- versions = select_sorted_versions(package, range)
171
-
172
- # Conditional avoids (among other things) calling
173
- # sort_versions_by_preferred with the root package
174
- if versions.size > 1
175
- sort_versions_by_preferred(package, versions)
176
- else
177
- versions
178
- end
171
+ range.select_versions(@sorted_versions[package])
179
172
  end
180
173
 
181
174
  def no_versions_incompatibility_for(package, unsatisfied_term)
@@ -284,8 +277,8 @@ module Bundler
284
277
  ruby_group = Resolver::SpecGroup.new(ruby_specs)
285
278
 
286
279
  unless ruby_group.empty?
287
- platform_specs.each do |specs|
288
- ruby_group.merge(Resolver::SpecGroup.new(specs))
280
+ platform_specs.each do |s|
281
+ ruby_group.merge(Resolver::SpecGroup.new(s))
289
282
  end
290
283
 
291
284
  groups << Resolver::Candidate.new(version, group: ruby_group, priority: -1)
@@ -355,6 +348,10 @@ module Bundler
355
348
  raise GemNotFound, message
356
349
  end
357
350
 
351
+ def sort_versions_by_preferred(package, versions)
352
+ @gem_version_promoter.sort_versions(package, versions)
353
+ end
354
+
358
355
  private
359
356
 
360
357
  def filtered_versions_for(package)
@@ -414,10 +411,6 @@ module Bundler
414
411
  requirement.satisfied_by?(spec.version) || spec.source.is_a?(Source::Gemspec)
415
412
  end
416
413
 
417
- def sort_versions_by_preferred(package, versions)
418
- @gem_version_promoter.sort_versions(package, versions)
419
- end
420
-
421
414
  def repository_for(package)
422
415
  source_for(package.name)
423
416
  end
@@ -433,7 +426,7 @@ module Bundler
433
426
  next [dep_package, dep_constraint] if name == "bundler"
434
427
 
435
428
  dep_range = dep_constraint.range
436
- versions = select_sorted_versions(dep_package, dep_range)
429
+ versions = versions_for(dep_package, dep_range)
437
430
  if versions.empty?
438
431
  if dep_package.ignores_prereleases? || dep_package.prefer_local?
439
432
  @all_versions.delete(dep_package)
@@ -441,7 +434,7 @@ module Bundler
441
434
  end
442
435
  dep_package.consider_prereleases! if dep_package.ignores_prereleases?
443
436
  dep_package.consider_remote_versions! if dep_package.prefer_local?
444
- versions = select_sorted_versions(dep_package, dep_range)
437
+ versions = versions_for(dep_package, dep_range)
445
438
  end
446
439
 
447
440
  if versions.empty? && select_all_versions(dep_package, dep_range).any?
@@ -456,10 +449,6 @@ module Bundler
456
449
  end.to_h
457
450
  end
458
451
 
459
- def select_sorted_versions(package, range)
460
- range.select_versions(@sorted_versions[package])
461
- end
462
-
463
452
  def select_all_versions(package, range)
464
453
  range.select_versions(@all_versions[package])
465
454
  end
@@ -262,6 +262,10 @@ module Gem
262
262
  !default_gem? && !File.directory?(full_gem_path)
263
263
  end
264
264
 
265
+ def lock_name
266
+ @lock_name ||= name_tuple.lock_name
267
+ end
268
+
265
269
  unless VALIDATES_FOR_RESOLUTION
266
270
  def validate_for_resolution
267
271
  SpecificationPolicy.new(self).validate_for_resolution
@@ -443,6 +447,17 @@ module Gem
443
447
  end
444
448
  end
445
449
 
450
+ unless Gem.rubygems_version >= Gem::Version.new("3.6.7")
451
+ module UnfreezeCompactIndexParsedResponse
452
+ def parse(line)
453
+ version, platform, dependencies, requirements = super
454
+ [version, platform, dependencies.frozen? ? dependencies.dup : dependencies, requirements.frozen? ? requirements.dup : requirements]
455
+ end
456
+ end
457
+
458
+ Resolver::APISet::GemParser.prepend(UnfreezeCompactIndexParsedResponse)
459
+ end
460
+
446
461
  if Gem.rubygems_version < Gem::Version.new("3.6.0")
447
462
  class Package; end
448
463
  require "rubygems/package/tar_reader"
@@ -130,11 +130,14 @@ module Bundler
130
130
 
131
131
  specs_to_cache.each do |spec|
132
132
  next if spec.name == "bundler"
133
- next if spec.source.is_a?(Source::Gemspec)
134
- if spec.source.respond_to?(:migrate_cache)
135
- spec.source.migrate_cache(custom_path, local: local)
136
- elsif spec.source.respond_to?(:cache)
137
- spec.source.cache(spec, custom_path)
133
+
134
+ source = spec.source
135
+ next if source.is_a?(Source::Gemspec)
136
+
137
+ if source.respond_to?(:migrate_cache)
138
+ source.migrate_cache(custom_path, local: local)
139
+ elsif source.respond_to?(:cache)
140
+ source.cache(spec, custom_path)
138
141
  end
139
142
  end
140
143
 
@@ -4,15 +4,12 @@ module Bundler
4
4
  class Source
5
5
  class Gemspec < Path
6
6
  attr_reader :gemspec
7
+ attr_writer :checksum_store
7
8
 
8
9
  def initialize(options)
9
10
  super
10
11
  @gemspec = options["gemspec"]
11
12
  end
12
-
13
- def as_path_source
14
- Path.new(options)
15
- end
16
13
  end
17
14
  end
18
15
  end
@@ -185,7 +185,8 @@ module Bundler
185
185
  _, err, status = capture(command, nil)
186
186
  return extra_ref if status.success?
187
187
 
188
- if err.include?("Could not find remote branch")
188
+ if err.include?("Could not find remote branch") || # git up to 2.49
189
+ err.include?("Remote branch #{branch_option} not found") # git 2.49 or higher
189
190
  raise MissingGitRevisionError.new(command_with_no_credentials, nil, explicit_ref, credential_filtered_uri)
190
191
  else
191
192
  idx = command.index("--depth")
@@ -262,7 +263,7 @@ module Bundler
262
263
  end
263
264
 
264
265
  def not_pinned?
265
- branch || tag || ref.nil?
266
+ branch_option || ref.nil?
266
267
  end
267
268
 
268
269
  def pinned_to_full_sha?
@@ -426,7 +427,7 @@ module Bundler
426
427
  # anyways.
427
428
  return args if @revision
428
429
 
429
- args += ["--branch", branch || tag] if branch || tag
430
+ args += ["--branch", branch_option] if branch_option
430
431
  args
431
432
  end
432
433
 
@@ -442,6 +443,10 @@ module Bundler
442
443
  extra_args
443
444
  end
444
445
 
446
+ def branch_option
447
+ branch || tag
448
+ end
449
+
445
450
  def full_clone?
446
451
  depth.nil?
447
452
  end
@@ -60,8 +60,8 @@ module Bundler
60
60
  end
61
61
 
62
62
  def eql?(other)
63
- return unless other.class == self.class
64
- expanded_original_path == other.expanded_original_path &&
63
+ [Gemspec, Path].include?(other.class) &&
64
+ expanded_original_path == other.expanded_original_path &&
65
65
  version == other.version
66
66
  end
67
67
 
@@ -173,39 +173,57 @@ module Bundler
173
173
 
174
174
  def map_sources(replacement_sources)
175
175
  rubygems = @rubygems_sources.map do |source|
176
- replace_rubygems_source(replacement_sources, source) || source
176
+ replace_rubygems_source(replacement_sources, source)
177
177
  end
178
178
 
179
179
  git, plugin = [@git_sources, @plugin_sources].map do |sources|
180
180
  sources.map do |source|
181
- replacement_sources.find {|s| s == source } || source
181
+ replace_source(replacement_sources, source)
182
182
  end
183
183
  end
184
184
 
185
185
  path = @path_sources.map do |source|
186
- replacement_sources.find {|s| s == (source.is_a?(Source::Gemspec) ? source.as_path_source : source) } || source
186
+ replace_path_source(replacement_sources, source)
187
187
  end
188
188
 
189
189
  [rubygems, path, git, plugin]
190
190
  end
191
191
 
192
192
  def global_replacement_source(replacement_sources)
193
- replacement_source = replace_rubygems_source(replacement_sources, global_rubygems_source)
194
- return global_rubygems_source unless replacement_source
195
-
196
- replacement_source.local!
197
- replacement_source
193
+ replace_rubygems_source(replacement_sources, global_rubygems_source, &:local!)
198
194
  end
199
195
 
200
196
  def replace_rubygems_source(replacement_sources, gemfile_source)
197
+ replace_source(replacement_sources, gemfile_source) do |replacement_source|
198
+ # locked sources never include credentials so always prefer remotes from the gemfile
199
+ replacement_source.remotes = gemfile_source.remotes
200
+
201
+ yield replacement_source if block_given?
202
+
203
+ replacement_source
204
+ end
205
+ end
206
+
207
+ def replace_source(replacement_sources, gemfile_source)
201
208
  replacement_source = replacement_sources.find {|s| s == gemfile_source }
202
- return unless replacement_source
209
+ return gemfile_source unless replacement_source
210
+
211
+ replacement_source = yield(replacement_source) if block_given?
203
212
 
204
- # locked sources never include credentials so always prefer remotes from the gemfile
205
- replacement_source.remotes = gemfile_source.remotes
206
213
  replacement_source
207
214
  end
208
215
 
216
+ def replace_path_source(replacement_sources, gemfile_source)
217
+ replace_source(replacement_sources, gemfile_source) do |replacement_source|
218
+ if gemfile_source.is_a?(Source::Gemspec)
219
+ gemfile_source.checksum_store = replacement_source.checksum_store
220
+ gemfile_source
221
+ else
222
+ replacement_source
223
+ end
224
+ end
225
+ end
226
+
209
227
  def different_sources?(lock_sources, replacement_sources)
210
228
  !equivalent_sources?(lock_sources, replacement_sources)
211
229
  end
@@ -29,9 +29,9 @@ module Bundler
29
29
  end
30
30
 
31
31
  def normalize_platforms!(deps, platforms)
32
- complete_platforms = add_extra_platforms!(platforms)
32
+ add_extra_platforms!(platforms)
33
33
 
34
- complete_platforms.map do |platform|
34
+ platforms.map! do |platform|
35
35
  next platform if platform == Gem::Platform::RUBY
36
36
 
37
37
  begin
@@ -44,11 +44,20 @@ module Bundler
44
44
  next platform if incomplete_for_platform?(deps, less_specific_platform)
45
45
 
46
46
  less_specific_platform
47
- end.uniq
47
+ end.uniq!
48
+ end
49
+
50
+ def add_originally_invalid_platforms!(platforms, originally_invalid_platforms)
51
+ originally_invalid_platforms.each do |originally_invalid_platform|
52
+ platforms << originally_invalid_platform if complete_platform(originally_invalid_platform)
53
+ end
48
54
  end
49
55
 
50
56
  def add_extra_platforms!(platforms)
51
- return platforms.concat([Gem::Platform::RUBY]).uniq if @specs.empty?
57
+ if @specs.empty?
58
+ platforms.concat([Gem::Platform::RUBY]).uniq
59
+ return
60
+ end
52
61
 
53
62
  new_platforms = all_platforms.select do |platform|
54
63
  next if platforms.include?(platform)
@@ -56,14 +65,13 @@ module Bundler
56
65
 
57
66
  complete_platform(platform)
58
67
  end
59
- return platforms if new_platforms.empty?
68
+ return if new_platforms.empty?
60
69
 
61
70
  platforms.concat(new_platforms)
71
+ return if new_platforms.include?(Bundler.local_platform)
62
72
 
63
73
  less_specific_platform = new_platforms.find {|platform| platform != Gem::Platform::RUBY && Bundler.local_platform === platform && platform === Bundler.local_platform }
64
74
  platforms.delete(Bundler.local_platform) if less_specific_platform
65
-
66
- platforms
67
75
  end
68
76
 
69
77
  def validate_deps(s)
@@ -173,7 +181,7 @@ module Bundler
173
181
  end
174
182
 
175
183
  def version_for(name)
176
- self[name].first&.version
184
+ exemplary_spec(name)&.version
177
185
  end
178
186
 
179
187
  def what_required(spec)
@@ -278,8 +286,13 @@ module Bundler
278
286
  end
279
287
 
280
288
  def additional_variants_from(other)
281
- other.select do |spec|
282
- version_for(spec.name) == spec.version && valid_dependencies?(spec)
289
+ other.select do |other_spec|
290
+ spec = exemplary_spec(other_spec.name)
291
+ next unless spec
292
+
293
+ selected = spec.version == other_spec.version && valid_dependencies?(other_spec)
294
+ other_spec.source = spec.source if selected
295
+ selected
283
296
  end
284
297
  end
285
298
 
@@ -356,5 +369,9 @@ module Bundler
356
369
  hash[key] ||= []
357
370
  hash[key] << value
358
371
  end
372
+
373
+ def exemplary_spec(name)
374
+ self[name].first
375
+ end
359
376
  end
360
377
  end
@@ -41,6 +41,7 @@ class Bundler::ConnectionPool::TimedStack
41
41
  def push(obj, options = {})
42
42
  @mutex.synchronize do
43
43
  if @shutdown_block
44
+ @created -= 1 unless @created == 0
44
45
  @shutdown_block.call(obj)
45
46
  else
46
47
  store_connection obj, options
@@ -98,6 +99,26 @@ class Bundler::ConnectionPool::TimedStack
98
99
  end
99
100
  end
100
101
 
102
+ ##
103
+ # Reaps connections that were checked in more than +idle_seconds+ ago.
104
+ def reap(idle_seconds, &block)
105
+ raise ArgumentError, "reap must receive a block" unless block
106
+ raise ArgumentError, "idle_seconds must be a number" unless idle_seconds.is_a?(Numeric)
107
+ raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
108
+
109
+ idle.times do
110
+ conn =
111
+ @mutex.synchronize do
112
+ raise Bundler::ConnectionPool::PoolShuttingDownError if @shutdown_block
113
+
114
+ reserve_idle_connection(idle_seconds)
115
+ end
116
+ break unless conn
117
+
118
+ block.call(conn)
119
+ end
120
+ end
121
+
101
122
  ##
102
123
  # Returns +true+ if there are no available connections.
103
124
 
@@ -112,6 +133,12 @@ class Bundler::ConnectionPool::TimedStack
112
133
  @max - @created + @que.length
113
134
  end
114
135
 
136
+ ##
137
+ # The number of connections created and available on the stack.
138
+ def idle
139
+ @que.length
140
+ end
141
+
115
142
  private
116
143
 
117
144
  def current_time
@@ -133,7 +160,7 @@ class Bundler::ConnectionPool::TimedStack
133
160
  # This method must return a connection from the stack.
134
161
 
135
162
  def fetch_connection(options = nil)
136
- @que.pop
163
+ @que.pop&.first
137
164
  end
138
165
 
139
166
  ##
@@ -144,9 +171,32 @@ class Bundler::ConnectionPool::TimedStack
144
171
  def shutdown_connections(options = nil)
145
172
  while connection_stored?(options)
146
173
  conn = fetch_connection(options)
174
+ @created -= 1 unless @created == 0
147
175
  @shutdown_block.call(conn)
148
176
  end
149
- @created = 0
177
+ end
178
+
179
+ ##
180
+ # This is an extension point for TimedStack and is called with a mutex.
181
+ #
182
+ # This method returns the oldest idle connection if it has been idle for more than idle_seconds.
183
+ # This requires that the stack is kept in order of checked in time (oldest first).
184
+
185
+ def reserve_idle_connection(idle_seconds)
186
+ return unless idle_connections?(idle_seconds)
187
+
188
+ @created -= 1 unless @created == 0
189
+
190
+ @que.shift.first
191
+ end
192
+
193
+ ##
194
+ # This is an extension point for TimedStack and is called with a mutex.
195
+ #
196
+ # Returns true if the first connection in the stack has been idle for more than idle_seconds
197
+
198
+ def idle_connections?(idle_seconds)
199
+ connection_stored? && (current_time - @que.first.last > idle_seconds)
150
200
  end
151
201
 
152
202
  ##
@@ -155,7 +205,7 @@ class Bundler::ConnectionPool::TimedStack
155
205
  # This method must return +obj+ to the stack.
156
206
 
157
207
  def store_connection(obj, options = nil)
158
- @que.push obj
208
+ @que.push [obj, current_time]
159
209
  end
160
210
 
161
211
  ##
@@ -1,3 +1,3 @@
1
1
  class Bundler::ConnectionPool
2
- VERSION = "2.4.1"
2
+ VERSION = "2.5.0"
3
3
  end
@@ -160,6 +160,12 @@ class Bundler::ConnectionPool
160
160
  @available.shutdown(reload: true, &block)
161
161
  end
162
162
 
163
+ ## Reaps idle connections that have been idle for over +idle_seconds+.
164
+ # +idle_seconds+ defaults to 60.
165
+ def reap(idle_seconds = 60, &block)
166
+ @available.reap(idle_seconds, &block)
167
+ end
168
+
163
169
  # Size of this connection pool
164
170
  attr_reader :size
165
171
  # Automatically drop all connections after fork
@@ -169,6 +175,11 @@ class Bundler::ConnectionPool
169
175
  def available
170
176
  @available.length
171
177
  end
178
+
179
+ # Number of pool entries created and idle in the pool.
180
+ def idle
181
+ @available.idle
182
+ end
172
183
  end
173
184
 
174
185
  require_relative "connection_pool/timed_stack"
@@ -79,29 +79,17 @@ module Bundler::PubGrub
79
79
  dependencies_for(@root_package, @root_version)
80
80
  end
81
81
 
82
- # Override me (maybe)
83
- #
84
- # If not overridden, the order returned by all_versions_for will be used
85
- #
86
- # Returns: Array of versions in preferred order
87
- def sort_versions_by_preferred(package, sorted_versions)
88
- indexes = @version_indexes[package]
89
- sorted_versions.sort_by { |version| indexes[version] }
90
- end
91
-
92
82
  def initialize
93
83
  @root_package = Package.root
94
84
  @root_version = Package.root_version
95
85
 
96
- @cached_versions = Hash.new do |h,k|
86
+ @sorted_versions = Hash.new do |h,k|
97
87
  if k == @root_package
98
88
  h[k] = [@root_version]
99
89
  else
100
- h[k] = all_versions_for(k)
90
+ h[k] = all_versions_for(k).sort
101
91
  end
102
92
  end
103
- @sorted_versions = Hash.new { |h,k| h[k] = @cached_versions[k].sort }
104
- @version_indexes = Hash.new { |h,k| h[k] = @cached_versions[k].each.with_index.to_h }
105
93
 
106
94
  @cached_dependencies = Hash.new do |packages, package|
107
95
  if package == @root_package
@@ -117,15 +105,7 @@ module Bundler::PubGrub
117
105
  end
118
106
 
119
107
  def versions_for(package, range=VersionRange.any)
120
- versions = range.select_versions(@sorted_versions[package])
121
-
122
- # Conditional avoids (among other things) calling
123
- # sort_versions_by_preferred with the root package
124
- if versions.size > 1
125
- sort_versions_by_preferred(package, versions)
126
- else
127
- versions
128
- end
108
+ range.select_versions(@sorted_versions[package])
129
109
  end
130
110
 
131
111
  def no_versions_incompatibility_for(_package, unsatisfied_term)
@@ -164,7 +144,7 @@ module Bundler::PubGrub
164
144
  sorted_versions[high]
165
145
  end
166
146
 
167
- range = VersionRange.new(min: low, max: high, include_min: true)
147
+ range = VersionRange.new(min: low, max: high, include_min: !low.nil?)
168
148
 
169
149
  self_constraint = VersionConstraint.new(package, range: range)
170
150
 
@@ -0,0 +1,42 @@
1
+ module Bundler::PubGrub
2
+ class Strategy
3
+ def initialize(source)
4
+ @source = source
5
+
6
+ @root_package = Package.root
7
+ @root_version = Package.root_version
8
+
9
+ @version_indexes = Hash.new do |h,k|
10
+ if k == @root_package
11
+ h[k] = { @root_version => 0 }
12
+ else
13
+ h[k] = @source.all_versions_for(k).each.with_index.to_h
14
+ end
15
+ end
16
+ end
17
+
18
+ def next_package_and_version(unsatisfied)
19
+ package, range = next_term_to_try_from(unsatisfied)
20
+
21
+ [package, most_preferred_version_of(package, range)]
22
+ end
23
+
24
+ private
25
+
26
+ def most_preferred_version_of(package, range)
27
+ versions = @source.versions_for(package, range)
28
+
29
+ indexes = @version_indexes[package]
30
+ versions.min_by { |version| indexes[version] }
31
+ end
32
+
33
+ def next_term_to_try_from(unsatisfied)
34
+ unsatisfied.min_by do |package, range|
35
+ matching_versions = @source.versions_for(package, range)
36
+ higher_versions = @source.versions_for(package, range.upper_invert)
37
+
38
+ [matching_versions.count <= 1 ? 0 : 1, higher_versions.count]
39
+ end
40
+ end
41
+ end
42
+ end