Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
df05fda
Read Bundler's settings from RubyGems through one helper
hsbt Sep 3, 2026
e91c414
Wire the --cooldown flag through one method
hsbt Sep 3, 2026
8a36080
Let the gem and bundle cooldown settings cover each other
hsbt Sep 3, 2026
9a080d8
Match the surrounding comment style, and isolate the outdated tests
hsbt Sep 3, 2026
a94281b
Treat an unparsable Bundler config as unconfigured
hsbt Sep 3, 2026
f869db7
Ignore a Bundler config file whose top level is not a mapping
hsbt Sep 3, 2026
23cbe16
Survive an unreadable working directory when looking for the Gemfile
hsbt Sep 3, 2026
2585f79
Read a configured cooldown in base 10
hsbt Sep 3, 2026
aa13a1d
Guard the shipped cooldown settings copy against reloading
hsbt Sep 3, 2026
9854339
Validate the gemrc cooldown where it is read
hsbt Sep 3, 2026
1cb4fa1
Clear BUNDLE_COOLDOWN for the RubyGems test suite
hsbt Sep 3, 2026
79a974a
Restore the gemrc cooldown the outdated tests found
hsbt Sep 3, 2026
4c77eac
Name the file an invalid cooldown value came from
hsbt Sep 3, 2026
b408162
Stop promising a permanent per-source cooldown exemption
hsbt Sep 3, 2026
12b74db
Correct what a cooldown of 0 does in the class documentation
hsbt Sep 3, 2026
bec5c06
Report the gemrc cooldown from bundle config
hsbt Sep 3, 2026
61cce25
Make the cooldown settings accessor private
hsbt Sep 3, 2026
655024b
Say what Gem::BundlerSettings does not follow
hsbt Sep 3, 2026
c4a3447
Note which gemrc cooldown Bundler reads
hsbt Sep 3, 2026
f1b9362
Normalize this round's comments
hsbt Sep 3, 2026
1aca807
Ship the cooldown settings copy in the ruby-core layout too
hsbt Sep 3, 2026
89a544c
Find the gemrc cooldown on RubyGems 3.4 too
hsbt Sep 3, 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
2 changes: 2 additions & 0 deletions Manifest.txt
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,7 @@ lib/bundler/worker.rb
lib/rubygems.rb
lib/rubygems/available_set.rb
lib/rubygems/basic_specification.rb
lib/rubygems/bundler_settings.rb
lib/rubygems/bundler_version_finder.rb
lib/rubygems/ci_detector.rb
lib/rubygems/command.rb
Expand Down Expand Up @@ -357,6 +358,7 @@ lib/rubygems/compact_index_client/updater.rb
lib/rubygems/config_file.rb
lib/rubygems/cooldown.rb
lib/rubygems/cooldown_option.rb
lib/rubygems/cooldown_settings.rb
lib/rubygems/core_ext/kernel_gem.rb
lib/rubygems/core_ext/kernel_require.rb
lib/rubygems/core_ext/kernel_warn.rb
Expand Down
7 changes: 4 additions & 3 deletions bundler.gemspec
Original file line number Diff line number Diff line change
Expand Up @@ -40,13 +40,14 @@ Gem::Specification.new do |s|
s.files = Dir.glob("lib/bundler{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }

# Bundler reuses RubyGems' vendored URI, SecureRandom and PubGrub, its
# pure-Ruby YAML serializer, its compact index client and its credential
# store. Ship a copy under lib/rubygems so Bundler stays self-contained on
# RubyGems versions that predate them.
# pure-Ruby YAML serializer, its shared cooldown setting rules, its compact
# index client and its credential store. Ship a copy under lib/rubygems so
# Bundler stays self-contained on RubyGems versions that predate them.
s.files += Dir.glob("lib/rubygems/vendor/uri/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
s.files += Dir.glob("lib/rubygems/vendor/securerandom/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
s.files += Dir.glob("lib/rubygems/vendor/pub_grub/**/*", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
s.files += Dir.glob("lib/rubygems/yaml_serializer.rb")
s.files += Dir.glob("lib/rubygems/cooldown_settings.rb")
s.files += Dir.glob("lib/rubygems/compact_index_client{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }
s.files += Dir.glob("lib/rubygems/credential_store{.rb,/**/*}", File::FNM_DOTMATCH).reject {|f| File.directory?(f) }

Expand Down
3 changes: 1 addition & 2 deletions lib/bundler/cli/add.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,7 @@ def initialize(options, gems)
def run
Bundler.ui.level = "warn" if options[:quiet]

Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
Bundler::CLI::Common.configure_cooldown(options)

validate_options!
inject_dependencies
Expand Down
33 changes: 22 additions & 11 deletions lib/bundler/cli/common.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,37 @@

module Bundler
module CLI::Common
# Validates the `--cooldown` flag and makes it the setting for this
# command. Every command that takes the flag goes through here, so they
# share one reading of the value.
def self.configure_cooldown(options)
value = options[:cooldown]

validate_cooldown!(value)
Bundler.settings.set_command_option_if_given :cooldown, value
end

def self.validate_cooldown!(value)
# Without the flag the config file and BUNDLE_COOLDOWN decide, and those
# only warn, so a typo left in a config file keeps the command usable.
# Without the flag the config files, BUNDLE_COOLDOWN and the gemrc
# setting decide, and those only warn, so a typo left in a config file
# keeps the command usable.
return warn_invalid_cooldown_setting if value.nil?
return if value.is_a?(Integer) && value >= 0
raise InvalidOption, "Expected `--cooldown` to be a non-negative integer, got #{value.inspect}"
end

# A cooldown value that cannot be read as a non-negative integer disables
# the cooldown for every source, overriding any per-source `cooldown:` in
# the Gemfile, so say so rather than letting the protection lapse quietly.
# A cooldown value that cannot be read as a non-negative integer takes no
# part in the resolution, so say so rather than letting the protection
# lapse quietly. The RubyGems `:cooldown:` setting feeds the same
# resolution and is checked in Bundler::Settings#rubygems_cooldown, where
# reading it is already being paid for.
def self.warn_invalid_cooldown_setting
value = Bundler.settings.locations(:cooldown).values.first
return if value.nil?
require "rubygems/cooldown_settings"

days = Integer(value.to_s, exception: false)
return if days && !days.negative?
value = Bundler.settings.locations(:cooldown).values.first
return unless Gem::CooldownSettings.invalid?(value)

Bundler.ui.warn "Invalid cooldown value #{value.inspect}, so the cooldown is disabled for all sources. " \
"Expected a non-negative integer number of days."
Bundler.ui.warn Gem::CooldownSettings.invalid_message(value, "Bundler's configuration")
end

def self.output_post_install_messages(messages)
Expand Down
3 changes: 1 addition & 2 deletions lib/bundler/cli/install.rb
Original file line number Diff line number Diff line change
Expand Up @@ -115,8 +115,7 @@ def normalize_settings

Bundler.settings.set_command_option_if_given :jobs, options["jobs"]

Bundler::CLI::Common.validate_cooldown!(options["cooldown"])
Bundler.settings.set_command_option_if_given :cooldown, options["cooldown"]
Bundler::CLI::Common.configure_cooldown(options)

Bundler.settings.set_command_option_if_given :no_install, options["no-install"]

Expand Down
3 changes: 1 addition & 2 deletions lib/bundler/cli/lock.rb
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ def run

check_for_conflicting_options

Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
Bundler::CLI::Common.configure_cooldown(options)

print = options[:print]
previous_output_stream = Bundler.ui.output_stream
Expand Down
3 changes: 1 addition & 2 deletions lib/bundler/cli/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,7 @@ def initialize(options, gems)
def run
check_for_deployment_mode!

Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
Bundler::CLI::Common.configure_cooldown(options)

Bundler.definition.validate_runtime!
current_specs = Bundler.ui.silence { Bundler.definition.resolve }
Expand Down
3 changes: 1 addition & 2 deletions lib/bundler/cli/update.rb
Original file line number Diff line number Diff line change
Expand Up @@ -74,8 +74,7 @@ def run
opts["force"] = options[:redownload] if options[:redownload]

Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
Bundler::CLI::Common.configure_cooldown(options)

Bundler.definition.validate_runtime!

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-add.1
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Adds pessimistic declaration of version\.
Adds strict declaration of version\.
.TP
\fB\-\-cooldown=<number>\fR
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1) for precedence rules\.
.SH "EXAMPLES"
.IP "1." 4
You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.
Expand Down
5 changes: 3 additions & 2 deletions lib/bundler/man/bundle-add.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -61,8 +61,9 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.

* `--cooldown=<number>`:
Only consider gem versions published at least <number> days ago when
resolving. Pass `0` to disable cooldown for this run. See `cooldown`
in bundle-config(1) for precedence rules.
resolving. Pass `0` to disable cooldown for this run, overriding every
other cooldown setting, RubyGems' own included. See `cooldown` in
bundle-config(1) for precedence rules.

## EXAMPLES

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-cache.1
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Include gems for all platforms present in the lockfile, not only the current one
Specify a different cache path than the default (vendor/cache)\.
.TP
\fB\-\-cooldown=<number>\fR
Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1)\.
Only consider gem versions published at least \fInumber\fR days ago when resolving before caching\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1)\.
.TP
\fB\-\-gemfile=GEMFILE\fR
Use the specified gemfile instead of Gemfile\.
Expand Down
4 changes: 2 additions & 2 deletions lib/bundler/man/bundle-cache.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ use the gems in the cache in preference to the ones on `rubygems.org`.
* `--cooldown=<number>`:
Only consider gem versions published at least <number> days ago when
resolving before caching. Pass `0` to disable cooldown for this run,
overriding any per-source or global configuration. See `cooldown` in
bundle-config(1).
overriding every other cooldown setting, RubyGems' own included. See
`cooldown` in bundle-config(1).

* `--gemfile=GEMFILE`:
Use the specified gemfile instead of Gemfile.
Expand Down
8 changes: 5 additions & 3 deletions lib/bundler/man/bundle-config.1
Original file line number Diff line number Diff line change
Expand Up @@ -90,16 +90,18 @@ The following is a list of all configuration keys and their purpose\. You can le
.IP
The effective cooldown for any given gem is resolved from three layers, highest precedence first:
.IP "1." 4
CLI flag \fB\-\-cooldown N\fR on \fBinstall\fR, \fBupdate\fR, \fBadd\fR, and \fBoutdated\fR\.
CLI flag \fB\-\-cooldown N\fR on \fBinstall\fR, \fBupdate\fR, \fBadd\fR, \fBcache\fR, \fBlock\fR, and \fBoutdated\fR\.
.IP "2." 4
This setting (\fBbundle config set cooldown N\fR or \fBBUNDLE_COOLDOWN=N\fR)\.
.IP "3." 4
The per\-source \fBcooldown:\fR keyword in the Gemfile, such as \fBsource "https://rubygems\.org", cooldown: 7\fR\.
.IP "" 0
.IP
The CLI flag and this setting apply uniformly to every source, including ones declared with their own \fBcooldown:\fR value\. To keep a private registry permanently exempt while still cooling down public gems, declare \fBsource "https://internal", cooldown: 0\fR in the Gemfile; remember that \fB\-\-cooldown N\fR on the command line will still override it for that single run\.
The CLI flag and this setting apply uniformly to every source, including ones declared with their own \fBcooldown:\fR value\. To exempt a private registry while still cooling down public gems, declare \fBsource "https://internal", cooldown: 0\fR in the Gemfile\. That exemption is not absolute: \fB\-\-cooldown N\fR on the command line overrides it for a single run, and a gemrc cooldown raises it the same way it raises any other layer, as described below\. Where a gemrc cooldown is configured, \fB\-\-cooldown 0\fR is the only way to get the exemption back\.
.IP
The value must be a non\-negative integer\. \fB\-\-cooldown\fR rejects anything else outright, while a value coming from this setting or from \fBBUNDLE_COOLDOWN\fR only warns, because a typo left in a config file should not make every command fail\. Such a value disables the cooldown for every source, including sources that declare their own \fBcooldown:\fR in the Gemfile\.
RubyGems has a cooldown of its own, set with \fBgem\fR's \fB\-\-cooldown\fR flag or the \fB:cooldown:\fR key in the gemrc file, and the two are read together so that a cooldown configured for only one of the two tools still protects both\. Whatever the three layers above resolve to is raised to the gemrc value, and the longer of the two wins; the same happens the other way round, where \fBgem install\fR and \fBgem update\fR read this setting\. A configured \fB0\fR takes part like any other value rather than switching the cooldown off, so \fBbundle config set cooldown 0\fR does not cancel a gemrc cooldown\. The \fB\-\-cooldown\fR flag is what bypasses the other tool's setting: it wins outright, which is why \fB\-\-cooldown 0\fR remains the escape hatch for an urgent update\.
.IP
The value must be a non\-negative integer\. \fB\-\-cooldown\fR rejects anything else outright, while a value coming from this setting, from \fBBUNDLE_COOLDOWN\fR or from the gemrc file only warns, because a typo left in a config file should not make every command fail\. Such a value takes no part in the resolution above, so a per\-source \fBcooldown:\fR in the Gemfile, or a usable value configured for the other tool, still applies\. The warning names where the value came from, since either tool reports the other's setting as well as its own\.
.IP
Cooldown filtering depends on the gem server providing a per\-version \fBcreated_at\fR timestamp in the v2 compact\-index format\. Versions without that metadata \- older gem servers, historical entries that predate the v2 cutover on \fBrubygems\.org\fR, or private registries that still emit the v1 format \- are treated as outside the cooldown window and remain resolvable\. If you rely on cooldown for supply\-chain protection, confirm that the gem server emits \fBcreated_at\fR in its \fB/info/<gem>\fR responses\.
.IP
Expand Down
42 changes: 30 additions & 12 deletions lib/bundler/man/bundle-config.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -145,26 +145,44 @@ learn more about their operation in [bundle install(1)](bundle-install.1.html).
The effective cooldown for any given gem is resolved from three
layers, highest precedence first:

1. CLI flag `--cooldown N` on `install`, `update`, `add`, and
`outdated`.
1. CLI flag `--cooldown N` on `install`, `update`, `add`, `cache`,
`lock`, and `outdated`.
2. This setting (`bundle config set cooldown N` or
`BUNDLE_COOLDOWN=N`).
3. The per-source `cooldown:` keyword in the Gemfile, such as
`source "https://rubygems.org", cooldown: 7`.

The CLI flag and this setting apply uniformly to every source,
including ones declared with their own `cooldown:` value. To keep a
private registry permanently exempt while still cooling down public
gems, declare `source "https://internal", cooldown: 0` in the
Gemfile; remember that `--cooldown N` on the command line will
still override it for that single run.
including ones declared with their own `cooldown:` value. To exempt a
private registry while still cooling down public gems, declare
`source "https://internal", cooldown: 0` in the Gemfile. That
exemption is not absolute: `--cooldown N` on the command line
overrides it for a single run, and a gemrc cooldown raises it the
same way it raises any other layer, as described below. Where a
gemrc cooldown is configured, `--cooldown 0` is the only way to get
the exemption back.

RubyGems has a cooldown of its own, set with `gem`'s `--cooldown`
flag or the `:cooldown:` key in the gemrc file, and the two are read
together so that a cooldown configured for only one of the two tools
still protects both. Whatever the three layers above resolve to is
raised to the gemrc value, and the longer of the two wins; the same
happens the other way round, where `gem install` and `gem update`
read this setting. A configured `0` takes part like any other value
rather than switching the cooldown off, so `bundle config set
cooldown 0` does not cancel a gemrc cooldown. The `--cooldown` flag
is what bypasses the other tool's setting: it wins outright, which is
why `--cooldown 0` remains the escape hatch for an urgent update.

The value must be a non-negative integer. `--cooldown` rejects
anything else outright, while a value coming from this setting or
from `BUNDLE_COOLDOWN` only warns, because a typo left in a config
file should not make every command fail. Such a value disables the
cooldown for every source, including sources that declare their own
`cooldown:` in the Gemfile.
anything else outright, while a value coming from this setting, from
`BUNDLE_COOLDOWN` or from the gemrc file only warns, because a typo
left in a config file should not make every command fail. Such a
value takes no part in the resolution above, so a per-source
`cooldown:` in the Gemfile, or a usable value configured for the
other tool, still applies. The warning names where the value came
from, since either tool reports the other's setting as well as its
own.

Cooldown filtering depends on the gem server providing a per-version
`created_at` timestamp in the v2 compact-index format. Versions
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-install.1
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ If a \fBGemfile\.lock\fR does exist, and you have updated your Gemfile(5), Bundl
.SH "OPTIONS"
.TP
\fB\-\-cooldown=<number>\fR
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1) for details on the precedence between the CLI flag, Bundler config, and Gemfile per\-source settings\.
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1) for details on the precedence between the CLI flag, Bundler config, Gemfile per\-source settings, and the gemrc file\.
.TP
\fB\-\-force\fR, \fB\-\-redownload\fR
Force reinstalling every gem, even if already installed\.
Expand Down
8 changes: 4 additions & 4 deletions lib/bundler/man/bundle-install.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ update process below under [CONSERVATIVE UPDATING][].

