diff options
author | David RodrÃguez <[email protected]> | 2025-02-13 19:08:16 +0100 |
---|---|---|
committer | Hiroshi SHIBATA <[email protected]> | 2025-02-18 12:12:54 +0900 |
commit | 507de2226bcc75b1d0d8c1736cdbe62e46a97916 (patch) | |
tree | 7dd7d53468a94956a81d5d784a1d295d9321369d | |
parent | 203a570f68b43ce591c5ef1784112725fa0692cd (diff) |
[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
-rw-r--r-- | lib/bundler/definition.rb | 14 | ||||
-rw-r--r-- | spec/bundler/commands/update_spec.rb | 66 |
2 files changed, 75 insertions, 5 deletions
diff --git a/lib/bundler/definition.rb b/lib/bundler/definition.rb index 61add4be10..24dae86493 100644 --- a/lib/bundler/definition.rb +++ b/lib/bundler/definition.rb @@ -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 diff --git a/spec/bundler/commands/update_spec.rb b/spec/bundler/commands/update_spec.rb index 058cd2ec82..f6d0188794 100644 --- a/spec/bundler/commands/update_spec.rb +++ b/spec/bundler/commands/update_spec.rb @@ -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| |