Merge pull request #10268 from Rylan12/completions-opt-out

completions: make opt-in only
This commit is contained in:
Rylan Polster 2021-01-13 16:08:30 -05:00 committed by GitHub
commit 701989968d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 517 additions and 54 deletions

View File

@ -85,16 +85,14 @@ begin
ENV["PATH"] = path
require "commands"
require "settings"
if cmd
internal_cmd = Commands.valid_internal_cmd?(cmd)
internal_cmd ||= begin
internal_dev_cmd = Commands.valid_internal_dev_cmd?(cmd)
if internal_dev_cmd && !Homebrew::EnvConfig.developer?
if (HOMEBREW_REPOSITORY/".git/config").exist?
system "git", "config", "--file=#{HOMEBREW_REPOSITORY}/.git/config",
"--replace-all", "homebrew.devcmdrun", "true"
end
Homebrew::Settings.write "devcmdrun", true
ENV["HOMEBREW_DEV_CMD_RUN"] = "1"
end
internal_dev_cmd

View File

@ -0,0 +1,52 @@
# typed: true
# frozen_string_literal: true
require "cli/parser"
require "completions"
module Homebrew
extend T::Sig
module_function
sig { returns(CLI::Parser) }
def completions_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`completions` [<subcommand>]
Control whether Homebrew automatically links external tap shell completion files.
Read more at <https://docs.brew.sh/Shell-Completion>.
`brew completions` [`state`]:
Display the current state of Homebrew's completions.
`brew completions` (`link`|`unlink`):
Link or unlink Homebrew's completions.
EOS
max_named 1
end
end
def completions
args = completions_args.parse
case args.named.first
when nil, "state"
if Completions.link_completions?
puts "Completions are linked."
else
puts "Completions are not linked."
end
when "link"
Completions.link!
puts "Completions are now linked."
when "unlink"
Completions.unlink!
puts "Completions are no longer linked."
else
raise UsageError, "unknown subcommand: #{args.named.first}"
end
end
end

View File

@ -8,6 +8,7 @@ require "descriptions"
require "cleanup"
require "description_cache_store"
require "cli/parser"
require "settings"
module Homebrew
extend T::Sig
@ -63,16 +64,12 @@ module Homebrew
Utils::Analytics.messages_displayed! if $stdout.tty?
end
HOMEBREW_REPOSITORY.cd do
donation_message_displayed =
Utils.popen_read("git", "config", "--get", "homebrew.donationmessage").chomp == "true"
if !donation_message_displayed && !args.quiet?
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n"
if Settings.read("donationmessage") != "true" && !args.quiet?
ohai "Homebrew is run entirely by unpaid volunteers. Please consider donating:"
puts " #{Formatter.url("https://github.com/Homebrew/brew#donations")}\n"
# Consider the message possibly missed if not a TTY.
safe_system "git", "config", "--replace-all", "homebrew.donationmessage", "true" if $stdout.tty?
end
# Consider the message possibly missed if not a TTY.
Settings.write "donationmessage", true if $stdout.tty?
end
install_core_tap_if_necessary
@ -89,19 +86,14 @@ module Homebrew
puts "Updated Homebrew from #{shorten_revision(initial_revision)} to #{shorten_revision(current_revision)}."
updated = true
old_tag = if (HOMEBREW_REPOSITORY/".git/config").exist?
Utils.popen_read(
"git", "config", "--file=#{HOMEBREW_REPOSITORY}/.git/config", "--get", "homebrew.latesttag"
).chomp.presence
end
old_tag = Settings.read "latesttag"
new_tag = Utils.popen_read(
"git", "-C", HOMEBREW_REPOSITORY, "tag", "--list", "--sort=-version:refname", "*.*"
).lines.first.chomp
if new_tag != old_tag
system "git", "config", "--file=#{HOMEBREW_REPOSITORY}/.git/config",
"--replace-all", "homebrew.latesttag", new_tag
Settings.write "latesttag", new_tag
new_repository_version = new_tag
end
end

View File

@ -0,0 +1,69 @@
# typed: true
# frozen_string_literal: true
require "utils/link"
require "settings"
module Homebrew
# Helper functions for generating shell completions.
#
# @api private
module Completions
extend T::Sig
module_function
SHELLS = %w[bash fish zsh].freeze
sig { void }
def link!
Settings.write :linkcompletions, true
Tap.each do |tap|
Utils::Link.link_completions tap.path, "brew completions link"
end
end
sig { void }
def unlink!
Settings.write :linkcompletions, false
Tap.each do |tap|
next if tap.official?
Utils::Link.unlink_completions tap.path
end
end
sig { returns(T::Boolean) }
def link_completions?
Settings.read(:linkcompletions) == "true"
end
sig { returns(T::Boolean) }
def completions_to_link?
Tap.each do |tap|
next if tap.official?
SHELLS.each do |shell|
return true if (tap.path/"completions/#{shell}").exist?
end
end
false
end
sig { void }
def show_completions_message_if_needed
return if Settings.read(:completionsmessageshown) == "true"
return unless completions_to_link?
ohai "Homebrew completions for external commands are unlinked by default!"
puts <<~EOS
To opt-in to automatically linking external tap shell competion files, run:
brew completions link
Then, follow the directions at #{Formatter.url("https://docs.brew.sh/Shell-Completion")}
EOS
Settings.write :completionsmessageshown, true
end
end
end

View File

@ -0,0 +1,7 @@
# typed: strict
module Homebrew
module Completions
include Kernel
end
end

View File

@ -0,0 +1,41 @@
# typed: true
# frozen_string_literal: true
module Homebrew
# Helper functions for reading and writing settings.
#
# @api private
module Settings
extend T::Sig
module_function
sig { params(setting: T.any(String, Symbol), repo: Pathname).returns(T.nilable(String)) }
def read(setting, repo: HOMEBREW_REPOSITORY)
return unless (repo/".git/config").exist?
repo.cd do
Utils.popen_read("git", "config", "--get", "homebrew.#{setting}").chomp.presence
end
end
sig { params(setting: T.any(String, Symbol), value: T.any(String, T::Boolean), repo: Pathname).void }
def write(setting, value, repo: HOMEBREW_REPOSITORY)
return unless (repo/".git/config").exist?
repo.cd do
safe_system "git", "config", "--replace-all", "homebrew.#{setting}", value.to_s
end
end
sig { params(setting: T.any(String, Symbol), repo: Pathname).void }
def delete(setting, repo: HOMEBREW_REPOSITORY)
return unless (repo/".git/config").exist?
return if read(setting, repo: repo).blank?
repo.cd do
safe_system "git", "config", "--unset-all", "homebrew.#{setting}"
end
end
end
end

View File

@ -0,0 +1,7 @@
# typed: strict
module Homebrew
module Settings
include Kernel
end
end

View File

@ -2,8 +2,10 @@
# frozen_string_literal: true
require "commands"
require "completions"
require "extend/cachable"
require "description_cache_store"
require "settings"
# 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
@ -340,7 +342,13 @@ class Tap
def link_completions_and_manpages
command = "brew tap --repair"
Utils::Link.link_manpages(path, command)
Utils::Link.link_completions(path, command)
Homebrew::Completions.show_completions_message_if_needed
if official? || Homebrew::Completions.link_completions?
Utils::Link.link_completions(path, command)
else
Utils::Link.unlink_completions(path)
end
end
# Uninstall this {Tap}.
@ -814,18 +822,14 @@ class TapConfig
return unless tap.git?
return unless Utils::Git.available?
tap.path.cd do
Utils.popen_read("git", "config", "--get", "homebrew.#{key}").chomp.presence
end
Homebrew::Settings.read key, repo: tap.path
end
def []=(key, value)
return unless tap.git?
return unless Utils::Git.available?
tap.path.cd do
safe_system "git", "config", "--replace-all", "homebrew.#{key}", value.to_s
end
Homebrew::Settings.write key, value.to_s, repo: tap.path
end
end

View File

@ -0,0 +1,22 @@
# typed: false
# frozen_string_literal: true
require "cmd/shared_examples/args_parse"
describe "brew completions" do
describe "Homebrew.completions_args" do
it_behaves_like "parseable arguments"
end
it "runs the status subcommand correctly", :integration_test do
HOMEBREW_REPOSITORY.cd do
system "git", "init"
end
brew "completions", "link"
expect { brew "completions" }
.to output(/Completions are linked/).to_stdout
.and not_to_output.to_stderr
.and be_a_success
end
end

View File

