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
69 changes: 57 additions & 12 deletions Library/Homebrew/cmd/outdated.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
require "formula"
require "cask/caskroom"
require "api"
require "minimum_version"

module Homebrew
module Cmd
Expand All @@ -26,6 +27,9 @@ class Outdated < AbstractCommand
description: "Print output in JSON format. There are two versions: `v1` and `v2`. " \
"`v1` is deprecated and is currently the default if no version is specified. " \
"`v2` prints outdated formulae and casks."
flag "--minimum-version=", "--min-version=",
description: "Only list a named formula or cask with an installed version below the given " \
"minimum version."
switch "--fetch-HEAD",
description: "Fetch the upstream repository to detect if the HEAD installation of the " \
"formula is outdated. Otherwise, the repository's HEAD will only be checked for " \
Expand All @@ -47,6 +51,9 @@ class Outdated < AbstractCommand

sig { override.void }
def run
raise UsageError, "`--minimum-version` requires exactly one formula or cask argument." if
minimum_version.present? && args.named.length != 1

case json_version(args.json)
when :v1
odie "`brew outdated --json=v1` is no longer supported. Use brew outdated --json=v2 instead."
Expand Down Expand Up @@ -92,10 +99,12 @@ def print_outdated(formulae_or_casks)
f = formula_or_cask

if verbose?
outdated_kegs = f.outdated_kegs(fetch_head: args.fetch_HEAD?)
outdated_kegs = formula_outdated_kegs(f)
latest_formula = f.latest_formula

current_version = if f.alias_changed? && !latest_formula.latest_version_installed?
current_version = if minimum_version.present?
minimum_version
elsif f.alias_changed? && !latest_formula.latest_version_installed?
"#{latest_formula.name} (#{latest_formula.pkg_version})"
elsif f.head?
latest_head_version = f.latest_head_pkg_version(fetch_head: args.fetch_HEAD?)
Expand Down Expand Up @@ -124,8 +133,18 @@ def print_outdated(formulae_or_casks)
else
c = formula_or_cask

puts c.outdated_info(upgrade_greedy_cask?(args.greedy?, formula_or_cask), verbose?,
false, args.greedy_latest?, args.greedy_auto_updates?)
if minimum_version.present?
if verbose?
pinned_version = " [pinned at #{c.pinned_version}]" if c.pinned?

puts "#{c.token} (#{c.installed_version}) < #{minimum_version}#{pinned_version}"
else
puts c.token
end
else
puts c.outdated_info(upgrade_greedy_cask?(args.greedy?, formula_or_cask), verbose?,
false, args.greedy_latest?, args.greedy_auto_updates?)
end
end
end
end
Expand All @@ -140,8 +159,10 @@ def json_info(formulae_or_casks)
if formula_or_cask.is_a?(Formula)
f = formula_or_cask

outdated_versions = f.outdated_kegs(fetch_head: args.fetch_HEAD?).map(&:version)
current_version = if f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
outdated_versions = formula_outdated_kegs(f).map(&:version)
current_version = if minimum_version.present?
minimum_version
elsif f.head? && outdated_versions.any? { |v| v.to_s == f.pkg_version.to_s }
"HEAD"
else
f.pkg_version.to_s
Expand All @@ -155,11 +176,19 @@ def json_info(formulae_or_casks)
else
c = formula_or_cask

T.cast(
c.outdated_info(upgrade_greedy_cask?(args.greedy?, formula_or_cask),
verbose?, true, args.greedy_latest?, args.greedy_auto_updates?),
T::Hash[Symbol, T.untyped],
)
if minimum_version.present?
{ name: c.token,
installed_versions: [T.must(c.installed_version)],
current_version: T.must(minimum_version),
pinned: c.pinned?,
pinned_version: c.pinned_version }
else
T.cast(
c.outdated_info(upgrade_greedy_cask?(args.greedy?, formula_or_cask),
verbose?, true, args.greedy_latest?, args.greedy_auto_updates?),
T::Hash[Symbol, T.untyped],
)
end
end
end
end
Expand All @@ -180,6 +209,9 @@ def json_version(version)
version_hash.fetch(version) { raise UsageError, "invalid JSON version: #{version}" }
end

