From 8aae1dac7b82457e93ca7f2f5e234380bfbf79fc Mon Sep 17 00:00:00 2001 From: Xiyue Deng Date: Wed, 28 Aug 2013 04:51:24 -0700 Subject: [PATCH] Make reinstall transaction safe. * Aborting during reinstall will now restore the originally installed keg. - Change install code to pass on CannotInstallFormulaError exception to caller so it can be reused in reinstall. * Add "--force-new-install" flag to force installing a new formula. Closes Homebrew/homebrew#22190. Signed-off-by: Samuel John --- Library/Homebrew/cmd/install.rb | 11 +++-- Library/Homebrew/cmd/reinstall.rb | 70 ++++++++++++++++++++++++++----- 2 files changed, 67 insertions(+), 14 deletions(-) diff --git a/Library/Homebrew/cmd/install.rb b/Library/Homebrew/cmd/install.rb index 46bc59b91e..2fe22cfa48 100644 --- a/Library/Homebrew/cmd/install.rb +++ b/Library/Homebrew/cmd/install.rb @@ -19,7 +19,13 @@ module Homebrew extend self end unless ARGV.force? perform_preinstall_checks - ARGV.formulae.each { |f| install_formula(f) } + ARGV.formulae.each do |f| + begin + install_formula(f) + rescue CannotInstallFormulaError => e + ofail e.message + end + end end def check_ppc @@ -80,7 +86,6 @@ module Homebrew extend self # another formula. In that case, don't generate an error, just move on. rescue FormulaAlreadyInstalledError => e opoo e.message - rescue CannotInstallFormulaError => e - ofail e.message + # Ignore CannotInstallFormulaError and let caller handle it. end end diff --git a/Library/Homebrew/cmd/reinstall.rb b/Library/Homebrew/cmd/reinstall.rb index 485cf4c30f..5e93900745 100644 --- a/Library/Homebrew/cmd/reinstall.rb +++ b/Library/Homebrew/cmd/reinstall.rb @@ -1,4 +1,3 @@ -require 'cmd/uninstall' require 'cmd/install' module Homebrew extend self @@ -6,9 +5,6 @@ module Homebrew extend self # At first save the named formulae and remove them from ARGV named = ARGV.named ARGV.delete_if { |arg| named.include? arg } - # We add --force because then uninstall always succeeds and so reinstall - # works for formulae not yet installed. - ARGV << "--force" clean_ARGV = ARGV.clone # Add the used_options for each named formula separately so @@ -21,13 +17,65 @@ module Homebrew extend self if tab.built_as_bottle and not tab.poured_from_bottle ARGV << '--build-bottle' end - # Todo: Be as smart as upgrade to restore the old state if reinstall fails. - self.uninstall - # Don't display --force in options; user didn't request it so a bit scary. - options_only = ARGV.options_only - options_only.delete "--force" - oh1 "Reinstalling #{name} #{options_only*' '}" - self.install + + canonical_name = Formula.canonical_name(name) + formula = Formula.factory(canonical_name) + + if not formula.installed? + if force_new_install? + oh1 "Force installing new formula: #{name}" + self.install_formula formula + next + else + raise <<-EOS.undent + #{formula} is not installed. Please install it first or use + "--force-new-install" flag. + EOS + end + end + + linked_keg_ref = HOMEBREW_REPOSITORY/'opt'/canonical_name + keg = Keg.new(linked_keg_ref.realpath) + + begin + oh1 "Reinstalling #{name} #{ARGV.options_only*' '}" + quarantine keg + self.install_formula formula + rescue Exception => e + ofail e.message unless e.message.empty? + restore_quarantine keg, formula + raise 'Reinstallation abort.' + else + remove_quarantine keg + end end end + + def force_new_install? + ARGV.include? '--force-new-install' + end + + def quarantine keg + keg.unlink + + path = Pathname.new(keg.to_s) + path.rename quarantine_path(path) + end + + def restore_quarantine keg, formula + path = Pathname.new(quarantine_path(keg)) + if path.directory? + path.rename keg.to_s + keg.link unless formula.keg_only? + end + end + + def remove_quarantine keg + path = Pathname.new(quarantine_path(keg)) + path.rmtree + end + + def quarantine_path path + path.to_s + '.reinstall' + end end