Support casks in brew fetch.

This commit is contained in:
Markus Reiter 2020-11-19 18:12:16 +01:00
parent 12495bc804
commit 7a83f34dd1
22 changed files with 204 additions and 154 deletions

View File

@ -23,4 +23,3 @@ require "cask/staged"
require "cask/topological_hash" require "cask/topological_hash"
require "cask/url" require "cask/url"
require "cask/utils" require "cask/utils"
require "cask/verify"

View File

@ -255,20 +255,20 @@ module Cask
add_error "you should use sha256 :no_check when version is :latest" add_error "you should use sha256 :no_check when version is :latest"
end end
def check_sha256_actually_256(sha256: cask.sha256, stanza: "sha256") def check_sha256_actually_256
odebug "Verifying #{stanza} string is a legal SHA-256 digest" odebug "Verifying sha256 string is a legal SHA-256 digest"
return unless sha256.is_a?(String) return unless cask.sha256.is_a?(Checksum)
return if sha256.length == 64 && sha256[/^[0-9a-f]+$/i] return if cask.sha256.length == 64 && cask.sha256[/^[0-9a-f]+$/i]
add_error "#{stanza} string must be of 64 hexadecimal characters" add_error "sha256 string must be of 64 hexadecimal characters"
end end
def check_sha256_invalid(sha256: cask.sha256, stanza: "sha256") def check_sha256_invalid
odebug "Verifying #{stanza} is not a known invalid value" odebug "Verifying sha256 is not a known invalid value"
empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855" empty_sha256 = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
return unless sha256 == empty_sha256 return unless cask.sha256 == empty_sha256
add_error "cannot use the sha256 for an empty string in #{stanza}: #{empty_sha256}" add_error "cannot use the sha256 for an empty string: #{empty_sha256}"
end end
def check_latest_with_appcast def check_latest_with_appcast
@ -428,8 +428,7 @@ module Cask
return unless download && cask.url return unless download && cask.url
odebug "Auditing download" odebug "Auditing download"
downloaded_path = download.perform download.fetch
Verify.all(cask, downloaded_path)
rescue => e rescue => e
add_error "download not possible: #{e}" add_error "download not possible: #{e}"
end end

View File

@ -32,7 +32,6 @@ module Cask
require "cask/installer" require "cask/installer"
options = { options = {
force: args.force?,
quarantine: args.quarantine?, quarantine: args.quarantine?,
}.compact }.compact
@ -41,8 +40,9 @@ module Cask
casks.each do |cask| casks.each do |cask|
puts Installer.caveats(cask) puts Installer.caveats(cask)
ohai "Downloading external files for Cask #{cask}" ohai "Downloading external files for Cask #{cask}"
downloaded_path = Download.new(cask, **options).perform download = Download.new(cask, **options)
Verify.all(cask, downloaded_path) download.clear_cache if args.force?
downloaded_path = download.fetch
ohai "Success! Downloaded to -> #{downloaded_path}" ohai "Success! Downloaded to -> #{downloaded_path}"
end end
end end

View File

@ -4,25 +4,32 @@
require "fileutils" require "fileutils"
require "cask/cache" require "cask/cache"
require "cask/quarantine" require "cask/quarantine"
require "cask/verify"
module Cask module Cask
# A download corresponding to a {Cask}. # A download corresponding to a {Cask}.
# #
# @api private # @api private
class Download class Download
include Context
attr_reader :cask attr_reader :cask
def initialize(cask, force: false, quarantine: nil) def initialize(cask, quarantine: nil)
@cask = cask @cask = cask
@force = force
@quarantine = quarantine @quarantine = quarantine
end end
def perform def fetch(verify_download_integrity: true)
clear_cache downloaded_path = begin
fetch downloader.fetch
quarantine downloader.cached_location
rescue => e
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
error.set_backtrace e.backtrace
raise error
end
quarantine(downloaded_path)
self.verify_download_integrity(downloaded_path) if verify_download_integrity
downloaded_path downloaded_path
end end
@ -33,32 +40,44 @@ module Cask
end end
end end
def clear_cache
downloader.clear_cache
end
def cached_download
downloader.cached_location
end
def verify_download_integrity(fn)
if @cask.sha256 == :no_check
opoo "No checksum defined for Cask '#{@cask}', skipping verification."
return
end
ohai "Verifying checksum for Cask '#{@cask}'." if verbose?
expected = @cask.sha256
actual = fn.sha256
begin
fn.verify_checksum(expected)
rescue ChecksumMissingError
raise CaskSha256MissingError.new(@cask.token, expected, actual)
rescue ChecksumMismatchError
raise CaskSha256MismatchError.new(@cask.token, expected, actual, fn)
end
end
private private
attr_reader :force def quarantine(path)
attr_accessor :downloaded_path
def clear_cache
downloader.clear_cache if force
end
def fetch
downloader.fetch
@downloaded_path = downloader.cached_location
rescue => e
error = CaskError.new("Download failed on Cask '#{cask}' with message: #{e}")
error.set_backtrace e.backtrace
raise error
end
def quarantine
return if @quarantine.nil? return if @quarantine.nil?
return unless Quarantine.available? return unless Quarantine.available?
if @quarantine if @quarantine
Quarantine.cask!(cask: @cask, download_path: @downloaded_path) Quarantine.cask!(cask: @cask, download_path: path)
else else
Quarantine.release!(download_path: @downloaded_path) Quarantine.release!(download_path: path)
end end
end end
end end

