# typed: true # frozen_string_literal: true require "migrator" require "formulary" require "cask/cask_loader" require "cask/migrator" require "descriptions" require "cleanup" require "description_cache_store" require "cli/parser" require "settings" require "linuxbrew-core-migration" module Homebrew module_function def auto_update_header(args:) @auto_update_header ||= begin ohai "Auto-updated Homebrew!" if args.auto_update? true end end sig { returns(CLI::Parser) } def update_report_args Homebrew::CLI::Parser.new do description <<~EOS The Ruby implementation of `brew update`. Never called manually. EOS switch "--auto-update", "--preinstall", description: "Run in 'auto-update' mode (faster, less output)." switch "-f", "--force", description: "Treat installed and updated formulae as if they are from " \ "the same taps and migrate them anyway." hide_from_man_page! end end def update_report return output_update_report if $stdout.tty? redirect_stdout($stderr) do output_update_report end end def output_update_report args = update_report_args.parse # Run `brew update` (again) if we've got a linuxbrew-core CoreTap if CoreTap.instance.installed? && CoreTap.instance.linuxbrew_core? && ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"].blank? ohai "Re-running `brew update` for linuxbrew-core migration" if Homebrew::EnvConfig.core_git_remote != HOMEBREW_CORE_DEFAULT_GIT_REMOTE opoo <<~EOS HOMEBREW_CORE_GIT_REMOTE was set: #{Homebrew::EnvConfig.core_git_remote}. It has been unset for the migration. You may need to change this from a linuxbrew-core mirror to a homebrew-core one. EOS end ENV.delete("HOMEBREW_CORE_GIT_REMOTE") if Homebrew::EnvConfig.bottle_domain != HOMEBREW_BOTTLE_DEFAULT_DOMAIN opoo <<~EOS HOMEBREW_BOTTLE_DOMAIN was set: #{Homebrew::EnvConfig.bottle_domain}. It has been unset for the migration. You may need to change this from a Linuxbrew package mirror to a Homebrew one. EOS end ENV.delete("HOMEBREW_BOTTLE_DOMAIN") ENV["HOMEBREW_LINUXBREW_CORE_MIGRATION"] = "1" FileUtils.rm_f HOMEBREW_LOCKS/"update" update_args = [] update_args << "--auto-update" if args.auto_update? update_args << "--force" if args.force? exec HOMEBREW_BREW_FILE, "update", *update_args end if ENV["HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID"].present? opoo "HOMEBREW_ADDITIONAL_GOOGLE_ANALYTICS_ID is now a no-op so can be unset." puts "All Homebrew Google Analytics code and data was destroyed." end if ENV["HOMEBREW_NO_GOOGLE_ANALYTICS"].present? opoo "HOMEBREW_NO_GOOGLE_ANALYTICS is now a no-op so can be unset." puts "All Homebrew Google Analytics code and data was destroyed." end unless args.quiet? analytics_message donation_message install_from_api_message untap_message end install_core_tap_if_necessary updated = false new_tag = nil initial_revision = ENV["HOMEBREW_UPDATE_BEFORE"].to_s current_revision = ENV["HOMEBREW_UPDATE_AFTER"].to_s odie "update-report should not be called directly!" if initial_revision.empty? || current_revision.empty? if initial_revision != current_revision auto_update_header args: args updated = true old_tag = Settings.read "latesttag" new_tag = Utils.popen_read( "git", "-C", HOMEBREW_REPOSITORY, "tag", "--list", "--sort=-version:refname", "*.*" ).lines.first.chomp Settings.write "latesttag", new_tag if new_tag != old_tag if new_tag == old_tag ohai "Updated Homebrew from #{shorten_revision(initial_revision)} to #{shorten_revision(current_revision)}." elsif old_tag.blank? ohai "Updated Homebrew from #{shorten_revision(initial_revision)} " \ "to #{new_tag} (#{shorten_revision(current_revision)})." else ohai "Updated Homebrew from #{old_tag} (#{shorten_revision(initial_revision)}) " \ "to #{new_tag} (#{shorten_revision(current_revision)})." end end # Check if we can parse the JSON and do any Ruby-side follow-up. unless Homebrew::EnvConfig.no_install_from_api? Homebrew::API::Formula.write_names_and_aliases Homebrew::API::Cask.write_names end Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"] return if Homebrew::EnvConfig.disable_load_formula? migrate_gcc_dependents_if_needed hub = ReporterHub.new updated_taps = [] Tap.each do |tap| next if !tap.git? || tap.git_repo.origin_url.nil? next if (tap.core_tap? || tap.core_cask_tap?) && !Homebrew::EnvConfig.no_install_from_api? if ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"].present? && tap.core_tap? && Settings.read("linuxbrewmigrated") != "true" ohai "Migrating formulae from linuxbrew-core to homebrew-core" LINUXBREW_CORE_MIGRATION_LIST.each do |name| begin formula = Formula[name] rescue FormulaUnavailableError next end next unless formula.any_version_installed? keg = formula.installed_kegs.last tab = Tab.for_keg(keg) # force a `brew upgrade` from the linuxbrew-core version to the homebrew-core version (even if lower) tab.source["versions"]["version_scheme"] = -1 tab.write end Settings.write "linuxbrewmigrated", true end begin reporter = Reporter.new(tap) rescue Reporter::ReporterRevisionUnsetError => e onoe "#{e.message}\n#{e.backtrace&.join("\n")}" if Homebrew::EnvConfig.developer? next end if reporter.updated? updated_taps << tap.name hub.add(reporter, auto_update: args.auto_update?) end end # If we're installing from the API: we cannot use Git to check for # # differences in packages so instead use {formula,cask}_names.txt to do so. # The first time this runs: we won't yet have a base state # ({formula,cask}_names.before.txt) to compare against so we don't output a # anything and just copy the files for next time. unless Homebrew::EnvConfig.no_install_from_api? api_cache = Homebrew::API::HOMEBREW_CACHE_API core_tap = CoreTap.instance cask_tap = CoreCaskTap.instance [ [:formula, core_tap, core_tap.formula_dir], [:cask, cask_tap, cask_tap.cask_dir], ].each do |type, tap, dir| names_txt = api_cache/"#{type}_names.txt" next unless names_txt.exist? names_before_txt = api_cache/"#{type}_names.before.txt" if names_before_txt.exist? reporter = Reporter.new( tap, api_names_txt: names_txt, api_names_before_txt: names_before_txt, api_dir_prefix: dir, ) if reporter.updated? updated_taps << tap.name hub.add(reporter, auto_update: args.auto_update?) end else FileUtils.cp names_txt, names_before_txt end end end unless updated_taps.empty? auto_update_header args: args puts "Updated #{Utils.pluralize("tap", updated_taps.count, include_count: true)} (#{updated_taps.to_sentence})." updated = true end if updated if hub.empty? puts no_changes_message unless args.quiet? else if ENV.fetch("HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED", false) opoo "HOMEBREW_UPDATE_REPORT_ONLY_INSTALLED is now the default behaviour, " \ "so you can unset it from your environment." end hub.dump(auto_update: args.auto_update?) unless args.quiet? hub.reporters.each(&:migrate_tap_migration) hub.reporters.each(&:migrate_cask_rename) hub.reporters.each { |r| r.migrate_formula_rename(force: args.force?, verbose: args.verbose?) } CacheStoreDatabase.use(:descriptions) do |db| DescriptionCacheStore.new(db) .update_from_report!(hub) end CacheStoreDatabase.use(:cask_descriptions) do |db| CaskDescriptionCacheStore.new(db) .update_from_report!(hub) end end puts if args.auto_update? elsif !args.auto_update? && !ENV["HOMEBREW_UPDATE_FAILED"] && !ENV["HOMEBREW_MIGRATE_LINUXBREW_FORMULAE"] puts "Already up-to-date." unless args.quiet? end Commands.rebuild_commands_completion_list link_completions_manpages_and_docs Tap.each(&:link_completions_and_manpages) failed_fetch_dirs = ENV["HOMEBREW_MISSING_REMOTE_REF_DIRS"]&.split("\n") if failed_fetch_dirs.present? failed_fetch_taps = failed_fetch_dirs.map { |dir| Tap.from_path(dir) } ofail <<~EOS Some taps failed to update! The following taps can not read their remote branches: #{failed_fetch_taps.join("\n ")} This is happening because the remote branch was renamed or deleted. Reset taps to point to the correct remote branches by running `brew tap --repair` EOS end return if new_tag.blank? || new_tag == old_tag || args.quiet? puts new_major_version, new_minor_version, new_patch_version = new_tag.split(".").map(&:to_i) old_major_version, old_minor_version = (old_tag.split(".")[0, 2]).map(&:to_i) if old_tag.present? if old_tag.blank? || new_major_version > old_major_version \ || new_minor_version > old_minor_version puts <<~EOS The #{new_major_version}.#{new_minor_version}.0 release notes are available on the Homebrew Blog: #{Formatter.url("https://brew.sh/blog/#{new_major_version}.#{new_minor_version}.0")} EOS end return if new_patch_version.zero? puts <<~EOS The #{new_tag} changelog can be found at: #{Formatter.url("https://github.com/Homebrew/brew/releases/tag/#{new_tag}")} EOS end def no_changes_message "No changes to formulae or casks." end def shorten_revision(revision) Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp end def install_core_tap_if_necessary return if ENV["HOMEBREW_UPDATE_TEST"] return unless Homebrew::EnvConfig.no_install_from_api? return if Homebrew::EnvConfig.automatically_set_no_install_from_api? return if CoreTap.instance.installed? CoreTap.ensure_installed! revision = CoreTap.instance.git_head ENV["HOMEBREW_UPDATE_BEFORE_HOMEBREW_HOMEBREW_CORE"] = revision ENV["HOMEBREW_UPDATE_AFTER_HOMEBREW_HOMEBREW_CORE"] = revision end def link_completions_manpages_and_docs(repository = HOMEBREW_REPOSITORY) command = "brew update" Utils::Link.link_completions(repository, command) Utils::Link.link_manpages(repository, command) Utils::Link.link_docs(repository, command) rescue => e ofail <<~EOS Failed to link all completions, docs and manpages: #{e} EOS end def migrate_gcc_dependents_if_needed # do nothing end def analytics_message return if Utils::Analytics.messages_displayed? return if Utils::Analytics.no_message_output? if Utils::Analytics.disabled? && !Utils::Analytics.influx_message_displayed? ohai "Homebrew's analytics have entirely moved to our InfluxDB instance in the EU." puts "We gather less data than before and have destroyed all Google Analytics data:" puts " #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset}" puts "Please reconsider re-enabling analytics to help our volunteer maintainers with:" puts " brew analytics on" elsif !Utils::Analytics.disabled? ENV["HOMEBREW_NO_ANALYTICS_THIS_RUN"] = "1" # Use the shell's audible bell. print "\a" # Use an extra newline and bold to avoid this being missed. ohai "Homebrew collects anonymous analytics." puts <<~EOS #{Tty.bold}Read the analytics documentation (and how to opt-out) here: #{Formatter.url("https://docs.brew.sh/Analytics")}#{Tty.reset} No analytics have been recorded yet (nor will be during this `brew` run). EOS end # Consider the messages possibly missed if not a TTY. Utils::Analytics.messages_displayed! if $stdout.tty? end def donation_message return if Settings.read("donationmessage") == "true" ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:" puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n\n" # Consider the message possibly missed if not a TTY. Settings.write "donationmessage", true if $stdout.tty? end def install_from_api_message return if Settings.read("installfromapimessage") == "true" api_auto_update_secs = Homebrew::EnvConfig.api_auto_update_secs.to_i api_auto_update_secs_default = Homebrew::EnvConfig::ENVS.fetch(:HOMEBREW_API_AUTO_UPDATE_SECS).fetch(:default) auto_update_secs_set = api_auto_update_secs.positive? && api_auto_update_secs != api_auto_update_secs_default no_auto_update_set = Homebrew::EnvConfig.no_auto_update? && !ENV["HOMEBREW_GITHUB_HOSTED_RUNNER"] && !ENV["GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED"] no_install_from_api_set = Homebrew::EnvConfig.no_install_from_api? && !Homebrew::EnvConfig.automatically_set_no_install_from_api? && !Homebrew::EnvConfig.install_from_api_unsupported? return if !no_auto_update_set && !no_install_from_api_set && !auto_update_secs_set ohai "You have set:" puts " HOMEBREW_NO_AUTO_UPDATE" if no_auto_update_set puts " HOMEBREW_API_AUTO_UPDATE_SECS" if auto_update_secs_set puts " HOMEBREW_NO_INSTALL_FROM_API" if no_install_from_api_set puts "but we have dramatically sped up and fixed many bugs in the way we do Homebrew updates since." puts "Please consider unsetting these and tweaking the values based on the new behaviour." puts "\n\n" # Consider the message possibly missed if not a TTY. Settings.write "installfromapimessage", true if $stdout.tty? end def untap_message return if Homebrew::EnvConfig.no_install_from_api? return if Homebrew::EnvConfig.developer? || ENV["HOMEBREW_DEV_CMD_RUN"] return if ENV["HOMEBREW_GITHUB_HOSTED_RUNNER"] || ENV["GITHUB_ACTIONS_HOMEBREW_SELF_HOSTED"] return if (HOMEBREW_PREFIX/".homebrewdocker").exist? core_tap = CoreTap.instance cask_tap = CoreCaskTap.instance return if !core_tap.installed? && !cask_tap.installed? puts "Installing from the API is now the default behaviour!" puts "You can save space and time by running:" puts " brew untap #{core_tap.name}" if core_tap.installed? puts " brew untap #{cask_tap.name}" if cask_tap.installed? end end require "extend/os/cmd/update-report" class Reporter class ReporterRevisionUnsetError < RuntimeError def initialize(var_name) super "#{var_name} is unset!" end end def initialize(tap, api_names_txt: nil, api_names_before_txt: nil, api_dir_prefix: nil) @tap = tap # This is slightly involved/weird but all the #report logic is shared so it's worth it. if installed_from_api?(api_names_txt, api_names_before_txt, api_dir_prefix) @api_names_txt = api_names_txt @api_names_before_txt = api_names_before_txt @api_dir_prefix = api_dir_prefix else initial_revision_var = "HOMEBREW_UPDATE_BEFORE#{tap.repo_var}" @initial_revision = ENV[initial_revision_var].to_s raise ReporterRevisionUnsetError, initial_revision_var if @initial_revision.empty? current_revision_var = "HOMEBREW_UPDATE_AFTER#{tap.repo_var}" @current_revision = ENV[current_revision_var].to_s raise ReporterRevisionUnsetError, current_revision_var if @current_revision.empty? end end def report(auto_update: false) return @report if @report @report = Hash.new { |h, k| h[k] = [] } return @report unless updated? diff.each_line do |line| status, *paths = line.split src = Pathname.new paths.first dst = Pathname.new paths.last next if dst.extname != ".rb" if paths.any? { |p| tap.cask_file?(p) } case status when "A" # Have a dedicated report array for new casks. @report[:AC] << tap.formula_file_to_name(src) when "D" # Have a dedicated report array for deleted casks. @report[:DC] << tap.formula_file_to_name(src) when "M" # Report updated casks @report[:MC] << tap.formula_file_to_name(src) when /^R\d{0,3}/ src_full_name = tap.formula_file_to_name(src) dst_full_name = tap.formula_file_to_name(dst) # Don't report formulae that are moved within a tap but not renamed next if src_full_name == dst_full_name @report[:DC] << src_full_name @report[:AC] << dst_full_name end end next unless paths.any? { |p| tap.formula_file?(p) } case status when "A", "D" full_name = tap.formula_file_to_name(src) name = full_name.split("/").last new_tap = tap.tap_migrations[name] @report[status.to_sym] << full_name unless new_tap when "M" name = tap.formula_file_to_name(src) @report[:M] << name when /^R\d{0,3}/ src_full_name = tap.formula_file_to_name(src) dst_full_name = tap.formula_file_to_name(dst) # Don't report formulae that are moved within a tap but not renamed next if src_full_name == dst_full_name @report[:D] << src_full_name @report[:A] << dst_full_name end end renamed_casks = Set.new @report[:DC].each do |old_full_name| old_name = old_full_name.split("/").last new_name = tap.cask_renames[old_name] next unless new_name new_full_name = if tap.core_cask_tap? new_name else "#{tap}/#{new_name}" end renamed_casks << [old_full_name, new_full_name] if @report[:AC].include?(new_full_name) end @report[:AC].each do |new_full_name| new_name = new_full_name.split("/").last old_name = tap.cask_renames.key(new_name) next unless old_name old_full_name = if tap.core_cask_tap? old_name else "#{tap}/#{old_name}" end renamed_casks << [old_full_name, new_full_name] end if renamed_casks.any? @report[:AC] -= renamed_casks.map(&:last) @report[:DC] -= renamed_casks.map(&:first) @report[:RC] = renamed_casks.to_a end renamed_formulae = Set.new @report[:D].each do |old_full_name| old_name = old_full_name.split("/").last new_name = tap.formula_renames[old_name] next unless new_name new_full_name = if tap.core_tap? new_name else "#{tap}/#{new_name}" end renamed_formulae << [old_full_name, new_full_name] if @report[:A].include? new_full_name end @report[:A].each do |new_full_name| new_name = new_full_name.split("/").last old_name = tap.formula_renames.key(new_name) next unless old_name old_full_name = if tap.core_tap? old_name else "#{tap}/#{old_name}" end renamed_formulae << [old_full_name, new_full_name] end if renamed_formulae.any? @report[:A] -= renamed_formulae.map(&:last) @report[:D] -= renamed_formulae.map(&:first) @report[:R] = renamed_formulae.to_a end @report end def updated? if installed_from_api? diff.present? else initial_revision != current_revision end end def migrate_tap_migration (report[:D] + report[:DC]).each do |full_name| name = full_name.split("/").last new_tap_name = tap.tap_migrations[name] next if new_tap_name.nil? # skip if not in tap_migrations list. new_tap_user, new_tap_repo, new_tap_new_name = new_tap_name.split("/") new_name = if new_tap_new_name new_full_name = new_tap_new_name new_tap_name = "#{new_tap_user}/#{new_tap_repo}" new_tap_new_name else new_full_name = "#{new_tap_name}/#{name}" name end # This means it is a cask if report[:DC].include? full_name next unless (HOMEBREW_PREFIX/"Caskroom"/new_name).exist? new_tap = Tap.fetch(new_tap_name) new_tap.ensure_installed! ohai "#{name} has been moved to Homebrew.", <<~EOS To uninstall the cask, run: brew uninstall --cask --force #{name} EOS next if (HOMEBREW_CELLAR/new_name.split("/").last).directory? ohai "Installing #{new_name}..." system HOMEBREW_BREW_FILE, "install", new_full_name begin unless Formulary.factory(new_full_name).keg_only? system HOMEBREW_BREW_FILE, "link", new_full_name, "--overwrite" end rescue Exception => e # rubocop:disable Lint/RescueException onoe "#{e.message}\n#{e.backtrace&.join("\n")}" if Homebrew::EnvConfig.developer? end next end next unless (dir = HOMEBREW_CELLAR/name).exist? # skip if formula is not installed. tabs = dir.subdirs.map { |d| Tab.for_keg(Keg.new(d)) } next if tabs.first.tap != tap # skip if installed formula is not from this tap. new_tap = Tap.fetch(new_tap_name) # For formulae migrated to cask: Auto-install cask or provide install instructions. if new_tap_name.start_with?("homebrew/cask") if new_tap.installed? && (HOMEBREW_PREFIX/"Caskroom").directory? ohai "#{name} has been moved to Homebrew Cask." ohai "brew unlink #{name}" system HOMEBREW_BREW_FILE, "unlink", name ohai "brew cleanup" system HOMEBREW_BREW_FILE, "cleanup" ohai "brew install --cask #{new_name}" system HOMEBREW_BREW_FILE, "install", "--cask", new_name ohai <<~EOS #{name} has been moved to Homebrew Cask. The existing keg has been unlinked. Please uninstall the formula when convenient by running: brew uninstall --force #{name} EOS else ohai "#{name} has been moved to Homebrew Cask.", <<~EOS To uninstall the formula and install the cask, run: brew uninstall --force #{name} brew tap #{new_tap_name} brew install --cask #{new_name} EOS end else new_tap.ensure_installed! # update tap for each Tab tabs.each { |tab| tab.tap = new_tap } tabs.each(&:write) end end end def migrate_cask_rename Cask::Caskroom.casks.each do |cask| Cask::Migrator.migrate_if_needed(cask) end end def migrate_formula_rename(force:, verbose:) Formula.installed.each do |formula| next unless Migrator.needs_migration?(formula) oldnames_to_migrate = formula.oldnames.select do |oldname| oldname_rack = HOMEBREW_CELLAR/oldname next false unless oldname_rack.exist? if oldname_rack.subdirs.empty? oldname_rack.rmdir_if_possible next false end true end next if oldnames_to_migrate.empty? Migrator.migrate_if_needed(formula, force: force) end end private attr_reader :tap, :initial_revision, :current_revision, :api_names_txt, :api_names_before_txt, :api_dir_prefix def installed_from_api?(api_names_txt = @api_names_txt, api_names_before_txt = @api_names_before_txt, api_dir_prefix = @api_dir_prefix) !api_names_txt.nil? && !api_names_before_txt.nil? && !api_dir_prefix.nil? end def diff @diff ||= if installed_from_api? # Hack `git diff` output with regexes to look like `git diff-tree` output. # Yes, I know this is a bit filthy but it saves duplicating the #report logic. diff_output = Utils.popen_read("git", "diff", "--no-ext-diff", api_names_before_txt, api_names_txt) header_regex = /^(---|\+\+\+) /.freeze add_delete_characters = ["+", "-"].freeze diff_output.lines.map do |line| next if line.match?(header_regex) next unless add_delete_characters.include?(line[0]) line.sub(/^\+/, "A #{api_dir_prefix.basename}/") .sub(/^-/, "D #{api_dir_prefix.basename}/") .sub(/$/, ".rb") .chomp end.compact.join("\n") else Utils.popen_read( "git", "-C", tap.path, "diff-tree", "-r", "--name-status", "--diff-filter=AMDR", "-M85%", initial_revision, current_revision ) end end end class ReporterHub attr_reader :reporters sig { void } def initialize @hash = {} @reporters = [] end def select_formula_or_cask(key) @hash.fetch(key, []) end def add(reporter, auto_update: false) @reporters << reporter report = reporter.report(auto_update: auto_update).delete_if { |_k, v| v.empty? } @hash.update(report) { |_key, oldval, newval| oldval.concat(newval) } end def empty? @hash.empty? end def dump(auto_update: false) report_all = ENV["HOMEBREW_UPDATE_REPORT_ALL_FORMULAE"].present? if report_all && !Homebrew::EnvConfig.no_install_from_api? odisabled "HOMEBREW_UPDATE_REPORT_ALL_FORMULAE" opoo "This will not report all formulae because Homebrew cannot get this data from the API." report_all = false end unless Homebrew::EnvConfig.no_update_report_new? dump_new_formula_report dump_new_cask_report end dump_renamed_formula_report if report_all dump_renamed_cask_report if report_all dump_deleted_formula_report(report_all) dump_deleted_cask_report(report_all) outdated_formulae = [] outdated_casks = [] if !auto_update && report_all dump_modified_formula_report dump_modified_cask_report elsif !auto_update outdated_formulae = Formula.installed.select(&:outdated?).map(&:name) output_dump_formula_or_cask_report "Outdated Formulae", outdated_formulae outdated_casks = Cask::Caskroom.casks.select(&:outdated?).map(&:token) output_dump_formula_or_cask_report "Outdated Casks", outdated_casks elsif report_all if (changed_formulae = select_formula_or_cask(:M).count) && changed_formulae.positive? ohai "Modified Formulae", "Modified #{Utils.pluralize("formula", changed_formulae, plural: "e", include_count: true)}." end if (changed_casks = select_formula_or_cask(:MC).count) && changed_casks.positive? ohai "Modified Casks", "Modified #{Utils.pluralize("cask", changed_casks, include_count: true)}." end else outdated_formulae = Formula.installed.select(&:outdated?).map(&:name) outdated_casks = Cask::Caskroom.casks.select(&:outdated?).map(&:token) end return if outdated_formulae.blank? && outdated_casks.blank? outdated_formulae = outdated_formulae.count outdated_casks = outdated_casks.count update_pronoun = if (outdated_formulae + outdated_casks) == 1 "it" else "them" end msg = "" if outdated_formulae.positive? noun = Utils.pluralize("formula", outdated_formulae, plural: "e") msg += "#{Tty.bold}#{outdated_formulae}#{Tty.reset} outdated #{noun}" end if outdated_casks.positive? msg += " and " if msg.present? msg += "#{Tty.bold}#{outdated_casks}#{Tty.reset} outdated #{Utils.pluralize("cask", outdated_casks)}" end return if msg.blank? puts puts "You have #{msg} installed." # If we're auto-updating, don't need to suggest commands that we're perhaps # already running. return if auto_update puts <<~EOS You can upgrade #{update_pronoun} with #{Tty.bold}brew upgrade#{Tty.reset} or list #{update_pronoun} with #{Tty.bold}brew outdated#{Tty.reset}. EOS end private def dump_new_formula_report formulae = select_formula_or_cask(:A).sort.reject { |name| installed?(name) } output_dump_formula_or_cask_report "New Formulae", formulae end def dump_new_cask_report casks = select_formula_or_cask(:AC).sort.map do |name| name.split("/").last unless cask_installed?(name) end.compact output_dump_formula_or_cask_report "New Casks", casks end def dump_renamed_formula_report formulae = select_formula_or_cask(:R).sort.map do |name, new_name| name = pretty_installed(name) if installed?(name) new_name = pretty_installed(new_name) if installed?(new_name) "#{name} -> #{new_name}" end output_dump_formula_or_cask_report "Renamed Formulae", formulae end def dump_renamed_cask_report casks = select_formula_or_cask(:RC).sort.map do |name, new_name| name = pretty_installed(name) if installed?(name) new_name = pretty_installed(new_name) if installed?(new_name) "#{name} -> #{new_name}" end output_dump_formula_or_cask_report "Renamed Casks", casks end def dump_deleted_formula_report(report_all) formulae = select_formula_or_cask(:D).sort.map do |name| if installed?(name) pretty_uninstalled(name) elsif report_all name end end.compact output_dump_formula_or_cask_report "Deleted Formulae", formulae end def dump_deleted_cask_report(report_all) casks = select_formula_or_cask(:DC).sort.map do |name| name = name.split("/").last if cask_installed?(name) pretty_uninstalled(name) elsif report_all name end end.compact output_dump_formula_or_cask_report "Deleted Casks", casks end def dump_modified_formula_report formulae = select_formula_or_cask(:M).sort.map do |name| if installed?(name) if outdated?(name) pretty_outdated(name) else pretty_installed(name) end else name end end output_dump_formula_or_cask_report "Modified Formulae", formulae end def dump_modified_cask_report casks = select_formula_or_cask(:MC).sort.map do |name| name = name.split("/").last if cask_installed?(name) if cask_outdated?(name) pretty_outdated(name) else pretty_installed(name) end else name end end output_dump_formula_or_cask_report "Modified Casks", casks end def output_dump_formula_or_cask_report(title, formulae_or_casks) return if formulae_or_casks.blank? ohai title, Formatter.columns(formulae_or_casks.sort) end def installed?(formula) (HOMEBREW_CELLAR/formula.split("/").last).directory? end def outdated?(formula) Formula[formula].outdated? rescue FormulaUnavailableError false end def cask_installed?(cask) (Cask::Caskroom.path/cask).directory? end def cask_outdated?(cask) Cask::CaskLoader.load(cask).outdated? rescue Cask::CaskError false end end