* `--cooldown=<number>`:
Only consider gem versions published at least <number> days ago when
resolving. Pass `0` to disable cooldown for this run, overriding any
per-source or global configuration. See `cooldown` in bundle-config(1)
for details on the precedence between the CLI flag, Bundler config,
and Gemfile per-source settings.
resolving. Pass `0` to disable cooldown for this run, overriding every
other cooldown setting, RubyGems' own included. See `cooldown` in
bundle-config(1) for details on the precedence between the CLI flag,
Bundler config, Gemfile per-source settings, and the gemrc file.

* `--force`, `--redownload`:
Force reinstalling every gem, even if already installed.
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-lock.1
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ If updating, do not allow any gem to be updated past latest \-\-patch | \-\-mino
If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated\.
.TP
\fB\-\-cooldown=<number>\fR
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. See \fBcooldown\fR in bundle\-config(1)\.
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. See \fBcooldown\fR in bundle\-config(1)\.
.SH "UPDATING ALL GEMS"
If you run \fBbundle lock\fR with \fB\-\-update\fR option without list of gems, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
.SH "UPDATING A LIST OF GEMS"
Expand Down
5 changes: 3 additions & 2 deletions lib/bundler/man/bundle-lock.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,9 @@ Lock the gems specified in Gemfile.