View File

@ -205,11 +205,14 @@ module Cask
def sha256(arg = nil) def sha256(arg = nil)
set_unique_stanza(:sha256, arg.nil?) do set_unique_stanza(:sha256, arg.nil?) do
if !arg.is_a?(String) && arg != :no_check case arg
when :no_check
arg
when String
Checksum.new(:sha256, arg)
else
raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'") raise CaskInvalidError.new(cask, "invalid 'sha256' value: '#{arg.inspect}'")
end end
arg
end end
end end

View File

@ -8,7 +8,6 @@ require "cask/topological_hash"
require "cask/config" require "cask/config"
require "cask/download" require "cask/download"
require "cask/staged" require "cask/staged"
require "cask/verify"
require "cask/quarantine" require "cask/quarantine"
require "cgi" require "cgi"
@ -68,7 +67,6 @@ module Cask
satisfy_dependencies satisfy_dependencies
download download
verify
end end
def stage def stage
@ -156,7 +154,7 @@ module Cask
return @downloaded_path if @downloaded_path return @downloaded_path if @downloaded_path
odebug "Downloading" odebug "Downloading"
@downloaded_path = Download.new(@cask, force: false, quarantine: quarantine?).perform @downloaded_path = Download.new(@cask, quarantine: quarantine?).fetch
odebug "Downloaded to -> #{@downloaded_path}" odebug "Downloaded to -> #{@downloaded_path}"
@downloaded_path @downloaded_path
end end
@ -168,10 +166,6 @@ module Cask
raise CaskNoShasumError, @cask.token raise CaskNoShasumError, @cask.token
end end
def verify
Verify.all(@cask, @downloaded_path)
end
def primary_container def primary_container
@primary_container ||= begin @primary_container ||= begin
download download

View File

@ -1,30 +0,0 @@
# typed: false
# frozen_string_literal: true
module Cask
# Helper module for verifying a cask's checksum.
#
# @api private
module Verify
module_function
def all(cask, downloaded_path)
if cask.sha256 == :no_check
ohai "No SHA-256 checksum defined for Cask '#{cask}', skipping verification."
return
end
ohai "Verifying SHA-256 checksum for Cask '#{cask}'."
expected = cask.sha256
computed = downloaded_path.sha256
raise CaskSha256MissingError.new(cask.token, expected, computed) if expected.nil? || expected.empty?
return if expected == computed
ohai "Note: Running `brew update` may fix SHA-256 checksum errors."
raise CaskSha256MismatchError.new(cask.token, expected, computed, downloaded_path)
end
end
end

View File

@ -13,12 +13,19 @@ class Checksum
def initialize(hash_type, hexdigest) def initialize(hash_type, hexdigest)
@hash_type = hash_type @hash_type = hash_type
@hexdigest = hexdigest @hexdigest = hexdigest.downcase
end end
delegate [:empty?, :to_s] => :@hexdigest delegate [:empty?, :to_s, :length, :[]] => :@hexdigest
def ==(other) def ==(other)
hash_type == other&.hash_type && hexdigest == other.hexdigest case other
when String
to_s == other.downcase
when Checksum
hash_type == other.hash_type && hexdigest == other.hexdigest
else
false
end
end end
end end

