Support loading formulae/casks from subdirectories

Previously, we required all formulae and casks to be in a specific
formula or cask directory but did not check any subdirectories.

This commit allows using subdirectories for official taps, the only
ones likely to be big enough to warrant sharding in this way and to
avoid potentially breaking backwards compatibility for existing taps.

This was inspired by the most recent issues with homebrew-cask.
This commit is contained in:
Mike McQuaid 2023-02-24 10:57:41 +00:00
parent d5ae257efe
commit f280ce069b
No known key found for this signature in database
GPG Key ID: 3338A31AFDB1D829
9 changed files with 184 additions and 66 deletions

View File

@ -167,7 +167,9 @@ module Cask
def initialize(tapped_name) def initialize(tapped_name)
user, repo, token = tapped_name.split("/", 3) user, repo, token = tapped_name.split("/", 3)
super Tap.fetch(user, repo).cask_dir/"#{token}.rb" tap = Tap.fetch(user, repo)
cask = CaskLoader.find_cask_in_tap(token, tap)
super cask
end end
def load(config:) def load(config:)
@ -421,8 +423,15 @@ module Cask
end end
def self.tap_paths(token) def self.tap_paths(token)
Tap.map { |t| t.cask_dir/"#{token.to_s.downcase}.rb" } Tap.map do |tap|
.select(&:exist?) find_cask_in_tap(token.to_s.downcase, tap)
end.select(&:exist?)
end
def self.find_cask_in_tap(token, tap)
filename = "#{token}.rb"
Tap.cask_files_by_name(tap).fetch(filename, tap.cask_dir/filename)
end end
end end
end end

View File

@ -50,17 +50,14 @@ module Homebrew
tap_count = 0 tap_count = 0
formula_count = 0 formula_count = 0
command_count = 0 command_count = 0
pinned_count = 0
private_count = 0 private_count = 0
Tap.each do |tap| Tap.each do |tap|
tap_count += 1 tap_count += 1
formula_count += tap.formula_files.size formula_count += tap.formula_files.size
command_count += tap.command_files.size command_count += tap.command_files.size
pinned_count += 1 if tap.pinned?
private_count += 1 if tap.private? private_count += 1 if tap.private?
end end
info = "#{tap_count} #{"tap".pluralize(tap_count)}" info = "#{tap_count} #{"tap".pluralize(tap_count)}"
info += ", #{pinned_count} pinned"
info += ", #{private_count} private" info += ", #{private_count} private"
info += ", #{formula_count} #{"formula".pluralize(formula_count)}" info += ", #{formula_count} #{"formula".pluralize(formula_count)}"
info += ", #{command_count} #{"command".pluralize(command_count)}" info += ", #{command_count} #{"command".pluralize(command_count)}"

View File