@ -0,0 +1,140 @@
# typed: false
# frozen_string_literal: true
require "completions"
describe Homebrew::Completions do
let(:internal_path) { HOMEBREW_REPOSITORY/"Library/Taps/homebrew/homebrew-bar" }
let(:external_path) { HOMEBREW_REPOSITORY/"Library/Taps/foo/homebrew-bar" }
before do
HOMEBREW_REPOSITORY.cd do
system "git", "init"
end
internal_path.mkpath
external_path.mkpath
end
def setup_completions(external:)
(internal_path/"completions/bash/foo_internal").write "#foo completions"
if external
(external_path/"completions/bash/foo_external").write "#foo completions"
elsif (external_path/"completions/bash/foo_external").exist?
(external_path/"completions/bash/foo_external").delete
end
end
def setup_completions_setting(state, setting: "linkcompletions")
HOMEBREW_REPOSITORY.cd do
system "git", "config", "--replace-all", "homebrew.#{setting}", state.to_s
end
end
def read_completions_setting(setting: "linkcompletions")
HOMEBREW_REPOSITORY.cd do
Utils.popen_read("git", "config", "--get", "homebrew.#{setting}").chomp.presence
end
end
def delete_completions_setting(setting: "linkcompletions")
HOMEBREW_REPOSITORY.cd do
system "git", "config", "--unset-all", "homebrew.#{setting}"
end
end
after do
FileUtils.rm_rf internal_path
FileUtils.rm_rf external_path.dirname
end
describe ".link!" do
it "sets homebrew.linkcompletions to true" do
setup_completions_setting false
expect { described_class.link! }.not_to raise_error
expect(read_completions_setting).to eq "true"
end
it "sets homebrew.linkcompletions to true if unset" do
delete_completions_setting
expect { described_class.link! }.not_to raise_error
expect(read_completions_setting).to eq "true"
end
it "keeps homebrew.linkcompletions set to true" do
setup_completions_setting true
expect { described_class.link! }.not_to raise_error
expect(read_completions_setting).to eq "true"
end
end
describe ".unlink!" do
it "sets homebrew.linkcompletions to false" do
setup_completions_setting true
expect { described_class.unlink! }.not_to raise_error
expect(read_completions_setting).to eq "false"
end
it "sets homebrew.linkcompletions to false if unset" do
delete_completions_setting
expect { described_class.unlink! }.not_to raise_error
expect(read_completions_setting).to eq "false"
end
it "keeps homebrew.linkcompletions set to false" do
setup_completions_setting false
expect { described_class.unlink! }.not_to raise_error
expect(read_completions_setting).to eq "false"
end
end
describe ".link_completions?" do
it "returns true if homebrew.linkcompletions is true" do
setup_completions_setting true
expect(described_class.link_completions?).to be true
end
it "returns false if homebrew.linkcompletions is false" do
setup_completions_setting false
expect(described_class.link_completions?).to be false
end
it "returns false if homebrew.linkcompletions is not set" do
expect(described_class.link_completions?).to be false
end
end
describe ".completions_to_link?" do
it "returns false if only internal taps have completions" do
setup_completions external: false
expect(described_class.completions_to_link?).to be false
end
it "returns true if external taps have completions" do
setup_completions external: true
expect(described_class.completions_to_link?).to be true
end
end
describe ".show_completions_message_if_needed" do
it "doesn't show the message if there are no completions to link" do
setup_completions external: false
delete_completions_setting setting: :completionsmessageshown
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
end
it "doesn't show the message if there are completions to link but the message has already been shown" do
setup_completions external: true
setup_completions_setting true, setting: :completionsmessageshown
expect { described_class.show_completions_message_if_needed }.not_to output.to_stdout
end
it "shows the message if there are completions to link and the message hasn't already been shown" do
setup_completions external: true
delete_completions_setting setting: :completionsmessageshown
message = /Homebrew completions for external commands are unlinked by default!/
expect { described_class.show_completions_message_if_needed }
.to output(message).to_stdout
end
end
end

View File

