281 lines
7.5 KiB
Ruby
Raw Normal View History

require "utils/json"
2015-11-07 16:25:34 +08:00
require "descriptions"
# a {Tap} is used to extend the formulae provided by Homebrew core.
# Usually, it's synced with a remote git repository. And it's likely
# a Github repository with the name of `user/homebrew-repo`. In such
# case, `user/repo` will be used as the {#name} of this {Tap}, where
# {#user} represents Github username and {#repo} represents repository
# name without leading `homebrew-`.
2015-06-10 15:39:47 +08:00
class Tap
TAP_DIRECTORY = HOMEBREW_LIBRARY/"Taps"
CACHE = {}
def self.clear_cache
CACHE.clear
end
def self.fetch(user, repo)
cache_key = "#{user}/#{repo}".downcase
CACHE.fetch(cache_key) { |key| CACHE[key] = Tap.new(user, repo) }
end
2015-06-10 15:39:47 +08:00
extend Enumerable
# The user name of this {Tap}. Usually, it's the Github username of
# this #{Tap}'s remote repository.
2015-06-10 15:39:47 +08:00
attr_reader :user
# The repository name of this {Tap} without leading `homebrew-`.
2015-06-10 15:39:47 +08:00
attr_reader :repo
# The name of this {Tap}. It combines {#user} and {#repo} with a slash.
# {#name} is always in lowercase.
# e.g. `user/repo`
2015-06-10 15:39:47 +08:00
attr_reader :name
# The local path to this {Tap}.
# e.g. `/usr/local/Library/Taps/user/homebrew-repo`
2015-06-10 15:39:47 +08:00
attr_reader :path
def initialize(user, repo)
2015-06-10 15:39:47 +08:00
# we special case homebrew so users don't have to shift in a terminal
@user = user == "homebrew" ? "Homebrew" : user
@repo = repo
@name = "#{@user}/#{@repo}".downcase
@path = TAP_DIRECTORY/"#{@user}/homebrew-#{@repo}".downcase
end
# The remote path to this {Tap}.
# e.g. `https://github.com/user/homebrew-repo`
def remote
@remote ||= if installed?
if git?
@path.cd do
Utils.popen_read("git", "config", "--get", "remote.origin.url").chomp
end
2015-06-10 15:39:47 +08:00
end
else
raise TapUnavailableError, name
2015-06-10 15:39:47 +08:00
end
end
# True if this {Tap} is a git repository.
def git?
(@path/".git").exist?
end
2015-06-10 15:39:47 +08:00
def to_s
name
end
# True if this {Tap} is an official Homebrew tap.
2015-06-10 15:39:47 +08:00
def official?
@user == "Homebrew"
end
# True if the remote of this {Tap} is a private repository.
2015-06-10 15:39:47 +08:00
def private?
return true if custom_remote?
GitHub.private_repo?(@user, "homebrew-#{@repo}")
rescue GitHub::HTTPNotFoundError
true
rescue GitHub::Error
false
end
# True if this {Tap} has been installed.
2015-06-10 15:39:47 +08:00
def installed?
@path.directory?
2015-06-10 15:39:47 +08:00
end
2015-11-07 16:25:34 +08:00
# install this {Tap}.
#
# @param [Hash] options
# @option options [String] :clone_targe If passed, it will be used as the clone remote.
# @option options [Boolean] :full_clone If set as true, full clone will be used.
def install(options = {})
raise TapAlreadyTappedError, name if installed?
# ensure git is installed
Utils.ensure_git_installed!
ohai "Tapping #{name}"
remote = options[:clone_target] || "https://github.com/#{@user}/homebrew-#{@repo}"
args = %W[clone #{remote} #{@path}]
args << "--depth=1" unless options.fetch(:full_clone, false)
begin
safe_system "git", *args
rescue Interrupt, ErrorDuringExecution
ignore_interrupts do
sleep 0.1 # wait for git to cleanup the top directory when interrupt happens.
@path.parent.rmdir_if_possible
end
raise
end
formula_count = formula_files.size
puts "Tapped #{formula_count} formula#{plural(formula_count, "e")} (#{@path.abv})"
Descriptions.cache_formulae(formula_names)
if !options[:clone_target] && private?
puts <<-EOS.undent
It looks like you tapped a private repository. To avoid entering your
credentials each time you update, you can use git HTTP credential
caching or issue the following command:
cd #{@path}
git remote set-url origin git@github.com:#{@user}/homebrew-#{@repo}.git
EOS
end
end
# uninstall this {Tap}.
def uninstall
raise TapUnavailableError, name unless installed?
puts "Untapping #{name}... (#{@path.abv})"
unpin if pinned?
formula_count = formula_files.size
Descriptions.uncache_formulae(formula_names)
@path.rmtree
@path.dirname.rmdir_if_possible
puts "Untapped #{formula_count} formula#{plural(formula_count, "e")}"
end
# True if the {#remote} of {Tap} is customized.
2015-06-10 15:39:47 +08:00
def custom_remote?
return true unless remote
remote.casecmp("https://github.com/#{@user}/homebrew-#{@repo}") != 0
2015-06-10 15:39:47 +08:00
end
# an array of all {Formula} files of this {Tap}.
2015-06-10 15:39:47 +08:00
def formula_files
@formula_files ||= if dir = [@path/"Formula", @path/"HomebrewFormula", @path].detect(&:directory?)
dir.children.select { |p| p.extname == ".rb" }
else
[]
end
2015-06-10 15:39:47 +08:00
end
# an array of all {Formula} names of this {Tap}.
2015-06-10 15:39:47 +08:00
def formula_names
@formula_names ||= formula_files.map { |f| "#{name}/#{f.basename(".rb")}" }
2015-06-10 15:39:47 +08:00
end
2015-09-12 18:21:22 +08:00
# an array of all alias files of this {Tap}.
# @private
def alias_files
@alias_files ||= Pathname.glob("#{path}/Aliases/*").select(&:file?)
2015-09-12 18:21:22 +08:00
end
# an array of all aliases of this {Tap}.
# @private
def aliases
@aliases ||= alias_files.map { |f| "#{name}/#{f.basename}" }
2015-09-12 18:21:22 +08:00
end
# a table mapping alias to formula name
# @private
def alias_table
return @alias_table if @alias_table
@alias_table = Hash.new
alias_files.each do |alias_file|
@alias_table["#{name}/#{alias_file.basename}"] = "#{name}/#{alias_file.resolved_path.basename(".rb")}"
end
@alias_table
end
# a table mapping formula name to aliases
# @private
def alias_reverse_table
return @alias_reverse_table if @alias_reverse_table
@alias_reverse_table = Hash.new
alias_table.each do |alias_name, formula_name|
@alias_reverse_table[formula_name] ||= []
@alias_reverse_table[formula_name] << alias_name
end
@alias_reverse_table
end
# an array of all commands files of this {Tap}.
2015-06-10 15:39:47 +08:00
def command_files
@command_files ||= Pathname.glob("#{path}/cmd/brew-*").select(&:executable?)
2015-06-10 15:39:47 +08:00
end
2015-11-07 16:00:45 +08:00
# path to the pin record for this {Tap}.
# @private
2015-08-09 22:42:46 +08:00
def pinned_symlink_path
HOMEBREW_LIBRARY/"PinnedTaps/#{@name}"
end
2015-11-07 16:00:45 +08:00
# True if this {Tap} has been pinned.
2015-08-09 22:42:46 +08:00
def pinned?
return @pinned if instance_variable_defined?(:@pinned)
@pinned = pinned_symlink_path.directory?
2015-08-09 22:42:46 +08:00
end
2015-11-07 16:00:45 +08:00
# pin this {Tap}.
2015-08-09 22:42:46 +08:00
def pin
raise TapUnavailableError, name unless installed?
raise TapPinStatusError.new(name, true) if pinned?
pinned_symlink_path.make_relative_symlink(@path)
@pinned = true
2015-08-09 22:42:46 +08:00
end
2015-11-07 16:00:45 +08:00
# unpin this {Tap}.
2015-08-09 22:42:46 +08:00
def unpin
raise TapUnavailableError, name unless installed?
raise TapPinStatusError.new(name, false) unless pinned?
pinned_symlink_path.delete
pinned_symlink_path.dirname.rmdir_if_possible
@pinned = false
2015-08-09 22:42:46 +08:00
end
2015-06-10 15:39:47 +08:00
def to_hash
hash = {
2015-06-10 15:39:47 +08:00
"name" => @name,
"user" => @user,
"repo" => @repo,
"path" => @path.to_s,
"installed" => installed?,
"official" => official?,
"formula_names" => formula_names,
"formula_files" => formula_files.map(&:to_s),
2015-08-09 22:42:46 +08:00
"command_files" => command_files.map(&:to_s),
"pinned" => pinned?
2015-06-10 15:39:47 +08:00
}
if installed?
hash["remote"] = remote
hash["custom_remote"] = custom_remote?
end
hash
2015-06-10 15:39:47 +08:00
end
# Hash with tap formula renames
def formula_renames
@formula_renames ||= if (rename_file = path/"formula_renames.json").file?
Utils::JSON.load(rename_file.read)
else
{}
end
end
2015-06-10 15:39:47 +08:00
def self.each
return unless TAP_DIRECTORY.directory?
TAP_DIRECTORY.subdirs.each do |user|
user.subdirs.each do |repo|
yield fetch(user.basename.to_s, repo.basename.to_s.sub("homebrew-", ""))
2015-06-10 15:39:47 +08:00
end
end
end
# an array of all installed {Tap} names.
2015-06-10 15:39:47 +08:00
def self.names
map(&:name)
end
end