Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
1ee42d2
[ruby/rubygems] Apply cooldown to locally installed gem versions
hsbt Jun 2, 2026
4af3a57
[ruby/rubygems] Address PR review on cooldown local stub bypass
hsbt Jun 2, 2026
3220428
Inline the skip-detection run command
hsbt Jun 2, 2026
220f66a
Build full path for HOME stat snapshot
hsbt Jun 2, 2026
6da78ca
[ruby/rubygems] Replace Molinillo with PubGrub for dependency resolution
mlarraz Mar 16, 2026
456a2db
[ruby/rubygems] Fix lockfile requirement preservation and orphaned de…
mlarraz Mar 16, 2026
600240d
[ruby/rubygems] Extend catching Gem::DependencyResolutionError error …
colby-swandale Apr 12, 2026
1429829
[ruby/rubygems] Simplify DependencyResolutionError by removing PubGru…
colby-swandale Apr 12, 2026
f07eabd
[ruby/rubygems] find_all_gems test coverage
colby-swandale Apr 12, 2026
b1b53a2
[ruby/rubygems] Remove dead Molinillo-era error classes and simplify …
colby-swandale Apr 12, 2026
c131a63
[ruby/rubygems] Rename orphaned deps test to reflect PubGrub backtrac…
colby-swandale Apr 12, 2026
c668279
[ruby/rubygems] Fix prerelease version inconsistency between all_vers…
colby-swandale Apr 12, 2026
87607bf
[ruby/rubygems] Remove unreachable spec_for string fallback
colby-swandale Apr 12, 2026
270f7de
[ruby/rubygems] Remove dead resolver methods
colby-swandale Apr 12, 2026
c7fc867
[ruby/rubygems] Define custom PubGrub Root Package to customise error
colby-swandale Apr 12, 2026
91ee03f
[ruby/rubygems] Add extended explanations for platform and Ruby versi…
colby-swandale Apr 12, 2026
56a402c
[ruby/rubygems] Add custom inline explanation when gems are filtered …
colby-swandale Apr 12, 2026
c7d6a21
[ruby/rubygems] Optimize PubGrub resolver performance
colby-swandale Apr 13, 2026
f609e7e
[ruby/rubygems] Skip self-dependencies in compute_dependencies
colby-swandale Apr 13, 2026
6b3ae73
[ruby/rubygems] Use PubGrub's intended root dependency pattern
colby-swandale Apr 13, 2026
a687a6b
[ruby/rubygems] Fetch development dependencies for API-sourced specs
colby-swandale Apr 13, 2026
43128a8
[ruby/rubygems] Align incompatibilities_for with BasicPackageSource d…
colby-swandale Apr 13, 2026
d6a233c
[ruby/rubygems] Distinguish unknown packages from filtered packages i…
colby-swandale Apr 13, 2026
ffde879
[ruby/rubygems] Improve error messages for contradictory requirements…
colby-swandale Apr 13, 2026
488ac14
[ruby/rubygems] Fix --force to skip unsatisfiable version constraints
colby-swandale Apr 13, 2026
759acb7
[ruby/rubygems] Resolve issue with pre-release root dependencies bein…
colby-swandale Apr 20, 2026
a56f5b4
[ruby/rubygems] Fix transitive prerelease filtering and clean up reso…
colby-swandale Apr 20, 2026
d9fb427
[ruby/rubygems] Performance optimisations
colby-swandale Apr 20, 2026
df20297
[ruby/rubygems] resolver performance optimisations
colby-swandale May 26, 2026
1a39445
[ruby/rubygems] Fix resolver eliminating all gem versions on a missin…
colby-swandale Jun 2, 2026
156935f
[ruby/rubygems] Remove dangling resolver stats call from --explain path
colby-swandale Jun 2, 2026
f7932a5
[ruby/rubygems] Prefer the earlier source for same-version dependency…
colby-swandale Jun 2, 2026
5e3e2a4
[ruby/rubygems] Include the missing dependency's version in resolver …
colby-swandale Jun 2, 2026
f916141
[DOC] Fix hash style for Struct
peterzhu2118 Jun 2, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions .github/workflows/tarball-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,7 @@ jobs:
skip: ${{ steps.skipping.outputs.skip }}
steps:
- id: skipping
run:
echo 'skip=true' >> $GITHUB_OUTPUT
run: echo 'skip=true' >> $GITHUB_OUTPUT
if: >-
${{(false
|| contains(github.event.head_commit.message, '[DOC]')
Expand Down
3 changes: 2 additions & 1 deletion .github/workflows/tarball-ubuntu.yml
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,8 @@ jobs:
[
Dir.home,
].each do |dir|
Dir.each_child(dir) do |pn|
Dir.each_child(dir) do |name|
pn = File.join(dir, name)
st = File.stat(pn)
if st.file?
content = Digest::SHA1.file(pn).hexdigest
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/remote_specification.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ class RemoteSpecification

attr_reader :name, :version, :platform
attr_writer :dependencies
attr_accessor :source, :remote, :locked_platform
attr_accessor :source, :remote, :locked_platform, :created_at

def initialize(name, version, platform, spec_fetcher)
@name = name
Expand Down
23 changes: 14 additions & 9 deletions lib/bundler/resolver.rb
Original file line number Diff line number Diff line change
Expand Up @@ -422,7 +422,7 @@ def filter_matching_specs(specs, requirements)
end

def filter_specs(specs, package)
filter_cooldown(filter_remote_specs(filter_prereleases(specs, package), package))
filter_remote_specs(filter_cooldown(filter_prereleases(specs, package)), package)
end

def filter_prereleases(specs, package)
Expand All @@ -433,19 +433,24 @@ def filter_prereleases(specs, package)

def filter_cooldown(specs)
return specs if specs.empty?
excluded = cooldown_excluded_specs(specs)
return specs if excluded.empty?
specs - excluded
excluded_versions = cooldown_excluded_versions(specs)
return specs if excluded_versions.empty?
specs.reject {|s| excluded_versions.include?([s.name, s.version]) }
end

def cooldown_excluded_specs(specs)
specs.select {|spec| cooldown_excluded?(spec) }
def cooldown_excluded_versions(specs)
excluded = {}
specs.each do |spec|
next unless cooldown_excluded?(spec)
excluded[[spec.name, spec.version]] = true
end
excluded
end

def cooldown_hint(specs)
excluded = cooldown_excluded_specs(specs)
return nil if excluded.empty?
"#{excluded.size} version#{"s" if excluded.size > 1} excluded by the cooldown setting; pass `--cooldown 0` to bypass"
excluded_versions = cooldown_excluded_versions(specs)
return nil if excluded_versions.empty?
"#{excluded_versions.size} version#{"s" if excluded_versions.size > 1} excluded by the cooldown setting; pass `--cooldown 0` to bypass"
end

def cooldown_excluded?(spec)
Expand Down
34 changes: 34 additions & 0 deletions lib/bundler/source/rubygems.rb
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,13 @@ def specs
# sources, and large_idx.merge! small_idx is way faster than
# small_idx.merge! large_idx.
index = @allow_remote ? remote_specs.dup : Index.new

# Snapshot per-version `created_at` from the remote info before installed
# / cached specs overwrite the EndpointSpecification objects that carry
# it. The cooldown filter consults `created_at` on every candidate, so
# local stubs need the published date back-filled to participate.
remote_created_at = collect_remote_created_at(index)

index.merge!(cached_specs) if @allow_cached
index.merge!(installed_specs) if @allow_local

Expand All @@ -163,6 +170,8 @@ def specs
end
end

backfill_created_at(index, remote_created_at) unless remote_created_at.empty?

index
end
end
Expand Down Expand Up @@ -470,6 +479,31 @@ def cache_path

private

def collect_remote_created_at(index)
return {} unless @allow_remote

snapshot = {}
index.each do |spec|
next unless spec.respond_to?(:created_at) && spec.created_at
# Remember the remote that supplied the date too: when a source has
# several remotes with different per-URI cooldown settings we must
# restore the same one during backfill so `effective_cooldown` agrees.
snapshot[[spec.name, spec.version]] = [spec.created_at, spec.remote]
end
snapshot
end

def backfill_created_at(index, snapshot)
index.each do |spec|
next unless spec.respond_to?(:created_at=)
next if spec.created_at
remote_created_at, remote = snapshot[[spec.name, spec.version]]
next unless remote_created_at
spec.created_at = remote_created_at
spec.remote ||= remote if remote && spec.respond_to?(:remote=)
end
end

def lockfile_remotes
@lockfile_remotes || credless_remotes
end
Expand Down
3 changes: 3 additions & 0 deletions lib/rubygems/commands/exec_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,9 @@ def install
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
terminate_interaction 1
rescue Gem::DependencyResolutionError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
terminate_interaction 2
rescue Gem::GemNotFoundException => e
show_lookup_failure e.name, e.version, e.errors, false

Expand Down
3 changes: 3 additions & 0 deletions lib/rubygems/commands/install_command.rb
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ def install_gems # :nodoc:
rescue Gem::InstallError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 1
rescue Gem::DependencyResolutionError => e
alert_error "Error installing #{gem_name}:\n\t#{e.message}"
exit_code |= 2
rescue Gem::UnsatisfiableDependencyError => e
show_lookup_failure e.name, e.version, e.errors, suppress_suggestions,
"'#{gem_name}' (#{gem_version})"
Expand Down
54 changes: 11 additions & 43 deletions lib/rubygems/exceptions.rb
Original file line number Diff line number Diff line change
Expand Up @@ -33,22 +33,24 @@ class Gem::DependencyError < Gem::Exception; end
class Gem::DependencyRemovalException < Gem::Exception; end

##
# Raised by Gem::Resolver when a Gem::Dependency::Conflict reaches the
# toplevel. Indicates which dependencies were incompatible through #conflict
# and #conflicting_dependencies
# Raised by Gem::Resolver when dependency resolution fails.

class Gem::DependencyResolutionError < Gem::DependencyError
attr_reader :conflict

def initialize(conflict)
@conflict = conflict
a, b = conflicting_dependencies
@explanation = conflict.explanation
super @explanation
end

super "conflicting dependencies #{a} and #{b}\n#{@conflict.explanation}"
def explanation
@explanation
end

def conflict
nil
end

def conflicting_dependencies
@conflict.conflicting_dependencies
[]
end
end

Expand Down Expand Up @@ -126,40 +128,6 @@ def initialize(name, version, errors = nil)

Gem.deprecate_constant :SpecificGemNotFoundException

##
# Raised by Gem::Resolver when dependencies conflict and create the
# inability to find a valid possible spec for a request.

class Gem::ImpossibleDependenciesError < Gem::Exception
attr_reader :conflicts
attr_reader :request

def initialize(request, conflicts)
@request = request
@conflicts = conflicts

super build_message
end

def build_message # :nodoc:
requester = @request.requester
requester = requester ? requester.spec.full_name : "The user"
dependency = @request.dependency

message = "#{requester} requires #{dependency} but it conflicted:\n".dup

@conflicts.each do |_, conflict|
message << conflict.explanation
end

message
end

def dependency
@request.dependency
end
end

class Gem::InstallError < Gem::Exception; end

class Gem::RuntimeRequirementNotMetError < Gem::InstallError
Expand Down
4 changes: 0 additions & 4 deletions lib/rubygems/request_set.rb
Original file line number Diff line number Diff line change
Expand Up @@ -234,10 +234,6 @@ def install_from_gemdeps(options, &block)
sorted_requests.each do |spec|
puts " #{spec.full_name}"
end

if Gem.configuration.really_verbose
@resolver.stats.display
end
else
installed = install options, &block

Expand Down
Loading