diff --git a/Library/Homebrew/dev-cmd/bump-cask-pr.rb b/Library/Homebrew/dev-cmd/bump-cask-pr.rb index cc235e9af9d3c..4f520d67c1616 100644 --- a/Library/Homebrew/dev-cmd/bump-cask-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-cask-pr.rb @@ -93,6 +93,8 @@ def run #{Formatter.url("#{cask.tap.remote}/blob/master/.github/autobump.txt")} EOS + odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(cask.tap) + new_version = BumpVersionParser.new( general: args.version, intel: args.version_intel, diff --git a/Library/Homebrew/dev-cmd/bump-formula-pr.rb b/Library/Homebrew/dev-cmd/bump-formula-pr.rb index 6ba96dd76b225..2c972aab94ab7 100644 --- a/Library/Homebrew/dev-cmd/bump-formula-pr.rb +++ b/Library/Homebrew/dev-cmd/bump-formula-pr.rb @@ -120,6 +120,8 @@ def run #{Formatter.url("#{formula.tap.remote}/blob/master/.github/autobump.txt")} EOS + odie "You have too many PRs open: close or merge some first!" if GitHub.too_many_open_prs?(formula.tap) + formula_spec = formula.stable odie "#{formula}: no stable specification found!" if formula_spec.blank? diff --git a/Library/Homebrew/dev-cmd/bump.rb b/Library/Homebrew/dev-cmd/bump.rb index cc19ca70be6c7..3de0bd860a4ef 100644 --- a/Library/Homebrew/dev-cmd/bump.rb +++ b/Library/Homebrew/dev-cmd/bump.rb @@ -499,6 +499,10 @@ def retrieve_and_display_info_and_open_pr(formula_or_cask, name, repositories, a return unless args.open_pr? + if GitHub.too_many_open_prs?(formula_or_cask.tap) + odie "You have too many PRs open: close or merge some first!" + end + if repology_latest.is_a?(Version) && repology_latest > current_version.general && repology_latest > new_version.general && diff --git a/Library/Homebrew/utils/github.rb b/Library/Homebrew/utils/github.rb index 5117f58b1268e..17f6ec40b0209 100644 --- a/Library/Homebrew/utils/github.rb +++ b/Library/Homebrew/utils/github.rb @@ -863,4 +863,60 @@ def self.count_repo_commits(nwo, user, args, max: nil) [author_count, committer_count] end + + MAXIMUM_OPEN_PRS = 15 + + sig { params(tap: T.nilable(Tap)).returns(T::Boolean) } + def self.too_many_open_prs?(tap) + # We don't enforce unofficial taps. + return false if tap.nil? || !tap.official? + + # BrewTestBot can open as many PRs as it wants. + return false if ENV["HOMEBREW_TEST_BOT_AUTOBUMP"].present? + + odie "Cannot count PRs, HOMEBREW_NO_GITHUB_API set!" if Homebrew::EnvConfig.no_github_api? + + query = <<~EOS + query { + viewer { + login + pullRequests(first: 100, states: OPEN) { + nodes { + headRepositoryOwner { + login + } + } + pageInfo { + hasNextPage + } + } + } + } + EOS + graphql_result = API.open_graphql(query) + puts + + github_user = graphql_result.dig("viewer", "login") + odie "Cannot count PRs, cannot get GitHub username from GraphQL API!" if github_user.blank? + + # BrewTestBot can open as many PRs as it wants. + return false if github_user.casecmp("brewtestbot").zero? + + prs = graphql_result.dig("viewer", "pullRequests", "nodes") + more_graphql_data = graphql_result.dig("viewer", "pullRequests", "pageInfo", "hasNextPage") + return false if !more_graphql_data && prs.length < MAXIMUM_OPEN_PRS + + homebrew_prs_count = graphql_result.dig("viewer", "pullRequests", "nodes").count do |pr| + pr["headRepositoryOwner"]["login"] == "Homebrew" + end + return true if homebrew_prs_count >= MAXIMUM_OPEN_PRS + return false unless more_graphql_data + return false if tap.nil? + + url = "#{API_URL}/repos/#{tap.full_name}/issues?state=open&creator=#{github_user}" + rest_result = API.open_rest(url) + repo_prs_count = rest_result.count { |issue_or_pr| issue_or_pr.key?("pull_request") } + + repo_prs_count >= MAXIMUM_OPEN_PRS + end end