From f16b2d744512bd16ad9178da69ab3407397a3b43 Mon Sep 17 00:00:00 2001 From: Rok Mihevc Date: Fri, 23 Oct 2026 19:21:51 +0200 Subject: [PATCH] GH-55163: [CI] Replace the PR Limit workflow with the .asf.yaml pull request creation cap GitHub's native pull request creation cap can now be configured in .asf.yaml (apache/infrastructure-asfyaml#111), so configure it there with the same limit and remove the workflow and its helper script. Draft pull requests are included to match the workflow's behaviour. Update the contributor docs: GitHub refuses to open a pull request over the limit instead of closing it afterwards with a comment, and the cap only exempts users with write access. --- .asf.yaml | 10 ++ .github/workflows/pr_limit.yml | 66 ------------- .github/workflows/pr_limit/check.js | 130 ------------------------- .github/workflows/pr_limit/comment.md | 31 ------ docs/source/developers/bug_reports.rst | 11 +-- 5 files changed, 15 insertions(+), 233 deletions(-) delete mode 100644 .github/workflows/pr_limit.yml delete mode 100644 .github/workflows/pr_limit/check.js delete mode 100644 .github/workflows/pr_limit/comment.md diff --git a/.asf.yaml b/.asf.yaml index 64a06da6b9c1..4f46a936b080 100644 --- a/.asf.yaml +++ b/.asf.yaml @@ -45,6 +45,16 @@ github: review_drafts: false review_on_push: true + pull_requests: + # Limit the number of concurrently open pull requests a contributor + # without write access can have. Pull requests over the cap can't be + # opened; draft pull requests count towards the cap. See + # https://arrow.apache.org/docs/dev/developers/bug_reports.html#pr-limit + creation_cap: + enabled: true + max_open_pull_requests: 3 + include_drafts: true + notifications: commits: commits@arrow.apache.org issues_status: issues@arrow.apache.org diff --git a/.github/workflows/pr_limit.yml b/.github/workflows/pr_limit.yml deleted file mode 100644 index a34099417252..000000000000 --- a/.github/workflows/pr_limit.yml +++ /dev/null @@ -1,66 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -# Limits the number of concurrently open pull requests a contributor -# without repository access can have. This mirrors GitHub's native -# "pull request creation cap" setting, which requires admin rights and -# so can only be configured for ASF repositories via .asf.yaml. Once -# https://github.com/apache/infrastructure-asfyaml/pull/111 is merged, -# this workflow can be replaced by the `github.pull_requests.creation_cap` -# directive in .asf.yaml. - -name: PR Limit - -on: - pull_request_target: - types: - - opened - - reopened - -concurrency: - group: ${{ github.workflow }}-${{ github.repository }}-${{ github.event.number }} - cancel-in-progress: true - -permissions: - contents: read - pull-requests: write - issues: write -env: - # Maximum number of open pull requests per contributor without repository - # access. A pull request that takes the contributor over this limit is - # closed with an explanatory comment. - PR_LIMIT: 3 - -jobs: - check: - name: Check - runs-on: ubuntu-latest - steps: - # Always run the script from main, never from the PR head. - - uses: actions/checkout@v7 - with: - repository: apache/arrow - ref: main - persist-credentials: false - - - name: Check open pull request count - uses: actions/github-script@v9 - with: - github-token: ${{ secrets.GITHUB_TOKEN }} - script: | - const script = require(`${process.env.GITHUB_WORKSPACE}/.github/workflows/pr_limit/check.js`); - await script({github, context, core}); diff --git a/.github/workflows/pr_limit/check.js b/.github/workflows/pr_limit/check.js deleted file mode 100644 index 1c4d50b07986..000000000000 --- a/.github/workflows/pr_limit/check.js +++ /dev/null @@ -1,130 +0,0 @@ -// Licensed to the Apache Software Foundation (ASF) under one -// or more contributor license agreements. See the NOTICE file -// distributed with this work for additional information -// regarding copyright ownership. The ASF licenses this file -// to you under the Apache License, Version 2.0 (the -// "License"); you may not use this file except in compliance -// with the License. You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, -// software distributed under the License is distributed on an -// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -// KIND, either express or implied. See the License for the -// specific language governing permissions and limitations -// under the License. - -const fs = require("fs"); - -const HAS_ACCESS = new Set(["triage", "write", "maintain", "admin"]); - -/** - * Returns whether the user has repository access. - * - * Note that `author_association` is not a reliable signal for this: - * ASF members show up as MEMBER regardless of their permission on this - * repository, and triage collaborators show up as COLLABORATOR. - * - * @param {Object} github - * @param {Object} context - * @param {String} username - */ -async function hasAccess(github, context, username) { - try { - const {data} = await github.rest.repos.getCollaboratorPermissionLevel({ - owner: context.repo.owner, - repo: context.repo.repo, - username: username - }); - return HAS_ACCESS.has(data.role_name); - } catch (error) { - if (error.status === 404) { - return false; - } - throw error; - } -} - -/** - * Returns the number of open pull requests authored by the user in this - * repository, including the one that triggered the workflow. - * - * @param {Object} github - * @param {Object} context - * @param {String} username - */ -async function countOpenPullRequests(github, context, username) { - const {data} = await github.rest.search.issuesAndPullRequests({ - q: `repo:${context.repo.owner}/${context.repo.repo} is:pr is:open author:${username}`, - per_page: 1 - }); - return data.total_count; -} - -/** - * Comments on the pull request explaining the limit, then closes it. - * - * @param {Object} github - * @param {Object} context - * @param {Number} pullRequestNumber - * @param {String} username - * @param {Number} limit - * @param {Number} count - */ -async function commentAndClose(github, context, pullRequestNumber, username, limit, count) { - const commentPath = ".github/workflows/pr_limit/comment.md"; - const comment = fs.readFileSync(commentPath).toString() - .replaceAll("${PR_LIMIT}", limit) - .replaceAll("${OPEN_COUNT}", count) - .replaceAll("${USERNAME}", username); - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pullRequestNumber, - body: comment - }); - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pullRequestNumber, - state: "closed" - }); -} - -module.exports = async ({github, context, core}) => { - const limit = parseInt(process.env.PR_LIMIT, 10); - if (!Number.isInteger(limit) || limit < 1) { - throw new Error(`PR_LIMIT must be a positive integer, got: ${process.env.PR_LIMIT}`); - } - - const pullRequestNumber = context.payload.number; - const user = context.payload.pull_request.user; - - if (user.type === "Bot") { - core.info(`Skipping: ${user.login} is a bot.`); - return; - } - - if (await hasAccess(github, context, user.login)) { - core.info(`Skipping: ${user.login} has repository access.`); - return; - } - - // A user with repository access reopening a previously closed pull request - // is a deliberate decision to accept it, so don't close it again. - const sender = context.payload.sender; - if (sender.login !== user.login && await hasAccess(github, context, sender.login)) { - core.info(`Skipping: ${context.payload.action} by ${sender.login}, who has repository access.`); - return; - } - - const count = await countOpenPullRequests(github, context, user.login); - core.info(`${user.login} has ${count} open pull request(s); limit is ${limit}.`); - if (count <= limit) { - return; - } - - core.info(`Closing #${pullRequestNumber}: over the limit.`); - await commentAndClose(github, context, pullRequestNumber, user.login, limit, count); -}; diff --git a/.github/workflows/pr_limit/comment.md b/.github/workflows/pr_limit/comment.md deleted file mode 100644 index 46cdb0f2df37..000000000000 --- a/.github/workflows/pr_limit/comment.md +++ /dev/null @@ -1,31 +0,0 @@ - - -Thanks for opening a pull request! - -**This pull request has been automatically closed because you currently have ${OPEN_COUNT} open pull requests, which is more than the limit of ${PR_LIMIT}.** - -Due to the increase in pull requests opened by AI bots, and in order to keep the review queue manageable, Apache Arrow limits contributors without repository access to at most ${PR_LIMIT} concurrently open pull requests. This helps make sure each pull request gets the attention it needs and that work in progress does not go stale. - -Once one of [your other open pull requests](https://github.com/apache/arrow/pulls/${USERNAME}) has been merged or closed, you are welcome to reopen this one. - -See also: - - * [Contribution Guidelines - Limit on concurrent pull requests](https://arrow.apache.org/docs/dev/developers/bug_reports.html#pr-limit) - * [Contribution Guidelines - Contributing Overview](https://arrow.apache.org/docs/developers/overview.html) diff --git a/docs/source/developers/bug_reports.rst b/docs/source/developers/bug_reports.rst index 1fcc0bb07e03..d3d01e1ba79a 100644 --- a/docs/source/developers/bug_reports.rst +++ b/docs/source/developers/bug_reports.rst @@ -243,12 +243,11 @@ Limit on concurrent pull requests +++++++++++++++++++++++++++++++++ Due to the increase in pull requests opened by AI bots, and in order to keep -the review queue manageable, contributors without the required access to the -repository may have at most **3 pull requests open at the same time**. -The required access is either write access (committers) or triage (collaborators). -A pull request opened beyond that limit is automatically closed by a GitHub -Actions workflow, with a comment explaining why. Once one of your other pull -requests has been merged or closed, you can reopen it. +the review queue manageable, contributors without write access to the +repository may have at most **3 pull requests open at the same time**, +including draft pull requests. GitHub does not allow opening a pull request +beyond that limit. Once one of your other pull requests has been merged or +closed, you can open a new one. .. _issue-assignment: