Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
44 changes: 37 additions & 7 deletions lib/bundler/cli/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -164,11 +164,29 @@ def retrieve_active_spec(definition, current_spec)

return active_spec if strict

active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
matching_specs(active_spec, current_spec).last
end

def matching_specs(active_spec, current_spec)
@matching_specs ||= {}
@matching_specs[[active_spec.source, current_spec.name, current_spec.platform]] ||= begin
active_specs = active_spec.source.specs.search(current_spec.name).select {|spec| spec.installable_on_platform?(current_spec.platform) }.sort_by(&:version)
if !current_spec.version.prerelease? && !options[:pre] && active_specs.size > 1
active_specs.delete_if {|b| b.respond_to?(:version) && b.version.prerelease? }
end
active_specs
end
active_specs.last
end

# The newest version the cooldown setting would let bundler adopt right
# now, when the newest overall version is still inside the window. Only a
# version strictly between the installed one and the newest one is an
# adoptable update worth showing.
def newest_out_of_cooldown(active_spec, current_spec)
newest = matching_specs(active_spec, current_spec).reverse_each.find {|spec| cooldown_days_remaining(spec).nil? }
return unless newest
return if newest.version >= active_spec.version || newest.version <= current_spec.version
newest
end

def print_gems(gems_list)
Expand Down Expand Up @@ -214,7 +232,11 @@ def print_gem(current_spec, active_spec, dependency, groups)
spec_outdated_info += ", released #{release_date}" unless release_date.empty?

remaining = cooldown_days_remaining(active_spec)
spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}" if remaining
if remaining
spec_outdated_info += ", in cooldown for #{remaining} more day#{"s" if remaining > 1}"
adoptable = newest_out_of_cooldown(active_spec, current_spec)
spec_outdated_info += ", newest out of cooldown #{adoptable.version}" if adoptable
end

spec_outdated_info += ")"

Expand All @@ -233,7 +255,11 @@ def gem_column_for(current_spec, active_spec, dependency, groups)
current_version = "#{current_spec.version}#{current_spec.git_version}"
spec_version = "#{active_spec.version}#{active_spec.git_version}"
remaining = cooldown_days_remaining(active_spec)
spec_version += " (cooldown #{remaining}d)" if remaining
if remaining
adoptable = newest_out_of_cooldown(active_spec, current_spec)
adoptable_note = adoptable ? ", #{adoptable.version} out of cooldown" : ""
spec_version += " (cooldown #{remaining}d#{adoptable_note})"
end
dependency = dependency.requirement if dependency

ret_val = [active_spec.name, current_version, spec_version, dependency.to_s, groups.to_s]
Expand All @@ -242,7 +268,7 @@ def gem_column_for(current_spec, active_spec, dependency, groups)
ret_val
end

def cooldown_days_remaining(spec, now = Time.now)
def cooldown_days_remaining(spec, now = cooldown_now)
return nil unless spec.respond_to?(:created_at) && spec.created_at
return nil unless spec.respond_to?(:remote) && spec.remote
days = spec.remote.effective_cooldown
Expand All @@ -251,6 +277,10 @@ def cooldown_days_remaining(spec, now = Time.now)
remaining > 0 ? remaining.ceil : nil
end

def cooldown_now
@cooldown_now ||= Time.now
end

def check_for_deployment_mode!
return unless Bundler.frozen_bundle?
suggested_command = if Bundler.settings.locations("frozen").keys.&([:global, :local]).any?
Expand Down
2 changes: 1 addition & 1 deletion lib/bundler/man/bundle-outdated.1
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ Only list patch newer versions\.
Only list gems specified in your Gemfile, not their dependencies\.
.TP
\fB\-\-cooldown=<number>\fR
Annotate (rather than hide) versions that are still inside the cooldown window of \fInumber\fR days\. The prose output appends "in cooldown for Nd more days" and the table form adds "(cooldown Nd)" to the Latest column\. See \fBcooldown\fR in bundle\-config(1)\.
Annotate (rather than hide) versions that are still inside the cooldown window of \fInumber\fR days\. The prose output appends "in cooldown for Nd more days" and the table form adds "(cooldown Nd)" to the Latest column\. When a version outside the window is newer than the installed one, the prose output also appends "newest out of cooldown X" and the table form becomes "(cooldown Nd, X out of cooldown)"\. With \fB\-\-filter\-strict\fR (or \fB\-\-patch\fR, \fB\-\-minor\fR, \fB\-\-major\fR, which imply it), "newest" is already the cooldown\-filtered resolved version, so none of these annotations appear\. See \fBcooldown\fR in bundle\-config(1)\.
.SH "PATCH LEVEL OPTIONS"
See bundle update(1) \fIbundle\-update\.1\.html\fR for details\.
.SH "FILTERING OUTPUT"
Expand Down
8 changes: 7 additions & 1 deletion lib/bundler/man/bundle-outdated.1.ronn
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,13 @@ are up to date, Bundler will exit with a status of 0. Otherwise, it will exit 1.
Annotate (rather than hide) versions that are still inside the
cooldown window of <number> days. The prose output appends "in
cooldown for Nd more days" and the table form adds "(cooldown Nd)" to
the Latest column. See `cooldown` in bundle-config(1).
the Latest column. When a version outside the window is newer than the
installed one, the prose output also appends "newest out of cooldown
X" and the table form becomes "(cooldown Nd, X out of cooldown)".
With `--filter-strict` (or `--patch`, `--minor`, `--major`, which
imply it), "newest" is already the cooldown-filtered resolved version,
so none of these annotations appear. See `cooldown` in
bundle-config(1).

