Merge pull request #10285 from nandahkrishna/refactor-utils-bump

Refactor `brew bump`
This commit is contained in:
Mike McQuaid 2021-01-25 09:59:48 +00:00 committed by GitHub
commit 97d56d122e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 138 additions and 216 deletions

View File

@ -2,6 +2,7 @@
# frozen_string_literal: true # frozen_string_literal: true
require "cli/parser" require "cli/parser"
require "livecheck/livecheck"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -17,8 +18,6 @@ module Homebrew
EOS EOS
flag "--limit=", flag "--limit=",
description: "Limit number of package results returned." description: "Limit number of package results returned."
switch :verbose
switch :debug
named_args :formula named_args :formula
end end
@ -27,64 +26,107 @@ module Homebrew
def bump def bump
args = bump_args.parse args = bump_args.parse
requested_formulae = args.named.to_formulae.map(&:name) if args.named.to_formulae.present? requested_formulae = args.named.to_formulae.presence
requested_limit = args.limit.to_i if args.limit.present? requested_limit = args.limit.to_i if args.limit.present?
repology_data = if requested_formulae
response = {}
requested_formulae.each do |formula|
raise FormulaUnavailableError, formula unless validate_formula(formula)
package_data = Repology.single_package_query(formula)
response[package_data.keys.first] = package_data.values.first if package_data
end
response
else
Repology.parse_api_response(requested_limit)
end
validated_formulae = {}
validated_formulae = Repology.validate_and_format_packages(repology_data, requested_limit) if repology_data
if requested_formulae if requested_formulae
repology_excluded_formulae = requested_formulae.reject do |formula| Livecheck.load_other_tap_strategies(requested_formulae)
repology_data[formula]
requested_formulae.each_with_index do |formula, i|
puts if i.positive?
if formula.head_only?
ohai formula.name
puts "Formula is HEAD-only."
next
end end
formulae = {} current_version = formula.stable.version.to_s
repology_excluded_formulae.each do |formula|
formulae[formula] = Repology.format_package(formula, nil) package_data = Repology.single_package_query(formula.name)
repology_latest = if package_data.present?
Repology.latest_version(package_data.values.first)
else
"not found"
end end
formulae.each { |formula, data| validated_formulae[formula] = data } livecheck_latest = livecheck_result(formula)
pull_requests = retrieve_pull_requests(formula)
display(formula, current_version, repology_latest, livecheck_latest, pull_requests)
end
else
outdated_packages = Repology.parse_api_response(requested_limit)
outdated_packages.each_with_index do |(_name, repositories), i|
puts if i.positive?
homebrew_repo = repositories.find do |repo|
repo["repo"] == "homebrew"
end end
display(validated_formulae) next if homebrew_repo.blank?
end
def validate_formula(formula_name) formula = begin
Formula[formula_name] Formula[homebrew_repo["srcname"]]
rescue rescue
nil next
end end
def up_to_date?(package) current_version = formula.stable.version.to_s
package && repology_latest = Repology.latest_version(repositories)
package[:current_formula_version] == package[:repology_latest_version] && livecheck_latest = livecheck_result(formula)
package[:current_formula_version] == package[:livecheck_latest_version] pull_requests = retrieve_pull_requests(formula)
display(formula, current_version, repology_latest, livecheck_latest, pull_requests)
break if requested_limit && i >= requested_limit
end
end
end
def livecheck_result(formula)
skip_result = Livecheck::SkipConditions.skip_information(formula)
if skip_result.present?
return "#{skip_result[:status]}#{" - #{skip_result[:messages].join(", ")}" if skip_result[:messages].present?}"
end
version_info = Livecheck.latest_version(
formula,
json: true, full_name: false, verbose: false, debug: false,
)
latest = version_info[:latest] if version_info.present?
return "unable to get versions" if latest.blank?
latest.to_s
end
def retrieve_pull_requests(formula)
pull_requests = GitHub.fetch_pull_requests(formula.name, formula.tap&.full_name, state: "open")
if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
return "none" if pull_requests.blank?
pull_requests
end
def up_to_date?(current_version, repology_latest, livecheck_latest)
current_version == repology_latest &&
current_version == livecheck_latest
end
def display(formula, current_version, repology_latest, livecheck_latest, pull_requests)
title = if current_version == repology_latest &&
current_version == livecheck_latest
"#{formula} is up to date!"
else
formula.name
end end
def display(formulae)
formulae.each do |formula, package_details|
title = (up_to_date?(package_details) ? "#{formula} is up to date!" : formula).to_s
ohai title ohai title
puts "Current formula version: #{package_details[:current_formula_version]}" puts "Current formula version: #{current_version}"
puts "Latest Repology version: #{package_details[:repology_latest_version]}" puts "Latest Repology version: #{repology_latest}"
puts "Latest livecheck version: #{package_details[:livecheck_latest_version]}" puts "Latest livecheck version: #{livecheck_latest}"
puts "Open pull requests: #{package_details[:open_pull_requests]}" puts "Open pull requests: #{pull_requests}"
end
end end
end end

