diff --git a/Library/Homebrew/dev-cmd/audit.rb b/Library/Homebrew/dev-cmd/audit.rb index 7451f2124f547..21682ae9ed7cc 100644 --- a/Library/Homebrew/dev-cmd/audit.rb +++ b/Library/Homebrew/dev-cmd/audit.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -329,6 +329,7 @@ def run private + sig { params(results: T::Hash[[Symbol, Pathname], T::Array[T::Hash[Symbol, T.untyped]]]).void } def print_problems(results) results.each do |(name, path), problems| problem_lines = format_problem_lines(problems) @@ -343,6 +344,7 @@ def print_problems(results) end end + sig { params(problems: T::Array[T::Hash[Symbol, T.untyped]]).returns(T::Array[String]) } def format_problem_lines(problems) problems.map do |problem| status = " #{Formatter.success("[corrected]")}" if problem.fetch(:corrected) diff --git a/Library/Homebrew/dev-cmd/bottle.rb b/Library/Homebrew/dev-cmd/bottle.rb index 32c04a66edfa5..6f96a13ee84d5 100644 --- a/Library/Homebrew/dev-cmd/bottle.rb +++ b/Library/Homebrew/dev-cmd/bottle.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -20,7 +20,7 @@ module DevCmd class Bottle < AbstractCommand include FileUtils - BOTTLE_ERB = <<-EOS.freeze + BOTTLE_ERB = T.let(<<-EOS.freeze, String) bottle do <% if [HOMEBREW_BOTTLE_DEFAULT_DOMAIN.to_s, "#{HOMEBREW_BOTTLE_DEFAULT_DOMAIN}/bottles"].exclude?(root_url) %> @@ -39,9 +39,9 @@ class Bottle < AbstractCommand MAXIMUM_STRING_MATCHES = 100 - ALLOWABLE_HOMEBREW_REPOSITORY_LINKS = [ + ALLOWABLE_HOMEBREW_REPOSITORY_LINKS = T.let([ %r{#{Regexp.escape(HOMEBREW_LIBRARY)}/Homebrew/os/(mac|linux)/pkgconfig}, - ].freeze + ].freeze, T::Array[Regexp]) cmd_args do description <<~EOS @@ -110,6 +110,10 @@ def run end end + sig { + params(tag: Symbol, digest: T.any(Checksum, String), cellar: T.nilable(T.any(String, Symbol)), + tag_column: Integer, digest_column: Integer).returns(String) + } def generate_sha256_line(tag, digest, cellar, tag_column, digest_column) line = "sha256 " tag_column += line.length @@ -125,6 +129,7 @@ def generate_sha256_line(tag, digest, cellar, tag_column, digest_column) %Q(#{line}"#{digest}") end + sig { params(bottle: BottleSpecification, root_url_using: T.nilable(String)).returns(String) } def bottle_output(bottle, root_url_using) cellars = bottle.checksums.filter_map do |checksum| cellar = checksum["cellar"] @@ -153,12 +158,14 @@ def bottle_output(bottle, root_url_using) erb.result(erb_binding).gsub(/^\s*$\n/, "") end + sig { params(filenames: T::Array[String]).returns(T::Array[T::Hash[String, T.untyped]]) } def parse_json_files(filenames) filenames.map do |filename| JSON.parse(File.read(filename)) end end + sig { params(json_files: T::Array[T::Hash[String, T.untyped]]).returns(T::Hash[String, T.untyped]) } def merge_json_files(json_files) json_files.reduce({}) do |hash, json_file| json_file.each_value do |json_hash| @@ -172,6 +179,10 @@ def merge_json_files(json_files) end end + sig { + params(old_keys: T::Array[String], old_bottle_spec: BottleSpecification, + new_bottle_hash: T::Hash[String, T.untyped]).returns(T::Array[T::Array[String]]) + } def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash) mismatches = [] checksums = [] @@ -214,16 +225,20 @@ def merge_bottle_spec(old_keys, old_bottle_spec, new_bottle_hash) private + sig { + params(string: String, keg: Keg, ignores: T::Array[String], + formula_and_runtime_deps_names: T.nilable(T::Array[String])).returns(T::Boolean) + } def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil) @put_string_exists_header, @put_filenames = nil print_filename = lambda do |str, filename| unless @put_string_exists_header opoo "String '#{str}' still exists in these files:" - @put_string_exists_header = true + @put_string_exists_header = T.let(true, T.nilable(T::Boolean)) end - @put_filenames ||= [] + @put_filenames ||= T.let([], T.nilable(T::Array[T.any(String, Pathname)])) return false if @put_filenames.include?(filename) @@ -265,6 +280,7 @@ def keg_contain?(string, keg, ignores, formula_and_runtime_deps_names = nil) keg_contain_absolute_symlink_starting_with?(string, keg) || result end + sig { params(string: String, keg: Keg).returns(T::Boolean) } def keg_contain_absolute_symlink_starting_with?(string, keg) absolute_symlinks_start_with_string = [] keg.find do |pn| @@ -283,6 +299,7 @@ def keg_contain_absolute_symlink_starting_with?(string, keg) !absolute_symlinks_start_with_string.empty? end + sig { params(cellar: T.nilable(T.any(String, Symbol))).returns(T::Boolean) } def cellar_parameter_needed?(cellar) default_cellars = [ Homebrew::DEFAULT_MACOS_CELLAR, @@ -292,6 +309,7 @@ def cellar_parameter_needed?(cellar) cellar.present? && default_cellars.exclude?(cellar) end + sig { returns(T.nilable(T::Boolean)) } def sudo_purge return unless ENV["HOMEBREW_BOTTLE_SUDO_PURGE"] @@ -354,6 +372,7 @@ def setup_tar_and_args!(mtime) [gnu_tar(gnu_tar_formula), reproducible_gnutar_args(mtime)].freeze end + sig { params(formula: T.untyped).returns(T::Array[T.untyped]) } def formula_ignores(formula) ignores = [] cellar_regex = Regexp.escape(HOMEBREW_CELLAR) @@ -384,6 +403,7 @@ def formula_ignores(formula) ignores.compact end + sig { params(formula: Formula).void } def bottle_formula(formula) local_bottle_json = args.json? && formula.local_bottle_path.present? @@ -453,6 +473,8 @@ def bottle_formula(formula) if local_bottle_json bottle_path = formula.local_bottle_path + return if bottle_path.blank? + local_filename = bottle_path.basename.to_s tab_path = Utils::Bottles.receipt_path(bottle_path) @@ -471,6 +493,7 @@ def bottle_formula(formula) else tar_filename = filename.to_s.sub(/.gz$/, "") tar_path = Pathname.pwd/tar_filename + return if tar_path.blank? keg = Keg.new(formula.prefix) end @@ -681,6 +704,7 @@ def bottle_formula(formula) json_path.write(JSON.pretty_generate(json)) end + sig { returns(T::Hash[String, T.untyped]) } def merge bottles_hash = merge_json_files(parse_json_files(args.named)) @@ -750,7 +774,7 @@ def merge end end - all_bottle_hash = T.let(nil, T.nilable(Hash)) + all_bottle_hash = T.let(nil, T.nilable(T::Hash[String, T.untyped])) bottle_hash["bottle"]["tags"].each do |tag, tag_hash| filename = ::Bottle::Filename.new( formula_name, @@ -801,7 +825,7 @@ def merge checksums = old_checksums(formula, formula_ast, bottle_hash) update_or_add = checksums.nil? ? "add" : "update" - checksums&.each(&bottle.method(:sha256)) + checksums&.each { |checksum| bottle.sha256(checksum) } output = bottle_output(bottle, args.root_url_using) puts output @@ -835,8 +859,12 @@ def merge end end + sig { + params(formula: Formula, formula_ast: Utils::AST::FormulaAST, + bottle_hash: T::Hash[String, T.untyped]).returns(T.nilable(T::Array[String])) + } def old_checksums(formula, formula_ast, bottle_hash) - bottle_node = formula_ast.bottle_block + bottle_node = T.cast(formula_ast.bottle_block, T.nilable(RuboCop::AST::BlockNode)) return if bottle_node.nil? return [] unless args.keep_old? diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 1bbbc6b9d8c50..74dea4cf15aa5 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -145,10 +145,10 @@ def run old_mirrors = formula_spec.mirrors new_mirrors ||= args.mirror - new_mirror ||= determine_mirror(new_url) - new_mirrors ||= [new_mirror] if new_mirror.present? - - check_for_mirrors(formula, old_mirrors, new_mirrors) if new_url.present? + if new_url.present? && (new_mirror = determine_mirror(new_url)) + new_mirrors ||= [new_mirror] + check_for_mirrors(formula, old_mirrors, new_mirrors) + end old_hash = formula_spec.checksum&.hexdigest new_hash = args.sha256 @@ -190,12 +190,14 @@ def run elsif new_url.blank? && new_version.blank? raise UsageError, "#{formula}: no `--url` or `--version` argument specified!" else - new_url ||= PyPI.update_pypi_url(old_url, T.must(new_version)) + return unless new_version.present? + + new_url ||= PyPI.update_pypi_url(old_url, new_version) if new_url.blank? - new_url = update_url(old_url, old_version, T.must(new_version)) + new_url = update_url(old_url, old_version, new_version) if new_mirrors.blank? && old_mirrors.present? new_mirrors = old_mirrors.map do |old_mirror| - update_url(old_mirror, old_version, T.must(new_version)) + update_url(old_mirror, old_version, new_version) end end end @@ -271,9 +273,9 @@ def run old_contents = formula.path.read - if new_mirrors.present? + if new_mirrors.present? && new_url.present? replacement_pairs << [ - /^( +)(url "#{Regexp.escape(T.must(new_url))}"[^\n]*?\n)/m, + /^( +)(url "#{Regexp.escape(new_url)}"[^\n]*?\n)/m, "\\1\\2\\1mirror \"#{new_mirrors.join("\"\n\\1mirror \"")}\"\n", ] end @@ -395,6 +397,7 @@ def run private + sig { params(url: String).returns(T.nilable(String)) } def determine_mirror(url) case url when %r{.*ftp\.gnu\.org/gnu.*} @@ -408,6 +411,7 @@ def determine_mirror(url) end end + sig { params(formula: String, old_mirrors: T::Array[String], new_mirrors: T::Array[String]).void } def check_for_mirrors(formula, old_mirrors, new_mirrors) return if new_mirrors.present? || old_mirrors.empty? @@ -427,11 +431,17 @@ def update_url(old_url, old_version, new_version) return new_url if (old_version_parts = old_version.split(".")).length < 2 return new_url if (new_version_parts = new_version.split(".")).length != old_version_parts.length - partial_old_version = T.must(old_version_parts[0..-2]).join(".") - partial_new_version = T.must(new_version_parts[0..-2]).join(".") + partial_old_version = old_version_parts[0..-2]&.join(".") + partial_new_version = new_version_parts[0..-2]&.join(".") + return new_url if partial_old_version.blank? || partial_new_version.blank? + new_url.gsub(%r{/(v?)#{Regexp.escape(partial_old_version)}/}, "/\\1#{partial_new_version}/") end + sig { + params(formula: Formula, new_version: T.nilable(String), url: String, + specs: Float).returns(T::Array[T.untyped]) + } def fetch_resource_and_forced_version(formula, new_version, url, **specs) resource = Resource.new resource.url(url, **specs) @@ -442,6 +452,7 @@ def fetch_resource_and_forced_version(formula, new_version, url, **specs) [resource.fetch, forced_version] end + sig { params(formula: Formula, contents: T.nilable(String)).returns(String) } def formula_version(formula, contents = nil) spec = :stable name = formula.name @@ -453,17 +464,29 @@ def formula_version(formula, contents = nil) end end + sig { params(formula: Formula, tap_remote_repo: String).returns(T.nilable(T::Array[String])) } def check_open_pull_requests(formula, tap_remote_repo) - GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo, - state: "open", - file: formula.path.relative_path_from(formula.tap.path).to_s, - quiet: args.quiet?) + tap = formula.tap + return if tap.nil? + + GitHub.check_for_duplicate_pull_requests( + formula.name, tap_remote_repo, + state: "open", + file: formula.path.relative_path_from(tap.path).to_s, + quiet: args.quiet? + ) end + sig { + params(formula: Formula, tap_remote_repo: String, version: T.nilable(String), url: T.nilable(String), + tag: T.nilable(String)).void + } def check_new_version(formula, tap_remote_repo, version: nil, url: nil, tag: nil) if version.nil? specs = {} specs[:tag] = tag if tag.present? + return if url.blank? + version = Version.detect(url, **specs).to_s return if version.blank? end @@ -472,9 +495,13 @@ def check_new_version(formula, tap_remote_repo, version: nil, url: nil, tag: nil check_closed_pull_requests(formula, tap_remote_repo, version:) end + sig { params(formula: Formula, new_version: String).returns(NilClass) } def check_throttle(formula, new_version) + tap = formula.tap + return if tap.nil? + throttled_rate = formula.livecheck.throttle - throttled_rate ||= if (rate = formula.tap.audit_exceptions.dig(:throttled_formulae, formula.name)) + throttled_rate ||= if (rate = tap.audit_exceptions.dig(:throttled_formulae, formula.name)) odisabled "throttled_formulae.json", "Livecheck#throttle" rate end @@ -486,27 +513,41 @@ def check_throttle(formula, new_version) odie "#{formula} should only be updated every #{throttled_rate} releases on multiples of #{throttled_rate}" end + sig { + params(formula: Formula, tap_remote_repo: String, + version: T.nilable(String)).returns(T.nilable(T::Array[String])) + } def check_closed_pull_requests(formula, tap_remote_repo, version:) + tap = formula.tap + return if tap.nil? + # if we haven't already found open requests, try for an exact match across closed requests - GitHub.check_for_duplicate_pull_requests(formula.name, tap_remote_repo, - version:, - state: "closed", - file: formula.path.relative_path_from(formula.tap.path).to_s, - quiet: args.quiet?) + GitHub.check_for_duplicate_pull_requests( + formula.name, tap_remote_repo, + version:, + state: "closed", + file: formula.path.relative_path_from(tap.path).to_s, + quiet: args.quiet? + ) end + sig { params(formula: Formula, new_formula_version: String).returns(T.nilable(T::Array[String])) } def alias_update_pair(formula, new_formula_version) versioned_alias = formula.aliases.grep(/^.*@\d+(\.\d+)?$/).first return if versioned_alias.nil? name, old_alias_version = versioned_alias.split("@") + return if old_alias_version.blank? + new_alias_regex = (old_alias_version.split(".").length == 1) ? /^\d+/ : /^\d+\.\d+/ new_alias_version, = *new_formula_version.to_s.match(new_alias_regex) + return if new_alias_version.blank? return if Version.new(new_alias_version) <= Version.new(old_alias_version) [versioned_alias, "#{name}@#{new_alias_version}"] end + sig { params(formula: Formula, alias_rename: T.nilable(T::Array[String]), old_contents: String).void } def run_audit(formula, alias_rename, old_contents) audit_args = ["--formula"] audit_args << "--strict" if args.strict? @@ -521,7 +562,9 @@ def run_audit(formula, alias_rename, old_contents) end return end - FileUtils.mv alias_rename.first, alias_rename.last if alias_rename.present? + if alias_rename && (source = alias_rename.first) && (destination = alias_rename.last) + FileUtils.mv source, destination + end failed_audit = false if args.no_audit? ohai "Skipping `brew audit`" @@ -535,7 +578,9 @@ def run_audit(formula, alias_rename, old_contents) return unless failed_audit formula.path.atomic_write(old_contents) - FileUtils.mv alias_rename.last, alias_rename.first if alias_rename.present? + if alias_rename && (source = alias_rename.first) && (destination = alias_rename.last) + FileUtils.mv source, destination + end odie "`brew audit` failed!" end end diff --git a/Library/Homebrew/dev-cmd/contributions.rb b/Library/Homebrew/dev-cmd/contributions.rb index 2e6452937b1dd..96159227fa281 100644 --- a/Library/Homebrew/dev-cmd/contributions.rb +++ b/Library/Homebrew/dev-cmd/contributions.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -10,12 +10,12 @@ module Homebrew module DevCmd class Contributions < AbstractCommand - PRIMARY_REPOS = %w[brew core cask].freeze - SUPPORTED_REPOS = [ + PRIMARY_REPOS = T.let(%w[brew core cask].freeze, T::Array[String]) + SUPPORTED_REPOS = T.let([ PRIMARY_REPOS, OFFICIAL_CMD_TAPS.keys.map { |t| t.delete_prefix("homebrew/") }, OFFICIAL_CASK_TAPS.reject { |t| t == "cask" }, - ].flatten.freeze + ].flatten.freeze, T::Array[String]) MAX_REPO_COMMITS = 1000 cmd_args do @@ -50,9 +50,9 @@ def run results = {} grand_totals = {} - repos = if args.repositories.blank? || T.must(args.repositories).include?("primary") + repos = if args.repositories.blank? || args.repositories&.include?("primary") PRIMARY_REPOS - elsif T.must(args.repositories).include?("all") + elsif args.repositories&.include?("all") SUPPORTED_REPOS else args.repositories @@ -116,7 +116,7 @@ def time_period(from:, to:) end end - sig { params(totals: Hash).returns(String) } + sig { params(totals: T::Hash[String, T::Hash[Symbol, Integer]]).returns(String) } def generate_csv(totals) CSV.generate do |csv| csv << %w[user repo author committer coauthor review total] @@ -127,7 +127,14 @@ def generate_csv(totals) end end - sig { params(user: String, grand_total: Hash).returns(Array) } + sig { + params( + user: String, + grand_total: T::Hash[Symbol, Integer], + ).returns( + [String, String, T.nilable(Integer), T.nilable(Integer), T.nilable(Integer), T.nilable(Integer), Integer], + ) + } def grand_total_row(user, grand_total) [ user, @@ -140,7 +147,10 @@ def grand_total_row(user, grand_total) ] end + sig { params(repos: T.nilable(T::Array[String]), person: String, from: String).void } def scan_repositories(repos, person, from:) + return if repos.blank? + data = {} repos.each do |repo| @@ -168,7 +178,7 @@ def scan_repositories(repos, person, from:) data[repo] = { author: author_commits, committer: committer_commits, - coauthor: git_log_trailers_cmd(T.must(repo_path), person, "Co-authored-by", from:, to: args.to), + coauthor: git_log_trailers_cmd(repo_path, person, "Co-authored-by", from:, to: args.to), review: count_reviews(repo_full_name, person, from:, to: args.to), } end @@ -176,7 +186,7 @@ def scan_repositories(repos, person, from:) data end - sig { params(results: Hash).returns(Hash) } + sig { params(results: T::Hash[Symbol, T.untyped]).returns(T::Hash[Symbol, Integer]) } def total(results) totals = { author: 0, committer: 0, coauthor: 0, review: 0 } diff --git a/Library/Homebrew/dev-cmd/extract.rb b/Library/Homebrew/dev-cmd/extract.rb index af8e3b6375d8a..5ffaac41ab780 100644 --- a/Library/Homebrew/dev-cmd/extract.rb +++ b/Library/Homebrew/dev-cmd/extract.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: true # This cannot be `# typed: strict` due to the use of `undef`. # frozen_string_literal: true require "abstract_command" diff --git a/Library/Homebrew/dev-cmd/generate-cask-api.rb b/Library/Homebrew/dev-cmd/generate-cask-api.rb index d72b20118e0bf..4e6d424f265d2 100644 --- a/Library/Homebrew/dev-cmd/generate-cask-api.rb +++ b/Library/Homebrew/dev-cmd/generate-cask-api.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -71,6 +71,7 @@ def run private + sig { params(title: String).returns(String) } def html_template(title) <<~EOS --- diff --git a/Library/Homebrew/dev-cmd/generate-formula-api.rb b/Library/Homebrew/dev-cmd/generate-formula-api.rb index 563fceb2b6f92..a3fe69de0d31a 100644 --- a/Library/Homebrew/dev-cmd/generate-formula-api.rb +++ b/Library/Homebrew/dev-cmd/generate-formula-api.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -69,6 +69,7 @@ def run private + sig { params(title: String).returns(String) } def html_template(title) <<~EOS --- diff --git a/Library/Homebrew/dev-cmd/irb.rb b/Library/Homebrew/dev-cmd/irb.rb index 9caafbed5a07f..40a3fd9fc9c27 100644 --- a/Library/Homebrew/dev-cmd/irb.rb +++ b/Library/Homebrew/dev-cmd/irb.rb @@ -1,18 +1,20 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" +require "formula" require "formulary" require "cask/cask_loader" class String # @!visibility private + sig { params(args: Integer).returns(Formula) } def f(*args) - require "formula" Formulary.factory(self, *args) end # @!visibility private + sig { params(config: T.nilable(T::Hash[Symbol, T.untyped])).returns(Cask::Cask) } def c(config: nil) Cask::CaskLoader.load(self, config:) end @@ -20,11 +22,13 @@ def c(config: nil) class Symbol # @!visibility private + sig { params(args: Integer).returns(Formula) } def f(*args) to_s.f(*args) end # @!visibility private + sig { params(config: T.nilable(T::Hash[Symbol, T.untyped])).returns(Cask::Cask) } def c(config: nil) to_s.c(config:) end @@ -72,7 +76,6 @@ def run require "irb" end - require "formula" require "keg" require "cask" @@ -94,6 +97,7 @@ def run # Remove the `--debug`, `--verbose` and `--quiet` options which cause problems # for IRB and have already been parsed by the CLI::Parser. + sig { returns(T.nilable(T::Array[Symbol])) } def clean_argv global_options = Homebrew::CLI::Parser .global_options diff --git a/Library/Homebrew/dev-cmd/livecheck.rb b/Library/Homebrew/dev-cmd/livecheck.rb index 017f1a9d295a1..b2ffcbdb16205 100644 --- a/Library/Homebrew/dev-cmd/livecheck.rb +++ b/Library/Homebrew/dev-cmd/livecheck.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -113,8 +113,9 @@ def run private + sig { returns(String) } def watchlist_path - @watchlist_path ||= File.expand_path(Homebrew::EnvConfig.livecheck_watchlist) + @watchlist_path ||= T.let(File.expand_path(Homebrew::EnvConfig.livecheck_watchlist), T.nilable(String)) end end end diff --git a/Library/Homebrew/dev-cmd/pr-pull.rb b/Library/Homebrew/dev-cmd/pr-pull.rb index c880c436308a2..d3f8225109210 100644 --- a/Library/Homebrew/dev-cmd/pr-pull.rb +++ b/Library/Homebrew/dev-cmd/pr-pull.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -141,7 +141,7 @@ def run user, repo, pr, workflow_id: workflow, artifact_pattern: ) if args.ignore_missing_artifacts.present? && - T.must(args.ignore_missing_artifacts).include?(workflow) && + args.ignore_missing_artifacts&.include?(workflow) && workflow_run.first.blank? # Ignore that workflow as it was not executed and we specified # that we could skip it. @@ -183,8 +183,10 @@ def run end # Separates a commit message into subject, body and trailers. + sig { params(message: String).returns([String, String, String]) } def separate_commit_message(message) - subject = message.lines.first.strip + first_line = message.lines.first + return ["", "", ""] unless first_line # Skip the subject and separate lines that look like trailers (e.g. "Co-authored-by") # from lines that look like regular body text. @@ -193,11 +195,15 @@ def separate_commit_message(message) trailers = trailers.uniq.join.strip body = body.join.strip.gsub(/\n{3,}/, "\n\n") - [subject, body, trailers] + [first_line.strip, body, trailers] end + sig { params(git_repo: GitRepository, pull_request: T.nilable(String), dry_run: T::Boolean).void } def signoff!(git_repo, pull_request: nil, dry_run: false) - subject, body, trailers = separate_commit_message(git_repo.commit_message) + msg = git_repo.commit_message + return if msg.blank? + + subject, body, trailers = separate_commit_message(msg) if pull_request # This is a tap pull request and approving reviewers should also sign-off. @@ -210,7 +216,7 @@ def signoff!(git_repo, pull_request: nil, dry_run: false) # Append the close message as well, unless the commit body already includes it. close_message = "Closes ##{pull_request}." - body += "\n\n#{close_message}" unless body.include? close_message + body.concat("\n\n#{close_message}") unless body.include?(close_message) end git_args = Utils::Git.git, "-C", git_repo.pathname, "commit", "--amend", "--signoff", "--allow-empty", @@ -223,6 +229,7 @@ def signoff!(git_repo, pull_request: nil, dry_run: false) end end + sig { params(tap: Tap, subject_name: String, subject_path: Pathname, content: String).returns(T.untyped) } def get_package(tap, subject_name, subject_path, content) if subject_path.to_s.start_with?("#{tap.cask_dir}/") cask = begin @@ -240,6 +247,10 @@ def get_package(tap, subject_name, subject_path, content) end end + sig { + params(old_contents: String, new_contents: String, subject_path: T.any(String, Pathname), + reason: T.nilable(String)).returns(String) + } def determine_bump_subject(old_contents, new_contents, subject_path, reason: nil) subject_path = Pathname(subject_path) tap = Tap.from_path(subject_path) @@ -268,6 +279,10 @@ def determine_bump_subject(old_contents, new_contents, subject_path, reason: nil # Cherry picks a single commit that modifies a single file. # Potentially rewords this commit using {determine_bump_subject}. + sig { + params(commit: String, file: String, git_repo: GitRepository, reason: T.nilable(String), verbose: T::Boolean, + resolve: T::Boolean).void + } def reword_package_commit(commit, file, git_repo:, reason: "", verbose: false, resolve: false) package_file = git_repo.pathname / file package_name = package_file.basename.to_s.chomp(".rb") @@ -279,7 +294,10 @@ def reword_package_commit(commit, file, git_repo:, reason: "", verbose: false, r new_package = Utils::Git.file_at_commit(git_repo.to_s, file, "HEAD") bump_subject = determine_bump_subject(old_package, new_package, package_file, reason:).strip - subject, body, trailers = separate_commit_message(git_repo.commit_message) + msg = git_repo.commit_message + return if msg.blank? + + subject, body, trailers = separate_commit_message(msg) if subject != bump_subject && !subject.start_with?("#{package_name}:") safe_system("git", "-C", git_repo.pathname, "commit", "--amend", "-q", @@ -293,6 +311,10 @@ def reword_package_commit(commit, file, git_repo:, reason: "", verbose: false, r # Cherry picks multiple commits that each modify a single file. # Words the commit according to {determine_bump_subject} with the body # corresponding to all the original commit messages combined. + sig { + params(commits: T::Array[String], file: String, git_repo: GitRepository, reason: T.nilable(String), + verbose: T::Boolean, resolve: T::Boolean).void + } def squash_package_commits(commits, file, git_repo:, reason: "", verbose: false, resolve: false) odebug "Squashing #{file}: #{commits.join " "}" @@ -304,7 +326,10 @@ def squash_package_commits(commits, file, git_repo:, reason: "", verbose: false, messages = [] trailers = [] commits.each do |commit| - subject, body, trailer = separate_commit_message(git_repo.commit_message(commit)) + msg = git_repo.commit_message(commit) + next if msg.blank? + + subject, body, trailer = separate_commit_message(msg) body = body.lines.map { |line| " #{line.strip}" }.join("\n") messages << "* #{subject}\n#{body}".strip trailers << trailer @@ -340,9 +365,12 @@ def squash_package_commits(commits, file, git_repo:, reason: "", verbose: false, end # TODO: fix test in `test/dev-cmd/pr-pull_spec.rb` and assume `cherry_picked: false`. + sig { + params(original_commit: String, tap: Tap, reason: T.nilable(String), verbose: T::Boolean, resolve: T::Boolean, + cherry_picked: T::Boolean).void + } def autosquash!(original_commit, tap:, reason: "", verbose: false, resolve: false, cherry_picked: true) git_repo = tap.git_repository - original_head = git_repo.head_ref commits = Utils.safe_popen_read("git", "-C", tap.path, "rev-list", "--reverse", "#{original_commit}..HEAD").lines.map(&:strip) @@ -402,14 +430,18 @@ def autosquash!(original_commit, tap:, reason: "", verbose: false, resolve: fals end end rescue + original_head = git_repo&.head_ref + return if original_head.nil? + opoo "Autosquash encountered an error; resetting to original state at #{original_head}" - system "git", "-C", tap.path, "reset", "--hard", original_head - system "git", "-C", tap.path, "cherry-pick", "--abort" if cherry_picked + system "git", "-C", tap.path.to_s, "reset", "--hard", original_head + system "git", "-C", tap.path.to_s, "cherry-pick", "--abort" if cherry_picked raise end private + sig { params(user: String, repo: String, pull_request: String, path: T.any(String, Pathname)).void } def cherry_pick_pr!(user, repo, pull_request, path: ".") if args.dry_run? puts <<~EOS @@ -427,6 +459,7 @@ def cherry_pick_pr!(user, repo, pull_request, path: ".") resolve: args.resolve?) end + sig { params(tap: Tap, original_commit: String, labels: T::Array[String]).returns(T::Boolean) } def formulae_need_bottles?(tap, original_commit, labels) return false if args.dry_run? @@ -437,6 +470,7 @@ def formulae_need_bottles?(tap, original_commit, labels) end end + sig { params(tap: Tap, original_commit: String).returns(T::Array[String]) } def changed_packages(tap, original_commit) formulae = Utils.popen_read("git", "-C", tap.path, "diff-tree", "-r", "--name-only", "--diff-filter=AM", @@ -473,6 +507,7 @@ def changed_packages(tap, original_commit) formulae + casks end + sig { params(repo: String, pull_request: String).void } def pr_check_conflicts(repo, pull_request) long_build_pr_files = GitHub.issues( repo:, state: "open", labels: "no long build conflict", diff --git a/Library/Homebrew/dev-cmd/pr-upload.rb b/Library/Homebrew/dev-cmd/pr-upload.rb index 672086f3efacd..4217f27e08e2f 100644 --- a/Library/Homebrew/dev-cmd/pr-upload.rb +++ b/Library/Homebrew/dev-cmd/pr-upload.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -120,6 +120,7 @@ def run private + sig { params(bottles_hash: T::Hash[String, T.untyped]).void } def check_bottled_formulae!(bottles_hash) bottles_hash.each do |name, bottle_hash| formula_path = HOMEBREW_REPOSITORY/bottle_hash["formula"]["path"] @@ -131,22 +132,25 @@ def check_bottled_formulae!(bottles_hash) end end + sig { params(bottles_hash: T::Hash[String, T.untyped]).returns(T::Boolean) } def github_releases?(bottles_hash) - @github_releases ||= bottles_hash.values.all? do |bottle_hash| + @github_releases ||= T.let(bottles_hash.values.all? do |bottle_hash| root_url = bottle_hash["bottle"]["root_url"] url_match = root_url.match GitHubReleases::URL_REGEX _, _, _, tag = *url_match tag - end + end, T.nilable(T::Boolean)) end + sig { params(bottles_hash: T::Hash[String, T.untyped]).returns(T::Boolean) } def github_packages?(bottles_hash) - @github_packages ||= bottles_hash.values.all? do |bottle_hash| + @github_packages ||= T.let(bottles_hash.values.all? do |bottle_hash| bottle_hash["bottle"]["root_url"].match? GitHubPackages::URL_REGEX - end + end, T.nilable(T::Boolean)) end + sig { params(json_files: T::Array[String], args: T.untyped).returns(T::Hash[String, T.untyped]) } def bottles_hash_from_json_files(json_files, args) puts "Reading JSON files: #{json_files.join(", ")}" if args.verbose? diff --git a/Library/Homebrew/dev-cmd/tap-new.rb b/Library/Homebrew/dev-cmd/tap-new.rb index 1471b16f58f40..aeda5d721cd31 100644 --- a/Library/Homebrew/dev-cmd/tap-new.rb +++ b/Library/Homebrew/dev-cmd/tap-new.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -197,6 +197,7 @@ def run private + sig { params(tap: Tap, filename: T.any(String, Pathname), content: String).void } def write_path(tap, filename, content) path = tap.path/filename tap.path.mkpath diff --git a/Library/Homebrew/dev-cmd/test.rb b/Library/Homebrew/dev-cmd/test.rb index 38e07963002e7..b2a9c47194598 100644 --- a/Library/Homebrew/dev-cmd/test.rb +++ b/Library/Homebrew/dev-cmd/test.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -110,8 +110,9 @@ def run private + sig { params(formula: Formula).returns(T::Boolean) } def retry_test?(formula) - @test_failed ||= Set.new + @test_failed ||= T.let(Set.new, T.nilable(T::Set[T.untyped])) if args.retry? && @test_failed.add?(formula) oh1 "Testing #{formula.full_name} (again)" formula.clear_cache diff --git a/Library/Homebrew/dev-cmd/tests.rb b/Library/Homebrew/dev-cmd/tests.rb index 60845d6cc2e65..1ac6edfe2c372 100644 --- a/Library/Homebrew/dev-cmd/tests.rb +++ b/Library/Homebrew/dev-cmd/tests.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -168,15 +168,17 @@ def run private + sig { returns(T.nilable(T::Boolean)) } def use_buildpulse? return @use_buildpulse if defined?(@use_buildpulse) - @use_buildpulse = ENV["HOMEBREW_BUILDPULSE_ACCESS_KEY_ID"].present? && + @use_buildpulse = T.let(ENV["HOMEBREW_BUILDPULSE_ACCESS_KEY_ID"].present? && ENV["HOMEBREW_BUILDPULSE_SECRET_ACCESS_KEY"].present? && ENV["HOMEBREW_BUILDPULSE_ACCOUNT_ID"].present? && - ENV["HOMEBREW_BUILDPULSE_REPOSITORY_ID"].present? + ENV["HOMEBREW_BUILDPULSE_REPOSITORY_ID"].present?, T.nilable(T::Boolean)) end + sig { void } def run_buildpulse require "formula" @@ -198,6 +200,7 @@ def run_buildpulse ] end + sig { returns(T::Array[String]) } def changed_test_files changed_files = Utils.popen_read("git", "diff", "--name-only", "master") @@ -215,6 +218,7 @@ def changed_test_files end.select(&:exist?) end + sig { returns(T::Array[String]) } def setup_environment! # Cleanup any unwanted user configuration. allowed_test_env = %w[ diff --git a/Library/Homebrew/dev-cmd/unbottled.rb b/Library/Homebrew/dev-cmd/unbottled.rb index 359724dfbbe09..7a73caf4a1b81 100644 --- a/Library/Homebrew/dev-cmd/unbottled.rb +++ b/Library/Homebrew/dev-cmd/unbottled.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -34,11 +34,15 @@ class Unbottled < AbstractCommand def run Formulary.enable_factory_cache! - @bottle_tag = if (tag = args.tag) - Utils::Bottles::Tag.from_symbol(tag.to_sym) - else - Utils::Bottles.tag - end + @bottle_tag = T.let( + if (tag = args.tag) + Utils::Bottles::Tag.from_symbol(tag.to_sym) + else + Utils::Bottles.tag + end, + T.nilable(Utils::Bottles::Tag), + ) + return unless @bottle_tag if args.lost? if args.named.present? @@ -98,12 +102,17 @@ def run ["installs", formula_installs] end + return if hash.nil? + output_unbottled(formulae, deps_hash, noun, hash, args.named.present?) end end private + sig { + params(all: T::Boolean).returns([T::Array[Formula], T::Array[Formula], T.nilable(T::Hash[Symbol, Integer])]) + } def formulae_all_installs_from_args(all) if args.named.present? formulae = all_formulae = args.named.to_formulae @@ -115,7 +124,7 @@ def formulae_all_installs_from_args(all) formulae = all_formulae = Formula.all(eval_all: args.eval_all?) - @sort = " (sorted by number of dependents)" + @sort = T.let(" (sorted by number of dependents)", T.nilable(String)) elsif all formulae = all_formulae = Formula.all(eval_all: args.eval_all?) else @@ -142,7 +151,7 @@ def formulae_all_installs_from_args(all) nil end end - @sort = " (sorted by installs in the last 90 days; top 10,000 only)" + @sort = T.let(" (sorted by installs in the last 90 days; top 10,000 only)", T.nilable(String)) all_formulae = Formula.all(eval_all: args.eval_all?) end @@ -151,9 +160,11 @@ def formulae_all_installs_from_args(all) formulae = Array(formulae).reject(&:deprecated?) if formulae.present? all_formulae = Array(all_formulae).reject(&:deprecated?) if all_formulae.present? - [formulae, all_formulae, formula_installs] + [T.let(formulae, T::Array[Formula]), T.let(all_formulae, T::Array[Formula]), + T.let(formula_installs, T.nilable(T::Hash[Symbol, Integer]))] end + sig { params(all_formulae: T.untyped).returns([T::Hash[String, T.untyped], T::Hash[String, T.untyped]]) } def deps_uses_from_formulae(all_formulae) ohai "Populating dependency tree..." @@ -175,7 +186,10 @@ def deps_uses_from_formulae(all_formulae) [deps_hash, uses_hash] end + sig { params(formulae: T::Array[Formula]).returns(NilClass) } def output_total(formulae) + return unless @bottle_tag + ohai "Unbottled :#{@bottle_tag} formulae" unbottled_formulae = 0 @@ -188,7 +202,14 @@ def output_total(formulae) puts "#{unbottled_formulae}/#{formulae.length} remaining." end + sig { + params(formulae: T::Array[Formula], deps_hash: T::Hash[T.any(Symbol, String), T.untyped], + noun: T.nilable(String), hash: T::Hash[T.any(Symbol, String), T.untyped], + any_named_args: T::Boolean).returns(NilClass) + } def output_unbottled(formulae, deps_hash, noun, hash, any_named_args) + return unless @bottle_tag + ohai ":#{@bottle_tag} bottle status#{@sort}" any_found = T.let(false, T::Boolean) @@ -258,6 +279,7 @@ def output_unbottled(formulae, deps_hash, noun, hash, any_named_args) puts "No unbottled dependencies found!" end + sig { returns(NilClass) } def output_lost_bottles ohai ":#{@bottle_tag} lost bottles" diff --git a/Library/Homebrew/dev-cmd/update-sponsors.rb b/Library/Homebrew/dev-cmd/update-sponsors.rb index 248a290413d2b..26a2202418ec8 100644 --- a/Library/Homebrew/dev-cmd/update-sponsors.rb +++ b/Library/Homebrew/dev-cmd/update-sponsors.rb @@ -1,4 +1,4 @@ -# typed: true +# typed: strict # frozen_string_literal: true require "abstract_command" @@ -62,14 +62,17 @@ def run private + sig { params(sponsor: T::Hash[Symbol, T.untyped]).returns(T.nilable(String)) } def sponsor_name(sponsor) sponsor[:name] || sponsor[:login] end + sig { params(sponsor: T::Hash[Symbol, T.untyped]).returns(String) } def sponsor_logo(sponsor) "https://github.com/#{sponsor[:login]}.png?size=64" end + sig { params(sponsor: T::Hash[Symbol, T.untyped]).returns(String) } def sponsor_url(sponsor) "https://github.com/#{sponsor[:login]}" end