diff --git a/.github/workflows/README.md b/.github/workflows/README.md index d50ef7cd58a..4d39a9280cb 100644 --- a/.github/workflows/README.md +++ b/.github/workflows/README.md @@ -378,18 +378,19 @@ exist in repository settings, like the `run-*` labels above. These workflows have their own triggers because they fire on events the umbrella doesn't watch, or operate independently of the rest of CI: -| File | Why standalone | -| ---------------------- | ---------------------------------------------------------------------------------------------------- | -| `pr_title_check.yml` | Fires on `pull_request.types: [edited]` so it re-runs when a PR title is edited without a code push. | -| `codeql.yml` | Security scanner; weekly schedule + on every push/PR. | -| `miri.yml` | Nightly Miri safety checks. | -| `publish_snapshot.yml` | Nightly SNAPSHOT jars to repository.apache.org; skips when main has not changed. `dry_run` dispatch. | -| `stale.yml` | Daily stale-PR closer. | -| `take.yml` | Issue-comment trigger for `take` / `untake`. | -| `label_new_issues.yml` | Issue trigger to apply `requires-triage`. | -| `label_prs.yml` | Runs on `pull_request_target` so it can label pull requests opened from forks. | -| `ci_fork.yml` | Runs `ci.yml` on a push to a fork's branch; skipped upstream. See [Fork runs](#fork-runs). | -| `fork_ci_status.yml` | Mirrors a fork's run onto its pull request as `CI (fork)`. See [Fork runs](#fork-runs). | +| File | Why standalone | +| ----------------------- | ------------------------------------------------------------------------------------------------------- | +| `pr_title_check.yml` | Fires on `pull_request.types: [edited]` so it re-runs when a PR title is edited without a code push. | +| `codeql.yml` | Security scanner; weekly schedule + on every push/PR. | +| `miri.yml` | Nightly Miri safety checks. | +| `publish_snapshot.yml` | Nightly SNAPSHOT jars to repository.apache.org; skips when main has not changed. `dry_run` dispatch. | +| `stale.yml` | Daily stale-PR closer. | +| `take.yml` | Issue-comment trigger for `take` / `untake`. | +| `label_new_issues.yml` | Issue trigger to apply `requires-triage`. | +| `label_prs_trigger.yml` | Fires on `pull_request` with a read-only token and does nothing; its completion starts `label_prs.yml`. | +| `label_prs.yml` | Runs on `workflow_run` from the default branch so it can label pull requests opened from forks. | +| `ci_fork.yml` | Runs `ci.yml` on a push to a fork's branch; skipped upstream. See [Fork runs](#fork-runs). | +| `fork_ci_status.yml` | Mirrors a fork's run onto its pull request as `CI (fork)`. See [Fork runs](#fork-runs). | ## Reusable workflows (called by `ci.yml`) diff --git a/.github/workflows/label_prs.yml b/.github/workflows/label_prs.yml index 7eaff2baf8f..e052011551a 100644 --- a/.github/workflows/label_prs.yml +++ b/.github/workflows/label_prs.yml @@ -15,73 +15,101 @@ # specific language governing permissions and limitations # under the License. + name: Label pull requests -# Runs on pull_request_target so it can label pull requests from forks. Nothing from the pull -# request is checked out or executed: the labeler reads the changed file list through the API -# and the title step reads the event payload. +# Labels a pull request after label_prs_trigger.yml runs for it. GitHub blocks +# pull_request_target on public repositories, and a pull_request run from a fork only gets a +# read-only token, so the labels are written here: workflow_run runs this file from the default +# branch with a token that can write them. Nothing from the pull request is checked out or +# executed, and nothing its run produced is read. The pull request is looked up from the run's +# head repository, branch and commit, and its title, labels and changed files come from the API. on: - pull_request_target: - types: [opened, synchronize, reopened] + workflow_run: + workflows: [Label pull requests trigger] + types: [completed] -permissions: - contents: read - pull-requests: write +permissions: {} -# One group per pull request with cancellation, so at most one run writes labels at a time: -# the labeler action reads the label set and writes it back whole, and two runs writing at -# once can drop a label the other just added. A cancelled or replaced run loses nothing -# because every step below is safe to repeat on the next event. +# One group per pull request branch with cancellation, so at most one run writes labels at a +# time: the labeler action reads the label set and writes it back whole, and two runs writing at +# once can drop a label the other just added. A cancelled or replaced run loses nothing because +# every step below is safe to repeat on the next event. workflow_run carries the head repository +# and branch rather than the pull request number. concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number }} + group: ${{ github.workflow }}-${{ github.event.workflow_run.head_repository.full_name }}-${{ github.event.workflow_run.head_branch }} cancel-in-progress: true jobs: label: + if: >- + github.repository == 'apache/datafusion-comet' + && github.event.workflow_run.event == 'pull_request' + permissions: + contents: read + pull-requests: write runs-on: ubuntu-slim + timeout-minutes: 10 steps: - # Checks out the base branch, never the pull request's code: under pull_request_target the - # default ref is the base commit, and only dev/ci/pr-type-label.mjs is read from it. + # Checks out the default branch, never the pull request's code: under workflow_run the + # default ref is the default branch, and only dev/ci/pr-type-label.mjs is read from it. - uses: actions/checkout@v7 with: persist-credentials: false # The type label follows the conventional commit prefix in the title, matching how - # maintainers label by hand. The decision reads the pull request's current labels, not the - # event payload, so a type label a maintainer applied before this run started stays and - # a run cut short by a newer push is completed by that push. Dependabot pull requests - # already carry their label. - - name: Label by title prefix - if: github.actor != 'dependabot[bot]' + # maintainers label by hand. The decision reads the pull request's current labels, so a + # type label a maintainer applied before this run started stays. A pull request whose head + # has moved on since the triggering run is skipped: the run for the newer push labels it. + # Dependabot pull requests already carry their label. + - name: Find the pull request and label by title prefix + id: pr uses: actions/github-script@v9 with: script: | + const run = context.payload.workflow_run; + const head = run.head_repository; + if (!head) { + core.info('The head repository no longer exists'); + return; + } + const repo = { owner: context.repo.owner, repo: context.repo.repo }; + const pulls = await github.paginate(github.rest.pulls.list, { + ...repo, + state: 'open', + head: `${head.owner.login}:${run.head_branch}`, + per_page: 100, + }); + const pr = pulls.find((item) => + item.head.sha === run.head_sha && item.head.repo?.full_name === head.full_name); + if (!pr) { + core.info(`No open pull request has head ${head.full_name}@${run.head_sha}`); + return; + } + core.setOutput('number', pr.number); + if (pr.user.login === 'dependabot[bot]') { + core.info('Dependabot pull requests already carry their type label'); + return; + } const { typeLabelsToAdd } = await import( `${process.env.GITHUB_WORKSPACE}/dev/ci/pr-type-label.mjs` ); const { data: current } = await github.rest.issues.listLabelsOnIssue({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, + ...repo, + issue_number: pr.number, per_page: 100, }); - const labels = typeLabelsToAdd( - context.payload.pull_request.title, - current.map((label) => label.name), - ); + const labels = typeLabelsToAdd(pr.title, current.map((label) => label.name)); if (labels.length === 0) { core.info('No type label to add'); return; } - await github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels, - }); + await github.rest.issues.addLabels({ ...repo, issue_number: pr.number, labels }); - name: Label by changed paths + if: steps.pr.outputs.number != '' uses: actions/labeler@v7 with: configuration-path: .github/labeler.yml + pr-number: ${{ steps.pr.outputs.number }} sync-labels: false diff --git a/.github/workflows/label_prs_trigger.yml b/.github/workflows/label_prs_trigger.yml new file mode 100644 index 00000000000..5ce8c6ecb8c --- /dev/null +++ b/.github/workflows/label_prs_trigger.yml @@ -0,0 +1,36 @@ +# 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. + + +name: Label pull requests trigger + +# Starts the labeler for a pull request. This run gets a read-only token and does nothing itself; +# when it completes, label_prs.yml runs from the default branch through workflow_run and writes +# the labels. Kept separate from Comet CI so labels arrive in seconds rather than after CI. +on: + pull_request: + types: [opened, synchronize, reopened] + +permissions: {} + +jobs: + trigger: + runs-on: ubuntu-slim + timeout-minutes: 5 + steps: + - name: Hand off to label_prs.yml + run: echo "Labels for pull request ${{ github.event.pull_request.number }} are applied by label_prs.yml"