View File

@ -60,6 +60,27 @@ module Homebrew
@livecheck_strategy_names.freeze @livecheck_strategy_names.freeze
end end
# Uses `formulae_and_casks_to_check` to identify taps in use other than
# homebrew/core and homebrew/cask and loads strategies from them.
sig { params(formulae_and_casks_to_check: T::Enumerable[T.any(Formula, Cask::Cask)]).void }
def load_other_tap_strategies(formulae_and_casks_to_check)
other_taps = {}
formulae_and_casks_to_check.each do |formula_or_cask|
next if formula_or_cask.tap.blank?
next if formula_or_cask.tap.name == CoreTap.instance.name
next if formula_or_cask.tap.name == "homebrew/cask"
next if other_taps[formula_or_cask.tap.name]
other_taps[formula_or_cask.tap.name] = formula_or_cask.tap
end
other_taps = other_taps.sort.to_h
other_taps.each_value do |tap|
tap_strategy_path = "#{tap.path}/livecheck/strategy"
Dir["#{tap_strategy_path}/*.rb"].sort.each(&method(:require)) if Dir.exist?(tap_strategy_path)
end
end
# Executes the livecheck logic for each formula/cask in the # Executes the livecheck logic for each formula/cask in the
# `formulae_and_casks_to_check` array and prints the results. # `formulae_and_casks_to_check` array and prints the results.
sig { sig {
@ -77,22 +98,7 @@ module Homebrew
formulae_and_casks_to_check, formulae_and_casks_to_check,
full_name: false, json: false, newer_only: false, debug: false, quiet: false, verbose: false full_name: false, json: false, newer_only: false, debug: false, quiet: false, verbose: false
) )
# Identify any non-homebrew/core taps in use for current formulae load_other_tap_strategies(formulae_and_casks_to_check)
non_core_taps = {}
formulae_and_casks_to_check.each do |formula_or_cask|
next if formula_or_cask.tap.blank?
next if formula_or_cask.tap.name == CoreTap.instance.name
next if non_core_taps[formula_or_cask.tap.name]
non_core_taps[formula_or_cask.tap.name] = formula_or_cask.tap
end
non_core_taps = non_core_taps.sort.to_h
# Load additional Strategy files from taps
non_core_taps.each_value do |tap|
tap_strategy_path = "#{tap.path}/livecheck/strategy"
Dir["#{tap_strategy_path}/*.rb"].sort.each(&method(:require)) if Dir.exist?(tap_strategy_path)
end
has_a_newer_upstream_version = T.let(false, T::Boolean) has_a_newer_upstream_version = T.let(false, T::Boolean)
@ -452,12 +458,6 @@ module Homebrew
end end
end end
# Skip Gists until/unless we create a method of identifying revisions
if original_url.include?("gist.github.com")
odebug "Skipping: GitHub Gists are not supported"
next
end
# Only preprocess the URL when it's appropriate # Only preprocess the URL when it's appropriate
url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy) url = if STRATEGY_SYMBOLS_TO_SKIP_PREPROCESS_URL.include?(livecheck_strategy)
original_url original_url

View File

@ -1,39 +0,0 @@
# typed: false
# frozen_string_literal: true
require "utils/livecheck_formula"
require "formula_installer"
describe LivecheckFormula do
describe "init" do
let(:f) { formula { url "foo-1.0" } }
let(:options) { FormulaInstaller.new(f).display_options(f) }
let(:action) { "#{f.full_name} #{options}".strip }
it "runs livecheck command for Formula" do
formatted_response = described_class.init(action)
expect(formatted_response).not_to be_nil
expect(formatted_response).to be_a(Hash)
expect(formatted_response.size).not_to eq(0)
end
end
describe "parse_livecheck_response" do
it "returns a hash of Formula version data" do
example_raw_command_response = "aacgain : 7834 ==> 1.8"
formatted_response = described_class.parse_livecheck_response(example_raw_command_response)
expect(formatted_response).not_to be_nil
expect(formatted_response).to be_a(Hash)
expect(formatted_response).to include(:name)
expect(formatted_response).to include(:formula_version)
expect(formatted_response).to include(:livecheck_version)
expect(formatted_response[:name]).to eq("aacgain")
expect(formatted_response[:formula_version]).to eq("7834")
expect(formatted_response[:livecheck_version]).to eq("1.8")
end
end
end

