diff --git a/Library/Homebrew/cask/artifact/moved.rb b/Library/Homebrew/cask/artifact/moved.rb index 477cd0470a..2ec672683e 100644 --- a/Library/Homebrew/cask/artifact/moved.rb +++ b/Library/Homebrew/cask/artifact/moved.rb @@ -34,39 +34,50 @@ module Cask private - def move(adopt: false, force: false, verbose: false, command: nil, **options) + def move(adopt: false, force: false, verbose: false, upgrade: false, command: nil, **options) unless source.exist? raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there." end if Utils.path_occupied?(target) - if adopt - ohai "Adopting existing #{self.class.english_name} at '#{target}'" - same = command.run( - "/usr/bin/diff", - args: ["--recursive", "--brief", source, target], - verbose: verbose, - print_stdout: verbose, - ).success? + if upgrade && target.directory? && target.children.empty? + # An upgrade removed the directory contents but left the directory itself (see below). + unless source.directory? + if target.parent.writable? && !force + target.rmdir + else + Utils.gain_permissions_remove(target, command: command) + end + end + else + if adopt + ohai "Adopting existing #{self.class.english_name} at '#{target}'" + same = command.run( + "/usr/bin/diff", + args: ["--recursive", "--brief", source, target], + verbose: verbose, + print_stdout: verbose, + ).success? - unless same - raise CaskError, - "It seems the existing #{self.class.english_name} is different from " \ - "the one being installed." + unless same + raise CaskError, + "It seems the existing #{self.class.english_name} is different from " \ + "the one being installed." + end + + # Remove the source as we don't need to move it to the target location + source.rmtree + + return post_move(command) end - # Remove the source as we don't need to move it to the target location - source.rmtree + message = "It seems there is already #{self.class.english_article} " \ + "#{self.class.english_name} at '#{target}'" + raise CaskError, "#{message}." unless force - return post_move(command) + opoo "#{message}; overwriting." + delete(target, force: force, command: command, **options) end - - message = "It seems there is already #{self.class.english_article} " \ - "#{self.class.english_name} at '#{target}'" - raise CaskError, "#{message}." unless force - - opoo "#{message}; overwriting." - delete(target, force: force, command: command, **options) end ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'" @@ -79,7 +90,16 @@ module Cask end end - if target.dirname.writable? + if target.directory? + if target.writable? + source.children.each { |child| FileUtils.move(child, target + child.basename) } + else + command.run!("/bin/cp", args: ["-pR", "#{source}/*", "#{source}/.*", "#{target}/"], + sudo: true) + end + # TODO: copy extended attributes + source.rmtree + elsif target.dirname.writable? FileUtils.move(source, target) else # default sudo user isn't necessarily able to write to Homebrew's locations @@ -125,13 +145,23 @@ module Cask delete(target, force: force, command: command, **options) end - def delete(target, force: false, command: nil, **_) + def delete(target, force: false, upgrade: false, command: nil, **_) ohai "Removing #{self.class.english_name} '#{target}'" raise CaskError, "Cannot remove undeletable #{self.class.english_name}." if MacOS.undeletable?(target) return unless Utils.path_occupied?(target) - if target.parent.writable? && !force + if upgrade && target.directory? + # If an app folder is deleted, macOS considers the app uninstalled and removes some data. + # Remove only the contents to handle this case. + target.children.each do |child| + if target.writable? && !force + child.rmtree + else + Utils.gain_permissions_remove(child, command: command) + end + end + elsif target.parent.writable? && !force target.rmtree else Utils.gain_permissions_remove(target, command: command) diff --git a/Library/Homebrew/cask/installer.rb b/Library/Homebrew/cask/installer.rb index e5d7ea2559..f92469e8e1 100644 --- a/Library/Homebrew/cask/installer.rb +++ b/Library/Homebrew/cask/installer.rb @@ -234,7 +234,7 @@ on_request: true) next if artifact.is_a?(Artifact::Binary) && !binaries? - artifact.install_phase(command: @command, verbose: verbose?, adopt: adopt?, force: force?) + artifact.install_phase(command: @command, verbose: verbose?, adopt: adopt?, force: force?, upgrade: upgrade?) already_installed_artifacts.unshift(artifact) end