diff --git a/Library/Homebrew/brew.rb b/Library/Homebrew/brew.rb index ca712613e4..d234adabc7 100644 --- a/Library/Homebrew/brew.rb +++ b/Library/Homebrew/brew.rb @@ -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 + Settings.write "devcmdrun", true ENV["HOMEBREW_DEV_CMD_RUN"] = "1" end internal_dev_cmd diff --git a/Library/Homebrew/cmd/update-report.rb b/Library/Homebrew/cmd/update-report.rb index f38b341394..3a91124812 100644 --- a/Library/Homebrew/cmd/update-report.rb +++ b/Library/Homebrew/cmd/update-report.rb @@ -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 diff --git a/Library/Homebrew/completions.rb b/Library/Homebrew/completions.rb index 9bde547f40..356a9dbabd 100644 --- a/Library/Homebrew/completions.rb +++ b/Library/Homebrew/completions.rb @@ -2,6 +2,7 @@ # frozen_string_literal: true require "utils/link" +require "settings" # Helper functions for generating shell completions. # @@ -13,7 +14,7 @@ module Completions sig { void } def link! - write_completions_option "yes" + Settings.write :linkcompletions, true Tap.each do |tap| Utils::Link.link_completions tap.path, "brew completions link" end @@ -21,7 +22,7 @@ module Completions sig { void } def unlink! - write_completions_option "no" + Settings.write :linkcompletions, false Tap.each do |tap| next if tap.official? @@ -31,7 +32,7 @@ module Completions sig { returns(T::Boolean) } def link_completions? - read_completions_option == "yes" + Settings.read(:linkcompletions) == "true" end sig { returns(T::Boolean) } @@ -48,23 +49,9 @@ module Completions false end - sig { params(option: String).returns(String) } - def read_completions_option(option: "linkcompletions") - HOMEBREW_REPOSITORY.cd do - Utils.popen_read("git", "config", "--get", "homebrew.#{option}").chomp - end - end - - sig { params(state: String, option: String).void } - def write_completions_option(state, option: "linkcompletions") - HOMEBREW_REPOSITORY.cd do - T.unsafe(self).safe_system "git", "config", "--replace-all", "homebrew.#{option}", state.to_s - end - end - sig { void } def show_completions_message_if_needed - return if read_completions_option(option: "completionsmessageshown") == "yes" + return if Settings.read(:completionsmessageshown) == "true" return unless completions_to_link? T.unsafe(self).ohai "Homebrew completions for external commands are unlinked by default!" @@ -74,6 +61,6 @@ module Completions Then, follow the directions at #{Formatter.url("https://docs.brew.sh/Shell-Completion")} EOS - write_completions_option("yes", option: "completionsmessageshown") + Settings.write :completionsmessageshown, true end end diff --git a/Library/Homebrew/settings.rb b/Library/Homebrew/settings.rb new file mode 100644 index 0000000000..24286026ac --- /dev/null +++ b/Library/Homebrew/settings.rb @@ -0,0 +1,39 @@ +# typed: true +# frozen_string_literal: true + +# 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 + T.unsafe(self).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 + T.unsafe(self).safe_system "git", "config", "--unset-all", "homebrew.#{setting}" + end + end +end diff --git a/Library/Homebrew/tap.rb b/Library/Homebrew/tap.rb index b8daea201a..b9e90b1686 100644 --- a/Library/Homebrew/tap.rb +++ b/Library/Homebrew/tap.rb @@ -5,6 +5,7 @@ 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 @@ -821,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 + 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 + Settings.write key, value.to_s, repo: tap.path end end diff --git a/Library/Homebrew/test/completions_spec.rb b/Library/Homebrew/test/completions_spec.rb new file mode 100644 index 0000000000..41c9f95ef0 --- /dev/null +++ b/Library/Homebrew/test/completions_spec.rb @@ -0,0 +1,143 @@ +# typed: false +# frozen_string_literal: true + +require "completions" + +describe 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 + + # This will fail because the method calls `puts`. + # If we output the `ohai` andcatch the error, we can be usre that the message is showing. + error_message = "private method `puts' called for Completions:Module" + expect { described_class.show_completions_message_if_needed } + .to output.to_stdout + .and raise_error(NoMethodError, error_message) + end + end +end diff --git a/Library/Homebrew/test/settings_spec.rb b/Library/Homebrew/test/settings_spec.rb new file mode 100644 index 0000000000..1c42f7fe76 --- /dev/null +++ b/Library/Homebrew/test/settings_spec.rb @@ -0,0 +1,74 @@ +# typed: false +# frozen_string_literal: true + +require "settings" + +describe 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 diff --git a/Library/Homebrew/test/tap_spec.rb b/Library/Homebrew/test/tap_spec.rb index eb4a64e121..691e5fa61c 100644 --- a/Library/Homebrew/test/tap_spec.rb +++ b/Library/Homebrew/test/tap_spec.rb @@ -87,8 +87,8 @@ describe Tap do def setup_completion(link:) HOMEBREW_REPOSITORY.cd do system "git", "init" - system "git", "config", "--replace-all", "homebrew.linkcompletions", link - system "git", "config", "--replace-all", "homebrew.completionsmessageshown", "yes" + system "git", "config", "--replace-all", "homebrew.linkcompletions", link.to_s + system "git", "config", "--replace-all", "homebrew.completionsmessageshown", "true" end end @@ -293,7 +293,7 @@ describe Tap do specify "#install and #uninstall" do setup_tap_files setup_git_repo - setup_completion link: "yes" + setup_completion link: true tap = described_class.new("Homebrew", "bar") @@ -320,7 +320,7 @@ describe Tap do specify "#link_completions_and_manpages when completions are enabled for non-official tap" do setup_tap_files setup_git_repo - setup_completion link: "yes" + 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 @@ -341,7 +341,7 @@ describe Tap do specify "#link_completions_and_manpages when completions are disabled for non-official tap" do setup_tap_files setup_git_repo - setup_completion link: "no" + 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 @@ -359,7 +359,7 @@ describe Tap do specify "#link_completions_and_manpages when completions are enabled for official tap" do setup_tap_files setup_git_repo - setup_completion link: "no" + 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 diff --git a/Library/Homebrew/utils/analytics.rb b/Library/Homebrew/utils/analytics.rb index 6dca9a2001..9ab040159d 100644 --- a/Library/Homebrew/utils/analytics.rb +++ b/Library/Homebrew/utils/analytics.rb @@ -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) + Settings.read :analyticsuuid end def messages_displayed! - config_set(:analyticsmessage, true) - config_set(:caskanalyticsmessage, true) + Settings.write :analyticsmessage, true + Settings.write :caskanalyticsmessage, true end def enable! - config_set(:analyticsdisabled, false) + Settings.write :analyticsdisabled, false messages_displayed! end def disable! - config_set(:analyticsdisabled, true) + Settings.write :analyticsdisabled, true regenerate_uuid! end def regenerate_uuid! # it will be regenerated in next run unless disabled. - config_delete(:analyticsuuid) + 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 + Settings.read(key) == "true" end def formulae_brew_sh_json(endpoint)