diff --git a/bundler/lib/dependabot/bundler/update_checker/force_updater.rb b/bundler/lib/dependabot/bundler/update_checker/force_updater.rb index c14558c41d9..686beaed4eb 100644 --- a/bundler/lib/dependabot/bundler/update_checker/force_updater.rb +++ b/bundler/lib/dependabot/bundler/update_checker/force_updater.rb @@ -77,6 +77,15 @@ def original_dependencies ).parse end + def top_level_dependencies + @top_level_dependencies ||= + FileParser.new( + dependency_files: dependency_files.reject { |file| file.name == lockfile.name }, + credentials: credentials, + source: nil + ).parse + end + def dependencies_from(updated_deps, specs) # You might think we'd want to remove dependencies whose version # hadn't changed from this array. We don't. We still need to unlock @@ -85,14 +94,17 @@ def dependencies_from(updated_deps, specs) # # This is kind of a bug in Bundler, and we should try to fix it, # but resolving it won't necessarily be easy. - updated_deps.filter_map do |dep| - original_dep = - original_dependencies.find { |d| d.name == dep.fetch("name") } - spec = specs.find { |d| d.fetch("name") == dep.fetch("name") } - next if spec.fetch("version") == original_dep.version + # put the lead dependency first + index = specs.index { |dep| dep["name"] == updated_deps.first["name"] } + specs.unshift(specs.delete_at(index)) + specs.filter_map do |dep| + next unless top_level_dependencies.find { |d| d.name == dep.fetch("name") } + + original_dep = original_dependencies.find { |d| d.name == dep.fetch("name") } + next if dep.fetch("version") == original_dep.version - build_dependency(original_dep, spec) + build_dependency(original_dep, dep) end end diff --git a/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb b/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb index 1c0fd2ff978..653251dfb83 100644 --- a/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb +++ b/bundler/spec/dependabot/bundler/update_checker/force_updater_spec.rb @@ -215,5 +215,50 @@ to raise_error(Dependabot::DependencyFileNotResolvable) end end + + context "when peer dependencies in the Gemfile should update together" do + let(:dependency_files) { bundler_project_dependency_files("top_level_update") } + let(:target_version) { "19.4" } + let(:dependency_name) { "octicons" } + let(:requirements) do + [{ + file: "Gemfile", + requirement: "~> 19.2", + groups: [:default], + source: nil + }] + end + let(:expected_requirements) do + [{ + file: "Gemfile", + requirement: "~> 19.4", + groups: [:default], + source: nil + }] + end + + it "updates all dependencies" do + expect(updated_dependencies).to eq( + [ + Dependabot::Dependency.new( + name: "octicons", + version: "19.4.0", + previous_version: "19.2.0", + requirements: expected_requirements, + previous_requirements: requirements, + package_manager: "bundler" + ), + Dependabot::Dependency.new( + name: "octicons_helper", + version: "19.4.0", + previous_version: "19.2.0", + requirements: expected_requirements, + previous_requirements: requirements, + package_manager: "bundler" + ) + ] + ) + end + end end end diff --git a/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile b/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile new file mode 100644 index 00000000000..3a4d403c749 --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem "octicons", "~> 19.2" +gem "octicons_helper", "~> 19.2" diff --git a/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile.lock b/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile.lock new file mode 100644 index 00000000000..dda2e5040f3 --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler1/top_level_update/Gemfile.lock @@ -0,0 +1,74 @@ +GEM + remote: https://rubygems.org/ + specs: + actionpack (7.0.6) + actionview (= 7.0.6) + activesupport (= 7.0.6) + rack (~> 2.0, >= 2.2.4) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (7.0.6) + activesupport (= 7.0.6) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activesupport (7.0.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + builder (3.2.4) + concurrent-ruby (1.2.2) + crass (1.0.6) + erubi (1.12.0) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + loofah (2.21.3) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + method_source (1.0.0) + mini_portile2 (2.8.4) + minitest (5.18.1) + nokogiri (1.15.3) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + octicons (19.2.0) + octicons_helper (19.2.0) + actionview + octicons (= 19.2.0) + railties + racc (1.7.1) + rack (2.2.7) + rack-test (2.1.0) + rack (>= 1.3) + rails-dom-testing (2.1.1) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.6) + actionpack (= 7.0.6) + activesupport (= 7.0.6) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.0.6) + thor (1.2.2) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + zeitwerk (2.6.8) + +PLATFORMS + ruby + +DEPENDENCIES + octicons (~> 19.2) + octicons_helper (~> 19.2) + +BUNDLED WITH + 1.17.3 diff --git a/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile b/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile new file mode 100644 index 00000000000..3a4d403c749 --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile @@ -0,0 +1,8 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem "octicons", "~> 19.2" +gem "octicons_helper", "~> 19.2" diff --git a/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile.lock b/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile.lock new file mode 100644 index 00000000000..fc0126ffccd --- /dev/null +++ b/bundler/spec/fixtures/projects/bundler2/top_level_update/Gemfile.lock @@ -0,0 +1,74 @@ +GEM + remote: https://rubygems.org/ + specs: + actionpack (7.0.6) + actionview (= 7.0.6) + activesupport (= 7.0.6) + rack (~> 2.0, >= 2.2.4) + rack-test (>= 0.6.3) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actionview (7.0.6) + activesupport (= 7.0.6) + builder (~> 3.1) + erubi (~> 1.4) + rails-dom-testing (~> 2.0) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activesupport (7.0.6) + concurrent-ruby (~> 1.0, >= 1.0.2) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + builder (3.2.4) + concurrent-ruby (1.2.2) + crass (1.0.6) + erubi (1.12.0) + i18n (1.14.1) + concurrent-ruby (~> 1.0) + loofah (2.21.3) + crass (~> 1.0.2) + nokogiri (>= 1.12.0) + method_source (1.0.0) + mini_portile2 (2.8.2) + minitest (5.18.1) + nokogiri (1.15.2) + mini_portile2 (~> 2.8.2) + racc (~> 1.4) + octicons (19.2.0) + octicons_helper (19.2.0) + actionview + octicons (= 19.2.0) + railties + racc (1.7.1) + rack (2.2.7) + rack-test (2.1.0) + rack (>= 1.3) + rails-dom-testing (2.1.1) + activesupport (>= 5.0.0) + minitest + nokogiri (>= 1.6) + rails-html-sanitizer (1.6.0) + loofah (~> 2.21) + nokogiri (~> 1.14) + railties (7.0.6) + actionpack (= 7.0.6) + activesupport (= 7.0.6) + method_source + rake (>= 12.2) + thor (~> 1.0) + zeitwerk (~> 2.5) + rake (13.0.6) + thor (1.2.2) + tzinfo (2.0.6) + concurrent-ruby (~> 1.0) + zeitwerk (2.6.8) + +PLATFORMS + ruby + +DEPENDENCIES + octicons (~> 19.2) + octicons_helper (~> 19.2) + +BUNDLED WITH + 2.1.4