## PATCH LEVEL OPTIONS

Expand Down
157 changes: 157 additions & 0 deletions spec/install/cooldown_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,18 @@
s.date = now - (30 * 86_400)
end

# an adoptable version sits between the installed one and the
# in-cooldown newest one
build_gem "mid_gem", "1.0.0" do |s|
s.date = now - (30 * 86_400)
end
build_gem "mid_gem", "1.5.0" do |s|
s.date = now - (30 * 86_400)
end
build_gem "mid_gem", "2.0.0" do |s|
s.date = now - (1 * 86_400)
end

# every published version is inside the cooldown window
build_gem "fresh_gem", "0.3.1" do |s|
s.date = now - (1 * 86_400)
Expand Down Expand Up @@ -466,6 +478,151 @@
expect(out).to match(/ripe_gem.*in cooldown for \d+ more day/)
end

it "shows the newest out-of-cooldown version next to the in-cooldown newest one" do
gemfile <<-G
source "https://gem.repo3"
gem "mid_gem", "1.0.0"
G

lockfile <<-L
GEM
remote: https://gem.repo3/
specs:
mid_gem (1.0.0)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
mid_gem (= 1.0.0)

BUNDLED WITH
#{Bundler::VERSION}
L

bundle "outdated --cooldown 7 --parseable", artifice: "compact_index_cooldown", raise_on_error: false

expect(out).to match(/mid_gem \(newest 2\.0\.0, installed 1\.0\.0.*in cooldown for \d+ more days, newest out of cooldown 1\.5\.0\)/)

bundle "outdated --cooldown 7", artifice: "compact_index_cooldown", raise_on_error: false

expect(out).to match(/mid_gem.*2\.0\.0 \(cooldown \d+d, 1\.5\.0 out of cooldown\)/)
end

it "shows the resolved version without cooldown notes in strict mode" do
gemfile <<-G
source "https://gem.repo3"
gem "mid_gem"
G

lockfile <<-L
GEM
remote: https://gem.repo3/
specs:
mid_gem (1.0.0)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
mid_gem

BUNDLED WITH
#{Bundler::VERSION}
L

bundle "outdated --strict --cooldown 7 --parseable", artifice: "compact_index_cooldown", raise_on_error: false

# in strict mode "newest" is the resolved (cooldown-filtered) version
# itself, so the annotations have nothing to add
expect(out).to match(/mid_gem \(newest 1\.5\.0, installed 1\.0\.0/)
expect(out).not_to include("cooldown")
end

it "shows no out-of-cooldown note when every version is inside the window" do
gemfile <<-G
source "https://gem.repo3"
gem "fresh_gem", "0.3.1"
G

lockfile <<-L
GEM
remote: https://gem.repo3/
specs:
fresh_gem (0.3.1)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
fresh_gem (= 0.3.1)

BUNDLED WITH
#{Bundler::VERSION}
L

bundle "outdated --cooldown 7 --parseable", artifice: "compact_index_cooldown", raise_on_error: false

expect(out).to match(/fresh_gem.*in cooldown for \d+ more day/)
expect(out).not_to include("out of cooldown")
end

it "uses the singular form when one cooldown day remains" do
gemfile <<-G
source "https://gem.repo3"
gem "mid_gem", "1.0.0"
G

lockfile <<-L
GEM
remote: https://gem.repo3/
specs:
mid_gem (1.0.0)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
mid_gem (= 1.0.0)

BUNDLED WITH
#{Bundler::VERSION}
L

# mid_gem 2.0.0 is one day old, so a two-day window leaves one day
bundle "outdated --cooldown 2 --parseable", artifice: "compact_index_cooldown", raise_on_error: false

expect(out).to match(/mid_gem \(newest 2\.0\.0, installed 1\.0\.0.*in cooldown for 1 more day, newest out of cooldown 1\.5\.0\)/)
end

it "leaves bundle outdated output untouched when cooldown is not enabled" do
gemfile <<-G
source "https://gem.repo3"
gem "mid_gem", "1.0.0"
G

lockfile <<-L
GEM
remote: https://gem.repo3/
specs:
mid_gem (1.0.0)

PLATFORMS
#{lockfile_platforms}

DEPENDENCIES
mid_gem (= 1.0.0)

BUNDLED WITH
#{Bundler::VERSION}
L

bundle "outdated --parseable", artifice: "compact_index_cooldown", raise_on_error: false

expect(out).to match(/mid_gem \(newest 2\.0\.0, installed 1\.0\.0/)
expect(out).not_to include("cooldown")
end

it "excludes a locally-installed version that is still within the cooldown window" do
system_gems "ripe_gem-2.0.0", gem_repo: gem_repo3

Expand Down