Enable more typing

This commit is contained in:
Douglas Eichelberger 2023-03-15 14:29:15 -07:00
parent 5a0cdaa749
commit 827fc87cde
8 changed files with 113 additions and 107 deletions

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "json" require "json"
@ -112,8 +112,8 @@ class AbstractDownloadStrategy
return return
end end
if File.directory? entries.first if File.directory? entries.fetch(0)
Dir.chdir(entries.first, &block) Dir.chdir(entries.fetch(0), &block)
else else
yield yield
end end
@ -472,7 +472,6 @@ class CurlDownloadStrategy < AbstractFileDownloadStrategy
lines = output.to_s.lines.map(&:chomp) lines = output.to_s.lines.map(&:chomp)
final_url = curl_response_follow_redirections(parsed_output[:responses], url) final_url = curl_response_follow_redirections(parsed_output[:responses], url)
final_url ||= url
content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new content_disposition_parser = Mechanize::HTTP::ContentDispositionParser.new
@ -603,7 +602,6 @@ class CurlGitHubPackagesDownloadStrategy < CurlDownloadStrategy
attr_writer :resolved_basename attr_writer :resolved_basename
def initialize(url, name, version, **meta) def initialize(url, name, version, **meta)
meta ||= {}
meta[:headers] ||= [] meta[:headers] ||= []
# GitHub Packages authorization header. # GitHub Packages authorization header.
# HOMEBREW_GITHUB_PACKAGES_AUTH set in brew.sh # HOMEBREW_GITHUB_PACKAGES_AUTH set in brew.sh
@ -723,7 +721,7 @@ class SubversionDownloadStrategy < VCSDownloadStrategy
# @api public # @api public
sig { returns(Time) } sig { returns(Time) }
def source_modified_time def source_modified_time
time = if Version.create(Utils::Svn.version) >= Version.create("1.9") time = if Version.create(T.must(Utils::Svn.version)) >= Version.create("1.9")
out, = silent_command("svn", args: ["info", "--show-item", "last-changed-date"], chdir: cached_location) out, = silent_command("svn", args: ["info", "--show-item", "last-changed-date"], chdir: cached_location)
out out
else else
@ -913,7 +911,7 @@ class GitDownloadStrategy < VCSDownloadStrategy
args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout? args << "--no-checkout" << "--filter=blob:none" if partial_clone_sparse_checkout?
args << "-c" << "advice.detachedHead=false" # silences detached head warning args << "-c" << "advice.detachedHead=false" # silences detached head warning
args << @url << cached_location args << @url << cached_location.to_s
end end
sig { returns(String) } sig { returns(String) }
@ -1071,14 +1069,15 @@ class GitHubGitDownloadStrategy < GitDownloadStrategy
def initialize(url, name, version, **meta) def initialize(url, name, version, **meta)
super super
return unless %r{^https?://github\.com/(?<user>[^/]+)/(?<repo>[^/]+)\.git$} =~ @url match_data = %r{^https?://github\.com/(?<user>[^/]+)/(?<repo>[^/]+)\.git$}.match(@url)
if match_data
@user = user @user = match_data[:user]
@repo = repo @repo = match_data[:repo]
end
end end
def commit_outdated?(commit) def commit_outdated?(commit)
@last_commit ||= GitHub.last_commit(@user, @repo, @ref) @last_commit ||= GitHub.last_commit(@user, @repo, @ref, version)
if @last_commit if @last_commit
return true unless commit return true unless commit
return true unless @last_commit.start_with?(commit) return true unless @last_commit.start_with?(commit)
@ -1103,7 +1102,7 @@ class GitHubGitDownloadStrategy < GitDownloadStrategy
end end
end end
sig { returns(String) } sig { returns(T.nilable(String)) }
def default_branch def default_branch
return @default_branch if defined?(@default_branch) return @default_branch if defined?(@default_branch)

View File

@ -0,0 +1,6 @@
# typed: strict
module AbstractDownloadStrategy::Pourable
include Kernel
requires_ancestor { AbstractDownloadStrategy }
end

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "resource" require "resource"
@ -141,14 +141,14 @@ class ExternalPatch
patch_dir = Pathname.pwd patch_dir = Pathname.pwd
if patch_files.empty? if patch_files.empty?
children = patch_dir.children children = patch_dir.children
if children.length != 1 || !children.first.file? if children.length != 1 || !children.fetch(0).file?
raise MissingApplyError, <<~EOS raise MissingApplyError, <<~EOS
There should be exactly one patch file in the staging directory unless There should be exactly one patch file in the staging directory unless
the "apply" method was used one or more times in the patch-do block. the "apply" method was used one or more times in the patch-do block.
EOS EOS
end end
patch_files << children.first.basename patch_files << children.fetch(0).basename
end end
dir = base_dir dir = base_dir
dir /= resource.directory if resource.directory.present? dir /= resource.directory if resource.directory.present?

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "version" require "version"

View File

@ -0,0 +1,8 @@
# typed: strict
class PkgVersion
# This is a workaround to enable `alias eql? ==`
# @see https://github.com/sorbet/sorbet/issues/2378#issuecomment-569474238
sig { params(arg0: BasicObject).returns(T::Boolean) }
def ==(arg0); end
end

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "shellwords" require "shellwords"
@ -8,11 +8,9 @@ module Homebrew
# #
# @api private # @api private
module Style module Style
module_function
# Checks style for a list of files, printing simple RuboCop output. # Checks style for a list of files, printing simple RuboCop output.
# Returns true if violations were found, false otherwise. # Returns true if violations were found, false otherwise.
def check_style_and_print(files, **options) def self.check_style_and_print(files, **options)
success = check_style_impl(files, :print, **options) success = check_style_impl(files, :print, **options)
if ENV["GITHUB_ACTIONS"] && !success if ENV["GITHUB_ACTIONS"] && !success
@ -32,11 +30,11 @@ module Homebrew
# Checks style for a list of files, returning results as an {Offenses} # Checks style for a list of files, returning results as an {Offenses}
# object parsed from its JSON output. # object parsed from its JSON output.
def check_style_json(files, **options) def self.check_style_json(files, **options)
check_style_impl(files, :json, **options) check_style_impl(files, :json, **options)
end end
def check_style_impl(files, output_type, def self.check_style_impl(files, output_type,
fix: false, fix: false,
except_cops: nil, only_cops: nil, except_cops: nil, only_cops: nil,
display_cop_names: false, display_cop_names: false,
@ -80,7 +78,7 @@ module Homebrew
RUBOCOP = (HOMEBREW_LIBRARY_PATH/"utils/rubocop.rb").freeze RUBOCOP = (HOMEBREW_LIBRARY_PATH/"utils/rubocop.rb").freeze
def run_rubocop(files, output_type, def self.run_rubocop(files, output_type,
fix: false, except_cops: nil, only_cops: nil, display_cop_names: false, reset_cache: false, fix: false, except_cops: nil, only_cops: nil, display_cop_names: false, reset_cache: false,
debug: false, verbose: false) debug: false, verbose: false)
Homebrew.install_bundler_gems! Homebrew.install_bundler_gems!
@ -160,7 +158,7 @@ module Homebrew
end end
end end
def run_shellcheck(files, output_type, fix: false) def self.run_shellcheck(files, output_type, fix: false)
files = shell_scripts if files.blank? files = shell_scripts if files.blank?
files = files.map(&:realpath) # use absolute file paths files = files.map(&:realpath) # use absolute file paths
@ -230,7 +228,7 @@ module Homebrew
end end
end end
def run_shfmt(files, fix: false) def self.run_shfmt(files, fix: false)
files = shell_scripts if files.blank? files = shell_scripts if files.blank?
# Do not format completions and Dockerfile # Do not format completions and Dockerfile
files.delete(HOMEBREW_REPOSITORY/"completions/bash/brew") files.delete(HOMEBREW_REPOSITORY/"completions/bash/brew")
@ -243,7 +241,7 @@ module Homebrew
$CHILD_STATUS.success? $CHILD_STATUS.success?
end end
def json_result!(result) def self.json_result!(result)
# An exit status of 1 just means violations were found; other numbers mean # An exit status of 1 just means violations were found; other numbers mean
# execution errors. # execution errors.
# JSON needs to be at least 2 characters. # JSON needs to be at least 2 characters.
@ -252,7 +250,7 @@ module Homebrew
JSON.parse(result.stdout) JSON.parse(result.stdout)
end end
def shell_scripts def self.shell_scripts
[ [
HOMEBREW_BREW_FILE, HOMEBREW_BREW_FILE,
HOMEBREW_REPOSITORY/"completions/bash/brew", HOMEBREW_REPOSITORY/"completions/bash/brew",
@ -271,12 +269,12 @@ module Homebrew
] ]
end end
def shellcheck def self.shellcheck
ensure_formula_installed!("shellcheck", latest: true, ensure_formula_installed!("shellcheck", latest: true,
reason: "shell style checks").opt_bin/"shellcheck" reason: "shell style checks").opt_bin/"shellcheck"
end end
def shfmt def self.shfmt
ensure_formula_installed!("shfmt", latest: true, ensure_formula_installed!("shfmt", latest: true,
reason: "formatting shell scripts") reason: "formatting shell scripts")
HOMEBREW_LIBRARY/"Homebrew/utils/shfmt.sh" HOMEBREW_LIBRARY/"Homebrew/utils/shfmt.sh"

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
module Utils module Utils
@ -9,45 +9,43 @@ module Utils
module Git module Git
extend T::Sig extend T::Sig
module_function def self.available?
def available?
version.present? version.present?
end end
def version def self.version
return @version if defined?(@version) return @version if defined?(@version)
stdout, _, status = system_command(git, args: ["--version"], verbose: false, print_stderr: false) stdout, _, status = system_command(git, args: ["--version"], verbose: false, print_stderr: false)
@version = status.success? ? stdout.chomp[/git version (\d+(?:\.\d+)*)/, 1] : nil @version = status.success? ? stdout.chomp[/git version (\d+(?:\.\d+)*)/, 1] : nil
end end
def path def self.path
return unless available? return unless available?
return @path if defined?(@path) return @path if defined?(@path)
@path = Utils.popen_read(git, "--homebrew=print-path").chomp.presence @path = Utils.popen_read(git, "--homebrew=print-path").chomp.presence
end end
def git def self.git
return @git if defined?(@git) return @git if defined?(@git)
@git = HOMEBREW_SHIMS_PATH/"shared/git" @git = HOMEBREW_SHIMS_PATH/"shared/git"
end end
def remote_exists?(url) def self.remote_exists?(url)
return true unless available? return true unless available?
quiet_system "git", "ls-remote", url quiet_system "git", "ls-remote", url
end end
def clear_available_cache def self.clear_available_cache
remove_instance_variable(:@version) if defined?(@version) remove_instance_variable(:@version) if defined?(@version)
remove_instance_variable(:@path) if defined?(@path) remove_instance_variable(:@path) if defined?(@path)
remove_instance_variable(:@git) if defined?(@git) remove_instance_variable(:@git) if defined?(@git)
end end
def last_revision_commit_of_file(repo, file, before_commit: nil) def self.last_revision_commit_of_file(repo, file, before_commit: nil)
args = if before_commit.nil? args = if before_commit.nil?
["--skip=1"] ["--skip=1"]
else else
@ -57,7 +55,7 @@ module Utils
Utils.popen_read(git, "-C", repo, "log", "--format=%h", "--abbrev=7", "--max-count=1", *args, "--", file).chomp Utils.popen_read(git, "-C", repo, "log", "--format=%h", "--abbrev=7", "--max-count=1", *args, "--", file).chomp
end end
def last_revision_commit_of_files(repo, files, before_commit: nil) def self.last_revision_commit_of_files(repo, files, before_commit: nil)
args = if before_commit.nil? args = if before_commit.nil?
["--skip=1"] ["--skip=1"]
else else
@ -82,19 +80,19 @@ module Utils
params(repo: T.any(Pathname, String), file: T.any(Pathname, String), before_commit: T.nilable(String)) params(repo: T.any(Pathname, String), file: T.any(Pathname, String), before_commit: T.nilable(String))
.returns(String) .returns(String)
} }
def last_revision_of_file(repo, file, before_commit: nil) def self.last_revision_of_file(repo, file, before_commit: nil)
relative_file = Pathname(file).relative_path_from(repo) relative_file = Pathname(file).relative_path_from(repo)
commit_hash = last_revision_commit_of_file(repo, relative_file, before_commit: before_commit) commit_hash = last_revision_commit_of_file(repo, relative_file, before_commit: before_commit)
file_at_commit(repo, file, commit_hash) file_at_commit(repo, file, commit_hash)
end end
def file_at_commit(repo, file, commit) def self.file_at_commit(repo, file, commit)
relative_file = Pathname(file) relative_file = Pathname(file)
relative_file = relative_file.relative_path_from(repo) if relative_file.absolute? relative_file = relative_file.relative_path_from(repo) if relative_file.absolute?
Utils.popen_read(git, "-C", repo, "show", "#{commit}:#{relative_file}") Utils.popen_read(git, "-C", repo, "show", "#{commit}:#{relative_file}")
end end
def ensure_installed! def self.ensure_installed!
return if available? return if available?
# we cannot install brewed git if homebrew/core is unavailable. # we cannot install brewed git if homebrew/core is unavailable.
@ -114,7 +112,7 @@ module Utils
raise "Git is unavailable" unless available? raise "Git is unavailable" unless available?
end end
def set_name_email!(author: true, committer: true) def self.set_name_email!(author: true, committer: true)
if Homebrew::EnvConfig.git_name if Homebrew::EnvConfig.git_name
ENV["GIT_AUTHOR_NAME"] = Homebrew::EnvConfig.git_name if author ENV["GIT_AUTHOR_NAME"] = Homebrew::EnvConfig.git_name if author
ENV["GIT_COMMITTER_NAME"] = Homebrew::EnvConfig.git_name if committer ENV["GIT_COMMITTER_NAME"] = Homebrew::EnvConfig.git_name if committer
@ -126,17 +124,16 @@ module Utils
ENV["GIT_COMMITTER_EMAIL"] = Homebrew::EnvConfig.git_email if committer ENV["GIT_COMMITTER_EMAIL"] = Homebrew::EnvConfig.git_email if committer
end end
def setup_gpg! def self.setup_gpg!
gnupg_bin = HOMEBREW_PREFIX/"opt/gnupg/bin" gnupg_bin = HOMEBREW_PREFIX/"opt/gnupg/bin"
return unless gnupg_bin.directory? return unless gnupg_bin.directory?
ENV["PATH"] = PATH.new(ENV.fetch("PATH")) ENV["PATH"] = PATH.new(ENV.fetch("PATH")).prepend(gnupg_bin).to_s
.prepend(gnupg_bin)
end end
# Special case of `git cherry-pick` that permits non-verbose output and # Special case of `git cherry-pick` that permits non-verbose output and
# optional resolution on merge conflict. # optional resolution on merge conflict.
def cherry_pick!(repo, *args, resolve: false, verbose: false) def self.cherry_pick!(repo, *args, resolve: false, verbose: false)
cmd = [git, "-C", repo, "cherry-pick"] + args cmd = [git, "-C", repo, "cherry-pick"] + args
output = Utils.popen_read(*cmd, err: :out) output = Utils.popen_read(*cmd, err: :out)
if $CHILD_STATUS.success? if $CHILD_STATUS.success?
@ -148,7 +145,7 @@ module Utils
end end
end end
def supports_partial_clone_sparse_checkout? def self.supports_partial_clone_sparse_checkout?
# There is some support for partial clones prior to 2.20, but we avoid using it # There is some support for partial clones prior to 2.20, but we avoid using it
# due to performance issues # due to performance issues
Version.create(version) >= Version.create("2.20.0") Version.create(version) >= Version.create("2.20.0")

View File

@ -1,4 +1,4 @@
# typed: false # typed: true
# frozen_string_literal: true # frozen_string_literal: true
require "uri" require "uri"
@ -15,9 +15,7 @@ module GitHub
include SystemCommand::Mixin include SystemCommand::Mixin
module_function def self.check_runs(repo: nil, commit: nil, pr: nil)
def check_runs(repo: nil, commit: nil, pr: nil)
if pr if pr
repo = pr.fetch("base").fetch("repo").fetch("full_name") repo = pr.fetch("base").fetch("repo").fetch("full_name")
commit = pr.fetch("head").fetch("sha") commit = pr.fetch("head").fetch("sha")
@ -26,83 +24,83 @@ module GitHub
API.open_rest(url_to("repos", repo, "commits", commit, "check-runs")) API.open_rest(url_to("repos", repo, "commits", commit, "check-runs"))
end end
def create_check_run(repo:, data:) def self.create_check_run(repo:, data:)
API.open_rest(url_to("repos", repo, "check-runs"), data: data) API.open_rest(url_to("repos", repo, "check-runs"), data: data)
end end
def issues(repo:, **filters) def self.issues(repo:, **filters)
uri = url_to("repos", repo, "issues") uri = url_to("repos", repo, "issues")
uri.query = URI.encode_www_form(filters) uri.query = URI.encode_www_form(filters)
API.open_rest(uri) API.open_rest(uri)
end end
def search_issues(query, **qualifiers) def self.search_issues(query, **qualifiers)
search_results_items("issues", query, **qualifiers) search_results_items("issues", query, **qualifiers)
end end
def count_issues(query, **qualifiers) def self.count_issues(query, **qualifiers)
search_results_count("issues", query, **qualifiers) search_results_count("issues", query, **qualifiers)
end end
def create_gist(files, description, private:) def self.create_gist(files, description, private:)
url = "#{API_URL}/gists" url = "#{API_URL}/gists"
data = { "public" => !private, "files" => files, "description" => description } data = { "public" => !private, "files" => files, "description" => description }
API.open_rest(url, data: data, scopes: CREATE_GIST_SCOPES)["html_url"] API.open_rest(url, data: data, scopes: CREATE_GIST_SCOPES)["html_url"]
end end
def create_issue(repo, title, body) def self.create_issue(repo, title, body)
url = "#{API_URL}/repos/#{repo}/issues" url = "#{API_URL}/repos/#{repo}/issues"
data = { "title" => title, "body" => body } data = { "title" => title, "body" => body }
API.open_rest(url, data: data, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)["html_url"] API.open_rest(url, data: data, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)["html_url"]
end end
def repository(user, repo) def self.repository(user, repo)
API.open_rest(url_to("repos", user, repo)) API.open_rest(url_to("repos", user, repo))
end end
def search_code(repo: nil, user: "Homebrew", path: ["Formula", "Casks", "."], filename: nil, extension: "rb") def self.search_code(repo: nil, user: "Homebrew", path: ["Formula", "Casks", "."], filename: nil, extension: "rb")
search_results_items("code", user: user, path: path, filename: filename, extension: extension, repo: repo) search_results_items("code", user: user, path: path, filename: filename, extension: extension, repo: repo)
end end
def issues_for_formula(name, tap: CoreTap.instance, tap_remote_repo: tap&.full_name, state: nil) def self.issues_for_formula(name, tap: CoreTap.instance, tap_remote_repo: tap&.full_name, state: nil)
return [] unless tap_remote_repo return [] unless tap_remote_repo
search_issues(name, repo: tap_remote_repo, state: state, in: "title") search_issues(name, repo: tap_remote_repo, state: state, in: "title")
end end
def user def self.user
@user ||= API.open_rest("#{API_URL}/user") @user ||= API.open_rest("#{API_URL}/user")
end end
def permission(repo, user) def self.permission(repo, user)
API.open_rest("#{API_URL}/repos/#{repo}/collaborators/#{user}/permission") API.open_rest("#{API_URL}/repos/#{repo}/collaborators/#{user}/permission")
end end
def write_access?(repo, user = nil) def self.write_access?(repo, user = nil)
user ||= self.user["login"] user ||= self.user["login"]
["admin", "write"].include?(permission(repo, user)["permission"]) ["admin", "write"].include?(permission(repo, user)["permission"])
end end
def branch_exists?(user, repo, branch) def self.branch_exists?(user, repo, branch)
API.open_rest("#{API_URL}/repos/#{user}/#{repo}/branches/#{branch}") API.open_rest("#{API_URL}/repos/#{user}/#{repo}/branches/#{branch}")
true true
rescue API::HTTPNotFoundError rescue API::HTTPNotFoundError
false false
end end
def pull_requests(repo, **options) def self.pull_requests(repo, **options)
url = "#{API_URL}/repos/#{repo}/pulls?#{URI.encode_www_form(options)}" url = "#{API_URL}/repos/#{repo}/pulls?#{URI.encode_www_form(options)}"
API.open_rest(url) API.open_rest(url)
end end
def merge_pull_request(repo, number:, sha:, merge_method:, commit_message: nil) def self.merge_pull_request(repo, number:, sha:, merge_method:, commit_message: nil)
url = "#{API_URL}/repos/#{repo}/pulls/#{number}/merge" url = "#{API_URL}/repos/#{repo}/pulls/#{number}/merge"
data = { sha: sha, merge_method: merge_method } data = { sha: sha, merge_method: merge_method }
data[:commit_message] = commit_message if commit_message data[:commit_message] = commit_message if commit_message
API.open_rest(url, data: data, request_method: :PUT, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) API.open_rest(url, data: data, request_method: :PUT, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def print_pull_requests_matching(query, only = nil) def self.print_pull_requests_matching(query, only = nil)
open_or_closed_prs = search_issues(query, is: only, type: "pr", user: "Homebrew") open_or_closed_prs = search_issues(query, is: only, type: "pr", user: "Homebrew")
open_prs, closed_prs = open_or_closed_prs.partition { |pr| pr["state"] == "open" } open_prs, closed_prs = open_or_closed_prs.partition { |pr| pr["state"] == "open" }
@ -125,7 +123,7 @@ module GitHub
puts "No pull requests found for #{query.inspect}" if open_prs.blank? && closed_prs.blank? puts "No pull requests found for #{query.inspect}" if open_prs.blank? && closed_prs.blank?
end end
def create_fork(repo, org: nil) def self.create_fork(repo, org: nil)
url = "#{API_URL}/repos/#{repo}/forks" url = "#{API_URL}/repos/#{repo}/forks"
data = {} data = {}
data[:organization] = org if org data[:organization] = org if org
@ -133,7 +131,7 @@ module GitHub
API.open_rest(url, data: data, scopes: scopes) API.open_rest(url, data: data, scopes: scopes)
end end
def fork_exists?(repo, org: nil) def self.fork_exists?(repo, org: nil)
_, reponame = repo.split("/") _, reponame = repo.split("/")
username = org || API.open_rest(url_to("user")) { |json| json["login"] } username = org || API.open_rest(url_to("user")) { |json| json["login"] }
@ -144,19 +142,19 @@ module GitHub
true true
end end
def create_pull_request(repo, title, head, base, body) def self.create_pull_request(repo, title, head, base, body)
url = "#{API_URL}/repos/#{repo}/pulls" url = "#{API_URL}/repos/#{repo}/pulls"
data = { title: title, head: head, base: base, body: body } data = { title: title, head: head, base: base, body: body }
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
API.open_rest(url, data: data, scopes: scopes) API.open_rest(url, data: data, scopes: scopes)
end end
def private_repo?(full_name) def self.private_repo?(full_name)
uri = url_to "repos", full_name uri = url_to "repos", full_name
API.open_rest(uri) { |json| json["private"] } API.open_rest(uri) { |json| json["private"] }
end end
def search_query_string(*main_params, **qualifiers) def self.search_query_string(*main_params, **qualifiers)
params = main_params params = main_params
if (args = qualifiers.fetch(:args, nil)) if (args = qualifiers.fetch(:args, nil))
@ -176,27 +174,27 @@ module GitHub
"q=#{URI.encode_www_form_component(params.compact.join(" "))}&per_page=100" "q=#{URI.encode_www_form_component(params.compact.join(" "))}&per_page=100"
end end
def url_to(*subroutes) def self.url_to(*subroutes)
URI.parse([API_URL, *subroutes].join("/")) URI.parse([API_URL, *subroutes].join("/"))
end end
def search(entity, *queries, **qualifiers) def self.search(entity, *queries, **qualifiers)
uri = url_to "search", entity uri = url_to "search", entity
uri.query = search_query_string(*queries, **qualifiers) uri.query = search_query_string(*queries, **qualifiers)
API.open_rest(uri) API.open_rest(uri)
end end
def search_results_items(entity, *queries, **qualifiers) def self.search_results_items(entity, *queries, **qualifiers)
json = search(entity, *queries, **qualifiers) json = search(entity, *queries, **qualifiers)
json.fetch("items", []) json.fetch("items", [])
end end
def search_results_count(entity, *queries, **qualifiers) def self.search_results_count(entity, *queries, **qualifiers)
json = search(entity, *queries, **qualifiers) json = search(entity, *queries, **qualifiers)
json.fetch("total_count", 0) json.fetch("total_count", 0)
end end
def approved_reviews(user, repo, pr, commit: nil) def self.approved_reviews(user, repo, pr, commit: nil)
query = <<~EOS query = <<~EOS
{ repository(name: "#{repo}", owner: "#{user}") { { repository(name: "#{repo}", owner: "#{user}") {
pullRequest(number: #{pr}) { pullRequest(number: #{pr}) {
@ -237,38 +235,38 @@ module GitHub
end.compact end.compact
end end
def dispatch_event(user, repo, event, **payload) def self.dispatch_event(user, repo, event, **payload)
url = "#{API_URL}/repos/#{user}/#{repo}/dispatches" url = "#{API_URL}/repos/#{user}/#{repo}/dispatches"
API.open_rest(url, data: { event_type: event, client_payload: payload }, API.open_rest(url, data: { event_type: event, client_payload: payload },
request_method: :POST, request_method: :POST,
scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def workflow_dispatch_event(user, repo, workflow, ref, **inputs) def self.workflow_dispatch_event(user, repo, workflow, ref, **inputs)
url = "#{API_URL}/repos/#{user}/#{repo}/actions/workflows/#{workflow}/dispatches" url = "#{API_URL}/repos/#{user}/#{repo}/actions/workflows/#{workflow}/dispatches"
API.open_rest(url, data: { ref: ref, inputs: inputs }, API.open_rest(url, data: { ref: ref, inputs: inputs },
request_method: :POST, request_method: :POST,
scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def get_release(user, repo, tag) def self.get_release(user, repo, tag)
url = "#{API_URL}/repos/#{user}/#{repo}/releases/tags/#{tag}" url = "#{API_URL}/repos/#{user}/#{repo}/releases/tags/#{tag}"
API.open_rest(url, request_method: :GET) API.open_rest(url, request_method: :GET)
end end
def get_latest_release(user, repo) def self.get_latest_release(user, repo)
url = "#{API_URL}/repos/#{user}/#{repo}/releases/latest" url = "#{API_URL}/repos/#{user}/#{repo}/releases/latest"
API.open_rest(url, request_method: :GET) API.open_rest(url, request_method: :GET)
end end
def generate_release_notes(user, repo, tag, previous_tag: nil) def self.generate_release_notes(user, repo, tag, previous_tag: nil)
url = "#{API_URL}/repos/#{user}/#{repo}/releases/generate-notes" url = "#{API_URL}/repos/#{user}/#{repo}/releases/generate-notes"
data = { tag_name: tag } data = { tag_name: tag }
data[:previous_tag_name] = previous_tag if previous_tag.present? data[:previous_tag_name] = previous_tag if previous_tag.present?
API.open_rest(url, data: data, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) API.open_rest(url, data: data, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def create_or_update_release(user, repo, tag, id: nil, name: nil, body: nil, draft: false) def self.create_or_update_release(user, repo, tag, id: nil, name: nil, body: nil, draft: false)
url = "#{API_URL}/repos/#{user}/#{repo}/releases" url = "#{API_URL}/repos/#{user}/#{repo}/releases"
method = if id method = if id
url += "/#{id}" url += "/#{id}"
@ -285,13 +283,13 @@ module GitHub
API.open_rest(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) API.open_rest(url, data: data, request_method: method, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def upload_release_asset(user, repo, id, local_file: nil, remote_file: nil) def self.upload_release_asset(user, repo, id, local_file: nil, remote_file: nil)
url = "https://uploads.github.com/repos/#{user}/#{repo}/releases/#{id}/assets" url = "https://uploads.github.com/repos/#{user}/#{repo}/releases/#{id}/assets"
url += "?name=#{remote_file}" if remote_file url += "?name=#{remote_file}" if remote_file
API.open_rest(url, data_binary_path: local_file, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES) API.open_rest(url, data_binary_path: local_file, request_method: :POST, scopes: CREATE_ISSUE_FORK_OR_PR_SCOPES)
end end
def get_workflow_run(user, repo, pr, workflow_id: "tests.yml", artifact_name: "bottles") def self.get_workflow_run(user, repo, pr, workflow_id: "tests.yml", artifact_name: "bottles")
scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES scopes = CREATE_ISSUE_FORK_OR_PR_SCOPES
# GraphQL unfortunately has no way to get the workflow yml name, so we need an extra REST call. # GraphQL unfortunately has no way to get the workflow yml name, so we need an extra REST call.
@ -344,7 +342,7 @@ module GitHub
[check_suite, user, repo, pr, workflow_id, scopes, artifact_name] [check_suite, user, repo, pr, workflow_id, scopes, artifact_name]
end end
def get_artifact_url(workflow_array) def self.get_artifact_url(workflow_array)
check_suite, user, repo, pr, workflow_id, scopes, artifact_name = *workflow_array check_suite, user, repo, pr, workflow_id, scopes, artifact_name = *workflow_array
if check_suite.empty? if check_suite.empty?
raise API::Error, <<~EOS raise API::Error, <<~EOS
@ -379,7 +377,7 @@ module GitHub
artifact.last["archive_download_url"] artifact.last["archive_download_url"]
end end
def public_member_usernames(org, per_page: 100) def self.public_member_usernames(org, per_page: 100)
url = "#{API_URL}/orgs/#{org}/public_members" url = "#{API_URL}/orgs/#{org}/public_members"
members = [] members = []
@ -391,7 +389,7 @@ module GitHub
end end
end end
def members_by_team(org, team) def self.members_by_team(org, team)
query = <<~EOS query = <<~EOS
{ organization(login: "#{org}") { { organization(login: "#{org}") {
teams(first: 100) { teams(first: 100) {
@ -420,11 +418,11 @@ module GitHub
result["organization"]["team"]["members"]["nodes"].to_h { |member| [member["login"], member["name"]] } result["organization"]["team"]["members"]["nodes"].to_h { |member| [member["login"], member["name"]] }
end end
def sponsorships(user) def self.sponsorships(user)
has_next_page = true has_next_page = T.let(true, T::Boolean)
after = "" after = ""
sponsorships = [] sponsorships = T.let([], T::Array[Hash])
errors = [] errors = T.let([], T::Array[Hash])
while has_next_page while has_next_page
query = <<~EOS query = <<~EOS
{ organization(login: "#{user}") { { organization(login: "#{user}") {
@ -494,7 +492,7 @@ module GitHub
end end
end end
def get_repo_license(user, repo) def self.get_repo_license(user, repo)
response = API.open_rest("#{API_URL}/repos/#{user}/#{repo}/license") response = API.open_rest("#{API_URL}/repos/#{user}/#{repo}/license")
return unless response.key?("license") return unless response.key?("license")
@ -503,7 +501,7 @@ module GitHub
nil nil
end end
def fetch_pull_requests(name, tap_remote_repo, state: nil, version: nil) def self.fetch_pull_requests(name, tap_remote_repo, state: nil, version: nil)
if version.present? if version.present?
query = "#{name} #{version} is:pr" query = "#{name} #{version} is:pr"
regex = /(^|\s)#{Regexp.quote(name)}(:|,|\s)(.*\s)?#{Regexp.quote(version)}(:|,|\s|$)/i regex = /(^|\s)#{Regexp.quote(name)}(:|,|\s)(.*\s)?#{Regexp.quote(version)}(:|,|\s|$)/i
@ -519,7 +517,7 @@ module GitHub
[] []
end end
def check_for_duplicate_pull_requests(name, tap_remote_repo, state:, file:, args:, version: nil) def self.check_for_duplicate_pull_requests(name, tap_remote_repo, state:, file:, args:, version: nil)
pull_requests = fetch_pull_requests(name, tap_remote_repo, state: state, version: version).select do |pr| pull_requests = fetch_pull_requests(name, tap_remote_repo, state: state, version: version).select do |pr|
get_pull_request_changed_files( get_pull_request_changed_files(
tap_remote_repo, pr["number"] tap_remote_repo, pr["number"]
@ -544,11 +542,11 @@ module GitHub
end end
end end
def get_pull_request_changed_files(tap_remote_repo, pr) def self.get_pull_request_changed_files(tap_remote_repo, pr)
API.open_rest(url_to("repos", tap_remote_repo, "pulls", pr, "files")) API.open_rest(url_to("repos", tap_remote_repo, "pulls", pr, "files"))
end end
def forked_repo_info!(tap_remote_repo, org: nil) def self.forked_repo_info!(tap_remote_repo, org: nil)
response = create_fork(tap_remote_repo, org: org) response = create_fork(tap_remote_repo, org: org)
# GitHub API responds immediately but fork takes a few seconds to be ready. # GitHub API responds immediately but fork takes a few seconds to be ready.
sleep 1 until fork_exists?(tap_remote_repo, org: org) sleep 1 until fork_exists?(tap_remote_repo, org: org)
@ -565,7 +563,7 @@ module GitHub
[remote_url, username] [remote_url, username]
end end
def create_bump_pr(info, args:) def self.create_bump_pr(info, args:)
tap = info[:tap] tap = info[:tap]
sourcefile_path = info[:sourcefile_path] sourcefile_path = info[:sourcefile_path]
old_contents = info[:old_contents] old_contents = info[:old_contents]
@ -658,7 +656,7 @@ module GitHub
end end
end end
def pull_request_commits(user, repo, pr, per_page: 100) def self.pull_request_commits(user, repo, pr, per_page: 100)
pr_data = API.open_rest(url_to("repos", user, repo, "pulls", pr)) pr_data = API.open_rest(url_to("repos", user, repo, "pulls", pr))
commits_api = pr_data["commits_url"] commits_api = pr_data["commits_url"]
commit_count = pr_data["commits"] commit_count = pr_data["commits"]
@ -679,12 +677,12 @@ module GitHub
end end
end end
def pull_request_labels(user, repo, pr) def self.pull_request_labels(user, repo, pr)
pr_data = API.open_rest(url_to("repos", user, repo, "pulls", pr)) pr_data = API.open_rest(url_to("repos", user, repo, "pulls", pr))
pr_data["labels"].map { |label| label["name"] } pr_data["labels"].map { |label| label["name"] }
end end
def last_commit(user, repo, ref) def self.last_commit(user, repo, ref, version)
return if Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_github_api?
output, _, status = curl_output( output, _, status = curl_output(
@ -702,7 +700,7 @@ module GitHub
commit commit
end end
def multiple_short_commits_exist?(user, repo, commit) def self.multiple_short_commits_exist?(user, repo, commit)
return if Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_github_api?
output, _, status = curl_output( output, _, status = curl_output(
@ -717,7 +715,7 @@ module GitHub
output[/^Status: (200)/, 1] != "200" output[/^Status: (200)/, 1] != "200"
end end
def repo_commits_for_user(nwo, user, filter, args) def self.repo_commits_for_user(nwo, user, filter, args)
return if Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_github_api?
params = ["#{filter}=#{user}"] params = ["#{filter}=#{user}"]
@ -731,7 +729,7 @@ module GitHub
commits commits
end end
def count_repo_commits(nwo, user, filter, args) def self.count_repo_commits(nwo, user, filter, args)
return if Homebrew::EnvConfig.no_github_api? return if Homebrew::EnvConfig.no_github_api?
author_shas = repo_commits_for_user(nwo, user, "author", args) author_shas = repo_commits_for_user(nwo, user, "author", args)