@ -516,15 +516,14 @@ module Formulary
def formula_name_path(tapped_name, warn: true) def formula_name_path(tapped_name, warn: true)
user, repo, name = tapped_name.split("/", 3).map(&:downcase) user, repo, name = tapped_name.split("/", 3).map(&:downcase)
@tap = Tap.fetch user, repo @tap = Tap.fetch user, repo
formula_dir = @tap.formula_dir || @tap.path path = find_formula_from_name(name)
path = formula_dir/"#{name}.rb"
unless path.file? unless path.file?
if (possible_alias = @tap.alias_dir/name).file? if (possible_alias = @tap.alias_dir/name).file?
path = possible_alias.resolved_path path = possible_alias.resolved_path
name = path.basename(".rb").to_s name = path.basename(".rb").to_s
elsif (new_name = @tap.formula_renames[name]) && elsif (new_name = @tap.formula_renames[name]) &&
(new_path = formula_dir/"#{new_name}.rb").file? (new_path = find_formula_from_name(new_name)).file?
old_name = name old_name = name
path = new_path path = new_path
name = new_name name = new_name
@ -562,6 +561,12 @@ module Formulary
e.issues_url = tap.issues_url || tap.to_s e.issues_url = tap.issues_url || tap.to_s
raise raise
end end
private
def find_formula_from_name(name)
Formulary.find_formula_in_tap(name, @tap)
end
end end
# Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula. # Pseudo-loader which will raise a {FormulaUnavailableError} when trying to load the corresponding formula.
@ -800,22 +805,28 @@ module Formulary
end end
def self.core_path(name) def self.core_path(name)
CoreTap.instance.formula_dir/"#{name.to_s.downcase}.rb" find_formula_in_tap(name.to_s.downcase, CoreTap.instance)
end end
def self.core_alias_path(name) def self.core_alias_path(name)
CoreTap.instance.alias_dir/name.to_s.downcase CoreTap.instance.alias_dir/name.to_s.downcase
end end
def self.tap_paths(name, taps = Dir[HOMEBREW_LIBRARY/"Taps/*/*/"]) def self.tap_paths(name, taps = Tap)
name = name.to_s.downcase name = name.to_s.downcase
taps.map do |tap| taps.map do |tap|
Pathname.glob([ formula_path = find_formula_in_tap(name, tap)
"#{tap}Formula/#{name}.rb",
"#{tap}HomebrewFormula/#{name}.rb", alias_path = tap.alias_dir/name
"#{tap}#{name}.rb", next alias_path if !formula_path.exist? && alias_path.exist?
"#{tap}Aliases/#{name}",
]).find(&:file?) formula_path
end.compact end.select(&:file?)
end
def self.find_formula_in_tap(name, tap)
filename = "#{name}.rb"
Tap.formula_files_by_name(tap).fetch(filename, tap.formula_dir/filename)
end end
end end

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "commands" require "commands"
@ -416,7 +416,6 @@ class Tap
abv = path.abv abv = path.abv
formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ") formatted_contents = contents.presence&.to_sentence&.dup&.prepend(" ")
unpin if pinned?
CacheStoreDatabase.use(:descriptions) do |db| CacheStoreDatabase.use(:descriptions) do |db|
DescriptionCacheStore.new(db) DescriptionCacheStore.new(db)
.delete_from_formula_names!(formula_names) .delete_from_formula_names!(formula_names)
@ -451,15 +450,23 @@ class Tap
end end
# Path to the directory of all {Formula} files for this {Tap}. # Path to the directory of all {Formula} files for this {Tap}.
sig { returns(Pathname) }
def formula_dir def formula_dir
@formula_dir ||= potential_formula_dirs.find(&:directory?) || (path/"Formula") # Official formulae taps always use this directory, saves time to hardcode.
@formula_dir ||= if official?
path/"Formula"
else
potential_formula_dirs.find(&:directory?) || (path/"Formula")
end
end end
sig { returns(T::Array[Pathname]) }
def potential_formula_dirs def potential_formula_dirs
@potential_formula_dirs ||= [path/"Formula", path/"HomebrewFormula", path].freeze @potential_formula_dirs ||= [path/"Formula", path/"HomebrewFormula", path].freeze
end end
# Path to the directory of all {Cask} files for this {Tap}. # Path to the directory of all {Cask} files for this {Tap}.
sig { returns(Pathname) }
def cask_dir def cask_dir
@cask_dir ||= path/"Casks" @cask_dir ||= path/"Casks"
end end
@ -483,15 +490,35 @@ class Tap
end end
# An array of all {Formula} files of this {Tap}. # An array of all {Formula} files of this {Tap}.
sig { returns(T::Array[Pathname]) }
def formula_files def formula_files
@formula_files ||= if formula_dir.directory? @formula_files ||= if formula_dir.directory?
formula_dir.children.select(&method(:ruby_file?)) # TODO: odeprecate the non-official/old logic with a new minor release somehow?
if official?
formula_dir.find
else
formula_dir.children
end.select(&method(:ruby_file?))
else else
[] []
end end
end end
# A cached hash of {Formula} basenames to {Formula} file pathnames for a {Tap}
sig { params(tap: Tap).returns(T::Hash[String, Pathname]) }
def self.formula_files_by_name(tap)
cache_key = "formula_files_by_name_#{tap}"
cache.fetch(cache_key) do |key|
cache[key] = tap.formula_files.each_with_object({}) do |file, hash|
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[file.basename.to_s] ||= file
end
end
end
# An array of all versioned {Formula} files of this {Tap}. # An array of all versioned {Formula} files of this {Tap}.
sig { returns(T::Array[Pathname]) }
def versioned_formula_files def versioned_formula_files
@versioned_formula_files ||= formula_files.select do |file| @versioned_formula_files ||= formula_files.select do |file|
file.basename(".rb").to_s =~ /@[\d.]+$/ file.basename(".rb").to_s =~ /@[\d.]+$/
@ -499,16 +526,37 @@ class Tap
end end
# An array of all {Cask} files of this {Tap}. # An array of all {Cask} files of this {Tap}.
sig { returns(T::Array[Pathname]) }
def cask_files def cask_files
@cask_files ||= if cask_dir.directory? @cask_files ||= if cask_dir.directory?
cask_dir.children.select(&method(:ruby_file?)) # TODO: odeprecate the non-official/old logic with a new minor release somehow?
if official?
cask_dir.find
else
cask_dir.children
end.select(&method(:ruby_file?))
else else
[] []
end end
end end
# A cached hash of {Cask} basenames to {Cask} file pathnames for a {Tap}
sig { params(tap: Tap).returns(T::Hash[String, Pathname]) }
def self.cask_files_by_name(tap)
cache_key = "cask_files_by_name_#{tap}"
cache.fetch(cache_key) do |key|
cache[key] = tap.cask_files.each_with_object({}) do |file, hash|
# If there's more than one file with the same basename: intentionally
# ignore the later ones here.
hash[file.basename.to_s] ||= file
end
end
end
# returns true if the file has a Ruby extension # returns true if the file has a Ruby extension
# @private # @private
sig { params(file: Pathname).returns(T::Boolean) }
def ruby_file?(file) def ruby_file?(file)
file.extname == ".rb" file.extname == ".rb"
end end
@ -516,45 +564,56 @@ class Tap
# return true if given path would present a {Formula} file in this {Tap}. # return true if given path would present a {Formula} file in this {Tap}.
# accepts both absolute path and relative path (relative to this {Tap}'s path) # accepts both absolute path and relative path (relative to this {Tap}'s path)
# @private # @private
sig { params(file: T.any(String, Pathname)).returns(T::Boolean) }
def formula_file?(file) def formula_file?(file)
file = Pathname.new(file) unless file.is_a? Pathname file = Pathname.new(file) unless file.is_a? Pathname
file = file.expand_path(path) file = file.expand_path(path)
ruby_file?(file) && file.parent == formula_dir return false unless ruby_file?(file)
file.to_s.start_with?("#{formula_dir}/")
end end
# return true if given path would present a {Cask} file in this {Tap}. # return true if given path would present a {Cask} file in this {Tap}.
# accepts both absolute path and relative path (relative to this {Tap}'s path) # accepts both absolute path and relative path (relative to this {Tap}'s path)
# @private # @private
sig { params(file: T.any(String, Pathname)).returns(T::Boolean) }
def cask_file?(file) def cask_file?(file)
file = Pathname.new(file) unless file.is_a? Pathname file = Pathname.new(file) unless file.is_a? Pathname
file = file.expand_path(path) file = file.expand_path(path)
ruby_file?(file) && file.parent == cask_dir return false unless ruby_file?(file)
file.to_s.start_with?("#{cask_dir}/")
end end
# An array of all {Formula} names of this {Tap}. # An array of all {Formula} names of this {Tap}.
sig { returns(T::Array[String]) }
def formula_names def formula_names
@formula_names ||= formula_files.map(&method(:formula_file_to_name)) @formula_names ||= formula_files.map(&method(:formula_file_to_name))
end end
# An array of all {Cask} tokens of this {Tap}. # An array of all {Cask} tokens of this {Tap}.
sig { returns(T::Array[String]) }
def cask_tokens def cask_tokens
@cask_tokens ||= cask_files.map(&method(:formula_file_to_name)) @cask_tokens ||= cask_files.map(&method(:formula_file_to_name))
end end
# path to the directory of all alias files for this {Tap}. # path to the directory of all alias files for this {Tap}.
# @private # @private
sig { returns(Pathname) }
def alias_dir def alias_dir
@alias_dir ||= path/"Aliases" @alias_dir ||= path/"Aliases"
end end
# an array of all alias files of this {Tap}. # an array of all alias files of this {Tap}.
# @private # @private
sig { returns(T::Array[Pathname]) }
def alias_files def alias_files
@alias_files ||= Pathname.glob("#{alias_dir}/*").select(&:file?) @alias_files ||= Pathname.glob("#{alias_dir}/*").select(&:file?)
end end
# an array of all aliases of this {Tap}. # an array of all aliases of this {Tap}.
# @private # @private
sig { returns(T::Array[String]) }
def aliases def aliases
@aliases ||= alias_files.map { |f| alias_file_to_name(f) } @aliases ||= alias_files.map { |f| alias_file_to_name(f) }
end end
@ -584,11 +643,13 @@ class Tap
@alias_reverse_table @alias_reverse_table
end end
sig { returns(Pathname) }
def command_dir def command_dir
@command_dir ||= path/"cmd" @command_dir ||= path/"cmd"
end end
# An array of all commands files of this {Tap}. # An array of all commands files of this {Tap}.
sig { returns(T::Array[Pathname]) }
def command_files def command_files
@command_files ||= if command_dir.directory? @command_files ||= if command_dir.directory?
Commands.find_commands(command_dir) Commands.find_commands(command_dir)
@ -597,19 +658,7 @@ class Tap
end end
end end
# path to the pin record for this {Tap}. sig { returns(Hash) }
# @private
def pinned_symlink_path
HOMEBREW_LIBRARY/"PinnedTaps/#{name}"
end
# True if this {Tap} has been pinned.
def pinned?
return @pinned if instance_variable_defined?(:@pinned)
@pinned = pinned_symlink_path.directory?
end
def to_hash def to_hash
hash = { hash = {
"name" => name, "name" => name,
@ -635,6 +684,7 @@ class Tap
end end
# Hash with tap formula renames. # Hash with tap formula renames.
sig { returns(Hash) }
def formula_renames def formula_renames
@formula_renames ||= if (rename_file = path/HOMEBREW_TAP_FORMULA_RENAMES_FILE).file? @formula_renames ||= if (rename_file = path/HOMEBREW_TAP_FORMULA_RENAMES_FILE).file?
JSON.parse(rename_file.read) JSON.parse(rename_file.read)
@ -644,6 +694,7 @@ class Tap
end end
# Hash with tap migrations. # Hash with tap migrations.
sig { returns(Hash) }
def tap_migrations def tap_migrations
@tap_migrations ||= if (migration_file = path/HOMEBREW_TAP_MIGRATIONS_FILE).file? @tap_migrations ||= if (migration_file = path/HOMEBREW_TAP_MIGRATIONS_FILE).file?
JSON.parse(migration_file.read) JSON.parse(migration_file.read)
@ -665,18 +716,19 @@ class Tap
end end
# Hash with pypi formula mappings # Hash with pypi formula mappings
sig { returns(Hash) }
def pypi_formula_mappings def pypi_formula_mappings
@pypi_formula_mappings = read_formula_list path/HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS @pypi_formula_mappings = read_formula_list path/HOMEBREW_TAP_PYPI_FORMULA_MAPPINGS
end end
# @private # @private
sig { returns(T::Boolean) }
def should_report_analytics? def should_report_analytics?
return Homebrew::EnvConfig.install_from_api? && official? unless installed? return Homebrew::EnvConfig.install_from_api? && official? unless installed?
!private? !private?
end end
sig { params(other: T.nilable(T.any(String, Tap))).returns(T::Boolean) }
def ==(other) def ==(other)
other = Tap.fetch(other) if other.is_a?(String) other = Tap.fetch(other) if other.is_a?(String)
self.class == other.class && name == other.name self.class == other.class && name == other.name
@ -695,6 +747,7 @@ class Tap
end end
# An array of all installed {Tap} names. # An array of all installed {Tap} names.
sig { returns(T::Array[String]) }
def self.names def self.names
map(&:name).sort map(&:name).sort
end end
@ -712,11 +765,13 @@ class Tap
end end
# @private # @private
sig { params(file: Pathname).returns(String) }
def formula_file_to_name(file) def formula_file_to_name(file)
"#{name}/#{file.basename(".rb")}" "#{name}/#{file.basename(".rb")}"
end end
# @private # @private
sig { params(file: Pathname).returns(String) }
def alias_file_to_name(file) def alias_file_to_name(file)
"#{name}/#{file.basename}" "#{name}/#{file.basename}"
end end
@ -796,10 +851,12 @@ class CoreTap < Tap
super "Homebrew", "core" super "Homebrew", "core"
end end
sig { returns(CoreTap) }
def self.instance def self.instance
@instance ||= new @instance ||= new
end end
sig { void }
def self.ensure_installed! def self.ensure_installed!
return if instance.installed? return if instance.installed?
return if Homebrew::EnvConfig.install_from_api? return if Homebrew::EnvConfig.install_from_api?
@ -811,6 +868,7 @@ class CoreTap < Tap
instance.install instance.install
end end
sig { returns(String) }
def remote def remote
super if installed? || !Homebrew::EnvConfig.install_from_api? super if installed? || !Homebrew::EnvConfig.install_from_api?
@ -840,24 +898,6 @@ class CoreTap < Tap
super super
end end
# @private
sig { void }
def pin
raise "Tap#pin is not available for CoreTap"
end
# @private
sig { void }
def unpin
raise "Tap#unpin is not available for CoreTap"
end
# @private
sig { returns(T::Boolean) }
def pinned?
false
end
# @private # @private
sig { returns(T::Boolean) } sig { returns(T::Boolean) }
def core_tap? def core_tap?
@ -871,6 +911,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Pathname) }
def formula_dir def formula_dir
@formula_dir ||= begin @formula_dir ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -879,6 +920,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Pathname) }
def alias_dir def alias_dir
@alias_dir ||= begin @alias_dir ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -887,6 +929,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Hash) }
def formula_renames def formula_renames
@formula_renames ||= begin @formula_renames ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -895,6 +938,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Hash) }
def tap_migrations def tap_migrations
@tap_migrations ||= begin @tap_migrations ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -903,6 +947,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Hash) }
def audit_exceptions def audit_exceptions
@audit_exceptions ||= begin @audit_exceptions ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -911,6 +956,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Hash) }
def style_exceptions def style_exceptions
@style_exceptions ||= begin @style_exceptions ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -919,6 +965,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(Hash) }
def pypi_formula_mappings def pypi_formula_mappings
@pypi_formula_mappings ||= begin @pypi_formula_mappings ||= begin
self.class.ensure_installed! self.class.ensure_installed!
@ -927,16 +974,19 @@ class CoreTap < Tap
end end
# @private # @private
sig { params(file: Pathname).returns(String) }
def formula_file_to_name(file) def formula_file_to_name(file)
file.basename(".rb").to_s file.basename(".rb").to_s
end end
# @private # @private
sig { params(file: Pathname).returns(String) }
def alias_file_to_name(file) def alias_file_to_name(file)
file.basename.to_s file.basename.to_s
end end
# @private # @private
sig { returns(T::Array[String]) }
def aliases def aliases
return super if installed? || !Homebrew::EnvConfig.install_from_api? return super if installed? || !Homebrew::EnvConfig.install_from_api?
@ -944,6 +994,7 @@ class CoreTap < Tap
end end
# @private # @private
sig { returns(T::Array[String]) }
def formula_names def formula_names
return super if installed? || !Homebrew::EnvConfig.install_from_api? return super if installed? || !Homebrew::EnvConfig.install_from_api?
@ -953,8 +1004,11 @@ end
# Permanent configuration per {Tap} using `git-config(1)`. # Permanent configuration per {Tap} using `git-config(1)`.
class TapConfig class TapConfig
extend T::Sig
attr_reader :tap attr_reader :tap
sig { params(tap: Tap).void }
def initialize(tap) def initialize(tap)
@tap = tap @tap = tap
end end