@ -0,0 +1,74 @@
# typed: false
# frozen_string_literal: true
require "settings"
describe Homebrew::Settings do
before do
HOMEBREW_REPOSITORY.cd do
system "git", "init"
end
end
def setup_setting
HOMEBREW_REPOSITORY.cd do
system "git", "config", "--replace-all", "homebrew.foo", "true"
end
end
describe ".read" do
it "returns the correct value for a setting" do
setup_setting
expect(described_class.read("foo")).to eq "true"
end
it "returns the correct value for a setting as a symbol" do
setup_setting
expect(described_class.read(:foo)).to eq "true"
end
it "returns nil when setting is not set" do
setup_setting
expect(described_class.read("bar")).to be_nil
end
it "runs on a repo without a configuration file" do
expect { described_class.read("foo", repo: HOMEBREW_REPOSITORY/"bar") }.not_to raise_error
end
end
describe ".write" do
it "writes over an existing value" do
setup_setting
described_class.write :foo, false
expect(described_class.read("foo")).to eq "false"
end
it "writes a new value" do
setup_setting
described_class.write :bar, "abcde"
expect(described_class.read("bar")).to eq "abcde"
end
it "returns if the repo doesn't have a configuration file" do
expect { described_class.write("foo", repo: HOMEBREW_REPOSITORY/"bar") }.not_to raise_error
end
end
describe ".delete" do
it "deletes an existing setting" do
setup_setting
described_class.delete(:foo)
expect(described_class.read("foo")).to be_nil
end
it "deletes a non-existing setting" do
setup_setting
expect { described_class.delete(:bar) }.not_to raise_error
end
it "returns if the repo doesn't have a configuration file" do
expect { described_class.delete("foo", repo: HOMEBREW_REPOSITORY/"bar") }.not_to raise_error
end
end
end

View File

