Newer
Older
require "formula_versions"
require "migrator"
require "formulary"
require "descriptions"
ohai "Auto-updated Homebrew!" if args.preinstall?
def update_report_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
The Ruby implementation of `brew update`. Never called manually.
EOS
switch "--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."
args = update_report_args.parse
if !Utils::Analytics.messages_displayed? &&
!Utils::Analytics.disabled? &&
!Utils::Analytics.no_message_output?
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 has enabled anonymous aggregate formula and cask 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 (or will be during this `brew` run).
EOS
# Consider the messages possibly missed if not a TTY.
Utils::Analytics.messages_displayed! if $stdout.tty?
end
HOMEBREW_REPOSITORY.cd do
Utils.popen_read("git", "config", "--get", "homebrew.donationmessage").chomp == "true"
unless donation_message_displayed
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n"
# Consider the message possibly missed if not a TTY.
safe_system "git", "config", "--replace-all", "homebrew.donationmessage", "true" if $stdout.tty?
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
puts "Updated Homebrew from #{shorten_revision(initial_revision)} to #{shorten_revision(current_revision)}."
updated = true
Homebrew.failed = true if ENV["HOMEBREW_UPDATE_FAILED"]
return if ENV["HOMEBREW_DISABLE_LOAD_FORMULA"]
hub = ReporterHub.new
updated_taps = []
Tap.each do |tap|
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, preinstall: args.preinstall?)
puts "Updated #{updated_taps.count} #{"tap".pluralize(updated_taps.count)} (#{updated_taps.to_sentence})."
puts "Already up-to-date." if !args.preinstall? && !ENV["HOMEBREW_UPDATE_FAILED"]
if hub.empty?
puts "No changes to formulae."
else
hub.dump(updated_formula_report: !args.preinstall?)
hub.reporters.each(&:migrate_tap_migration)
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
Commands.rebuild_commands_completion_list
link_completions_manpages_and_docs
Tap.each(&:link_completions_and_manpages)
end
def shorten_revision(revision)
Utils.popen_read("git", "-C", HOMEBREW_REPOSITORY, "rev-parse", "--short", revision).chomp
return if ENV["HOMEBREW_UPDATE_TEST"]
core_tap = CoreTap.instance
return if core_tap.installed?
revision = core_tap.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)
Utils::Link.link_completions(repository, command)
Utils::Link.link_manpages(repository, command)
Utils::Link.link_docs(repository, command)
Failed to link all completions, docs and manpages:
#{e}
EOS
class ReporterRevisionUnsetError < RuntimeError
def initialize(var_name)
super "#{var_name} is unset!"
attr_reader :tap, :initial_revision, :current_revision
def initialize(tap)
@tap = tap
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?
def report(preinstall: 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 unless dst.extname == ".rb"
if paths.any? { |p| tap.cask_file?(p) }
when "A"
# Have a dedicated report array for new casks.
@report[:AC] << tap.formula_file_to_name(src)
# Have a dedicated report array for deleted casks.
@report[:DC] << tap.formula_file_to_name(src)
# Report updated casks
@report[:MC] << tap.formula_file_to_name(src)
end
end
next unless paths.any? { |p| tap.formula_file?(p) }
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
name = tap.formula_file_to_name(src)
# Skip reporting updated formulae to speed up automatic updates.
if preinstall
@report[:M] << name
next
end
begin
formula = Formulary.factory(tap.path/src)
new_version = formula.pkg_version
old_version = FormulaVersions.new(formula).formula_at_revision(@initial_revision, &:pkg_version)
next if new_version == old_version
rescue FormulaUnavailableError
# Don't care if the formula isn't available right now.
nil
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "#{e.message}\n#{e.backtrace.join "\n"}" if Homebrew::EnvConfig.developer?
Josh Hagins
committed
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
Josh Hagins
committed
@report[:D] << src_full_name
@report[:A] << dst_full_name
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
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
end
renamed_formulae << [old_full_name, new_full_name]
end
unless renamed_formulae.empty?
@report[:A] -= renamed_formulae.map(&:last)
@report[:D] -= renamed_formulae.map(&:first)
@report[:R] = renamed_formulae.to_a
(report[:D] + report[:DC]).each do |full_name|
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.install unless new_tap.installed?
ohai "#{name} has been moved to Homebrew.", <<~EOS
To uninstall the cask run:
brew cask uninstall --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"
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 unless tabs.first.tap == tap # skip if installed formula is not from this tap.
# 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 cask install #{new_name}"
system HOMEBREW_BREW_FILE, "cask", "install", new_name
#{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
ohai "#{name} has been moved to Homebrew Cask.", <<~EOS
To uninstall the formula and install the cask run:
brew uninstall --force #{name}
brew cask install #{new_name}
EOS
end
else
new_tap.install unless new_tap.installed?
# update tap for each Tab
tabs.each { |tab| tab.tap = new_tap }
tabs.each(&:write)
end
def migrate_formula_rename(force:, verbose:)
Formula.installed.each do |formula|
next unless Migrator.needs_migration?(formula)
oldname = formula.oldname
oldname_rack = HOMEBREW_CELLAR/oldname
if oldname_rack.subdirs.empty?
oldname_rack.rmdir_if_possible
next
end
new_name = tap.formula_renames[oldname]
next unless new_name
new_full_name = "#{tap}/#{new_name}"
begin
f = Formulary.factory(new_full_name)
rescue Exception => e # rubocop:disable Lint/RescueException
onoe "#{e.message}\n#{e.backtrace.join "\n"}" if Homebrew::EnvConfig.developer?
Migrator.migrate_if_needed(f, force: force)
"git", "-C", tap.path, "diff-tree", "-r", "--name-status", "--diff-filter=AMDR",
"-M85%", initial_revision, current_revision
)
end
end
extend Forwardable
def select_formula(key)
@hash.fetch(key, [])
def add(reporter, preinstall: false)
report = reporter.report(preinstall: preinstall).delete_if { |_k, v| v.empty? }
@hash.update(report) { |_key, oldval, newval| oldval.concat(newval) }
def dump(updated_formula_report: true)
# Key Legend: Added (A), Copied (C), Deleted (D), Modified (M), Renamed (R)
dump_formula_report :A, "New Formulae"
if updated_formula_report
dump_formula_report :M, "Updated Formulae"
else
updated = select_formula(:M).count
if updated.positive?
ohai "Updated Formulae"
puts "Updated #{updated} #{"formula".pluralize(updated)}."
end
end
dump_formula_report :R, "Renamed Formulae"
dump_formula_report :D, "Deleted Formulae"
dump_formula_report :AC, "New Casks"
dump_formula_report :MC, "Updated Casks"
dump_formula_report :DC, "Deleted Casks"
def dump_formula_report(key, title)
only_installed = Homebrew::EnvConfig.update_report_only_installed?
formulae = select_formula(key).sort.map do |name, new_name|
# Format list items of renamed formulae
name = pretty_installed(name) if installed?(name)
new_name = pretty_installed(new_name) if installed?(new_name)
"#{name} -> #{new_name}" unless only_installed
name if !installed?(name) && !only_installed
name.split("/").last if !cask_installed?(name) && !only_installed
when :MC, :DC
name = name.split("/").last
if cask_installed?(name)
pretty_installed(name)
elsif !only_installed
name
end
if installed?(name)
pretty_installed(name)
elsif !only_installed
name
end
puts Formatter.columns(formulae.sort)
end
def installed?(formula)
(HOMEBREW_CELLAR/formula.split("/").last).directory?
end
def cask_installed?(cask)
(Cask::Caskroom.path/cask).directory?
end