sig { returns(T.nilable(String)) }
def minimum_version = args.minimum_version || args.min_version

sig { returns(T::Array[Formula]) }
def outdated_formulae
T.cast(
Expand Down Expand Up @@ -217,8 +249,16 @@ def outdated_formulae_casks
def select_outdated(formulae_or_casks)
formulae_or_casks.select do |formula_or_cask|
if formula_or_cask.is_a?(Formula)
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
if minimum_version.present?
formula_outdated_kegs(formula_or_cask).present?
else
formula_or_cask.outdated?(fetch_head: args.fetch_HEAD?)
end
else
if minimum_version.present?
next MinimumVersion.cask_installed_below?(formula_or_cask, T.must(minimum_version))
end

cask_greedy = upgrade_greedy_cask?(args.greedy?, formula_or_cask)

formula_or_cask.outdated?(greedy: cask_greedy,
Expand All @@ -228,6 +268,11 @@ def select_outdated(formulae_or_casks)
end
end

sig { params(formula: Formula).returns(T::Array[Keg]) }
def formula_outdated_kegs(formula)
MinimumVersion.formula_outdated_kegs(formula, minimum_version, fetch_head: args.fetch_HEAD?)
end

sig { params(greedy: T::Boolean, cask: Cask::Cask).returns(T::Boolean) }
def upgrade_greedy_cask?(greedy, cask)
return true if greedy
Expand Down
60 changes: 59 additions & 1 deletion Library/Homebrew/cmd/upgrade.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
require "cask/upgrade"
require "api"
require "reinstall"
require "minimum_version"

module Homebrew
module Cmd
Expand Down Expand Up @@ -55,6 +56,9 @@ class FinalUpgradeSummary < T::Struct
description: "Print the verification and post-install steps."
switch "-n", "--dry-run",
description: "Show what would be upgraded, but do not actually upgrade anything."
flag "--minimum-version=", "--min-version=",
description: "Only upgrade a named formula or cask with an installed version below the given " \
"minimum version."
switch "--ask",
description: "Ask for confirmation before downloading and upgrading. " \
"Print the same plan as `--dry-run`, including available download sizes.",
Expand Down Expand Up @@ -151,6 +155,8 @@ def run
if args.build_from_source? && args.named.empty?
raise ArgumentError, "`--build-from-source` requires at least one formula"
end
raise UsageError, "`--minimum-version` requires exactly one formula or cask argument." if
minimum_version.present? && args.named.length != 1

formulae = T.let([], T::Array[Formula])
casks = T.let([], T::Array[Cask::Cask])
Expand Down Expand Up @@ -294,6 +300,35 @@ def run

private

sig { returns(T.nilable(String)) }
def minimum_version = args.minimum_version || args.min_version

sig { params(formula: Formula).returns(T::Boolean) }
def formula_outdated?(formula)
version = minimum_version
return formula.outdated?(fetch_head: args.fetch_HEAD?) if version.blank?

formula.outdated?(fetch_head: args.fetch_HEAD?) &&
MinimumVersion.formula_outdated_kegs(formula, version, fetch_head: args.fetch_HEAD?).present?
end

sig { params(casks: T::Array[Cask::Cask], quiet: T::Boolean).returns(T::Array[Cask::Cask]) }
def minimum_version_casks(casks, quiet: args.quiet?)
version = minimum_version
return casks if version.blank?

casks.select do |cask|
if MinimumVersion.cask_installed_below?(cask, version)
true
else
unless quiet
opoo "Not upgrading #{cask.token}, the installed version is not below the minimum version #{version}"
end
false
end
end
end

sig {
params(formulae: T::Array[Formula], show_upgrade_summary: T::Boolean,
dry_run: T::Boolean).returns(T.nilable(FormulaeUpgradeContext))
Expand All @@ -310,15 +345,32 @@ def formulae_upgrade_context(formulae, show_upgrade_summary: true, dry_run: args
end
end

not_outdated = T.let([], T::Array[Formula])
if formulae.blank?
outdated = Formula.installed.select do |f|
formula_outdated?(f)
end
elsif minimum_version.present?
outdated, not_outdated = formulae.partition do |f|
f.outdated?(fetch_head: args.fetch_HEAD?)
end
outdated, minimum_version_skipped = outdated.partition do |f|
MinimumVersion.formula_outdated_kegs(f, minimum_version, fetch_head: args.fetch_HEAD?).present?
end

minimum_version_skipped.each do |f|
next if args.quiet?

opoo "Not upgrading #{f.full_specified_name}, the installed version is not below " \
"the minimum version #{minimum_version}"
end
else
outdated, not_outdated = formulae.partition do |f|
f.outdated?(fetch_head: args.fetch_HEAD?)
formula_outdated?(f)
end
end

Comment thread
MikeMcQuaid marked this conversation as resolved.
if formulae.present?
not_outdated.each do |f|
latest_keg = f.installed_kegs.max_by(&:scheme_and_version)
if latest_keg.nil?
Expand Down Expand Up @@ -650,6 +702,9 @@ def prefetch_outdated_casks!(casks, download_queue:, prefetch_names: nil,
return false if args.formula?
return false if args.ask?

casks = minimum_version_casks(casks, quiet: true)
return false if minimum_version.present? && casks.empty?

outdated_casks = Cask::Upgrade.outdated_casks(
casks,
args:,
Expand Down Expand Up @@ -709,6 +764,9 @@ def upgrade_outdated_casks!(casks, skip_prefetch: false, show_upgrade_summary: t
download_queue: nil)
return false if args.formula?

casks = minimum_version_casks(casks)
return false if minimum_version.present? && casks.empty?

Cask::Upgrade.upgrade_casks!(
*casks,
force: args.force?,
Expand Down
49 changes: 49 additions & 0 deletions Library/Homebrew/minimum_version.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# typed: strict
# frozen_string_literal: true

require "cask/cask"
require "cask/dsl/version"
require "formula"
require "pkg_version"

module Homebrew
module MinimumVersion
sig {
params(formula: Formula, minimum_version: T.nilable(String), fetch_head: T::Boolean).returns(T::Array[Keg])
}
def self.formula_outdated_kegs(formula, minimum_version, fetch_head:)
return formula.outdated_kegs(fetch_head:) if minimum_version.blank?

minimum_pkg_version = PkgVersion.parse(minimum_version)
formula.installed_kegs.select do |keg|
keg.version_scheme < formula.version_scheme ||
(keg.version_scheme == formula.version_scheme && keg.version < minimum_pkg_version)
end
end

sig { params(cask: Cask::Cask, minimum_version: String).returns(T::Boolean) }
def self.cask_installed_below?(cask, minimum_version)
minimum_cask_version = comparable_cask_version(minimum_version)
raise UsageError, "invalid `--minimum-version`: #{minimum_version}" if minimum_cask_version.nil?

installed_version = cask.installed_version
return false if installed_version.blank?

installed_cask_version = comparable_cask_version(installed_version)
return false if installed_cask_version.nil?

installed_cask_version < minimum_cask_version
end

sig { params(version: String).returns(T.nilable(::Version)) }
def self.comparable_cask_version(version)
cask_version = Cask::DSL::Version.new(version)
return if cask_version.latest?

::Version.new(cask_version.to_s)
rescue TypeError
Comment thread
MikeMcQuaid marked this conversation as resolved.
nil
end
private_class_method :comparable_cask_version
end
end
6 changes: 6 additions & 0 deletions Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/outdated.rbi

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions Library/Homebrew/sorbet/rbi/dsl/homebrew/cmd/upgrade_cmd.rbi

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading