diff --git a/lib/bundler/cli.rb b/lib/bundler/cli.rb index e15e708f7777..783469e56bf2 100644 --- a/lib/bundler/cli.rb +++ b/lib/bundler/cli.rb @@ -462,6 +462,7 @@ def fund method_option "path", type: :string, banner: "Specify a different path than the system default, namely, $BUNDLE_PATH or $GEM_HOME (removed)." method_option "quiet", type: :boolean, banner: "Only output warnings and errors." method_option "frozen", type: :boolean, banner: "Do not allow the Gemfile.lock to be updated after this bundle cache operation's install (removed)" + method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable." long_desc <<-D The cache command will copy the .gem files for every gem in the bundle into the directory ./vendor/cache. If you then check that directory into your source @@ -643,6 +644,7 @@ def inject(*) method_option "strict", type: :boolean, banner: "If updating, do not allow any gem to be updated past latest --patch | --minor | --major" method_option "conservative", type: :boolean, banner: "If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated" method_option "bundler", type: :string, lazy_default: "> 0.a", banner: "Update the locked version of bundler" + method_option "cooldown", type: :numeric, banner: "Only consider gem versions published at least N days ago. Use 0 to disable." def lock require_relative "cli/lock" Lock.new(options).run diff --git a/lib/bundler/cli/lock.rb b/lib/bundler/cli/lock.rb index 2f78868936c1..f6732c59254a 100644 --- a/lib/bundler/cli/lock.rb +++ b/lib/bundler/cli/lock.rb @@ -16,6 +16,9 @@ def run check_for_conflicting_options + Bundler::CLI::Common.validate_cooldown!(options[:cooldown]) + Bundler.settings.set_command_option_if_given :cooldown, options[:cooldown] + print = options[:print] previous_output_stream = Bundler.ui.output_stream Bundler.ui.output_stream = :stderr if print diff --git a/lib/bundler/man/bundle-cache.1 b/lib/bundler/man/bundle-cache.1 index 38ea04796124..81d4a9e04265 100644 --- a/lib/bundler/man/bundle-cache.1 +++ b/lib/bundler/man/bundle-cache.1 @@ -17,6 +17,9 @@ Include gems for all platforms present in the lockfile, not only the current one \fB\-\-cache\-path=CACHE\-PATH\fR Specify a different cache path than the default (vendor/cache)\. .TP +\fB\-\-cooldown=\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)\. +.TP \fB\-\-gemfile=GEMFILE\fR Use the specified gemfile instead of Gemfile\. .TP diff --git a/lib/bundler/man/bundle-cache.1.ronn b/lib/bundler/man/bundle-cache.1.ronn index 51846c96b42a..91e16cde2284 100644 --- a/lib/bundler/man/bundle-cache.1.ronn +++ b/lib/bundler/man/bundle-cache.1.ronn @@ -21,6 +21,12 @@ use the gems in the cache in preference to the ones on `rubygems.org`. * `--cache-path=CACHE-PATH`: Specify a different cache path than the default (vendor/cache). +* `--cooldown=`: + Only consider gem versions published at least 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). + * `--gemfile=GEMFILE`: Use the specified gemfile instead of Gemfile. diff --git a/lib/bundler/man/bundle-lock.1 b/lib/bundler/man/bundle-lock.1 index 396c8ff6ca97..8f6a77e435ee 100644 --- a/lib/bundler/man/bundle-lock.1 +++ b/lib/bundler/man/bundle-lock.1 @@ -4,7 +4,7 @@ .SH "NAME" \fBbundle\-lock\fR \- Creates / Updates a lockfile without installing .SH "SYNOPSIS" -\fBbundle lock\fR [\-\-update] [\-\-bundler[=BUNDLER]] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-add\-checksums] [\-\-add\-platform] [\-\-remove\-platform] [\-\-normalize\-platforms] [\-\-patch] [\-\-minor] [\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] +\fBbundle lock\fR [\-\-update] [\-\-bundler[=BUNDLER]] [\-\-local] [\-\-print] [\-\-lockfile=PATH] [\-\-full\-index] [\-\-gemfile=GEMFILE] [\-\-add\-checksums] [\-\-add\-platform] [\-\-remove\-platform] [\-\-normalize\-platforms] [\-\-patch] [\-\-minor] [\-\-major] [\-\-pre] [\-\-strict] [\-\-conservative] [\-\-cooldown=NUMBER] .SH "DESCRIPTION" Lock the gems specified in Gemfile\. .SH "OPTIONS" @@ -59,6 +59,9 @@ If updating, do not allow any gem to be updated past latest \-\-patch | \-\-mino .TP \fB\-\-conservative\fR If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated\. +.TP +\fB\-\-cooldown=\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)\. .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" diff --git a/lib/bundler/man/bundle-lock.1.ronn b/lib/bundler/man/bundle-lock.1.ronn index 6d3e63c982c5..683a429b0e1e 100644 --- a/lib/bundler/man/bundle-lock.1.ronn +++ b/lib/bundler/man/bundle-lock.1.ronn @@ -20,6 +20,7 @@ bundle-lock(1) -- Creates / Updates a lockfile without installing [--pre] [--strict] [--conservative] + [--cooldown=NUMBER] ## DESCRIPTION @@ -84,6 +85,11 @@ Lock the gems specified in Gemfile. * `--conservative`: If updating, use bundle install conservative update behavior and do not allow shared dependencies to be updated. +* `--cooldown=`: + Only consider gem versions published at least 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). + ## UPDATING ALL GEMS If you run `bundle lock` with `--update` option without list of gems, bundler will diff --git a/spec/install/cooldown_spec.rb b/spec/install/cooldown_spec.rb index 8d42f4ad8255..2724132263cd 100644 --- a/spec/install/cooldown_spec.rb +++ b/spec/install/cooldown_spec.rb @@ -115,6 +115,19 @@ build_gem "fresh_gem", "0.3.2" do |s| s.date = now - (1 * 86_400) end + + # the generic build is outside the window, but a platform-specific + # build of the same version was pushed inside it + build_gem "late_platform", "1.0.0" do |s| + s.date = now - (30 * 86_400) + end + build_gem "late_platform", "2.0.0" do |s| + s.date = now - (30 * 86_400) + end + build_gem "late_platform", "2.0.0" do |s| + s.platform = "x86_64-linux" + s.date = now - (1 * 86_400) + end end end @@ -712,6 +725,85 @@ expect(lockfile).not_to include("ripe_gem (2.0.0)") end + it "excludes a version on every platform when a platform-specific build of it is inside the window" do + # Exclusion is keyed on [name, version] and deliberately ignores + # platform: otherwise pushing a fresh platform-specific build under an + # already-ripe version number would slip new code past the cooldown. + gemfile <<-G + source "https://gem.repo3" + gem "late_platform" + G + + bundle "install --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("late_platform 1.0.0") + end + + it "selects the version with a late platform-specific build when --cooldown 0 bypasses the filter" do + gemfile <<-G + source "https://gem.repo3" + gem "late_platform" + G + + bundle "install --cooldown 0", artifice: "compact_index_cooldown" + + # On x86_64-linux hosts this resolves to the platform-specific build, so + # assert on the lockfile instead of the installed platform. + expect(lockfile).to include("late_platform (2.0.0") + expect(lockfile).not_to include("late_platform (1.0.0)") + end + + it "applies CLI --cooldown on bundle lock --update" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + lockfile <<-L + GEM + remote: https://gem.repo3/ + specs: + ripe_gem (1.0.0) + + PLATFORMS + #{lockfile_platforms} + + DEPENDENCIES + ripe_gem + + BUNDLED WITH + #{Bundler::VERSION} + L + + bundle "lock --update --cooldown 7", artifice: "compact_index_cooldown" + + expect(lockfile).to include("ripe_gem (1.0.0)") + expect(lockfile).not_to include("ripe_gem (2.0.0)") + end + + it "rejects a negative --cooldown value on bundle lock" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "lock --cooldown=-7", artifice: "compact_index_cooldown", raise_on_error: false + + expect(err).to match(/non-negative integer/) + end + + it "applies CLI --cooldown on bundle cache" do + gemfile <<-G + source "https://gem.repo3" + gem "ripe_gem" + G + + bundle "cache --cooldown 7", artifice: "compact_index_cooldown" + + expect(the_bundle).to include_gems("ripe_gem 1.0.0") + expect(bundled_app("vendor/cache/ripe_gem-1.0.0.gem")).to exist + end + it "ignores cooldown and installs the locked version when frozen" do # Frozen installs read the lockfile instead of resolving, so cooldown has # no say. A version already locked inside the window must still install.