diff --git a/Library/Homebrew/bundle/cask.rb b/Library/Homebrew/bundle/cask.rb index 6be2021e822aa..cb2c978503488 100644 --- a/Library/Homebrew/bundle/cask.rb +++ b/Library/Homebrew/bundle/cask.rb @@ -27,26 +27,6 @@ def reset! @outdated_casks = T.let(nil, T.nilable(T::Array[String])) end - private - - sig { params(no_upgrade: T::Boolean, name: String, options: Homebrew::Bundle::EntryOptions).returns(T::Boolean) } - def upgrading?(no_upgrade, name, options) - return false if no_upgrade - return true if cask_upgradable?(name) - return false unless options[:greedy] - - cask_is_outdated_using_greedy?(name) - end - - sig { params(name: String, options: Homebrew::Bundle::EntryOptions, verbose: T::Boolean).returns(T::Boolean) } - def postinstall_change_state!(name:, options:, verbose:) - postinstall = T.cast(options.fetch(:postinstall, nil), T.nilable(String)) - return true if postinstall.blank? - - puts "Running postinstall for #{name}: #{postinstall}" if verbose - Kernel.system(postinstall) || false - end - sig { returns(T::Array[::Cask::Cask]) } def casks return [] unless Bundle.cask_installed? @@ -55,20 +35,6 @@ def casks @casks ||= T.let(::Cask::Caskroom.casks, T.nilable(T::Array[::Cask::Cask])) end - sig { params(cask_config: ::Cask::Config).returns(String) } - def explicit_s(cask_config) - cask_config.explicit.map do |key, value| - # inverse of #env - converts :languages config key back to --language flag - if key == :languages - key = "language" - value = Array(cask_config.explicit.fetch(:languages, [])).join(",") - end - "#{key}: \"#{value.to_s.sub(/^#{Dir.home}/, "~")}\"" - end.join(", ") - end - - public - # Override makes `name` a required argument unlike the parent's default-argument signature. # rubocop:disable Sorbet/AllowIncompatibleOverride sig { @@ -270,6 +236,38 @@ def formula_dependencies(cask_list) cask.depends_on[:formula] end.compact end + + private + + sig { params(no_upgrade: T::Boolean, name: String, options: Homebrew::Bundle::EntryOptions).returns(T::Boolean) } + def upgrading?(no_upgrade, name, options) + return false if no_upgrade + return true if cask_upgradable?(name) + return false unless options[:greedy] + + cask_is_outdated_using_greedy?(name) + end + + sig { params(name: String, options: Homebrew::Bundle::EntryOptions, verbose: T::Boolean).returns(T::Boolean) } + def postinstall_change_state!(name:, options:, verbose:) + postinstall = T.cast(options.fetch(:postinstall, nil), T.nilable(String)) + return true if postinstall.blank? + + puts "Running postinstall for #{name}: #{postinstall}" if verbose + Kernel.system(postinstall) || false + end + + sig { params(cask_config: ::Cask::Config).returns(String) } + def explicit_s(cask_config) + cask_config.explicit.map do |key, value| + # inverse of #env - converts :languages config key back to --language flag + if key == :languages + key = "language" + value = Array(cask_config.explicit.fetch(:languages, [])).join(",") + end + "#{key}: \"#{value.to_s.sub(/^#{Dir.home}/, "~")}\"" + end.join(", ") + end end sig { override.params(cask: Object, no_upgrade: T::Boolean).returns(T::Boolean) } diff --git a/Library/Homebrew/bundle/dumper.rb b/Library/Homebrew/bundle/dumper.rb index 7ba5c664681e2..a7add27810e36 100644 --- a/Library/Homebrew/bundle/dumper.rb +++ b/Library/Homebrew/bundle/dumper.rb @@ -30,11 +30,25 @@ def self.build_brewfile(describe:, no_restart:, formulae:, taps:, casks:, extens selected_package_types[:tap] = taps selected_package_types[:brew] = formulae selected_package_types[:cask] = casks + dumped_formulae = if formulae + Homebrew::Bundle::Brew.formulae.filter_map { |f| f[:full_name] if f[:installed_on_request?] } + else + [] + end + dumped_casks = if casks + Homebrew::Bundle::Cask.casks.map(&:full_name) + else + [] + end content = [] Homebrew::Bundle.dump_package_types.select(&:dump_supported?).each do |package_type| next unless selected_package_types.fetch(package_type.type, false) - content << package_type.dump_output(describe:, no_restart:) + content << if package_type == Homebrew::Bundle::Tap + Homebrew::Bundle::Tap.dump(dumped_formulae:, dumped_casks:) + else + package_type.dump_output(describe:, no_restart:) + end end "#{content.reject(&:empty?).join("\n")}\n" end diff --git a/Library/Homebrew/bundle/installer.rb b/Library/Homebrew/bundle/installer.rb index b10dce1256959..64ec445053f26 100644 --- a/Library/Homebrew/bundle/installer.rb +++ b/Library/Homebrew/bundle/installer.rb @@ -4,6 +4,8 @@ require "bundle/dsl" require "bundle/package_types" require "bundle/skipper" +require "bundle/trust" +require "trust" require "utils/output" module Homebrew @@ -53,7 +55,9 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade: success = 0 failure = 0 - installable_entries = entries.filter_map do |entry| + installable_entries = T.let([], T::Array[InstallableEntry]) + installable_brewfile_entries = T.let([], T::Array[Dsl::Entry]) + entries.each do |entry| next if Homebrew::Bundle::Skipper.skip? entry name = entry.name @@ -62,10 +66,17 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade: cls = Homebrew::Bundle.installable(type) next if cls.nil? || !cls.install_supported? - InstallableEntry.new(name:, options:, verb: cls.install_verb(name, options), cls:) + installable_brewfile_entries << entry + installable_entries << InstallableEntry.new(name:, options:, verb: cls.install_verb(name, options), cls:) end - apply_trust!(installable_entries) + # Apply `trusted: true` Brewfile options before anything fetches or + # loads the entries: the fetch phase and upgrade checks load formulae + # and casks, which triggers the tap trust check before the per-entry + # install step could grant trust. + Homebrew::Bundle::Trust.entries(installable_brewfile_entries).each do |type, name| + Homebrew::Trust.trust!(type, name) + end if (fetchable_names = fetchable_formulae_and_casks(installable_entries, no_upgrade:).presence) fetchable_names_joined = fetchable_names.join(", ") @@ -111,39 +122,6 @@ def self.install!(entries, global: false, file: nil, no_lock: false, no_upgrade: true end - # Apply `trusted: true` Brewfile options before anything fetches or - # loads the entries: the fetch phase and upgrade checks load formulae - # and casks, which triggers the tap trust check before the per-entry - # install step could grant trust. - sig { params(entries: T::Array[InstallableEntry]).void } - def self.apply_trust!(entries) - entries.each do |entry| - next unless entry.options[:trusted] - - require "trust" - - if entry.cls == Tap - clone_target = entry.options[:clone_target].presence - reference = if clone_target - require "tap" - ::Tap.remote_to_reference(clone_target.to_s) || clone_target.to_s - else - entry.name - end - Homebrew::Trust.trust!(:tap, reference) - elsif ::Utils.full_name?(entry.full_name) - # Only fully-qualified names map to a tap, so unqualified names - # cannot be meaningfully trusted. - if entry.cls == Brew - Homebrew::Trust.trust!(:formula, entry.full_name) - elsif entry.cls == Cask - Homebrew::Trust.trust!(:cask, entry.full_name) - end - end - end - end - private_class_method :apply_trust! - sig { params( entries: T::Array[InstallableEntry], diff --git a/Library/Homebrew/bundle/subcommand/cleanup.rb b/Library/Homebrew/bundle/subcommand/cleanup.rb index 81c234b104b32..d06c032e37cc5 100644 --- a/Library/Homebrew/bundle/subcommand/cleanup.rb +++ b/Library/Homebrew/bundle/subcommand/cleanup.rb @@ -9,6 +9,8 @@ require "utils" require "bundle/dsl" require "bundle/extensions" +require "bundle/trust" +require "trust" require "ask" module Homebrew module Cmd @@ -145,6 +147,11 @@ def self.cleanup(global: false, file: nil, force: false, zap: false, dsl: nil, [extension, extension.cleanup_items(@dsl.entries)] end if force + dsl = @dsl + raise ArgumentError, "dsl is unset!" unless dsl + + Homebrew::Trust.replace!(Homebrew::Bundle::Trust.entries(dsl.entries)) + if casks.any? args = if zap ["--zap"] @@ -156,11 +163,9 @@ def self.cleanup(global: false, file: nil, force: false, zap: false, dsl: nil, end if formulae.any? - raise ArgumentError, "dsl is unset!" unless @dsl - # Mark Brewfile formulae as installed_on_request to prevent autoremove # from removing them when their dependents are uninstalled - Homebrew::Bundle.mark_as_installed_on_request!(@dsl.entries) + Homebrew::Bundle.mark_as_installed_on_request!(dsl.entries) Kernel.system HOMEBREW_BREW_FILE, "uninstall", "--formula", "--force", *formulae puts "Uninstalled #{formulae.size} formula#{"e" if formulae.size != 1}" diff --git a/Library/Homebrew/bundle/tap.rb b/Library/Homebrew/bundle/tap.rb index e27696a192eb1..bac980679f8c6 100644 --- a/Library/Homebrew/bundle/tap.rb +++ b/Library/Homebrew/bundle/tap.rb @@ -85,8 +85,8 @@ def install_verb(_name = "", _options = {}) "Tapping" end - sig { override.returns(String) } - def dump + sig { override.params(dumped_formulae: T::Array[String], dumped_casks: T::Array[String]).returns(String) } + def dump(dumped_formulae: [], dumped_casks: []) taps.map do |tap| remote = if (tap_remote = tap.remote) && tap_remote != tap.default_remote if (api_token = ENV.fetch("HOMEBREW_GITHUB_API_TOKEN", false).presence) @@ -97,7 +97,37 @@ def dump ", \"#{tap_remote}\"" end tapline = "tap \"#{tap.name}\"#{remote}" - tapline += ", trusted: true" if Homebrew::Trust.explicitly_trusted_tap?(tap) + trusted = if Homebrew::Trust.explicitly_trusted_tap?(tap) + true + else + tap_trust = T.let({}, T::Hash[Symbol, T::Array[String]]) + { + formula: [:formulae, dumped_formulae], + cask: [:casks, dumped_casks], + command: [:commands, []], + }.each do |type, values| + key, dumped_items = values + trusted_items = Homebrew::Trust.trusted_entries(type).filter_map do |entry| + reference, _, item = entry.rpartition("/") + next if reference.blank? || item.blank? + next if reference != tap.name && !tap.matches_reference?(reference) + next if dumped_items.include?("#{tap.name}/#{item}") + + item + end.sort.uniq + tap_trust[key] = trusted_items if trusted_items.present? + end + tap_trust.presence + end + + if trusted == true + tapline += ", trusted: true" + elsif trusted.present? + trusted_options = trusted.map do |key, values| + "#{key}: [#{values.map(&:inspect).join(", ")}]" + end.join(", ") + tapline += ", trusted: { #{trusted_options} }" + end tapline end.sort.uniq.join("\n") end diff --git a/Library/Homebrew/bundle/trust.rb b/Library/Homebrew/bundle/trust.rb new file mode 100644 index 0000000000000..642d3cbbbcbb5 --- /dev/null +++ b/Library/Homebrew/bundle/trust.rb @@ -0,0 +1,70 @@ +# typed: strict +# frozen_string_literal: true + +require "bundle/dsl" +require "utils" + +module Homebrew + module Bundle + # Converts Brewfile `trusted` options into trust-store entries. + module Trust + TRUSTED_ITEM_KEYS = T.let({ + formula: [:formula, :formulae], + cask: [:cask, :casks], + command: [:command, :commands], + }.freeze, T::Hash[Symbol, T::Array[Symbol]]) + private_constant :TRUSTED_ITEM_KEYS + + sig { params(entries: T::Array[Homebrew::Bundle::Dsl::Entry]).returns(T::Array[[Symbol, String]]) } + def self.entries(entries) + entries.flat_map do |entry| + trusted = entry.options[:trusted] + full_name = T.cast(entry.options.fetch(:full_name, entry.name), String) + + entry_type = entry.type + + case entry_type + when :tap + next [] if trusted.blank? + + clone_target = entry.options[:clone_target].presence + tap_reference = if clone_target + require "tap" + ::Tap.remote_to_reference(clone_target.to_s) || clone_target.to_s + else + entry.name + end + next [[:tap, tap_reference]] if trusted == true + next [] unless trusted.is_a?(Hash) + + unsupported_keys = trusted.keys - TRUSTED_ITEM_KEYS.values.flatten + raise UsageError, "Unsupported trusted keys: #{unsupported_keys.join(", ")}" if unsupported_keys.present? + + TRUSTED_ITEM_KEYS.flat_map do |type, keys| + keys.flat_map do |key| + Array(trusted[key]).filter_map do |item| + item_name = case item + when String, Symbol, Integer + Utils.name_from_full_name(item.to_s) + end + next if item_name.blank? + + [type, "#{tap_reference}/#{item_name}"] + end + end + end + when :brew, :cask + # Only fully-qualified names map to a tap, so unqualified names + # cannot be meaningfully trusted. + next [] if trusted != true || !Utils.full_name?(full_name) + + type = (entry_type == :brew) ? :formula : :cask + [[type, full_name]] + else + [] + end + end.uniq + end + end + end +end diff --git a/Library/Homebrew/test/bundle/dumper_spec.rb b/Library/Homebrew/test/bundle/dumper_spec.rb index eb7e2efd5568b..611dc29e87fdd 100644 --- a/Library/Homebrew/test/bundle/dumper_spec.rb +++ b/Library/Homebrew/test/bundle/dumper_spec.rb @@ -54,6 +54,43 @@ )).to eql("cask \"google-chrome\"\ncask \"java\"\ncask \"homebrew/cask-versions/iterm2-beta\"\n") end + it "dumps tap trust entries not represented by dumped formulae" do + tap = instance_double(Tap, name: "thirdparty/tap", custom_remote?: false, remote: nil) + allow(tap).to receive(:matches_reference?) { |reference| reference == "thirdparty/tap" } + allow(Tap).to receive(:select).and_return([tap]) + allow(Homebrew::Bundle::Brew).to receive(:formulae).and_return([ + { + args: [], + full_name: "thirdparty/tap/requested", + installed_on_request?: true, + link?: nil, + }, + { + args: [], + full_name: "thirdparty/tap/dependency", + installed_on_request?: false, + link?: nil, + }, + ]) + allow(Homebrew::Bundle::Brew::Services).to receive(:started?).and_return(false) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:tap).and_return([]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:formula) + .and_return(%w[ + thirdparty/tap/dependency + thirdparty/tap/requested + ]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:cask).and_return([]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:command).and_return([]) + + expect(dumper.build_brewfile( + describe: false, no_restart: false, formulae: true, taps: true, casks: false, + extension_types: {} + )).to eql(<<~BREWFILE) + tap "thirdparty/tap", trusted: { formulae: ["dependency"] } + brew "thirdparty/tap/requested", trusted: true + BREWFILE + end + it "determines the brewfile correctly" do expect(dumper.brewfile_path).to eql(Pathname.new(Dir.pwd).join("Brewfile")) end diff --git a/Library/Homebrew/test/bundle/installer_spec.rb b/Library/Homebrew/test/bundle/installer_spec.rb index fc157da1aa468..45ee235a9b71c 100644 --- a/Library/Homebrew/test/bundle/installer_spec.rb +++ b/Library/Homebrew/test/bundle/installer_spec.rb @@ -127,6 +127,38 @@ described_class.install!([tap_entry], quiet: true) end + it "trusts tap `trusted` hash entries" do + tap_entry = Homebrew::Bundle::Dsl::Entry.new( + :tap, "thirdparty/tap", + { + trusted: { + formula: "foo", + formulae: ["bar"], + cask: "baz", + casks: ["qux"], + command: "hello", + commands: ["world"], + }, + } + ) + + expect(Homebrew::Trust).to receive(:trust!).with(:formula, "thirdparty/tap/foo").and_return(true) + expect(Homebrew::Trust).to receive(:trust!).with(:formula, "thirdparty/tap/bar").and_return(true) + expect(Homebrew::Trust).to receive(:trust!).with(:cask, "thirdparty/tap/baz").and_return(true) + expect(Homebrew::Trust).to receive(:trust!).with(:cask, "thirdparty/tap/qux").and_return(true) + expect(Homebrew::Trust).to receive(:trust!).with(:command, "thirdparty/tap/hello").and_return(true) + expect(Homebrew::Trust).to receive(:trust!).with(:command, "thirdparty/tap/world").and_return(true) + + described_class.install!([tap_entry], quiet: true) + end + + it "rejects unsupported tap `trusted` hash keys" do + tap_entry = Homebrew::Bundle::Dsl::Entry.new(:tap, "thirdparty/tap", { trusted: { tap: "foo" } }) + + expect { described_class.install!([tap_entry], quiet: true) } + .to raise_error(UsageError, /Unsupported trusted keys: tap/) + end + it "does not trust unqualified `trusted: true` names" do trusted_formula_entry = Homebrew::Bundle::Dsl::Entry.new(:brew, "mysql", { trusted: true }) diff --git a/Library/Homebrew/test/bundle/tap_spec.rb b/Library/Homebrew/test/bundle/tap_spec.rb index 760e643feb4f4..d15fff104e660 100644 --- a/Library/Homebrew/test/bundle/tap_spec.rb +++ b/Library/Homebrew/test/bundle/tap_spec.rb @@ -64,6 +64,7 @@ end it "dumps trusted taps with trusted true" do + allow(Homebrew::Trust).to receive(:trusted_entries).and_return([]) allow(Homebrew::Trust).to receive(:trusted_entries).with(:tap) .and_return(["https://bitbucket.org/bitbucket/bar.git"]) @@ -85,6 +86,21 @@ "tap \"alternatert/tap\", \"git@github.com:AlternateRT/homebrew-tap.git\"", ) end + + it "dumps partially trusted tap entries with trusted hash values" do + allow(Homebrew::Trust).to receive(:trusted_entries).with(:tap).and_return([]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:formula) + .and_return(["https://bitbucket.org/bitbucket/bar.git/foo"]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:cask) + .and_return(["https://bitbucket.org/bitbucket/bar.git/baz"]) + allow(Homebrew::Trust).to receive(:trusted_entries).with(:command) + .and_return(["https://bitbucket.org/bitbucket/bar.git/qux"]) + + expect(dumper.dump).to include( + "tap \"bitbucket/bar\", \"https://bitbucket.org/bitbucket/bar.git\", " \ + "trusted: { formulae: [\"foo\"], casks: [\"baz\"], commands: [\"qux\"] }", + ) + end end end diff --git a/Library/Homebrew/test/cmd/bundle/cleanup_subcommand_spec.rb b/Library/Homebrew/test/cmd/bundle/cleanup_subcommand_spec.rb index 3d09e62923abc..7f70e6a4aa3c8 100644 --- a/Library/Homebrew/test/cmd/bundle/cleanup_subcommand_spec.rb +++ b/Library/Homebrew/test/cmd/bundle/cleanup_subcommand_spec.rb @@ -3,6 +3,7 @@ require "bundle" require "bundle/subcommand/cleanup" +require "trust" require "utils" RSpec.describe Homebrew::Cmd::Bundle::CleanupSubcommand do @@ -306,6 +307,44 @@ end end + context "when there are trusted Brewfile entries", :trust_store do + let(:dsl) do + Homebrew::Bundle::Dsl.new(StringIO.new(<<~RUBY)) + tap "trusted/tap", trusted: true + tap "thirdparty/tap", trusted: { + formula: "foo", + casks: ["bar"], + command: "baz", + } + brew "thirdparty/tap/qux", trusted: true + cask "thirdparty/tap/quux", trusted: true + RUBY + end + + before do + described_class.reset! + allow(described_class).to receive_messages(casks_to_uninstall: [], + formulae_to_uninstall: [], taps_to_untap: []) + allow(Homebrew::Bundle::VscodeExtension).to receive(:cleanup_items).and_return([]) + allow(Homebrew::Bundle::Flatpak).to receive(:cleanup_items).and_return([]) + allow(described_class).to receive(:system_output_no_stderr).and_return("") + + Homebrew::Trust.trust!(:tap, "old/tap") + Homebrew::Trust.trust!(:formula, "old/tap/foo") + Homebrew::Trust.trust!(:cask, "old/tap/bar") + Homebrew::Trust.trust!(:command, "old/tap/baz") + end + + it "resets the trust store to the Brewfile entries on forced cleanup" do + described_class.cleanup(force: true, dsl:) + + expect(Homebrew::Trust.trusted_entries(:tap)).to eq(["trusted/tap"]) + expect(Homebrew::Trust.trusted_entries(:formula)).to eq(%w[thirdparty/tap/foo thirdparty/tap/qux]) + expect(Homebrew::Trust.trusted_entries(:cask)).to eq(%w[thirdparty/tap/bar thirdparty/tap/quux]) + expect(Homebrew::Trust.trusted_entries(:command)).to eq(["thirdparty/tap/baz"]) + end + end + context "when there are casks to uninstall" do before do described_class.reset! diff --git a/Library/Homebrew/trust.rb b/Library/Homebrew/trust.rb index e06902e9e0e2b..2e28508d0facf 100644 --- a/Library/Homebrew/trust.rb +++ b/Library/Homebrew/trust.rb @@ -142,6 +142,23 @@ def self.clear!(type) end end + sig { params(entries: T::Array[[Symbol, String]]).void } + def self.replace!(entries) + store = T.let({}, T::Hash[String, T::Array[String]]) + entries.each do |type, name| + key = setting_key(type) + store[key] ||= [] + store.fetch(key) << normalise_name(name) + end + store.keys.each do |key| + store[key] = store.fetch(key).uniq.sort + end + + with_trust_store_lock do + write_trust_store(store) + end + end + sig { params(type: Symbol, name: String).returns(T::Boolean) } def self.trusted?(type, name) name = normalise_name(name) diff --git a/docs/Brew-Bundle-and-Brewfile.md b/docs/Brew-Bundle-and-Brewfile.md index 304846c5fb585..84048fa733cd5 100644 --- a/docs/Brew-Bundle-and-Brewfile.md +++ b/docs/Brew-Bundle-and-Brewfile.md @@ -283,7 +283,7 @@ Rather than all `Brewfile` functionality one-by-one: here's a commented example ```ruby # Run `brew tap` with a custom URL -tap "user/tap-repo", "https://user@bitbucket.org/user/homebrew-tap-repo.git" +tap "user/tap-repository", "https://user@bitbucket.org/user/homebrew-tap-repository.git" # Set arguments passed to all `brew install --cask` commands for `cask "..."` # In this example, pass `--appdir=~/Applications` and `--require_sha` @@ -306,9 +306,16 @@ brew "ruby", version_file: ".ruby-version" # Trusts the tap, formula or cask so Homebrew loads it when tap trust is required. # This works on `tap`, `brew` and `cask` entries. -tap "user/repo", trusted: true -brew "user/repo/formula", trusted: true -cask "user/repo/cask", trusted: true +tap "user/repository", trusted: true +brew "user/repository/formula", trusted: true +cask "user/repository/cask", trusted: true + +# Trusts only named formulae, casks or commands from a tap. +tap "user/repository", trusted: { + formula: "formula", + casks: ["cask"], + commands: ["command"], +} # Runs `brew install gnupg` or `brew install glibc` only on the specified OS. # Note: `brew bundle list` will not output `gnupg` on Linux or `glibc` on macOS` in this case: @@ -353,14 +360,38 @@ brew "ruby", version_file: ".ruby-version" entry before installing it, so it loads even when tap trust is required. ```ruby -tap "user/repo", trusted: true -brew "user/repo/formula", trusted: true -cask "user/repo/cask", trusted: true +tap "user/repository", trusted: true +brew "user/repository/formula", trusted: true +cask "user/repository/cask", trusted: true ``` -As with `brew trust`, prefer trusting the specific formula or cask you need over -trusting a whole tap. `brew bundle dump` writes `trusted: true` for entries you -have already trusted. +Tap entries can also trust specific formulae, casks and commands from that tap +without trusting the whole tap: + +```ruby +tap "user/repository", trusted: { + formula: "formula", + formulae: ["another-formula"], + cask: "cask", + casks: ["another-cask"], + command: "command", + commands: ["another-command"], +} +``` + +The singular and plural keys can both be used. The values are item names inside +that tap, so `formula: "foo"` on `tap "user/repository"` trusts +`user/repository/foo` as a formula. + +As with `brew trust`, prefer trusting the specific formula, cask or command you +need over trusting a whole tap. `brew bundle dump` writes `trusted: true` for +trusted `brew`, `cask` and whole-tap entries. It writes tap-level trust hashes +for trusted formulae, casks and commands from a tap that are not otherwise +present in the dumped `Brewfile`. + +When `brew bundle cleanup --force` runs, it resets Homebrew's tap trust file to +the trust values declared by the `Brewfile` and removes trust entries that are +not declared there. ## Versions diff --git a/docs/Tap-Trust.md b/docs/Tap-Trust.md index 9ebdf84f5bd94..120c1893b1f29 100644 --- a/docs/Tap-Trust.md +++ b/docs/Tap-Trust.md @@ -40,27 +40,27 @@ and external commands from that tap being loaded by Homebrew. Installing a fully-qualified formula or cask name trusts only that item: ```sh -brew install user/repo/formula -brew install --cask user/repo/cask +brew install user/repository/formula +brew install --cask user/repository/cask ``` To install by short name from a tapped repository, trust the specific item first: ```sh -brew tap user/repo -brew trust --formula user/repo/formula +brew tap user/repository +brew trust --formula user/repository/formula brew install formula ``` -Use `brew trust --cask user/repo/cask` for casks and -`brew trust --command user/repo/command` for external commands. +Use `brew trust --cask user/repository/cask` for casks and +`brew trust --command user/repository/command` for external commands. You can also trust the whole tap: ```sh -brew tap user/repo -brew trust user/repo +brew tap user/repository +brew trust user/repository brew install formula ``` @@ -72,19 +72,8 @@ you need. ## Trusting in a `Brewfile` -You can declare trust in a [`Brewfile`](Brew-Bundle-and-Brewfile.md) with -`trusted: true` on `tap`, `brew` and `cask` entries: - -```ruby -tap "user/repo", trusted: true -brew "user/repo/formula", trusted: true -cask "user/repo/cask", trusted: true -``` - -`brew bundle` trusts each entry before installing it, so non-official taps, -formulae and casks load even when tap trust is required. As on the command line, -prefer trusting the specific formula or cask you need over the whole tap. -`brew bundle dump` writes `trusted: true` for entries you have already trusted. +For `Brewfile` trust syntax and `brew bundle` dump and cleanup behaviour, see +the [`trusted` section of the Homebrew Bundle documentation](Brew-Bundle-and-Brewfile.md#trusted). ## Managing trust @@ -103,8 +92,8 @@ brew untrust Stop trusting a tap or item: ```sh -brew untrust user/repo -brew untrust --formula user/repo/formula +brew untrust user/repository +brew untrust --formula user/repository/formula ``` A trusted tap behaves as it did before tap trust checks were introduced. An