View File

@ -113,7 +113,7 @@ module Homebrew
def build_from_source_formulae def build_from_source_formulae
if build_from_source? || build_bottle? if build_from_source? || build_bottle?
named.to_formulae.map(&:full_name) named.to_formulae_and_casks.select { |f| f.is_a?(Formula) }.map(&:full_name)
else else
[] []
end end

View File

@ -4,6 +4,7 @@
require "formula" require "formula"
require "fetch" require "fetch"
require "cli/parser" require "cli/parser"
require "cask/download"
module Homebrew module Homebrew
extend T::Sig extend T::Sig
@ -18,8 +19,8 @@ module Homebrew
usage_banner <<~EOS usage_banner <<~EOS
`fetch` [<options>] <formula> `fetch` [<options>] <formula>
Download a bottle (if available) or source packages for <formula>. Download a bottle (if available) or source packages for <formula>e
For tarballs, also print SHA-256 checksums. and binaries for <cask>s. For files, also print SHA-256 checksums.
EOS EOS
switch "--HEAD", switch "--HEAD",
description: "Fetch HEAD version instead of stable version." description: "Fetch HEAD version instead of stable version."
@ -42,29 +43,56 @@ module Homebrew
switch "--force-bottle", switch "--force-bottle",
description: "Download a bottle if it exists for the current or newest version of macOS, "\ description: "Download a bottle if it exists for the current or newest version of macOS, "\
"even if it would not be used during installation." "even if it would not be used during installation."
switch "--[no-]quarantine",
description: "Disable/enable quarantining of downloads (default: enabled).",
env: :cask_opts_quarantine
switch "--formula", "--formulae",
description: "Treat all named arguments as formulae."
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
conflicts "--devel", "--HEAD" conflicts "--devel", "--HEAD"
conflicts "--build-from-source", "--build-bottle", "--force-bottle" conflicts "--build-from-source", "--build-bottle", "--force-bottle"
min_named :formula conflicts "--cask", "--HEAD"
conflicts "--cask", "--devel"
conflicts "--cask", "--deps"
conflicts "--cask", "-s"
conflicts "--cask", "--build-bottle"
conflicts "--cask", "--force-bottle"
min_named :formula_or_cask
end end
end end
def fetch def fetch
args = fetch_args.parse args = fetch_args.parse
if args.deps? only = :formula if args.formula? && !args.cask?
bucket = [] only = :cask if args.cask? && !args.formula?
args.named.to_formulae.each do |f|
bucket << f bucket = if args.deps?
bucket.concat f.recursive_dependencies.map(&:to_formula) args.named.to_formulae_and_casks.flat_map do |formula_or_cask|
end case formula_or_cask
bucket.uniq! when Formula
f = formula_or_cask
[f, *f.recursive_dependencies.map(&:to_formula)]
else else
bucket = args.named.to_formulae formula_or_cask
end end
end
else
args.named.to_formulae_and_casks(only: only)
end.uniq
puts "Fetching: #{bucket * ", "}" if bucket.size > 1 puts "Fetching: #{bucket * ", "}" if bucket.size > 1
bucket.each do |f| bucket.each do |formula_or_cask|
case formula_or_cask
when Formula
f = formula_or_cask
f.print_tap_action verb: "Fetching" f.print_tap_action verb: "Fetching"
fetched_bottle = false fetched_bottle = false
@ -94,6 +122,19 @@ module Homebrew
end end
f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? } f.patchlist.each { |p| fetch_patch(p, args: args) if p.external? }
else
cask = formula_or_cask
options = {
force: args.force?,
quarantine: args.quarantine?,
}.compact
options[:quarantine] = true if options[:quarantine].nil?
download = Cask::Download.new(cask, **options)
fetch_cask(download, args: args)
end
end end
end end
@ -112,6 +153,13 @@ module Homebrew
opoo "Formula reports different #{e.hash_type}: #{e.expected}" opoo "Formula reports different #{e.hash_type}: #{e.expected}"
end end
def fetch_cask(cask_download, args:)
fetch_fetchable cask_download, args: args
rescue ChecksumMismatchError => e
retry if retry_fetch?(cask_download, args: args)
opoo "Cask reports different #{e.hash_type}: #{e.expected}"
end
def fetch_patch(p, args:) def fetch_patch(p, args:)
fetch_fetchable p, args: args fetch_fetchable p, args: args
rescue ChecksumMismatchError => e rescue ChecksumMismatchError => e

