Add --adopt switch
Allows `brew install` & co. to adopt existing cask artifacts that are identical to those being installed. Discussion: #14006
This commit is contained in:
parent
faa995022d
commit
a715dec49d
@ -34,8 +34,33 @@ module Cask
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def move(force: false, command: nil, **options)
|
def move(adopt: false, force: false, verbose: 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 Utils.path_occupied?(target)
|
||||||
|
if adopt
|
||||||
|
ohai "Adopting existing #{self.class.english_name} at '#{target}'"
|
||||||
|
same = command.run(
|
||||||
|
"/usr/bin/diff",
|
||||||
|
args: ["-rq", 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."
|
||||||
|
end
|
||||||
|
|
||||||
|
# Simulate moving the source to the target location
|
||||||
|
source.rmtree
|
||||||
|
|
||||||
|
return post_move(command)
|
||||||
|
end
|
||||||
|
|
||||||
message = "It seems there is already #{self.class.english_article} " \
|
message = "It seems there is already #{self.class.english_article} " \
|
||||||
"#{self.class.english_name} at '#{target}'"
|
"#{self.class.english_name} at '#{target}'"
|
||||||
raise CaskError, "#{message}." unless force
|
raise CaskError, "#{message}." unless force
|
||||||
@ -44,10 +69,6 @@ module Cask
|
|||||||
delete(target, force: force, command: command, **options)
|
delete(target, force: force, command: command, **options)
|
||||||
end
|
end
|
||||||
|
|
||||||
unless source.exist?
|
|
||||||
raise CaskError, "It seems the #{self.class.english_name} source '#{source}' is not there."
|
|
||||||
end
|
|
||||||
|
|
||||||
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
|
ohai "Moving #{self.class.english_name} '#{source.basename}' to '#{target}'"
|
||||||
if target.dirname.ascend.find(&:directory?).writable?
|
if target.dirname.ascend.find(&:directory?).writable?
|
||||||
target.dirname.mkpath
|
target.dirname.mkpath
|
||||||
@ -61,6 +82,11 @@ module Cask
|
|||||||
command.run!("/bin/mv", args: [source, target], sudo: true)
|
command.run!("/bin/mv", args: [source, target], sudo: true)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
post_move(command)
|
||||||
|
end
|
||||||
|
|
||||||
|
# Performs any actions necessary after the source has been moved to the target location.
|
||||||
|
def post_move(command)
|
||||||
FileUtils.ln_sf target, source
|
FileUtils.ln_sf target, source
|
||||||
|
|
||||||
add_altname_metadata(target, source.basename, command: command)
|
add_altname_metadata(target, source.basename, command: command)
|
||||||
|
|||||||
@ -10,6 +10,10 @@ module Cask
|
|||||||
extend T::Sig
|
extend T::Sig
|
||||||
|
|
||||||
OPTIONS = [
|
OPTIONS = [
|
||||||
|
[:switch, "--adopt", {
|
||||||
|
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
|
||||||
|
"Cannot be combined with --force.",
|
||||||
|
}],
|
||||||
[:switch, "--skip-cask-deps", {
|
[:switch, "--skip-cask-deps", {
|
||||||
description: "Skip installing cask dependencies.",
|
description: "Skip installing cask dependencies.",
|
||||||
}],
|
}],
|
||||||
@ -40,6 +44,7 @@ module Cask
|
|||||||
binaries: args.binaries?,
|
binaries: args.binaries?,
|
||||||
verbose: args.verbose?,
|
verbose: args.verbose?,
|
||||||
force: args.force?,
|
force: args.force?,
|
||||||
|
adopt: args.adopt?,
|
||||||
skip_cask_deps: args.skip_cask_deps?,
|
skip_cask_deps: args.skip_cask_deps?,
|
||||||
require_sha: args.require_sha?,
|
require_sha: args.require_sha?,
|
||||||
quarantine: args.quarantine?,
|
quarantine: args.quarantine?,
|
||||||
@ -52,6 +57,7 @@ module Cask
|
|||||||
*casks,
|
*casks,
|
||||||
verbose: nil,
|
verbose: nil,
|
||||||
force: nil,
|
force: nil,
|
||||||
|
adopt: nil,
|
||||||
binaries: nil,
|
binaries: nil,
|
||||||
skip_cask_deps: nil,
|
skip_cask_deps: nil,
|
||||||
require_sha: nil,
|
require_sha: nil,
|
||||||
@ -65,6 +71,7 @@ module Cask
|
|||||||
options = {
|
options = {
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
force: force,
|
force: force,
|
||||||
|
adopt: adopt,
|
||||||
binaries: binaries,
|
binaries: binaries,
|
||||||
skip_cask_deps: skip_cask_deps,
|
skip_cask_deps: skip_cask_deps,
|
||||||
require_sha: require_sha,
|
require_sha: require_sha,
|
||||||
|
|||||||
@ -16,6 +16,7 @@ module Cask
|
|||||||
binaries: args.binaries?,
|
binaries: args.binaries?,
|
||||||
verbose: args.verbose?,
|
verbose: args.verbose?,
|
||||||
force: args.force?,
|
force: args.force?,
|
||||||
|
adopt: args.adopt?,
|
||||||
skip_cask_deps: args.skip_cask_deps?,
|
skip_cask_deps: args.skip_cask_deps?,
|
||||||
require_sha: args.require_sha?,
|
require_sha: args.require_sha?,
|
||||||
quarantine: args.quarantine?,
|
quarantine: args.quarantine?,
|
||||||
@ -27,6 +28,7 @@ module Cask
|
|||||||
*casks,
|
*casks,
|
||||||
verbose: nil,
|
verbose: nil,
|
||||||
force: nil,
|
force: nil,
|
||||||
|
adopt: nil,
|
||||||
skip_cask_deps: nil,
|
skip_cask_deps: nil,
|
||||||
binaries: nil,
|
binaries: nil,
|
||||||
require_sha: nil,
|
require_sha: nil,
|
||||||
@ -39,6 +41,7 @@ module Cask
|
|||||||
binaries: binaries,
|
binaries: binaries,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
force: force,
|
force: force,
|
||||||
|
adopt: adopt,
|
||||||
skip_cask_deps: skip_cask_deps,
|
skip_cask_deps: skip_cask_deps,
|
||||||
require_sha: require_sha,
|
require_sha: require_sha,
|
||||||
quarantine: quarantine,
|
quarantine: quarantine,
|
||||||
|
|||||||
@ -13,6 +13,10 @@ module Cask
|
|||||||
extend T::Sig
|
extend T::Sig
|
||||||
|
|
||||||
OPTIONS = [
|
OPTIONS = [
|
||||||
|
[:switch, "--adopt", {
|
||||||
|
description: "Adopt existing artifacts in the destination that are identical to those being installed. " \
|
||||||
|
"Cannot be combined with --force.",
|
||||||
|
}],
|
||||||
[:switch, "--skip-cask-deps", {
|
[:switch, "--skip-cask-deps", {
|
||||||
description: "Skip installing cask dependencies.",
|
description: "Skip installing cask dependencies.",
|
||||||
}],
|
}],
|
||||||
@ -66,6 +70,7 @@ module Cask
|
|||||||
casks: Cask,
|
casks: Cask,
|
||||||
args: Homebrew::CLI::Args,
|
args: Homebrew::CLI::Args,
|
||||||
force: T.nilable(T::Boolean),
|
force: T.nilable(T::Boolean),
|
||||||
|
adopt: T.nilable(T::Boolean),
|
||||||
greedy: T.nilable(T::Boolean),
|
greedy: T.nilable(T::Boolean),
|
||||||
greedy_latest: T.nilable(T::Boolean),
|
greedy_latest: T.nilable(T::Boolean),
|
||||||
greedy_auto_updates: T.nilable(T::Boolean),
|
greedy_auto_updates: T.nilable(T::Boolean),
|
||||||
@ -81,6 +86,7 @@ module Cask
|
|||||||
*casks,
|
*casks,
|
||||||
args:,
|
args:,
|
||||||
force: false,
|
force: false,
|
||||||
|
adopt: false,
|
||||||
greedy: false,
|
greedy: false,
|
||||||
greedy_latest: false,
|
greedy_latest: false,
|
||||||
greedy_auto_updates: false,
|
greedy_auto_updates: false,
|
||||||
@ -156,7 +162,7 @@ module Cask
|
|||||||
upgradable_casks.each do |(old_cask, new_cask)|
|
upgradable_casks.each do |(old_cask, new_cask)|
|
||||||
upgrade_cask(
|
upgrade_cask(
|
||||||
old_cask, new_cask,
|
old_cask, new_cask,
|
||||||
binaries: binaries, force: force, skip_cask_deps: skip_cask_deps, verbose: verbose,
|
binaries: binaries, force: force, adopt: adopt, skip_cask_deps: skip_cask_deps, verbose: verbose,
|
||||||
quarantine: quarantine, require_sha: require_sha
|
quarantine: quarantine, require_sha: require_sha
|
||||||
)
|
)
|
||||||
rescue => e
|
rescue => e
|
||||||
@ -171,7 +177,7 @@ module Cask
|
|||||||
|
|
||||||
def self.upgrade_cask(
|
def self.upgrade_cask(
|
||||||
old_cask, new_cask,
|
old_cask, new_cask,
|
||||||
binaries:, force:, quarantine:, require_sha:, skip_cask_deps:, verbose:
|
binaries:, force:, adopt:, quarantine:, require_sha:, skip_cask_deps:, verbose:
|
||||||
)
|
)
|
||||||
require "cask/installer"
|
require "cask/installer"
|
||||||
|
|
||||||
@ -195,6 +201,7 @@ module Cask
|
|||||||
binaries: binaries,
|
binaries: binaries,
|
||||||
verbose: verbose,
|
verbose: verbose,
|
||||||
force: force,
|
force: force,
|
||||||
|
adopt: adopt,
|
||||||
skip_cask_deps: skip_cask_deps,
|
skip_cask_deps: skip_cask_deps,
|
||||||
require_sha: require_sha,
|
require_sha: require_sha,
|
||||||
upgrade: true,
|
upgrade: true,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ module Cask
|
|||||||
|
|
||||||
extend Predicable
|
extend Predicable
|
||||||
|
|
||||||
def initialize(cask, command: SystemCommand, force: false,
|
def initialize(cask, command: SystemCommand, force: false, adopt: false,
|
||||||
skip_cask_deps: false, binaries: true, verbose: false,
|
skip_cask_deps: false, binaries: true, verbose: false,
|
||||||
zap: false, require_sha: false, upgrade: false,
|
zap: false, require_sha: false, upgrade: false,
|
||||||
installed_as_dependency: false, quarantine: true,
|
installed_as_dependency: false, quarantine: true,
|
||||||
@ -28,6 +28,7 @@ module Cask
|
|||||||
@cask = cask
|
@cask = cask
|
||||||
@command = command
|
@command = command
|
||||||
@force = force
|
@force = force
|
||||||
|
@adopt = adopt
|
||||||
@skip_cask_deps = skip_cask_deps
|
@skip_cask_deps = skip_cask_deps
|
||||||
@binaries = binaries
|
@binaries = binaries
|
||||||
@verbose = verbose
|
@verbose = verbose
|
||||||
@ -41,7 +42,7 @@ module Cask
|
|||||||
@quiet = quiet
|
@quiet = quiet
|
||||||
end
|
end
|
||||||
|
|
||||||
attr_predicate :binaries?, :force?, :skip_cask_deps?, :require_sha?,
|
attr_predicate :binaries?, :force?, :adopt?, :skip_cask_deps?, :require_sha?,
|
||||||
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?,
|
:reinstall?, :upgrade?, :verbose?, :zap?, :installed_as_dependency?,
|
||||||
:quarantine?, :quiet?
|
:quarantine?, :quiet?
|
||||||
|
|
||||||
@ -237,7 +238,7 @@ module Cask
|
|||||||
|
|
||||||
next if artifact.is_a?(Artifact::Binary) && !binaries?
|
next if artifact.is_a?(Artifact::Binary) && !binaries?
|
||||||
|
|
||||||
artifact.install_phase(command: @command, verbose: verbose?, force: force?)
|
artifact.install_phase(command: @command, verbose: verbose?, adopt: adopt?, force: force?)
|
||||||
already_installed_artifacts.unshift(artifact)
|
already_installed_artifacts.unshift(artifact)
|
||||||
end
|
end
|
||||||
|
|
||||||
@ -348,6 +349,7 @@ module Cask
|
|||||||
|
|
||||||
Installer.new(
|
Installer.new(
|
||||||
cask_or_formula,
|
cask_or_formula,
|
||||||
|
adopt: adopt?,
|
||||||
binaries: binaries?,
|
binaries: binaries?,
|
||||||
verbose: verbose?,
|
verbose: verbose?,
|
||||||
installed_as_dependency: true,
|
installed_as_dependency: true,
|
||||||
|
|||||||
@ -140,6 +140,7 @@ module Homebrew
|
|||||||
|
|
||||||
conflicts "--ignore-dependencies", "--only-dependencies"
|
conflicts "--ignore-dependencies", "--only-dependencies"
|
||||||
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
|
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
|
||||||
|
conflicts "--adopt", "--force"
|
||||||
|
|
||||||
named_args [:formula, :cask], min: 1
|
named_args [:formula, :cask], min: 1
|
||||||
end
|
end
|
||||||
@ -193,6 +194,7 @@ module Homebrew
|
|||||||
binaries: args.binaries?,
|
binaries: args.binaries?,
|
||||||
verbose: args.verbose?,
|
verbose: args.verbose?,
|
||||||
force: args.force?,
|
force: args.force?,
|
||||||
|
adopt: args.adopt?,
|
||||||
require_sha: args.require_sha?,
|
require_sha: args.require_sha?,
|
||||||
skip_cask_deps: args.skip_cask_deps?,
|
skip_cask_deps: args.skip_cask_deps?,
|
||||||
quarantine: args.quarantine?,
|
quarantine: args.quarantine?,
|
||||||
|
|||||||
@ -152,6 +152,7 @@ module Homebrew
|
|||||||
binaries: args.binaries?,
|
binaries: args.binaries?,
|
||||||
verbose: args.verbose?,
|
verbose: args.verbose?,
|
||||||
force: args.force?,
|
force: args.force?,
|
||||||
|
adopt: args.adopt?,
|
||||||
require_sha: args.require_sha?,
|
require_sha: args.require_sha?,
|
||||||
skip_cask_deps: args.skip_cask_deps?,
|
skip_cask_deps: args.skip_cask_deps?,
|
||||||
quarantine: args.quarantine?,
|
quarantine: args.quarantine?,
|
||||||
|
|||||||
@ -97,6 +97,7 @@ module Homebrew
|
|||||||
cask_options
|
cask_options
|
||||||
|
|
||||||
conflicts "--build-from-source", "--force-bottle"
|
conflicts "--build-from-source", "--force-bottle"
|
||||||
|
conflicts "--adopt", "--force"
|
||||||
|
|
||||||
named_args [:outdated_formula, :outdated_cask]
|
named_args [:outdated_formula, :outdated_cask]
|
||||||
end
|
end
|
||||||
@ -226,6 +227,7 @@ module Homebrew
|
|||||||
Cask::Cmd::Upgrade.upgrade_casks(
|
Cask::Cmd::Upgrade.upgrade_casks(
|
||||||
*casks,
|
*casks,
|
||||||
force: args.force?,
|
force: args.force?,
|
||||||
|
adopt: args.adopt?,
|
||||||
greedy: args.greedy?,
|
greedy: args.greedy?,
|
||||||
greedy_latest: args.greedy_latest?,
|
greedy_latest: args.greedy_latest?,
|
||||||
greedy_auto_updates: args.greedy_auto_updates?,
|
greedy_auto_updates: args.greedy_auto_updates?,
|
||||||
|
|||||||
@ -4,13 +4,14 @@
|
|||||||
describe Cask::Artifact::App, :cask do
|
describe Cask::Artifact::App, :cask do
|
||||||
let(:cask) { Cask::CaskLoader.load(cask_path("local-caffeine")) }
|
let(:cask) { Cask::CaskLoader.load(cask_path("local-caffeine")) }
|
||||||
let(:command) { SystemCommand }
|
let(:command) { SystemCommand }
|
||||||
|
let(:adopt) { false }
|
||||||
let(:force) { false }
|
let(:force) { false }
|
||||||
let(:app) { cask.artifacts.find { |a| a.is_a?(described_class) } }
|
let(:app) { cask.artifacts.find { |a| a.is_a?(described_class) } }
|
||||||
|
|
||||||
let(:source_path) { cask.staged_path.join("Caffeine.app") }
|
let(:source_path) { cask.staged_path.join("Caffeine.app") }
|
||||||
let(:target_path) { cask.config.appdir.join("Caffeine.app") }
|
let(:target_path) { cask.config.appdir.join("Caffeine.app") }
|
||||||
|
|
||||||
let(:install_phase) { app.install_phase(command: command, force: force) }
|
let(:install_phase) { app.install_phase(command: command, adopt: adopt, force: force) }
|
||||||
let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) }
|
let(:uninstall_phase) { app.uninstall_phase(command: command, force: force) }
|
||||||
|
|
||||||
before do
|
before do
|
||||||
@ -79,6 +80,57 @@ describe Cask::Artifact::App, :cask do
|
|||||||
expect(contents_path).not_to exist
|
expect(contents_path).not_to exist
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "given the adopt option" do
|
||||||
|
let(:adopt) { true }
|
||||||
|
|
||||||
|
describe "when the target compares different from the source" do
|
||||||
|
it "avoids clobbering the existing app" do
|
||||||
|
stdout = <<~EOS
|
||||||
|
==> Adopting existing App at '#{target_path}'
|
||||||
|
EOS
|
||||||
|
|
||||||
|
expect { install_phase }
|
||||||
|
.to output(stdout).to_stdout
|
||||||
|
.and raise_error(
|
||||||
|
Cask::CaskError,
|
||||||
|
"It seems the existing App is different from the one being installed.",
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(source_path).to be_a_directory
|
||||||
|
expect(target_path).to be_a_directory
|
||||||
|
expect(File.identical?(source_path, target_path)).to be false
|
||||||
|
|
||||||
|
contents_path = target_path.join("Contents/Info.plist")
|
||||||
|
expect(contents_path).not_to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "when the target compares the same as the source" do
|
||||||
|
before do
|
||||||
|
target_path.delete
|
||||||
|
FileUtils.cp_r source_path, target_path
|
||||||
|
end
|
||||||
|
|
||||||
|
it "adopts the existing app" do
|
||||||
|
stdout = <<~EOS
|
||||||
|
==> Adopting existing App at '#{target_path}'
|
||||||
|
EOS
|
||||||
|
|
||||||
|
stderr = ""
|
||||||
|
|
||||||
|
expect { install_phase }
|
||||||
|
.to output(stdout).to_stdout
|
||||||
|
.and output(stderr).to_stderr
|
||||||
|
|
||||||
|
expect(source_path).to be_a_symlink
|
||||||
|
expect(target_path).to be_a_directory
|
||||||
|
|
||||||
|
contents_path = target_path.join("Contents/Info.plist")
|
||||||
|
expect(contents_path).to exist
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "given the force option" do
|
describe "given the force option" do
|
||||||
let(:force) { true }
|
let(:force) { true }
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user