@ -84,6 +84,14 @@ describe Tap do
end
end
def setup_completion(link:)
HOMEBREW_REPOSITORY.cd do
system "git", "init"
system "git", "config", "--replace-all", "homebrew.linkcompletions", link.to_s
system "git", "config", "--replace-all", "homebrew.completionsmessageshown", "true"
end
end
specify "::fetch" do
expect(described_class.fetch("Homebrew", "core")).to be_kind_of(CoreTap)
expect(described_class.fetch("Homebrew", "homebrew")).to be_kind_of(CoreTap)
@ -285,6 +293,7 @@ describe Tap do
specify "#install and #uninstall" do
setup_tap_files
setup_git_repo
setup_completion link: true
tap = described_class.new("Homebrew", "bar")
@ -308,9 +317,49 @@ describe Tap do
(HOMEBREW_PREFIX/"share").rmtree if (HOMEBREW_PREFIX/"share").exist?
end
specify "#link_completions_and_manpages" do
specify "#link_completions_and_manpages when completions are enabled for non-official tap" do
setup_tap_files
setup_git_repo
setup_completion link: true
tap = described_class.new("NotHomebrew", "baz")
tap.install clone_target: subject.path/".git"
(HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").delete
(HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd").delete
(HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd").delete
(HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish").delete
tap.link_completions_and_manpages
expect(HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").to be_a_file
expect(HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd").to be_a_file
expect(HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd").to be_a_file
expect(HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish").to be_a_file
tap.uninstall
ensure
(HOMEBREW_PREFIX/"etc").rmtree if (HOMEBREW_PREFIX/"etc").exist?
(HOMEBREW_PREFIX/"share").rmtree if (HOMEBREW_PREFIX/"share").exist?
end
specify "#link_completions_and_manpages when completions are disabled for non-official tap" do
setup_tap_files
setup_git_repo
setup_completion link: false
tap = described_class.new("NotHomebrew", "baz")
tap.install clone_target: subject.path/".git"
(HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").delete
tap.link_completions_and_manpages
expect(HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").to be_a_file
expect(HOMEBREW_PREFIX/"etc/bash_completion.d/brew-tap-cmd").not_to be_a_file
expect(HOMEBREW_PREFIX/"share/zsh/site-functions/_brew-tap-cmd").not_to be_a_file
expect(HOMEBREW_PREFIX/"share/fish/vendor_completions.d/brew-tap-cmd.fish").not_to be_a_file
tap.uninstall
ensure
(HOMEBREW_PREFIX/"etc").rmtree if (HOMEBREW_PREFIX/"etc").exist?
(HOMEBREW_PREFIX/"share").rmtree if (HOMEBREW_PREFIX/"share").exist?
end
specify "#link_completions_and_manpages when completions are enabled for official tap" do
setup_tap_files
setup_git_repo
setup_completion link: false
tap = described_class.new("Homebrew", "baz")
tap.install clone_target: subject.path/".git"
(HOMEBREW_PREFIX/"share/man/man1/brew-tap-cmd.1").delete

View File

@ -3,6 +3,7 @@
require "context"
require "erb"
require "settings"
module Utils
# Helper module for fetching and reporting analytics data.
@ -102,27 +103,27 @@ module Utils
end
def uuid
config_get(:analyticsuuid)
Homebrew::Settings.read :analyticsuuid
end
def messages_displayed!
config_set(:analyticsmessage, true)
config_set(:caskanalyticsmessage, true)
Homebrew::Settings.write :analyticsmessage, true
Homebrew::Settings.write :caskanalyticsmessage, true
end
def enable!
config_set(:analyticsdisabled, false)
Homebrew::Settings.write :analyticsdisabled, false
messages_displayed!
end
def disable!
config_set(:analyticsdisabled, true)
Homebrew::Settings.write :analyticsdisabled, true
regenerate_uuid!
end
def regenerate_uuid!
# it will be regenerated in next run unless disabled.
config_delete(:analyticsuuid)
Homebrew::Settings.delete :analyticsuuid
end
def output(args:, filter: nil)
@ -313,25 +314,7 @@ module Utils
end
def config_true?(key)
config_get(key) == "true"
end
def config_get(key)
HOMEBREW_REPOSITORY.cd do
Utils.popen_read("git", "config", "--get", "homebrew.#{key}").chomp
end
end
def config_set(key, value)
HOMEBREW_REPOSITORY.cd do
safe_system "git", "config", "--replace-all", "homebrew.#{key}", value.to_s
end
end
def config_delete(key)
HOMEBREW_REPOSITORY.cd do
system "git", "config", "--unset-all", "homebrew.#{key}"
end
Homebrew::Settings.read(key) == "true"
end
def formulae_brew_sh_json(endpoint)

View File

@ -25,6 +25,7 @@ cat
cleanup
command
commands
completions
config
configure
create

View File

@ -94,6 +94,17 @@ Show lists of built-in and external commands.
* `--include-aliases`:
Include aliases of internal commands.
### `completions` [*`subcommand`*]
Control whether Homebrew automatically links external tap shell completion files.
Read more at <https://docs.brew.sh/Shell-Completion>.
`brew completions` [`state`]
<br>Display the current state of Homebrew's completions.
`brew completions` (`link`|`unlink`)
<br>Link or unlink Homebrew's completions.
### `config`
Show Homebrew and system configuration info useful for debugging. If you file

View File

@ -4,7 +4,9 @@ Homebrew comes with completion definitions for the `brew` command. Some packages
`zsh`, `bash` and `fish` are currently supported.
You must configure your shell to enable its completion support. This is because the Homebrew-managed completions are stored under `HOMEBREW_PREFIX` which your system shell may not be aware of, and since it is difficult to automatically configure `bash` and `zsh` completions in a robust manner, the Homebrew installer does not do it for you.
You must then configure your shell to enable its completion support. This is because the Homebrew-managed completions are stored under `HOMEBREW_PREFIX` which your system shell may not be aware of, and since it is difficult to automatically configure `bash` and `zsh` completions in a robust manner, the Homebrew installer does not do it for you.
Shell completions for external Homebrew commands are not automatically installed. To opt-in to using completions for external commands (if provided), they need to be linked to `HOMEBREW_PREFIX` by running `brew completions link`.
## Configuring Completions in `bash`

View File

@ -93,6 +93,17 @@ List only the names of commands without category headers\.
\fB\-\-include\-aliases\fR
Include aliases of internal commands\.
.
.SS "\fBcompletions\fR [\fIsubcommand\fR]"
Control whether Homebrew automatically links external tap shell completion files\. Read more at \fIhttps://docs\.brew\.sh/Shell\-Completion\fR\.
.
.P
\fBbrew completions\fR [\fBstate\fR]
Display the current state of Homebrew\'s completions\.
.
.P
\fBbrew completions\fR (\fBlink\fR|\fBunlink\fR)
Link or unlink Homebrew\'s completions\.
.
.SS "\fBconfig\fR"
Show Homebrew and system configuration info useful for debugging\. If you file a bug report, you will be required to provide this information\.
.