* `--cooldown=<number>`:
Only consider gem versions published at least <number> days ago when
resolving. Pass `0` to disable cooldown for this run, overriding any
per-source or global configuration. See `cooldown` in bundle-config(1).
resolving. Pass `0` to disable cooldown for this run, overriding every
other cooldown setting, RubyGems' own included. See `cooldown` in
bundle-config(1).

## UPDATING ALL GEMS

Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-update.1
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Do not allow any gem to be updated past latest \fB\-\-patch\fR | \fB\-\-minor\fR
Use bundle install conservative update behavior and do not allow indirect dependencies to be updated\.
.TP
\fB\-\-cooldown=<number>\fR
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding any per\-source or global configuration\. Combine with \fB\-\-conservative\fR to minimize transitive churn when bypassing cooldown for an urgent update\. See \fBcooldown\fR in bundle\-config(1)\.
Only consider gem versions published at least \fInumber\fR days ago when resolving\. Pass \fB0\fR to disable cooldown for this run, overriding every other cooldown setting, RubyGems' own included\. Combine with \fB\-\-conservative\fR to minimize transitive churn when bypassing cooldown for an urgent update\. See \fBcooldown\fR in bundle\-config(1)\.
.SH "UPDATING ALL GEMS"
If you run \fBbundle update \-\-all\fR, bundler will ignore any previously installed gems and resolve all dependencies again based on the latest versions of all gems available in the sources\.
.P
Expand Down
Loading