[rubygems/rubygems] Fix Bundler incorrectly downgrading direct dependencies

There's no reason to call `converge_specs` when adding additional
lower bound requirements to prevent downgrades, and it actually causes
the extra requirements to be missed sometimes.

Loop over the originally locked specs directly, adding the additional
precaution of not adding the requirement if the Gemfile dependency has
changed and it no longer matches the locked spec.

https://github.com/rubygems/rubygems/commit/5154506912
This commit is contained in:
David Rodríguez 2025-02-13 19:08:16 +01:00 committed by Hiroshi SHIBATA
parent 203a570f68
commit 507de2226b
2 changed files with 75 additions and 5 deletions

View File

@ -938,7 +938,7 @@ module Bundler
def converge_dependencies
@missing_lockfile_dep = nil
changes = false
@changed_dependencies = []
current_dependencies.each do |dep|
if dep.source
@ -960,10 +960,10 @@ module Bundler
end
end
changes ||= dep_changed
@changed_dependencies << name if dep_changed
end
changes
@changed_dependencies.any?
end
# Remove elements from the locked specs that are expired. This will most
@ -1095,9 +1095,13 @@ module Bundler
def additional_base_requirements_to_prevent_downgrades(resolution_packages)
return resolution_packages unless @locked_gems && !sources.expired_sources?(@locked_gems.sources)
converge_specs(@originally_locked_specs).each do |locked_spec|
@originally_locked_specs.each do |locked_spec|
next if locked_spec.source.is_a?(Source::Path)
resolution_packages.base_requirements[locked_spec.name] = Gem::Requirement.new(">= #{locked_spec.version}")
name = locked_spec.name
next if @changed_dependencies.include?(name)
resolution_packages.base_requirements[name] = Gem::Requirement.new(">= #{locked_spec.version}")
end
resolution_packages
end

View File

@ -428,6 +428,72 @@ RSpec.describe "bundle update" do
expect(out).to include("Installing sneakers 2.11.0").and include("Installing rake 13.0.6")
end
it "does not downgrade direct dependencies unnecessarily" do
build_repo4 do
build_gem "redis", "4.8.1"
build_gem "redis", "5.3.0"
build_gem "sidekiq", "6.5.5" do |s|
s.add_dependency "redis", ">= 4.5.0"
end
build_gem "sidekiq", "6.5.12" do |s|
s.add_dependency "redis", ">= 4.5.0", "< 5"
end
# one version of sidekiq above Gemfile's range is needed to make the
# resolver choose `redis` first and trying to upgrade it, reproducing
# the accidental sidekiq downgrade as a result
build_gem "sidekiq", "7.0.0 " do |s|
s.add_dependency "redis", ">= 4.2.0"
end
build_gem "sentry-sidekiq", "5.22.0" do |s|
s.add_dependency "sidekiq", ">= 3.0"
end
build_gem "sentry-sidekiq", "5.22.4" do |s|
s.add_dependency "sidekiq", ">= 3.0"
end
end
gemfile <<~G
source "https://gem.repo4"
gem "redis"
gem "sidekiq", "~> 6.5"
gem "sentry-sidekiq"
G
original_lockfile = <<~L
GEM
remote: https://gem.repo4/
specs:
redis (4.8.1)
sentry-sidekiq (5.22.0)
sidekiq (>= 3.0)
sidekiq (6.5.12)
redis (>= 4.5.0, < 5)
PLATFORMS
#{lockfile_platforms}
DEPENDENCIES
redis
sentry-sidekiq
sidekiq (~> 6.5)
BUNDLED WITH
#{Bundler::VERSION}
L
lockfile original_lockfile
bundle "lock --update sentry-sidekiq"
expect(lockfile).to eq(original_lockfile.sub("sentry-sidekiq (5.22.0)", "sentry-sidekiq (5.22.4)"))
end
it "does not downgrade indirect dependencies unnecessarily" do
build_repo4 do
build_gem "a" do |s|