From 679219f890fb0695fc478acdbf491a8a4751721c Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 25 Mar 2026 11:40:49 +0900 Subject: [PATCH 01/21] Merge pull request #4892 from godfat/fix-setup-custom-gemfile Respect Gemfile bundler setting in `Bundler.setup` (cherry picked from commit dfa3e34bf1037adcc5b8ec7935686dd3fe616b3f) --- bundler/lib/bundler.rb | 10 ++++++++++ bundler/lib/bundler/cli.rb | 6 +----- bundler/spec/runtime/setup_spec.rb | 26 ++++++++++++++++++++++++++ 3 files changed, 37 insertions(+), 5 deletions(-) diff --git a/bundler/lib/bundler.rb b/bundler/lib/bundler.rb index 26e946e05981..8bec10c0db87 100644 --- a/bundler/lib/bundler.rb +++ b/bundler/lib/bundler.rb @@ -156,6 +156,7 @@ def setup(*groups) # Return if all groups are already loaded return @setup if defined?(@setup) && @setup + configure_custom_gemfile definition.validate_runtime! SharedHelpers.print_major_deprecations! @@ -586,6 +587,15 @@ def configure_gem_home_and_path(path = bundle_path) Bundler.rubygems.clear_paths end + def configure_custom_gemfile(custom_gemfile = nil) + custom_gemfile ||= Bundler.settings[:gemfile] + + if custom_gemfile && !custom_gemfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) + reset_settings_and_root! + end + end + def self_manager @self_manager ||= begin require_relative "bundler/self_manager" diff --git a/bundler/lib/bundler/cli.rb b/bundler/lib/bundler/cli.rb index 0f234f26b956..9a0f756bcf82 100644 --- a/bundler/lib/bundler/cli.rb +++ b/bundler/lib/bundler/cli.rb @@ -61,11 +61,7 @@ def initialize(*args) current_cmd = args.last[:current_command].name - custom_gemfile = options[:gemfile] || Bundler.settings[:gemfile] - if custom_gemfile && !custom_gemfile.empty? - Bundler::SharedHelpers.set_env "BUNDLE_GEMFILE", File.expand_path(custom_gemfile) - reset_settings = true - end + Bundler.configure_custom_gemfile(options[:gemfile]) # lock --lockfile works differently than install --lockfile unless current_cmd == "lock" diff --git a/bundler/spec/runtime/setup_spec.rb b/bundler/spec/runtime/setup_spec.rb index 2cbb61dbff5d..63448151c2a0 100644 --- a/bundler/spec/runtime/setup_spec.rb +++ b/bundler/spec/runtime/setup_spec.rb @@ -303,6 +303,32 @@ def clean_load_path(lp) expect(out).to eq("WIN") end end + + context "user sets it via `config set --local gemfile`" do + it "uses the value in the config" do + gemfile <<-G + source "https://gem.repo1" + gem "myrack" + G + + gemfile bundled_app("CustomGemfile"), <<-G + source "https://gem.repo1" + gem "activesupport", "2.3.5" + G + + bundle "config set --local gemfile #{bundled_app("CustomGemfile")}" + bundle "install" + + ruby <<-R + require 'bundler' + Bundler.setup + require 'activesupport' + puts ACTIVESUPPORT + R + + expect(out).to eq("2.3.5") + end + end end it "prioritizes gems in BUNDLE_PATH over gems in GEM_HOME" do From 8d162667b0893706494e8fc28971d7b43d2c4554 Mon Sep 17 00:00:00 2001 From: Jun Aruga Date: Tue, 26 Oct 2021 22:14:23 +0200 Subject: [PATCH 02/21] Print a warning for a potential confusion from the indirect dependencies. Print a warning when a confusion by the indirect dependencies may happen. See CVE-2020-36327 for the security risk. (cherry picked from commit 403d6744b227169bbfb01a0e204461e67d5da75c) --- bundler/lib/bundler/definition.rb | 22 +++++++++- bundler/spec/bundler/definition_spec.rb | 55 +++++++++++++++++++++++++ 2 files changed, 76 insertions(+), 1 deletion(-) diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index 69842b4813fa..b5710de7ba52 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -783,7 +783,27 @@ def start_resolution end def precompute_source_requirements_for_indirect_dependencies? - sources.non_global_rubygems_sources.all?(&:dependency_api_available?) + return false unless @remote && !sources.aggregate_global_source? + + if sources.non_global_rubygems_sources.all?(&:dependency_api_available?) + true + else + non_dependency_api_warning + false + end + end + + def non_dependency_api_warning + non_api_sources = sources.non_global_rubygems_sources.reject(&:dependency_api_available?) + non_api_source_names = non_api_sources.map {|d| " * #{d}" }.join("\n") + + msg = String.new + msg << "Your Gemfile contains scoped sources that don't implement a dependency API, namely:\n\n" + msg << non_api_source_names + msg << "\n\nUsing the above gem servers may result in installing unexpected gems. " \ + "To resolve this warning, make sure you use gem servers that implement dependency APIs, " \ + "such as gemstash or geminabox gem servers." + Bundler.ui.warn msg end def current_platform_locked? diff --git a/bundler/spec/bundler/definition_spec.rb b/bundler/spec/bundler/definition_spec.rb index 9524c70193eb..1811f2db567e 100644 --- a/bundler/spec/bundler/definition_spec.rb +++ b/bundler/spec/bundler/definition_spec.rb @@ -289,6 +289,61 @@ end end + describe "#precompute_source_requirements_for_indirect_dependencies?" do + before do + allow(Bundler::SharedHelpers).to receive(:find_gemfile) { Pathname.new("Gemfile") } + end + + let(:sources) { Bundler::SourceList.new } + subject { Bundler::Definition.new(nil, [], sources, []) } + + context "when remote and does not have multiple global sources" do + before do + subject.instance_variable_set(:@remote, true) + allow(sources).to receive(:aggregate_global_source?).and_return(false) + allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources) + end + + context "when all the scoped sources contain a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), + double("non-global-source-1", :dependency_api_available? => true, :to_s => "b"), + ] + end + + it "will not raise a warning" do + expect(subject).not_to receive(:non_dependency_api_warning) + + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy + end + end + + context "when scoped sources do not contain a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), + double("non-global-source-1", :dependency_api_available? => false, :to_s => "b"), + double("non-global-source-2", :dependency_api_available? => false, :to_s => "c"), + ] + end + + it "will raise a warning" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) +Your Gemfile contains scoped sources that don't implement a dependency API, namely: + + * b + * c + +Using the above gem servers may result in installing unexpected gems. To resolve this warning, make sure you use gem servers that implement dependency APIs, such as gemstash or geminabox gem servers. + W + + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy + end + end + end + end + def mock_source_list Class.new do def all_sources From 09a31b34c3f238612b232590d471b80450b9b3de Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Apr 2026 17:11:43 +0900 Subject: [PATCH 03/21] Remove extra guard conditions to preserve existing behavior The original PR added @remote and aggregate_global_source? checks to precompute_source_requirements_for_indirect_dependencies?, but these conditions did not exist in the current codebase and would change the method's behavior in cases where @remote is false or aggregate_global_source? is true. Since the goal is only to warn when falling back to the insecure aggregate resolution path, keep the existing condition as-is and just add the warning on the false branch. Simplify the corresponding tests accordingly. Co-Authored-By: Claude Opus 4.6 (1M context) (cherry picked from commit ddd292acf1e10f54693cfcccaedb5d71972dcbde) --- bundler/lib/bundler/definition.rb | 2 - bundler/spec/bundler/definition_spec.rb | 56 ++++++++++++------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index b5710de7ba52..d86829a10cde 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -783,8 +783,6 @@ def start_resolution end def precompute_source_requirements_for_indirect_dependencies? - return false unless @remote && !sources.aggregate_global_source? - if sources.non_global_rubygems_sources.all?(&:dependency_api_available?) true else diff --git a/bundler/spec/bundler/definition_spec.rb b/bundler/spec/bundler/definition_spec.rb index 1811f2db567e..1e821f590180 100644 --- a/bundler/spec/bundler/definition_spec.rb +++ b/bundler/spec/bundler/definition_spec.rb @@ -297,49 +297,45 @@ let(:sources) { Bundler::SourceList.new } subject { Bundler::Definition.new(nil, [], sources, []) } - context "when remote and does not have multiple global sources" do - before do - subject.instance_variable_set(:@remote, true) - allow(sources).to receive(:aggregate_global_source?).and_return(false) - allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources) - end + before do + allow(sources).to receive(:non_global_rubygems_sources).and_return(non_global_rubygems_sources) + end - context "when all the scoped sources contain a dependency API" do - let(:non_global_rubygems_sources) do - [ - double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), - double("non-global-source-1", :dependency_api_available? => true, :to_s => "b"), - ] - end + context "when all the scoped sources implement a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), + double("non-global-source-1", :dependency_api_available? => true, :to_s => "b"), + ] + end - it "will not raise a warning" do - expect(subject).not_to receive(:non_dependency_api_warning) + it "returns true without warning" do + expect(subject).not_to receive(:non_dependency_api_warning) - expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy - end + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_truthy end + end - context "when scoped sources do not contain a dependency API" do - let(:non_global_rubygems_sources) do - [ - double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), - double("non-global-source-1", :dependency_api_available? => false, :to_s => "b"), - double("non-global-source-2", :dependency_api_available? => false, :to_s => "c"), - ] - end + context "when some scoped sources do not implement a dependency API" do + let(:non_global_rubygems_sources) do + [ + double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), + double("non-global-source-1", :dependency_api_available? => false, :to_s => "b"), + double("non-global-source-2", :dependency_api_available? => false, :to_s => "c"), + ] + end - it "will raise a warning" do - expect(Bundler.ui).to receive(:warn).with(<<-W.strip) + it "returns false and warns about the non-API sources" do + expect(Bundler.ui).to receive(:warn).with(<<-W.strip) Your Gemfile contains scoped sources that don't implement a dependency API, namely: * b * c Using the above gem servers may result in installing unexpected gems. To resolve this warning, make sure you use gem servers that implement dependency APIs, such as gemstash or geminabox gem servers. - W + W - expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy - end + expect(subject.send(:precompute_source_requirements_for_indirect_dependencies?)).to be_falsy end end end From 41d986947425c1074abb82ffcdb702919f2a8d88 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 15 Apr 2026 17:16:41 +0900 Subject: [PATCH 04/21] Fix Style/HashSyntax offenses in definition_spec.rb Co-Authored-By: Claude Opus 4.6 (1M context) (cherry picked from commit 97d05b3fc5723cd7f083f1bbcfcb537901f6b0d2) --- bundler/spec/bundler/definition_spec.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bundler/spec/bundler/definition_spec.rb b/bundler/spec/bundler/definition_spec.rb index 1e821f590180..8c7d5667ac66 100644 --- a/bundler/spec/bundler/definition_spec.rb +++ b/bundler/spec/bundler/definition_spec.rb @@ -304,8 +304,8 @@ context "when all the scoped sources implement a dependency API" do let(:non_global_rubygems_sources) do [ - double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), - double("non-global-source-1", :dependency_api_available? => true, :to_s => "b"), + double("non-global-source-0", "dependency_api_available?":true, to_s:"a"), + double("non-global-source-1", "dependency_api_available?":true, to_s:"b"), ] end @@ -319,9 +319,9 @@ context "when some scoped sources do not implement a dependency API" do let(:non_global_rubygems_sources) do [ - double("non-global-source-0", :dependency_api_available? => true, :to_s => "a"), - double("non-global-source-1", :dependency_api_available? => false, :to_s => "b"), - double("non-global-source-2", :dependency_api_available? => false, :to_s => "c"), + double("non-global-source-0", "dependency_api_available?":true, to_s:"a"), + double("non-global-source-1", "dependency_api_available?":false, to_s:"b"), + double("non-global-source-2", "dependency_api_available?":false, to_s:"c"), ] end From 0b2a6e0ad66a1ff147d6efe47099b899c6899437 Mon Sep 17 00:00:00 2001 From: "Daisuke Fujimura (fd0)" Date: Mon, 4 May 2026 18:54:35 +0900 Subject: [PATCH 05/21] Remove cygwin from WIN_PATTERNS (cherry picked from commit c01e3b9990485a5f96ae0b489a435120714c1c32) --- lib/rubygems/win_platform.rb | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/rubygems/win_platform.rb b/lib/rubygems/win_platform.rb index 78e968fe4930..10556871b2a6 100644 --- a/lib/rubygems/win_platform.rb +++ b/lib/rubygems/win_platform.rb @@ -8,7 +8,6 @@ module Gem WIN_PATTERNS = [ /bccwin/i, - /cygwin/i, /djgpp/i, /mingw/i, /mswin/i, From 558d922e65b60944a098dc841516a7e5b8ef9c4d Mon Sep 17 00:00:00 2001 From: Yuta Kurotaki Date: Fri, 24 Apr 2026 05:26:56 +0900 Subject: [PATCH 06/21] Deprecate parsing non-lockfile content in LockfileParser Bundler::LockfileParser#initialize silently accepted any string input, including Gemfiles or arbitrary text, producing an empty parser with no indication that the input was invalid. This caused downstream tooling like bundler-audit to operate on unvalidated content. Detect non-lockfile content by checking for any of the known section headers; empty input is left untouched for backward compatibility. Rather than raising immediately, emit a deprecation warning via SharedHelpers.feature_deprecated! announcing that a future Bundler version will raise LockfileError. Expose LockfileParser#valid? so callers can branch on the result without string-matching the message. Fixes #8932 (cherry picked from commit 7607fe4a5d4aefe4d0e6618fafaf9097af879c53) --- bundler/lib/bundler/lockfile_parser.rb | 15 +++++ bundler/spec/bundler/lockfile_parser_spec.rb | 61 ++++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/bundler/lib/bundler/lockfile_parser.rb b/bundler/lib/bundler/lockfile_parser.rb index a837f994cd90..e0bb83ab6669 100644 --- a/bundler/lib/bundler/lockfile_parser.rb +++ b/bundler/lib/bundler/lockfile_parser.rb @@ -115,6 +115,17 @@ def initialize(lockfile, strict: false) "Run `git checkout HEAD -- #{@lockfile_path}` first to get a clean lock." end + @valid = lockfile.strip.empty? || + lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) } + + unless @valid + SharedHelpers.feature_deprecated!( + "Your #{@lockfile_path} does not appear to be a valid lockfile. " \ + "Run `rm #{@lockfile_path}` and then `bundle install` to generate a new lockfile. " \ + "This will raise a LockfileError in a future version of Bundler." + ) + end + lockfile.split(/((?:\r?\n)+)/) do |line| # split alternates between the line and the following whitespace next @pos.advance!(line) if line.match?(/^\s*$/) @@ -164,6 +175,10 @@ def may_include_redundant_platform_specific_gems? bundler_version.nil? || bundler_version < Gem::Version.new("1.16.2") end + def valid? + @valid + end + private TYPES = { diff --git a/bundler/spec/bundler/lockfile_parser_spec.rb b/bundler/spec/bundler/lockfile_parser_spec.rb index f38da2c99321..4b493a37576b 100644 --- a/bundler/spec/bundler/lockfile_parser_spec.rb +++ b/bundler/spec/bundler/lockfile_parser_spec.rb @@ -129,6 +129,7 @@ shared_examples_for "parsing" do it "parses correctly" do + expect(subject.valid?).to be(true) expect(subject.sources).to eq sources expect(subject.dependencies).to eq dependencies expect(subject.specs).to eq specs @@ -191,6 +192,66 @@ include_examples "parsing" end + context "when the content does not contain any recognized lockfile sections" do + let(:lockfile_contents) { "hello world\nlorem ipsum\n" } + + it "does not raise, is not valid, and deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + + it "does not raise when strict: true, and still deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents, strict: true) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + end + + context "when the content looks like a Gemfile DSL" do + let(:lockfile_contents) { <<~G } + source "https://rubygems.org" + gem "rake" + G + + it "does not raise, is not valid, and deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + + it "does not raise when strict: true, and still deprecates" do + expect(Bundler::SharedHelpers).to receive(:feature_deprecated!).with( + /does not appear to be a valid lockfile.*future version of Bundler/m + ) + parser = described_class.new(lockfile_contents, strict: true) + expect(parser.valid?).to be(false) + expect(parser.specs).to eq([]) + expect(parser.dependencies).to eq({}) + end + end + + context "when the content is empty" do + let(:lockfile_contents) { "" } + + it "does not raise and is valid" do + expect { subject }.not_to raise_error + expect(subject.valid?).to be(true) + end + end + context "when CHECKSUMS has duplicate checksums in the lockfile that don't match" do let(:bad_checksum) { "sha256=c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11c0ffee11" } let(:lockfile_contents) { super().split(/(?<=CHECKSUMS\n)/m).insert(1, " rake (10.3.2) #{bad_checksum}\n").join } From 9ffc0753218125749a20ca119f1844a4f5e99815 Mon Sep 17 00:00:00 2001 From: Yuta Kurotaki Date: Fri, 1 May 2026 19:01:19 +0900 Subject: [PATCH 07/21] Fix RuboCop Layout/MultilineOperationIndentation offense Co-Authored-By: Claude Opus 4.7 (1M context) (cherry picked from commit 2a47650f64102510ee692d67bb9898171a18c53b) --- bundler/lib/bundler/lockfile_parser.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundler/lib/bundler/lockfile_parser.rb b/bundler/lib/bundler/lockfile_parser.rb index e0bb83ab6669..9e21f8f1d2a1 100644 --- a/bundler/lib/bundler/lockfile_parser.rb +++ b/bundler/lib/bundler/lockfile_parser.rb @@ -116,7 +116,7 @@ def initialize(lockfile, strict: false) end @valid = lockfile.strip.empty? || - lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) } + lockfile.split(/(?:\r?\n)+/).any? {|l| KNOWN_SECTIONS.include?(l) } unless @valid SharedHelpers.feature_deprecated!( From 008b410759b22e3e95c68916ee92831cf96c6571 Mon Sep 17 00:00:00 2001 From: Nobuyoshi Nakada Date: Thu, 7 May 2026 20:32:26 +0900 Subject: [PATCH 08/21] Use Pathname#absolute? `Pathname::SEPARATOR_PAT` should be private, but was not set to private just due to a typo. (cherry picked from commit 67ce6df4c973842fb4ed5410623e2fbad67c7928) --- bundler/lib/bundler/source/path.rb | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bundler/lib/bundler/source/path.rb b/bundler/lib/bundler/source/path.rb index 82e782ba257a..366a23aea725 100644 --- a/bundler/lib/bundler/source/path.rb +++ b/bundler/lib/bundler/source/path.rb @@ -220,10 +220,11 @@ def generate_bin(spec, options = {}) # Some gem authors put absolute paths in their gemspec # and we have to save them from themselves spec.files = spec.files.filter_map do |path| - next path unless /\A#{Pathname::SEPARATOR_PAT}/o.match?(path) + pathname = Pathname.new(path) + next path unless pathname.absolute? next if File.directory?(path) begin - Pathname.new(path).relative_path_from(gem_dir).to_s + pathname.relative_path_from(gem_dir).to_s rescue ArgumentError path end From e7d1e0241ad3560cc057fda80175583872f077f5 Mon Sep 17 00:00:00 2001 From: Andrii Furmanets Date: Thu, 30 Apr 2026 18:46:44 +0300 Subject: [PATCH 09/21] Fix bundle config gemfile unset behavior (cherry picked from commit 38f87aa2bce7db46799b784bfe8a02cc5504b4ea) --- bundler/lib/bundler/cli.rb | 20 +++++++++++-------- bundler/spec/commands/config_spec.rb | 30 ++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 8 deletions(-) diff --git a/bundler/lib/bundler/cli.rb b/bundler/lib/bundler/cli.rb index 9a0f756bcf82..7a04f3da2390 100644 --- a/bundler/lib/bundler/cli.rb +++ b/bundler/lib/bundler/cli.rb @@ -61,14 +61,18 @@ def initialize(*args) current_cmd = args.last[:current_command].name - Bundler.configure_custom_gemfile(options[:gemfile]) - - # lock --lockfile works differently than install --lockfile - unless current_cmd == "lock" - custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] - if custom_lockfile && !custom_lockfile.empty? - Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) - reset_settings = true + # `bundle config` manages stored settings, so avoid promoting settings + # like `gemfile` or `lockfile` to environment variables before it runs. + unless current_cmd == "config" + Bundler.configure_custom_gemfile(options[:gemfile]) + + # lock --lockfile works differently than install --lockfile + unless current_cmd == "lock" + custom_lockfile = options[:lockfile] || ENV["BUNDLE_LOCKFILE"] || Bundler.settings[:lockfile] + if custom_lockfile && !custom_lockfile.empty? + Bundler::SharedHelpers.set_env "BUNDLE_LOCKFILE", File.expand_path(custom_lockfile) + reset_settings = true + end end end diff --git a/bundler/spec/commands/config_spec.rb b/bundler/spec/commands/config_spec.rb index 954cae09d846..5cafbe346810 100644 --- a/bundler/spec/commands/config_spec.rb +++ b/bundler/spec/commands/config_spec.rb @@ -577,6 +577,36 @@ end RSpec.describe "setting gemfile via config" do + context "when a default Gemfile exists" do + before do + gemfile <<-G + source "https://gem.repo1" + G + + gemfile bundled_app("foo/bar_gemfile"), <<-G + source "https://gem.repo1" + G + end + + it "reports the local gemfile setting without promoting it to the environment" do + bundle "config set gemfile foo/bar_gemfile" + + bundle "config list" + expect(out).to include("Set for your local app (#{bundled_app(".bundle/config")}): \"foo/bar_gemfile\"") + expect(out).not_to include("Set via BUNDLE_GEMFILE") + end + + it "unsets the local gemfile setting from the app config" do + bundle "config set gemfile foo/bar_gemfile" + + bundle "config unset gemfile" + bundle "config get gemfile" + + expect(out).to include("You have not configured a value for `gemfile`") + expect(File.read(bundled_app(".bundle/config"))).not_to include("BUNDLE_GEMFILE") + end + end + context "when only the non-default Gemfile exists" do it "persists the gemfile location to .bundle/config" do gemfile bundled_app("NotGemfile"), <<-G From 19ecdfe7d8f5e2ac2ca8cf38feb4d6807498aa28 Mon Sep 17 00:00:00 2001 From: Shinichi Maeshima Date: Wed, 29 Apr 2026 19:11:00 +0900 Subject: [PATCH 10/21] Make `bundle config get` return status 1 when the value is not set MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix #3215 Change the exit status to 1 when trying to `get` a config key that does not exist, as shown below. ```sh $ bundle config get foo Settings for `foo` in order of priority. The top value will be used You have not configured a value for `foo` $ echo $? 1 ``` It seems that showing “Settings for `foo` in order of priority. The top value will be used” when the key does not exist is not very meaningful, but for now I have left the behavior unchanged except for the exit status. In the tests, some existing cases try to `get` a missing config without `raise_on_error: false`, so set the value in advance or add `raise_on_error: false` to handle them. (cherry picked from commit 73205e3d64c90f057c141589cddb9c12a5ebfc90) --- bundler/lib/bundler/cli/config.rb | 15 +++++++++++++-- bundler/spec/bundler/cli_spec.rb | 4 +++- bundler/spec/commands/config_spec.rb | 15 ++++++++++----- bundler/spec/other/major_deprecation_spec.rb | 2 +- 4 files changed, 27 insertions(+), 9 deletions(-) diff --git a/bundler/lib/bundler/cli/config.rb b/bundler/lib/bundler/cli/config.rb index 6a77e4a65ea3..3ac973cfe181 100644 --- a/bundler/lib/bundler/cli/config.rb +++ b/bundler/lib/bundler/cli/config.rb @@ -88,15 +88,26 @@ def run if value.nil? warn_unused_scope "Ignoring --#{scope} since no value to set was given" + configured = Bundler.settings.locations(name).any? + if options[:parseable] if value = Bundler.settings[name] Bundler.ui.info("#{name}=#{value}") end - return + if configured + return + else + exit 1 + end end confirm(name) - return + + if configured + return + else + exit 1 + end end Bundler.ui.info(message) if message diff --git a/bundler/spec/bundler/cli_spec.rb b/bundler/spec/bundler/cli_spec.rb index e2c64b939401..746330e7ef54 100644 --- a/bundler/spec/bundler/cli_spec.rb +++ b/bundler/spec/bundler/cli_spec.rb @@ -251,8 +251,10 @@ def out_with_macos_man_workaround context "running a parseable command" do it "prints no warning" do + bundle "config set foo value", env: { "BUNDLER_VERSION" => bundler_version } bundle "config get --parseable foo", env: { "BUNDLER_VERSION" => bundler_version } - expect(stdboth).to eq "" + expect(out).to eq "foo=value" + expect(err).to eq "" bundle "platform --ruby", env: { "BUNDLER_VERSION" => bundler_version }, raise_on_error: false expect(stdboth).to eq "Could not locate Gemfile" diff --git a/bundler/spec/commands/config_spec.rb b/bundler/spec/commands/config_spec.rb index 5cafbe346810..0aaae98ccbc0 100644 --- a/bundler/spec/commands/config_spec.rb +++ b/bundler/spec/commands/config_spec.rb @@ -313,9 +313,10 @@ describe "parseable option" do it "prints an empty string" do - bundle "config get foo --parseable" + bundle "config get foo --parseable", raise_on_error: false expect(out).to eq "" + expect(last_command).to be_failure end it "only prints the value of the config" do @@ -501,8 +502,9 @@ it "get" do ENV["BUNDLE_BAR"] = "bar_val" - bundle "config get foo" + bundle "config get foo", raise_on_error: false expect(out).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure ENV["BUNDLE_FOO"] = "foo_val" @@ -547,7 +549,8 @@ bundle "config unset foo" expect(out).to eq "" - expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config set --local foo 1" bundle "config set --global foo 2" @@ -557,7 +560,8 @@ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for the current user (#{home(".bundle/config")}): \"2\"" bundle "config unset foo --global" expect(out).to eq "" - expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config set --local foo 1" bundle "config set --global foo 2" @@ -567,7 +571,8 @@ expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nSet for your local app (#{bundled_app(".bundle/config")}): \"1\"" bundle "config unset foo --local" expect(out).to eq "" - expect(bundle("config get foo")).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(bundle("config get foo", raise_on_error: false)).to eq "Settings for `foo` in order of priority. The top value will be used\nYou have not configured a value for `foo`" + expect(last_command).to be_failure bundle "config unset foo --local --global", raise_on_error: false expect(last_command).to be_failure diff --git a/bundler/spec/other/major_deprecation_spec.rb b/bundler/spec/other/major_deprecation_spec.rb index 24d4153dfabe..b0228cf94378 100644 --- a/bundler/spec/other/major_deprecation_spec.rb +++ b/bundler/spec/other/major_deprecation_spec.rb @@ -290,7 +290,7 @@ describe "old get interface" do before do - bundle "config waka" + bundle "config waka", raise_on_error: false end it "warns", bundler: "4" do From b80e141a61799761a8183c843b4c69baf4cd79ab Mon Sep 17 00:00:00 2001 From: Shinichi Maeshima Date: Tue, 5 May 2026 17:13:52 +0900 Subject: [PATCH 11/21] Simplify the code Refactor the code based on the feedback in https://github.com/ruby/rubygems/pull/9505#discussion_r3167085736 . (cherry picked from commit 153abcb5e3c2a878bc3e9a2868851399261fa859) --- bundler/lib/bundler/cli/config.rb | 14 ++++---------- 1 file changed, 4 insertions(+), 10 deletions(-) diff --git a/bundler/lib/bundler/cli/config.rb b/bundler/lib/bundler/cli/config.rb index 3ac973cfe181..8248e3b15b33 100644 --- a/bundler/lib/bundler/cli/config.rb +++ b/bundler/lib/bundler/cli/config.rb @@ -87,23 +87,17 @@ def run if value.nil? warn_unused_scope "Ignoring --#{scope} since no value to set was given" - - configured = Bundler.settings.locations(name).any? + current_value = Bundler.settings[name] if options[:parseable] if value = Bundler.settings[name] Bundler.ui.info("#{name}=#{value}") end - if configured - return - else - exit 1 - end + else + confirm(name) end - confirm(name) - - if configured + if current_value return else exit 1 From 43fab516080d40c4b22899a30d0595cce4d34abd Mon Sep 17 00:00:00 2001 From: Shinichi Maeshima Date: Thu, 7 May 2026 19:03:03 +0900 Subject: [PATCH 12/21] Return exit status 1 only when the config value is nil When using something like `bundle config set foo false`, the config value is converted via `Bundler::Settings#converted_value`, so the stored value becomes `false` instead of the string `"false"`. Because of that, passing `Bundler.settings[name]` directly to an `if` statement can execute `exit 1` even when the value is actually configured. Since config values do not appear to become `nil` explicitly, use `nil?` to determine whether the value is configured. (cherry picked from commit 8fd32cb6110b80e61af719cec4c2bddbddcdbb07) --- bundler/lib/bundler/cli/config.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bundler/lib/bundler/cli/config.rb b/bundler/lib/bundler/cli/config.rb index 8248e3b15b33..976cda748466 100644 --- a/bundler/lib/bundler/cli/config.rb +++ b/bundler/lib/bundler/cli/config.rb @@ -97,10 +97,10 @@ def run confirm(name) end - if current_value - return - else + if current_value.nil? exit 1 + else + return end end From 82a2d464de51cbe32c1830a576897bdf5719b889 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Fri, 8 May 2026 18:09:22 +0900 Subject: [PATCH 13/21] [ruby/rubygems] Allow non-zero exit status in bundle config gemfile unset spec After 607648d5fc9 (`bundle config get` exits 1 when the value is unset), the spec added in 4658d6bd78b raises in the bundle helper because the new test invokes `config get gemfile` after unsetting it. Co-Authored-By: Claude Opus 4.7 (1M context) (cherry picked from commit 66ae777975c0ceb78e4003b2e3edb3dfa9ab9088) --- bundler/spec/commands/config_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bundler/spec/commands/config_spec.rb b/bundler/spec/commands/config_spec.rb index 0aaae98ccbc0..e8ab32ca5da1 100644 --- a/bundler/spec/commands/config_spec.rb +++ b/bundler/spec/commands/config_spec.rb @@ -605,7 +605,7 @@ bundle "config set gemfile foo/bar_gemfile" bundle "config unset gemfile" - bundle "config get gemfile" + bundle "config get gemfile", raise_on_error: false expect(out).to include("You have not configured a value for `gemfile`") expect(File.read(bundled_app(".bundle/config"))).not_to include("BUNDLE_GEMFILE") From 3aedfc98c49663da5db9f48554ccc29aba561c90 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 14 May 2026 09:51:02 +0900 Subject: [PATCH 14/21] Merge pull request #9538 from ruby/bundle-version-env-system Read `BUNDLE_VERSION` env var in `BundlerVersionFinder` (cherry picked from commit 65ae6ea43080a24cbaa058080ff18bc3a2fe9ff4) --- lib/rubygems/bundler_version_finder.rb | 3 ++ .../test_gem_bundler_version_finder.rb | 42 +++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index d792358da701..c2d6f4106c52 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -70,6 +70,9 @@ def self.lockfile_contents private_class_method :lockfile_contents def self.bundle_config_version + env_version = ENV["BUNDLE_VERSION"] + return env_version if env_version && !env_version.empty? + version = nil [bundler_local_config_file, bundler_global_config_file].each do |config_file| diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index 88ee9c6759f2..be8c5914f48e 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -104,6 +104,48 @@ def test_bundler_version_with_bundle_config_version end end + def test_bundler_version_with_bundle_version_env_system + ENV["BUNDLE_VERSION"] = "system" + + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_nil bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_version_env_overrides_config + ENV["BUNDLE_VERSION"] = "2.3.4" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + assert_equal v("2.3.4"), bvf.bundler_version + end + end + end + + def test_bundler_version_with_empty_bundle_version_env + ENV["BUNDLE_VERSION"] = "" + + config_content = <<~CONFIG + BUNDLE_VERSION: "1.2.3" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + assert_equal v("1.2.3"), bvf.bundler_version + end + end + end + def test_bundler_version_with_bundle_config_non_existent_file bvf.stub(:bundler_global_config_file, "/non/existent/path") do assert_nil bvf.bundler_version From 7c94b94ee5071b0110754583a6eb9763b4b6cdd8 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 14 May 2026 10:26:37 +0900 Subject: [PATCH 15/21] Merge pull request #9545 from ruby/bundle-version-lockfile-fallback Fall back to lockfile version when `BUNDLE_VERSION` is "lockfile" (cherry picked from commit 24065896be7f26d3f0f2148e438071e4f14db36d) --- lib/rubygems/bundler_version_finder.rb | 5 ++-- .../test_gem_bundler_version_finder.rb | 25 +++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/lib/rubygems/bundler_version_finder.rb b/lib/rubygems/bundler_version_finder.rb index c2d6f4106c52..bbe7bf0ab55e 100644 --- a/lib/rubygems/bundler_version_finder.rb +++ b/lib/rubygems/bundler_version_finder.rb @@ -2,7 +2,8 @@ module Gem::BundlerVersionFinder def self.bundler_version - return if bundle_config_version == "system" + bcv = bundle_config_version + return if bcv == "system" v = ENV["BUNDLER_VERSION"] v = nil if v&.empty? @@ -10,7 +11,7 @@ def self.bundler_version v ||= bundle_update_bundler_version return if v == true - v ||= bundle_config_version + v ||= bcv unless bcv == "lockfile" v ||= lockfile_version return unless v diff --git a/test/rubygems/test_gem_bundler_version_finder.rb b/test/rubygems/test_gem_bundler_version_finder.rb index be8c5914f48e..b5ef6293abfd 100644 --- a/test/rubygems/test_gem_bundler_version_finder.rb +++ b/test/rubygems/test_gem_bundler_version_finder.rb @@ -146,6 +146,31 @@ def test_bundler_version_with_empty_bundle_version_env end end + def test_bundler_version_with_bundle_version_env_lockfile + ENV["BUNDLE_VERSION"] = "lockfile" + + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + + def test_bundler_version_with_bundle_config_version_lockfile + config_content = <<~CONFIG + BUNDLE_VERSION: "lockfile" + CONFIG + + Tempfile.create("bundle_config") do |f| + f.write(config_content) + f.flush + + bvf.stub(:bundler_global_config_file, f.path) do + bvf.stub(:lockfile_contents, "\n\nBUNDLED WITH\n 1.1.1.1\n") do + assert_equal v("1.1.1.1"), bvf.bundler_version + end + end + end + end + def test_bundler_version_with_bundle_config_non_existent_file bvf.stub(:bundler_global_config_file, "/non/existent/path") do assert_nil bvf.bundler_version From b7c7af2fb9809318c489557cd1743b2f14030a1e Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 14 May 2026 14:33:51 +0900 Subject: [PATCH 16/21] Merge pull request #9544 from yahonda/fix-gh-9536 Skip git source exclusion when lockfile cannot backfill (cherry picked from commit 41e547491e7ce285a162b59108d2804d1c873584) --- bundler/lib/bundler/definition.rb | 10 +++++-- bundler/spec/install/git_spec.rb | 48 +++++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+), 3 deletions(-) diff --git a/bundler/lib/bundler/definition.rb b/bundler/lib/bundler/definition.rb index d86829a10cde..6c12d790722e 100644 --- a/bundler/lib/bundler/definition.rb +++ b/bundler/lib/bundler/definition.rb @@ -1177,16 +1177,20 @@ def excluded_git_sources def find_source_requirements preload_git_sources + # Only safe to exclude when locked_requirements (merged below) backfills the gap. + nothing_changed = nothing_changed? + excluded = nothing_changed ? excluded_git_sources : [] + # Record the specs available in each gem's source, so that those # specs will be available later when the resolver knows where to # look for that gemspec (or its dependencies) source_requirements = if precompute_source_requirements_for_indirect_dependencies? - all_requirements = source_map.all_requirements(excluded_git_sources) + all_requirements = source_map.all_requirements(excluded) { default: default_source }.merge(all_requirements) else - { default: Source::RubygemsAggregate.new(sources, source_map, excluded_git_sources) }.merge(source_map.direct_requirements) + { default: Source::RubygemsAggregate.new(sources, source_map, excluded) }.merge(source_map.direct_requirements) end - source_requirements.merge!(source_map.locked_requirements) if nothing_changed? + source_requirements.merge!(source_map.locked_requirements) if nothing_changed metadata_dependencies.each do |dep| source_requirements[dep.name] = sources.metadata_source end diff --git a/bundler/spec/install/git_spec.rb b/bundler/spec/install/git_spec.rb index b711f6e61408..6c2af5f33e99 100644 --- a/bundler/spec/install/git_spec.rb +++ b/bundler/spec/install/git_spec.rb @@ -317,5 +317,53 @@ expect(the_bundle).to include_gems("production_gem 1.0") expect(the_bundle).not_to include_gems("development_gem 1.0") end + + it "resolves indirect dependencies from a git source not in the requested groups" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + bundle "config set --local only ci" + bundle :install + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end + + it "resolves indirect dependencies from a git source not in the requested groups (without compact_index dependency API)" do + build_lib "activesupport", "1.0", path: lib_path("rails/activesupport") + build_git "activerecord", "1.0", path: lib_path("rails") do |s| + s.add_dependency "activesupport", "= 1.0" + end + + gemfile <<-G + source "https://gem.repo1" + + gem "activerecord", :git => "#{lib_path("rails")}" + + group :ci do + gem "myrack" + end + G + + # Force the RubygemsAggregate code path in find_source_requirements by + # making the dependency API unavailable. + bundle "config set --local only ci" + bundle :install, artifice: "endpoint_api_forbidden" + + expect(the_bundle).to include_gems("myrack 1.0.0") + expect(the_bundle).not_to include_gems("activerecord 1.0") + end end end From 0d42fe59df36f5d31cc788d16f121d63a9125991 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Thu, 14 May 2026 14:59:28 +0900 Subject: [PATCH 17/21] Merge pull request #9492 from jneen/bugfix.error-on-missing-checksum Gracefully handle missing checksums in Compact Index (cherry picked from commit fcc03ef8ddbf229f4e610eb32560392c906396cf) --- bundler/lib/bundler/compact_index_client/parser.rb | 5 ++++- .../spec/bundler/compact_index_client/parser_spec.rb | 12 ++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/bundler/lib/bundler/compact_index_client/parser.rb b/bundler/lib/bundler/compact_index_client/parser.rb index 43581fd7efe5..ad0d17ed4a4a 100644 --- a/bundler/lib/bundler/compact_index_client/parser.rb +++ b/bundler/lib/bundler/compact_index_client/parser.rb @@ -71,7 +71,10 @@ def gem_parser # This method gets called at least once for every gem when parsing versions. def parse_version_checksum(line, checksums) return unless (name_end = line.index(" ")) # Artifactory bug causes blank lines in artifactor index files - return unless (checksum_start = line.index(" ", name_end + 1) + 1) + checksum_start = line.index(" ", name_end + 1) + return unless checksum_start + checksum_start += 1 + checksum_end = line.size - checksum_start line.freeze # allows slicing into the string to not allocate a copy of the line diff --git a/bundler/spec/bundler/compact_index_client/parser_spec.rb b/bundler/spec/bundler/compact_index_client/parser_spec.rb index 1f6b9e593bcd..6015f66f33a7 100644 --- a/bundler/spec/bundler/compact_index_client/parser_spec.rb +++ b/bundler/spec/bundler/compact_index_client/parser_spec.rb @@ -233,5 +233,17 @@ def set_info_data(name, value) VERSIONS expect(parser.info("a")).to eq(a_result) end + + it "handles lines without a checksum" do + compact_index.versions = <<~VERSIONS + created_at: 2024-05-01T00:00:04Z + --- + a 1.0.0,1.0.1,1.1.0 aaa111 + b 2.0.0,2.0.0-java + c 3.0.0,3.0.3,3.3.3 ccc333 + VERSIONS + + expect(parser.info("a")).to eq(a_result) + end end end From 91cf33ac32e0954b543456e735145186a9510259 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 20 May 2026 10:50:45 +0900 Subject: [PATCH 18/21] Changelog for Bundler version 4.0.12 --- bundler/CHANGELOG.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/bundler/CHANGELOG.md b/bundler/CHANGELOG.md index 368b4dc51578..78b630a1254a 100644 --- a/bundler/CHANGELOG.md +++ b/bundler/CHANGELOG.md @@ -1,5 +1,21 @@ # Changelog +## 4.0.12 / 2026-05-20 + +### Enhancements: + +* Make `bundle config get` return status 1 when the value is not set. Pull request [#9505](https://github.com/ruby/rubygems/pull/9505) by willnet +* Use Pathname#absolute?. Pull request [#9529](https://github.com/ruby/rubygems/pull/9529) by nobu +* Deprecate parsing non-lockfile content in LockfileParser. Pull request [#9502](https://github.com/ruby/rubygems/pull/9502) by kurotaky +* Print a warning for a potential confusion from the indirect dependencies. Pull request [#5029](https://github.com/ruby/rubygems/pull/5029) by junaruga +* Respect Gemfile bundler setting in `Bundler.setup`. Pull request [#4892](https://github.com/ruby/rubygems/pull/4892) by godfat + +### Bug fixes: + +* Gracefully handle missing checksums in Compact Index. Pull request [#9492](https://github.com/ruby/rubygems/pull/9492) by jneen +* Skip git source exclusion when lockfile cannot backfill. Pull request [#9544](https://github.com/ruby/rubygems/pull/9544) by yahonda +* Fix bundle config gemfile unset behavior. Pull request [#9514](https://github.com/ruby/rubygems/pull/9514) by afurm + ## 4.0.11 / 2026-04-30 ### Enhancements: From c2e83ba3229ae5a5164c158766f2b3cfeb717321 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 20 May 2026 10:51:19 +0900 Subject: [PATCH 19/21] Bump Bundler version to 4.0.12 --- bundler/lib/bundler/version.rb | 2 +- bundler/spec/realworld/fixtures/tapioca/Gemfile.lock | 2 +- bundler/spec/realworld/fixtures/warbler/Gemfile.lock | 2 +- tool/bundler/dev_gems.rb.lock | 2 +- tool/bundler/lint_gems.rb.lock | 2 +- tool/bundler/release_gems.rb.lock | 2 +- tool/bundler/rubocop_gems.rb.lock | 2 +- tool/bundler/standard_gems.rb.lock | 2 +- tool/bundler/test_gems.rb.lock | 2 +- tool/bundler/vendor_gems.rb.lock | 2 +- 10 files changed, 10 insertions(+), 10 deletions(-) diff --git a/bundler/lib/bundler/version.rb b/bundler/lib/bundler/version.rb index e8b442674a61..405bd339ae48 100644 --- a/bundler/lib/bundler/version.rb +++ b/bundler/lib/bundler/version.rb @@ -1,7 +1,7 @@ # frozen_string_literal: false module Bundler - VERSION = "4.0.11".freeze + VERSION = "4.0.12".freeze def self.bundler_major_version @bundler_major_version ||= gem_version.segments.first diff --git a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock index bbaf8328d638..347fd5aaa470 100644 --- a/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/tapioca/Gemfile.lock @@ -46,4 +46,4 @@ DEPENDENCIES tapioca BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock index 3c6fd5218080..795dfdac6641 100644 --- a/bundler/spec/realworld/fixtures/warbler/Gemfile.lock +++ b/bundler/spec/realworld/fixtures/warbler/Gemfile.lock @@ -36,4 +36,4 @@ DEPENDENCIES warbler! BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/dev_gems.rb.lock b/tool/bundler/dev_gems.rb.lock index b2d1783acb39..4b7ac8d612b9 100644 --- a/tool/bundler/dev_gems.rb.lock +++ b/tool/bundler/dev_gems.rb.lock @@ -129,4 +129,4 @@ CHECKSUMS turbo_tests (2.2.5) sha256=3fa31497d12976d11ccc298add29107b92bda94a90d8a0a5783f06f05102509f BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/lint_gems.rb.lock b/tool/bundler/lint_gems.rb.lock index 80baf85f0370..b959288a2d65 100644 --- a/tool/bundler/lint_gems.rb.lock +++ b/tool/bundler/lint_gems.rb.lock @@ -119,4 +119,4 @@ CHECKSUMS wmi-lite (1.0.7) sha256=116ef5bb470dbe60f58c2db9047af3064c16245d6562c646bc0d90877e27ddda BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/release_gems.rb.lock b/tool/bundler/release_gems.rb.lock index 657e6158b8df..a7edf0627313 100644 --- a/tool/bundler/release_gems.rb.lock +++ b/tool/bundler/release_gems.rb.lock @@ -87,4 +87,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/rubocop_gems.rb.lock b/tool/bundler/rubocop_gems.rb.lock index 2893c91a2ca0..7c1610f2d94b 100644 --- a/tool/bundler/rubocop_gems.rb.lock +++ b/tool/bundler/rubocop_gems.rb.lock @@ -156,4 +156,4 @@ CHECKSUMS unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/standard_gems.rb.lock b/tool/bundler/standard_gems.rb.lock index 285bafab7442..24e33d943a0d 100644 --- a/tool/bundler/standard_gems.rb.lock +++ b/tool/bundler/standard_gems.rb.lock @@ -176,4 +176,4 @@ CHECKSUMS unicode-emoji (4.2.0) sha256=519e69150f75652e40bf736106cfbc8f0f73aa3fb6a65afe62fefa7f80b0f80f BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/test_gems.rb.lock b/tool/bundler/test_gems.rb.lock index 031216b465ab..ccb9db178541 100644 --- a/tool/bundler/test_gems.rb.lock +++ b/tool/bundler/test_gems.rb.lock @@ -103,4 +103,4 @@ CHECKSUMS tilt (2.6.1) sha256=35a99bba2adf7c1e362f5b48f9b581cce4edfba98117e34696dde6d308d84770 BUNDLED WITH - 4.0.11 + 4.0.12 diff --git a/tool/bundler/vendor_gems.rb.lock b/tool/bundler/vendor_gems.rb.lock index 2534e5c595c2..4619364802ee 100644 --- a/tool/bundler/vendor_gems.rb.lock +++ b/tool/bundler/vendor_gems.rb.lock @@ -72,4 +72,4 @@ CHECKSUMS uri (1.1.1) sha256=379fa58d27ffb1387eaada68c749d1426738bd0f654d812fcc07e7568f5c57c6 BUNDLED WITH - 4.0.11 + 4.0.12 From ee9e76013b4838489f5e31a5993d8b2987b4b8de Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 20 May 2026 10:51:20 +0900 Subject: [PATCH 20/21] Changelog for Rubygems version 4.0.12 --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ab75347eeab..7d28ecb8896c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 4.0.12 / 2026-05-20 + +### Enhancements: + +* Remove cygwin from WIN_PATTERNS. Pull request [#9527](https://github.com/ruby/rubygems/pull/9527) by fd00 +* Installs bundler 4.0.12 as a default gem. + +### Bug fixes: + +* Fall back to lockfile version when `BUNDLE_VERSION` is "lockfile". Pull request [#9545](https://github.com/ruby/rubygems/pull/9545) by hsbt +* Read `BUNDLE_VERSION` env var in `BundlerVersionFinder`. Pull request [#9538](https://github.com/ruby/rubygems/pull/9538) by hsbt + ## 4.0.11 / 2026-04-30 ### Enhancements: From 665f998196f2ab6b5722e3d576a0b28eed1bed59 Mon Sep 17 00:00:00 2001 From: Hiroshi SHIBATA Date: Wed, 20 May 2026 10:51:20 +0900 Subject: [PATCH 21/21] Bump Rubygems version to 4.0.12 --- lib/rubygems.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rubygems.rb b/lib/rubygems.rb index d9da84ed3b1f..1adb74dded26 100644 --- a/lib/rubygems.rb +++ b/lib/rubygems.rb @@ -9,7 +9,7 @@ require "rbconfig" module Gem - VERSION = "4.0.11" + VERSION = "4.0.12" end require_relative "rubygems/defaults"