Skip to content

Commit c0b484b

Browse files
committed
Merge pull request #9576 from ruby/cooldown-feature
Add `cooldown` to delay newly published gem (cherry picked from commit 88057ed)
1 parent 92617c5 commit c0b484b

35 files changed

Lines changed: 725 additions & 20 deletions

bundler/lib/bundler/cli.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -274,6 +274,7 @@ def remove(*gems)
274274
method_option "target-rbconfig", type: :string, banner: "Path to rbconfig.rb for the deployment target platform"
275275
method_option "without", type: :array, banner: "Exclude gems that are part of the specified named group (removed)."
276276
method_option "with", type: :array, banner: "Include gems that are part of the specified named group (removed)."
277+
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
277278
def install
278279
%w[clean deployment frozen no-prune path shebang without with].each do |option|
279280
remembered_flag_deprecation(option)
@@ -324,6 +325,7 @@ def install
324325
method_option "strict", type: :boolean, banner: "Do not allow any gem to be updated past latest --patch | --minor | --major"
325326
method_option "conservative", type: :boolean, banner: "Use bundle install conservative update behavior and do not allow shared dependencies to be updated."
326327
method_option "all", type: :boolean, banner: "Update everything."
328+
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
327329
def update(*gems)
328330
require_relative "cli/update"
329331
Bundler.settings.temporary(no_install: false) do
@@ -405,6 +407,7 @@ def binstubs(*gems)
405407
method_option "skip-install", type: :boolean, banner: "Adds gem to the Gemfile but does not install it"
406408
method_option "optimistic", type: :boolean, banner: "Adds optimistic declaration of version to gem"
407409
method_option "strict", type: :boolean, banner: "Adds strict declaration of version to gem"
410+
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
408411
def add(*gems)
409412
require_relative "cli/add"
410413
Add.new(options.dup, gems).run
@@ -435,6 +438,7 @@ def add(*gems)
435438
method_option "filter-patch", type: :boolean, banner: "Only list patch newer versions"
436439
method_option "parseable", aliases: "--porcelain", type: :boolean, banner: "Use minimal formatting for more parseable output"
437440
method_option "only-explicit", type: :boolean, banner: "Only list gems specified in your Gemfile, not their dependencies"
441+
method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable."
438442
def outdated(*gems)
439443
require_relative "cli/outdated"
440444
Outdated.new(options, gems).run

bundler/lib/bundler/cli/add.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@ def initialize(options, gems)
1414
def run
1515
Bundler.ui.level = "warn" if options[:quiet]
1616

17+
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
18+
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
19+
1720
validate_options!
1821
inject_dependencies
1922
perform_bundle_install unless options["skip-install"]

bundler/lib/bundler/cli/common.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,12 @@
22

33
module Bundler
44
module CLI::Common
5+
def self.validate_cooldown!(value)
6+
return if value.nil?
7+
return if value.is_a?(Integer) && value >= 0
8+
raise InvalidOption, "Expected `--cooldown` to be a non-negative integer, got #{value.inspect}"
9+
end
10+
511
def self.output_post_install_messages(messages)
612
return if Bundler.settings["ignore_messages"]
713
messages.to_a.each do |name, msg|

bundler/lib/bundler/cli/install.rb

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,9 @@ def normalize_settings
112112

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

115+
Bundler::CLI::Common.validate_cooldown!(options["cooldown"])
116+
Bundler.settings.set_command_option_if_given :cooldown, options["cooldown"]
117+
115118
Bundler.settings.set_command_option_if_given :no_prune, options["no-prune"]
116119

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

bundler/lib/bundler/cli/outdated.rb

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,9 @@ def initialize(options, gems)
2626
def run
2727
check_for_deployment_mode!
2828

29+
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
30+
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
31+
2932
Bundler.definition.validate_runtime!
3033
current_specs = Bundler.ui.silence { Bundler.definition.resolve }
3134

@@ -203,6 +206,10 @@ def print_gem(current_spec, active_spec, dependency, groups)
203206

204207
release_date = release_date_for(active_spec)
205208
spec_outdated_info += ", released #{release_date}" unless release_date.empty?
209+
210+
remaining = cooldown_days_remaining(active_spec)
211+
spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}" if remaining
212+
206213
spec_outdated_info += ")"
207214

208215
output_message = if options[:parseable]
@@ -219,6 +226,8 @@ def print_gem(current_spec, active_spec, dependency, groups)
219226
def gem_column_for(current_spec, active_spec, dependency, groups)
220227
current_version = "#{current_spec.version}#{current_spec.git_version}"
221228
spec_version = "#{active_spec.version}#{active_spec.git_version}"
229+
remaining = cooldown_days_remaining(active_spec)
230+
spec_version += " (cooldown #{remaining}d)" if remaining
222231
dependency = dependency.requirement if dependency
223232

224233
ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
@@ -227,6 +236,15 @@ def gem_column_for(current_spec, active_spec, dependency, groups)
227236
ret_val
228237
end
229238

239+
def cooldown_days_remaining(spec, now = Time.now)
240+
return nil unless spec.respond_to?(:created_at) && spec.created_at
241+
return nil unless spec.respond_to?(:remote) && spec.remote
242+
days = spec.remote.effective_cooldown
243+
return nil if days.nil? || days <= 0
244+
remaining = days - ((now - spec.created_at) / 86_400.0)
245+
remaining > 0 ? remaining.ceil : nil
246+
end
247+
230248
def check_for_deployment_mode!
231249
return unless Bundler.frozen_bundle?
232250
suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?