View File

@ -4,12 +4,6 @@
require "utils/repology" require "utils/repology"
describe Repology do describe Repology do
describe "formula_data" do
it "returns nil for invalid Homebrew Formula" do
expect(described_class.formula_data("invalidName")).to be_nil
end
end
describe "single_package_query", :needs_network do describe "single_package_query", :needs_network do
it "returns nil for non-existent package" do it "returns nil for non-existent package" do
response = described_class.single_package_query("invalidName") response = described_class.single_package_query("invalidName")

View File

@ -11,7 +11,6 @@ require "utils/git_repository"
require "utils/github" require "utils/github"
require "utils/inreplace" require "utils/inreplace"
require "utils/link" require "utils/link"
require "utils/livecheck_formula"
require "utils/popen" require "utils/popen"
require "utils/repology" require "utils/repology"
require "utils/svn" require "utils/svn"

View File

@ -1,35 +0,0 @@
# typed: false
# frozen_string_literal: true
require "context"
# Helper module for parsing output of `brew livecheck`.
#
# @api private
module LivecheckFormula
extend Context
module_function
def init(formula)
ohai "Checking livecheck formula: #{formula}" if verbose?
response = Utils.popen_read(HOMEBREW_BREW_FILE, "livecheck", formula, "--quiet").chomp
parse_livecheck_response(response)
end
def parse_livecheck_response(response)
# e.g response => aacgain : 7834 ==> 1.8
output = response.delete(" ").split(/:|==>/)
# e.g. ["openclonk", "7.0", "8.1"]
package_name, brew_version, latest_version = output
{
name: package_name,
formula_version: brew_version,
livecheck_version: latest_version,
}
end
end

View File

@ -21,16 +21,17 @@ module Repology
end end
def single_package_query(name) def single_package_query(name)
url = "https://repology.org/api/v1/project/#{name}" url = "https://repology.org/tools/project-by?repo=homebrew&" \
"name_type=srcname&target_page=api_v1_project&name=#{name}"
output, _errors, _status = curl_output(url.to_s) output, _errors, _status = curl_output("--location", url.to_s)
begin
data = JSON.parse(output) data = JSON.parse(output)
{ name => data }
homebrew = data.select do |repo| rescue
repo["repo"] == "homebrew" nil
end end
homebrew.empty? ? nil : { name => data }
end end
def parse_api_response(limit = nil) def parse_api_response(limit = nil)
@ -58,63 +59,23 @@ module Repology
outdated_packages outdated_packages
end end
def validate_and_format_packages(outdated_repology_packages, limit) def latest_version(repositories)
if outdated_repology_packages.size > 10 && (limit.blank? || limit > 10) # The status is "unique" when the package is present only in Homebrew, so
ohai "Verifying outdated repology packages" # Repology has no way of knowing if the package is up-to-date.
is_unique = repositories.find do |repo|
repo["status"] == "unique"
end.present?
return "present only in Homebrew" if is_unique
latest_version = repositories.find do |repo|
repo["status"] == "newest"
end end
packages = {} # Repology cannot identify "newest" versions for packages without a version
# scheme
return "no latest version" if latest_version.blank?
outdated_repology_packages.each do |_name, repositories| latest_version["version"]
repology_homebrew_repo = repositories.find do |repo|
repo["repo"] == "homebrew"
end
next if repology_homebrew_repo.blank?
latest_version = repositories.find { |repo| repo["status"] == "newest" }
next if latest_version.blank?
latest_version = latest_version["version"]
srcname = repology_homebrew_repo["srcname"]
package_details = format_package(srcname, latest_version)
packages[srcname] = package_details unless package_details.nil?
break if limit && packages.size >= limit
end
packages
end
def format_package(package_name, latest_version)
formula = formula_data(package_name)
return if formula.blank?
formula_name = formula.to_s
tap_full_name = formula.tap&.full_name
current_version = formula.version.to_s
livecheck_response = LivecheckFormula.init(package_name)
pull_requests = GitHub.fetch_pull_requests(formula_name, tap_full_name, state: "open")
if pull_requests.try(:any?)
pull_requests = pull_requests.map { |pr| "#{pr["title"]} (#{Formatter.url(pr["html_url"])})" }.join(", ")
end
pull_requests = "none" if pull_requests.blank?
{
repology_latest_version: latest_version || "not found",
current_formula_version: current_version.to_s,
livecheck_latest_version: livecheck_response[:livecheck_version] || "not found",
open_pull_requests: pull_requests,
}
end
def formula_data(package_name)
Formula[package_name]
rescue
nil
end end
end end