View File

@ -808,7 +808,6 @@ describe Cask::Audit, :cask do
let(:cask_token) { "with-binary" } let(:cask_token) { "with-binary" }
let(:cask) { Cask::CaskLoader.load(cask_token) } let(:cask) { Cask::CaskLoader.load(cask_token) }
let(:download_double) { instance_double(Cask::Download) } let(:download_double) { instance_double(Cask::Download) }
let(:verify) { class_double(Cask::Verify).as_stubbed_const }
let(:message) { "Download Failed" } let(:message) { "Download Failed" }
before do before do
@ -817,19 +816,12 @@ describe Cask::Audit, :cask do
end end
it "when download and verification succeed it does not fail" do it "when download and verification succeed it does not fail" do
expect(download_double).to receive(:perform) expect(download_double).to receive(:fetch)
expect(verify).to receive(:all)
expect(subject).to pass expect(subject).to pass
end end
it "when download fails it fails" do it "when download fails it fails" do
expect(download_double).to receive(:perform).and_raise(StandardError.new(message)) expect(download_double).to receive(:fetch).and_raise(StandardError.new(message))
expect(subject).to fail_with(/#{message}/)
end
it "when verification fails it fails" do
expect(download_double).to receive(:perform)
expect(verify).to receive(:all).and_raise(StandardError.new(message))
expect(subject).to fail_with(/#{message}/) expect(subject).to fail_with(/#{message}/)
end end
end end

View File

@ -36,7 +36,7 @@ describe Cask::Cmd::Fetch, :cask do
end end
it "prevents double fetch (without nuking existing installation)" do it "prevents double fetch (without nuking existing installation)" do
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
old_ctime = File.stat(cached_location).ctime old_ctime = File.stat(cached_location).ctime
@ -47,7 +47,7 @@ describe Cask::Cmd::Fetch, :cask do
end end
it "allows double fetch with --force" do it "allows double fetch with --force" do
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
old_ctime = File.stat(cached_location).ctime old_ctime = File.stat(cached_location).ctime
sleep(1) sleep(1)

View File

@ -11,7 +11,6 @@ describe Cask::Cmd::Install, :cask do
it "displays the installation progress" do it "displays the installation progress" do
output = Regexp.new <<~EOS output = Regexp.new <<~EOS
==> Downloading file:.*caffeine.zip ==> Downloading file:.*caffeine.zip
==> Verifying SHA-256 checksum for Cask 'local-caffeine'.
==> Installing Cask local-caffeine ==> Installing Cask local-caffeine
==> Moving App 'Caffeine.app' to '.*Caffeine.app'. ==> Moving App 'Caffeine.app' to '.*Caffeine.app'.
.*local-caffeine was successfully installed! .*local-caffeine was successfully installed!

View File

@ -14,7 +14,6 @@ describe Cask::Cmd::Reinstall, :cask do
output = Regexp.new <<~EOS output = Regexp.new <<~EOS
==> Downloading file:.*caffeine.zip ==> Downloading file:.*caffeine.zip
Already downloaded: .*--caffeine.zip Already downloaded: .*--caffeine.zip
==> Verifying SHA-256 checksum for Cask 'local-caffeine'.
==> Uninstalling Cask local-caffeine ==> Uninstalling Cask local-caffeine
==> Backing App 'Caffeine.app' up to '.*Caffeine.app'. ==> Backing App 'Caffeine.app' up to '.*Caffeine.app'.
==> Removing App '.*Caffeine.app'. ==> Removing App '.*Caffeine.app'.

View File

@ -2,26 +2,30 @@
# frozen_string_literal: true # frozen_string_literal: true
module Cask module Cask
describe Verify, :cask do describe Download, :cask do
describe "::all" do describe "#verify_download_integrity" do
subject(:verification) { described_class.all(cask, downloaded_path) } subject(:verification) { described_class.new(cask).verify_download_integrity(downloaded_path) }
let(:cask) { instance_double(Cask, token: "cask", sha256: expected_sha256) } let(:cask) { instance_double(Cask, token: "cask", sha256: expected_sha256) }
let(:cafebabe) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" } let(:cafebabe) { "cafebabecafebabecafebabecafebabecafebabecafebabecafebabecafebabe" }
let(:deadbeef) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" } let(:deadbeef) { "deadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeefdeadbeef" }
let(:computed_sha256) { cafebabe } let(:computed_sha256) { cafebabe }
let(:downloaded_path) { instance_double(Pathname, sha256: computed_sha256) } let(:downloaded_path) { Pathname.new("cask.zip") }
before do
allow(downloaded_path).to receive(:sha256).and_return(computed_sha256)
end
context "when the expected checksum is :no_check" do context "when the expected checksum is :no_check" do
let(:expected_sha256) { :no_check } let(:expected_sha256) { :no_check }
it "skips the check" do it "skips the check" do
expect { verification }.to output(/skipping verification/).to_stdout expect { verification }.to output(/skipping verification/).to_stderr
end end
end end
context "when expected and computed checksums match" do context "when expected and computed checksums match" do
let(:expected_sha256) { cafebabe } let(:expected_sha256) { Checksum.new(:sha256, cafebabe) }
it "does not raise an error" do it "does not raise an error" do
expect { verification }.not_to raise_error expect { verification }.not_to raise_error
@ -37,7 +41,7 @@ module Cask
end end
context "when the expected checksum is empty" do context "when the expected checksum is empty" do
let(:expected_sha256) { "" } let(:expected_sha256) { Checksum.new(:sha256, "") }
it "raises an error" do it "raises an error" do
expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/) expect { verification }.to raise_error(CaskSha256MissingError, /sha256 "#{computed_sha256}"/)
@ -45,7 +49,7 @@ module Cask
end end
context "when expected and computed checksums do not match" do context "when expected and computed checksums do not match" do
let(:expected_sha256) { deadbeef } let(:expected_sha256) { Checksum.new(:sha256, deadbeef) }
it "raises an error" do it "raises an error" do
expect { verification }.to raise_error CaskSha256MismatchError expect { verification }.to raise_error CaskSha256MismatchError

View File

@ -116,7 +116,6 @@ describe Cask::Installer, :cask do
}.to output( }.to output(
<<~EOS, <<~EOS,
==> Downloading file://#{HOMEBREW_LIBRARY_PATH}/test/support/fixtures/cask/caffeine.zip ==> Downloading file://#{HOMEBREW_LIBRARY_PATH}/test/support/fixtures/cask/caffeine.zip
==> Verifying SHA-256 checksum for Cask 'with-installer-manual'.
==> Installing Cask with-installer-manual ==> Installing Cask with-installer-manual
To complete the installation of Cask with-installer-manual, you must also To complete the installation of Cask with-installer-manual, you must also
run the installer at: run the installer at:

View File

@ -31,7 +31,7 @@ describe Cask::Quarantine, :cask do
it "quarantines Cask fetches" do it "quarantines Cask fetches" do
Cask::Cmd::Fetch.run("local-transmission") Cask::Cmd::Fetch.run("local-transmission")
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
expect(cached_location).to be_quarantined expect(cached_location).to be_quarantined
end end
@ -40,7 +40,7 @@ describe Cask::Quarantine, :cask do
Cask::Cmd::Audit.run("local-transmission", "--download") Cask::Cmd::Audit.run("local-transmission", "--download")
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
expect(cached_location).to be_quarantined expect(cached_location).to be_quarantined
end end
@ -142,7 +142,7 @@ describe Cask::Quarantine, :cask do
it "does not quarantine Cask fetches" do it "does not quarantine Cask fetches" do
Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine") Cask::Cmd::Fetch.run("local-transmission", "--no-quarantine")
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
expect(cached_location).not_to be_quarantined expect(cached_location).not_to be_quarantined
end end
@ -151,7 +151,7 @@ describe Cask::Quarantine, :cask do
Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine") Cask::Cmd::Audit.run("local-transmission", "--download", "--no-quarantine")
local_transmission = Cask::CaskLoader.load(cask_path("local-transmission")) local_transmission = Cask::CaskLoader.load(cask_path("local-transmission"))
cached_location = Cask::Download.new(local_transmission).perform cached_location = Cask::Download.new(local_transmission).fetch
expect(cached_location).not_to be_quarantined expect(cached_location).not_to be_quarantined
end end

View File

@ -1,6 +1,6 @@
cask "invalid-manpage-no-section" do cask "invalid-manpage-no-section" do
version "1.2.3" version "1.2.3"
sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94" sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0"
url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip" url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip"
homepage "https://brew.sh/with-generic-artifact" homepage "https://brew.sh/with-generic-artifact"

View File

@ -1,6 +1,6 @@
cask "with-autodetected-manpage-section" do cask "with-autodetected-manpage-section" do
version "1.2.3" version "1.2.3"
sha256 "67cdb8a02803ef37fdbf7e0be205863172e41a561ca446cd84f0d7ab35a99d94" sha256 "68b7e71a2ca7585b004f52652749589941e3029ff0884e8aa3b099594e0282c0"
url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip" url "file://#{TEST_FIXTURE_DIR}/cask/AppWithManpage.zip"
homepage "https://brew.sh/with-autodetected-manpage-section" homepage "https://brew.sh/with-autodetected-manpage-section"

View File

@ -1,6 +1,6 @@
cask "with-non-executable-binary" do cask "with-non-executable-binary" do
version "1.2.3" version "1.2.3"
sha256 "d5b2dfbef7ea28c25f7a77cd7fa14d013d82b626db1d82e00e25822464ba19e2" sha256 "306c6ca7407560340797866e077e053627ad409277d1b9da58106fce4cf717cb"
url "file://#{TEST_FIXTURE_DIR}/cask/naked_non_executable" url "file://#{TEST_FIXTURE_DIR}/cask/naked_non_executable"
homepage "https://brew.sh/with-binary" homepage "https://brew.sh/with-binary"

View File

@ -199,8 +199,8 @@ an issue; just ignore this.
### `fetch` [*`options`*] *`formula`* ### `fetch` [*`options`*] *`formula`*
Download a bottle (if available) or source packages for *`formula`*. Download a bottle (if available) or source packages for *`formula`*e
For tarballs, also print SHA-256 checksums. and binaries for *`cask`*s. For files, also print SHA-256 checksums.
* `--HEAD`: * `--HEAD`:
Fetch HEAD version instead of stable version. Fetch HEAD version instead of stable version.
@ -220,6 +220,12 @@ For tarballs, also print SHA-256 checksums.
Download source packages (for eventual bottling) rather than a bottle. Download source packages (for eventual bottling) rather than a bottle.
* `--force-bottle`: * `--force-bottle`:
Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation. Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation.
* `--[no-]quarantine`:
Disable/enable quarantining of downloads (default: enabled).
* `--formula`:
Treat all named arguments as formulae.
* `--cask`:
Treat all named arguments as casks.
### `gist-logs` [*`options`*] *`formula`* ### `gist-logs` [*`options`*] *`formula`*

View File

@ -254,7 +254,7 @@ List all audit methods, which can be run individually if provided as arguments\.
Enable debugging and profiling of audit methods\. Enable debugging and profiling of audit methods\.
. .
.SS "\fBfetch\fR [\fIoptions\fR] \fIformula\fR" .SS "\fBfetch\fR [\fIoptions\fR] \fIformula\fR"
Download a bottle (if available) or source packages for \fIformula\fR\. For tarballs, also print SHA\-256 checksums\. Download a bottle (if available) or source packages for \fIformula\fRe and binaries for \fIcask\fRs\. For files, also print SHA\-256 checksums\.
. .
.TP .TP
\fB\-\-HEAD\fR \fB\-\-HEAD\fR
@ -292,6 +292,18 @@ Download source packages (for eventual bottling) rather than a bottle\.
\fB\-\-force\-bottle\fR \fB\-\-force\-bottle\fR
Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation\. Download a bottle if it exists for the current or newest version of macOS, even if it would not be used during installation\.
. .
.TP
\fB\-\-[no\-]quarantine\fR
Disable/enable quarantining of downloads (default: enabled)\.
.
.TP
\fB\-\-formula\fR
Treat all named arguments as formulae\.
.
.TP
\fB\-\-cask\fR
Treat all named arguments as casks\.
.
.SS "\fBgist\-logs\fR [\fIoptions\fR] \fIformula\fR" .SS "\fBgist\-logs\fR [\fIoptions\fR] \fIformula\fR"
Upload logs for a failed build of \fIformula\fR to a new Gist\. Presents an error message if no logs are found\. Upload logs for a failed build of \fIformula\fR to a new Gist\. Presents an error message if no logs are found\.
. .