295 lines
9.8 KiB
Ruby
Raw Normal View History

2020-11-25 17:03:23 +01:00
# typed: true
2020-06-29 10:16:58 -05:00
# frozen_string_literal: true
2020-06-29 09:47:19 -05:00
require "cli/parser"
2021-01-11 08:29:34 +05:30
require "livecheck/livecheck"
2020-06-29 09:47:19 -05:00
module Homebrew
module_function
2020-10-20 12:03:48 +02:00
sig { returns(CLI::Parser) }
2020-06-29 09:47:19 -05:00
def bump_args
Homebrew::CLI::Parser.new do
description <<~EOS
Display out-of-date brew formulae and the latest version available. If the
returned current and livecheck versions differ or when querying specific
formulae, also displays whether a pull request has been opened with the URL.
2020-06-29 09:47:19 -05:00
EOS
switch "--full-name",
description: "Print formulae/casks with fully-qualified names."
2021-02-17 00:09:02 +05:30
switch "--no-pull-requests",
description: "Do not retrieve pull requests from GitHub."
switch "--formula", "--formulae",
description: "Check only formulae."
switch "--cask", "--casks",
description: "Check only casks."
switch "--open-pr",
description: "Open a pull request for the new version if none have been opened yet."
2020-11-12 10:40:41 -05:00
flag "--limit=",
description: "Limit number of package results returned."
flag "--start-with=",
description: "Letter or word that the list of package results should alphabetically follow."
2021-02-17 00:09:02 +05:30
conflicts "--cask", "--formula"
conflicts "--no-pull-requests", "--open-pr"
2021-02-17 00:09:02 +05:30
named_args [:formula, :cask], without_api: true
2020-06-29 09:47:19 -05:00
end
end
def bump
2020-08-05 10:10:05 -05:00
args = bump_args.parse
2020-07-01 10:21:57 -05:00
2021-02-17 00:09:02 +05:30
if args.limit.present? && !args.formula? && !args.cask?
raise UsageError, "`--limit` must be used with either `--formula` or `--cask`."
end
formulae_and_casks = if args.formula?
args.named.to_formulae
elsif args.cask?
args.named.to_casks
else
args.named.to_formulae_and_casks
end
formulae_and_casks = formulae_and_casks&.sort_by do |formula_or_cask|
formula_or_cask.respond_to?(:token) ? formula_or_cask.token : formula_or_cask.name
end
2020-08-16 20:38:03 -05:00
2021-02-17 00:09:02 +05:30
limit = args.limit.to_i if args.limit.present?
unless Utils::Curl.curl_supports_tls13?
begin
ensure_formula_installed!("curl", reason: "Repology queries") unless HOMEBREW_BREWED_CURL_PATH.exist?
rescue FormulaUnavailableError
opoo "A newer `curl` is required for Repology queries."
end
end
2021-05-09 18:07:33 +05:30
if formulae_and_casks.present?
2021-02-17 00:09:02 +05:30
Livecheck.load_other_tap_strategies(formulae_and_casks)
2021-03-04 01:01:56 +05:30
ambiguous_casks = []
if !args.formula? && !args.cask?
2023-04-04 15:37:24 +01:00
ambiguous_casks = formulae_and_casks
.group_by { |item| Livecheck.package_or_resource_name(item, full_name: true) }
.values
.select { |items| items.length > 1 }
.flatten
.select { |item| item.is_a?(Cask::Cask) }
2021-03-04 01:01:56 +05:30
end
ambiguous_names = []
unless args.full_name?
ambiguous_names =
(formulae_and_casks - ambiguous_casks).group_by { |item| Livecheck.package_or_resource_name(item) }
.values
.select { |items| items.length > 1 }
.flatten
end
2021-02-17 00:09:02 +05:30
formulae_and_casks.each_with_index do |formula_or_cask, i|
2021-01-11 08:29:34 +05:30
puts if i.positive?
use_full_name = args.full_name? || ambiguous_names.include?(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask, full_name: use_full_name)
2021-02-17 00:09:02 +05:30
repository = if formula_or_cask.is_a?(Formula)
if formula_or_cask.head_only?
ohai name
puts "Formula is HEAD-only."
next
end
Repology::HOMEBREW_CORE
2021-02-17 00:09:02 +05:30
else
Repology::HOMEBREW_CASK
2021-01-11 08:29:34 +05:30
end
package_data = if formula_or_cask.is_a?(Formula) && formula_or_cask.versioned_formula?
nil
else
Repology.single_package_query(name, repository: repository)
end
retrieve_and_display_info_and_open_pr(
2021-03-04 01:01:56 +05:30
formula_or_cask,
name,
package_data&.values&.first,
args: args,
ambiguous_cask: ambiguous_casks.include?(formula_or_cask),
)
2020-08-16 20:38:03 -05:00
end
2021-01-11 08:29:34 +05:30
else
2021-02-17 00:09:02 +05:30
api_response = {}
unless args.cask?
api_response[:formulae] =
Repology.parse_api_response(limit, args.start_with, repository: Repology::HOMEBREW_CORE)
end
unless args.formula?
api_response[:casks] =
Repology.parse_api_response(limit, args.start_with, repository: Repology::HOMEBREW_CASK)
end
2021-02-17 00:09:02 +05:30
api_response.each_with_index do |(package_type, outdated_packages), idx|
2021-02-17 00:09:02 +05:30
repository = if package_type == :formulae
Repology::HOMEBREW_CORE
2021-02-17 00:09:02 +05:30
else
Repology::HOMEBREW_CASK
2021-01-11 08:29:34 +05:30
end
puts if idx.positive?
oh1 package_type.capitalize if api_response.size > 1
2021-01-11 08:29:34 +05:30
2021-02-17 00:09:02 +05:30
outdated_packages.each_with_index do |(_name, repositories), i|
break if limit && i >= limit
2021-02-17 00:09:02 +05:30
homebrew_repo = repositories.find do |repo|
repo["repo"] == repository
end
2021-01-11 08:29:34 +05:30
2021-02-17 00:09:02 +05:30
next if homebrew_repo.blank?
formula_or_cask = begin
if repository == Repology::HOMEBREW_CORE
2021-02-17 00:09:02 +05:30
Formula[homebrew_repo["srcname"]]
else
Cask::CaskLoader.load(homebrew_repo["srcname"])
end
rescue
next
end
name = Livecheck.package_or_resource_name(formula_or_cask)
2021-03-04 01:01:56 +05:30
ambiguous_cask = begin
formula_or_cask.is_a?(Cask::Cask) && !args.cask? && Formula[name]
2021-03-04 01:01:56 +05:30
rescue FormulaUnavailableError
false
end
2020-08-16 20:38:03 -05:00
2021-02-17 00:09:02 +05:30
puts if i.positive?
retrieve_and_display_info_and_open_pr(
formula_or_cask,
name,
repositories,
args: args,
ambiguous_cask: ambiguous_cask,
)
2021-02-17 00:09:02 +05:30
end
2020-08-16 20:38:03 -05:00
end
2021-01-11 08:29:34 +05:30
end
end
2020-08-16 20:38:03 -05:00
2021-02-17 00:09:02 +05:30
def livecheck_result(formula_or_cask)
name = Livecheck.package_or_resource_name(formula_or_cask)
referenced_formula_or_cask, =
Livecheck.resolve_livecheck_reference(formula_or_cask, full_name: false, debug: false)
# Check skip conditions for a referenced formula/cask
if referenced_formula_or_cask
skip_info = Livecheck::SkipConditions.referenced_skip_information(
referenced_formula_or_cask,
name,
full_name: false,
verbose: false,
)
end
skip_info ||= Livecheck::SkipConditions.skip_information(formula_or_cask, full_name: false, verbose: false)
if skip_info.present?
return "#{skip_info[:status]}#{" - #{skip_info[:messages].join(", ")}" if skip_info[:messages].present?}"
2020-08-05 10:10:05 -05:00
end
2021-01-11 08:29:34 +05:30
version_info = Livecheck.latest_version(
2021-02-17 00:09:02 +05:30
formula_or_cask,
referenced_formula_or_cask: referenced_formula_or_cask,
json: true, full_name: false, verbose: true, debug: false
2021-01-11 08:29:34 +05:30
)
return "unable to get versions" if version_info.blank?
2021-01-11 08:29:34 +05:30
latest = version_info[:latest]
2021-01-11 08:29:34 +05:30
Version.new(latest)
rescue => e
"error: #{e}"
end
def retrieve_pull_requests(formula_or_cask, name, state:, version: nil)
tap_remote_repo = formula_or_cask.tap&.remote_repo || formula_or_cask.tap&.full_name
pull_requests = GitHub.fetch_pull_requests(name, tap_remote_repo, state: state, version: version)
2021-01-11 08:29:34 +05:30
if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
pull_requests
2020-07-06 03:32:18 +00:00
end
def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, args:, ambiguous_cask: false)
if formula_or_cask.is_a?(Formula)
2023-07-24 14:01:53 -07:00
current_version = T.must(formula_or_cask.stable).version
type = :formula
version_name = "formula version"
2021-02-17 00:09:02 +05:30
else
current_version = Version.new(formula_or_cask.version)
type = :cask
version_name = "cask version "
2021-02-17 00:09:02 +05:30
end
2021-01-25 17:12:43 +05:30
livecheck_latest = livecheck_result(formula_or_cask)
2021-01-25 17:12:43 +05:30
repology_latest = if repositories.present?
Repology.latest_version(repositories)
else
"not found"
end
new_version = if livecheck_latest.is_a?(Version) && livecheck_latest > current_version
livecheck_latest
2023-04-26 19:53:10 -04:00
elsif repology_latest.is_a?(Version) &&
repology_latest > current_version &&
!formula_or_cask.livecheckable? &&
current_version != "latest"
repology_latest
end.presence
open_pull_requests = if !args.no_pull_requests? && (args.named.present? || new_version)
retrieve_pull_requests(formula_or_cask, name, state: "open")
end.presence
closed_pull_requests = if !args.no_pull_requests? && !open_pull_requests && new_version.present?
# if we haven't already found open requests, try for an exact match across closed requests
retrieve_pull_requests(formula_or_cask, name, state: "closed", version: new_version)
end.presence
title_name = ambiguous_cask ? "#{name} (cask)" : name
title = if current_version == repology_latest &&
current_version == livecheck_latest
"#{title_name} #{Tty.green}is up to date!#{Tty.reset}"
2021-01-11 08:29:34 +05:30
else
title_name
end
2021-01-11 08:29:34 +05:30
ohai title
puts <<~EOS
Current #{version_name}: #{current_version}
Latest livecheck version: #{livecheck_latest}
Latest Repology version: #{repology_latest}
Open pull requests: #{open_pull_requests || "none"}
Closed pull requests: #{closed_pull_requests || "none"}
EOS
return unless args.open_pr?
if repology_latest.is_a?(Version) &&
repology_latest > current_version &&
repology_latest > livecheck_latest &&
formula_or_cask.livecheckable?
puts "#{title_name} was not bumped to the Repology version because it's livecheckable."
end
return unless new_version
return if open_pull_requests
return if closed_pull_requests
system HOMEBREW_BREW_FILE, "bump-#{type}-pr", "--no-browse",
"--message=Created by `brew bump`", "--version=#{new_version}", name
2020-06-29 09:47:19 -05:00
end
end