bundler/lib/bundler/cli/update.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,8 @@ def run
6666
opts["force"] = options[:redownload] if options[:redownload]
6767

6868
Bundler.settings.set_command_option_if_given :jobs, opts["jobs"]
69+
Bundler::CLI::Common.validate_cooldown!(options[:cooldown])
70+
Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown]
6971

7072
Bundler.definition.validate_runtime!
7173

bundler/lib/bundler/dsl.rb

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ def source(source, *args, &blk)
116116
options = args.last.is_a?(Hash) ? args.pop.dup : {}
117117
options = normalize_hash(options)
118118
source = normalize_source(source)
119+
cooldown = options["cooldown"]
120+
if cooldown && !(cooldown.is_a?(Integer) && cooldown >= 0)
121+
raise InvalidOption, "Expected `cooldown` to be a non-negative integer, got #{cooldown.inspect}"
122+
end
119123

120124
if options.key?("type")
121125
options["type"] = options["type"].to_s
@@ -130,9 +134,9 @@ def source(source, *args, &blk)
130134
source_opts = options.merge("uri" => source)
131135
with_source(@sources.add_plugin_source(options["type"], source_opts), &blk)
132136
elsif block_given?
133-
with_source(@sources.add_rubygems_source("remotes" => source), &blk)
137+
with_source(@sources.add_rubygems_source("remotes" => source, "cooldown" => cooldown), &blk)
134138
else
135-
@sources.add_global_rubygems_remote(source)
139+
@sources.add_global_rubygems_remote(source, cooldown: cooldown)
136140
end
137141
end
138142

bundler/lib/bundler/endpoint_specification.rb

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ module Bundler
55
class EndpointSpecification < Gem::Specification
66
include MatchRemoteMetadata
77

8-
attr_reader :name, :version, :platform, :checksum
8+
attr_reader :name, :version, :platform, :checksum, :created_at
99
attr_writer :dependencies
1010
attr_accessor :remote, :locked_platform
1111

@@ -145,6 +145,7 @@ def parse_metadata(data)
145145
unless data
146146
@required_ruby_version = nil
147147
@required_rubygems_version = nil
148+
@created_at = nil
148149
return
149150
end
150151

@@ -161,6 +162,16 @@ def parse_metadata(data)
161162
@required_rubygems_version = Gem::Requirement.new(v)
162163
when "ruby"
163164
@required_ruby_version = Gem::Requirement.new(v)
165+
when "created_at"
166+
value = v.is_a?(Array) ? v.last : v
167+
if value.is_a?(String)
168+
require "time"
169+
begin
170+
@created_at = Time.iso8601(value)
171+
rescue ArgumentError
172+
@created_at = nil
173+
end
174+
end
164175
end
165176
end
166177
rescue StandardError => e

bundler/lib/bundler/man/bundle-add.1

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
.SH "NAME"
55
\fBbundle\-add\fR \- Add gem to the Gemfile and run bundle install
66
.SH "SYNOPSIS"
7-
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-quiet] [\-\-skip\-install] [\-\-strict|\-\-optimistic]
7+
\fBbundle add\fR \fIGEM_NAME\fR [\-\-group=GROUP] [\-\-version=VERSION] [\-\-source=SOURCE] [\-\-path=PATH] [\-\-git=GIT|\-\-github=GITHUB] [\-\-branch=BRANCH] [\-\-ref=REF] [\-\-cooldown=NUMBER] [\-\-quiet] [\-\-skip\-install] [\-\-strict|\-\-optimistic]
88
.SH "DESCRIPTION"
99
Adds the named gem to the [\fBGemfile(5)\fR][Gemfile(5)] and run \fBbundle install\fR\. \fBbundle install\fR can be avoided by using the flag \fB\-\-skip\-install\fR\.
1010
.SH "OPTIONS"
@@ -50,6 +50,9 @@ Adds optimistic declaration of version\.
5050
.TP
5151
\fB\-\-strict\fR
5252
Adds strict declaration of version\.
53+
.TP
54+
\fB\-\-cooldown=<number>\fR
55+
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\.
5356
.SH "EXAMPLES"
5457
.IP "1." 4
5558
You can add the \fBrails\fR gem to the Gemfile without any version restriction\. The source of the gem will be the global source\.

bundler/lib/bundler/man/bundle-add.1.ronn

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ bundle-add(1) -- Add gem to the Gemfile and run bundle install
55

66
`bundle add` <GEM_NAME> [--group=GROUP] [--version=VERSION] [--source=SOURCE]
77
[--path=PATH] [--git=GIT|--github=GITHUB] [--branch=BRANCH] [--ref=REF]
8-
[--quiet] [--skip-install] [--strict|--optimistic]
8+
[--cooldown=NUMBER] [--quiet] [--skip-install] [--strict|--optimistic]
99

1010
## DESCRIPTION
1111

@@ -56,6 +56,11 @@ Adds the named gem to the [`Gemfile(5)`][Gemfile(5)] and run `bundle install`.
5656
* `--strict`:
5757
Adds strict declaration of version.
5858

59+
* `--cooldown=<number>`:
60+
Only consider gem versions published at least <number> days ago when
61+
resolving. Pass `0` to disable cooldown for this run. See `cooldown`
62+
in bundle-config(1) for precedence rules.
63+
5964
## EXAMPLES
6065

6166
1. You can add the `rails` gem to the Gemfile without any version restriction.

0 commit comments

Comments
 (0)