View File

@ -0,0 +1,35 @@
# typed: false
# frozen_string_literal: true
describe Cask::CaskLoader::FromTapLoader do
let(:cask_name) { "testball" }
let(:cask_full_name) { "homebrew/cask/#{cask_name}" }
let(:cask_path) { Tap.default_cask_tap.cask_dir/"#{cask_name}.rb" }
describe "#load" do
before do
cask_path.parent.mkpath
cask_path.write <<~RUBY
cask '#{cask_name}' do
url 'https://brew.sh/'
end
RUBY
end
it "returns a Cask" do
expect(described_class.new(cask_full_name).load(config: nil)).to be_a(Cask::Cask)
end
it "raises an error if the Cask cannot be found" do
expect { described_class.new("foo/bar/baz").load(config: nil) }.to raise_error(Cask::CaskUnavailableError)
end
context "with sharded Cask directory" do
let(:cask_path) { Tap.default_cask_tap.cask_dir/cask_name[0]/"#{cask_name}.rb" }
it "returns a Cask" do
expect(described_class.new(cask_full_name).load(config: nil)).to be_a(Cask::Cask)
end
end
end
end

View File

@ -81,6 +81,21 @@ describe Formulary do
}.to raise_error(ArgumentError) }.to raise_error(ArgumentError)
end end
context "with sharded Formula directory" do
before { CoreTap.instance.clear_cache }
let(:formula_name) { "testball_sharded" }
let(:formula_path) { CoreTap.new.formula_dir/formula_name[0]/"#{formula_name}.rb" }
it "returns a Formula" do
expect(described_class.factory(formula_name)).to be_a(Formula)
end
it "returns a Formula when given a fully qualified name" do
expect(described_class.factory("homebrew/core/#{formula_name}")).to be_a(Formula)
end
end
context "when the Formula has the wrong class" do context "when the Formula has the wrong class" do
let(:formula_name) { "giraffe" } let(:formula_name) { "giraffe" }
let(:formula_content) do let(:formula_content) do
@ -171,7 +186,7 @@ describe Formulary do
context "when loading from Tap" do context "when loading from Tap" do
let(:tap) { Tap.new("homebrew", "foo") } let(:tap) { Tap.new("homebrew", "foo") }
let(:another_tap) { Tap.new("homebrew", "bar") } let(:another_tap) { Tap.new("homebrew", "bar") }
let(:formula_path) { tap.path/"#{formula_name}.rb" } let(:formula_path) { tap.path/"Formula/#{formula_name}.rb" }
it "returns a Formula when given a name" do it "returns a Formula when given a name" do
expect(described_class.factory(formula_name)).to be_a(Formula) expect(described_class.factory(formula_name)).to be_a(Formula)
@ -195,8 +210,8 @@ describe Formulary do
end end
it "raises an error if a Formula is in multiple Taps" do it "raises an error if a Formula is in multiple Taps" do
another_tap.path.mkpath (another_tap.path/"Formula").mkpath
(another_tap.path/"#{formula_name}.rb").write formula_content (another_tap.path/"Formula/#{formula_name}.rb").write formula_content
expect { expect {
described_class.factory(formula_name) described_class.factory(formula_name)

View File

@ -61,8 +61,8 @@ describe Homebrew::MissingFormula do
before do before do
tap_path = Tap::TAP_DIRECTORY/"homebrew/homebrew-foo" tap_path = Tap::TAP_DIRECTORY/"homebrew/homebrew-foo"
tap_path.mkpath (tap_path/"Formula").mkpath
(tap_path/"deleted-formula.rb").write "placeholder" (tap_path/"Formula/deleted-formula.rb").write "placeholder"
ENV.delete "GIT_AUTHOR_DATE" ENV.delete "GIT_AUTHOR_DATE"
ENV.delete "GIT_COMMITTER_DATE" ENV.delete "GIT_COMMITTER_DATE"
@ -70,7 +70,7 @@ describe Homebrew::MissingFormula do
system "git", "init" system "git", "init"
system "git", "add", "--all" system "git", "add", "--all"
system "git", "commit", "-m", "initial state" system "git", "commit", "-m", "initial state"
system "git", "rm", "deleted-formula.rb" system "git", "rm", "Formula/deleted-formula.rb"
system "git", "commit", "-m", "delete formula 'deleted-formula'" system "git", "commit", "-m", "delete formula 'deleted-formula'"
end end
end end

View File

@ -528,15 +528,12 @@ describe Tap do
expect(core_tap.name).to eq("homebrew/core") expect(core_tap.name).to eq("homebrew/core")
expect(core_tap.command_files).to eq([]) expect(core_tap.command_files).to eq([])
expect(core_tap).to be_installed expect(core_tap).to be_installed
expect(core_tap).not_to be_pinned
expect(core_tap).to be_official expect(core_tap).to be_official
expect(core_tap).to be_a_core_tap expect(core_tap).to be_a_core_tap
end end
specify "forbidden operations" do specify "forbidden operations" do
expect { core_tap.uninstall }.to raise_error(RuntimeError) expect { core_tap.uninstall }.to raise_error(RuntimeError)
expect { core_tap.pin }.to raise_error(RuntimeError)
expect { core_tap.unpin }.to raise_error(RuntimeError)
end end
specify "files" do specify "files" do