jerryshao commented on code in PR #13049: URL: https://github.com/apache/gravitino/pull/13049#discussion_r4344501580
########## .github/workflows/asf-allowlist-check.yml: ########## @@ -20,15 +20,13 @@ name: "ASF Allowlist Check" on: - workflow_dispatch: + # Also run upstream for pull requests that change workflows or actions, so the Review Comment: This allowlist job does not actually escape the fork's self-report. `pull_request` runs the workflow from the merge commit, so a PR can edit `asf-allowlist-check.yml` and skip the scan, and `.asf.yaml` does not require the check anyway. Please run it with `pull_request_target` (definition from the default branch), check out the PR head with `persist-credentials: false`, run only the Apache allowlist action, and add the job's check name to `required_status_checks.contexts`. ########## .github/workflows/fork-ci.yml: ########## @@ -0,0 +1,310 @@ +# 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. + +# Run the complete CI entry point from branch pushes in the contributor's repository. +name: Fork CI + +on: + push: + branches: ['**'] + +concurrency: + group: fork-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + eligibility: + name: Resolve fork CI eligibility + # Downstream mirrors can set this repository variable to keep the + # inherited workflow completely dormant. + if: vars.DISABLE_FORK_CI != 'true' + runs-on: ubuntu-slim + outputs: + applicable: ${{ steps.resolve.outputs.applicable }} + valid: ${{ steps.resolve.outputs.valid == 'true' || steps.merge.outputs.valid == 'true' }} + steps: + - name: Resolve repository and pull request + id: resolve + env: + GH_TOKEN: ${{ github.token }} + HEAD_BRANCH: ${{ github.ref_name }} + HEAD_OWNER: ${{ github.repository_owner }} + HEAD_REPOSITORY: ${{ github.repository }} + HEAD_SHA: ${{ github.sha }} + REPOSITORY_FORK: ${{ github.event.repository.fork }} + run: | + set -euo pipefail + + echo "applicable=false" >> "${GITHUB_OUTPUT}" + echo "valid=false" >> "${GITHUB_OUTPUT}" + + if [[ "${HEAD_REPOSITORY}" == "apache/gravitino" && \ + ("${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == refs/heads/branch-*) ]]; then + echo "applicable=true" >> "${GITHUB_OUTPUT}" + echo "base_ref=${GITHUB_REF_NAME}" >> "${GITHUB_OUTPUT}" + echo "valid=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ "${HEAD_REPOSITORY}" != "apache/gravitino" && "${REPOSITORY_FORK}" != "true" ]]; then + echo "This repository is not a fork; it runs its own CI and Fork CI is dormant." + exit 0 + fi + + repository_response="$(curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "${GITHUB_API_URL}/repos/${HEAD_REPOSITORY}")" + source_repository="$(jq -r '.source.full_name // empty' <<< "${repository_response}")" + + if [[ "${HEAD_REPOSITORY}" != "apache/gravitino" && \ + "${source_repository}" != "apache/gravitino" ]]; then + echo "This repository is not in the apache/gravitino fork network; Fork CI is dormant." + exit 0 + fi + + echo "applicable=true" >> "${GITHUB_OUTPUT}" + matches="[]" + match_count=0 + # Give the contributor a few minutes to open the pull request after pushing. + for attempt in $(seq 1 12); do + response="$(curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + --get \ + --data-urlencode "state=open" \ + --data-urlencode "head=${HEAD_OWNER}:${HEAD_BRANCH}" \ + --data-urlencode "per_page=10" \ + "https://api.github.com/repos/apache/gravitino/pulls")" + matches="$(jq -r \ + --arg head_sha "${HEAD_SHA}" \ + --arg head_repository "${HEAD_REPOSITORY}" \ + '[.[] | select(.head.sha == $head_sha and .head.repo.full_name == $head_repository)]' \ + <<< "${response}")" + match_count="$(jq -r 'length' <<< "${matches}")" + if [[ "${match_count}" == "1" ]]; then + break + fi + if [[ "${match_count}" != "0" || "${attempt}" == "12" ]]; then + break + fi + sleep 15 + done + + if [[ "${match_count}" == "0" && "${HEAD_REPOSITORY}" == "apache/gravitino" ]]; then + # Branches in the upstream repository only run for pull requests. + echo "No open pull request for ${HEAD_BRANCH}; Fork CI is dormant." + echo "applicable=false" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + api() { + curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/apache/gravitino$1" + } + + if [[ "${match_count}" == "0" ]]; then Review Comment: If the push has no open PR yet, Fork CI guesses a base (default branch or one of the three newest `branch-X.Y` branches) and the status workflows mirror that run onto whatever PR is opened later. They never compare the tested base with `pr.base.ref`, and `edited` is not a `pull_request_target` type, so retargeting the PR does not retest. A branch cut from a release branch will guess that release branch, while the GitHub PR form defaults to `main`, and the required check can be green for a merge that was not tested. Please do not treat a pre-PR run as success unless its base is the PR base, and invalidate the check when the base branch changes. ########## .asf.yaml: ########## @@ -46,7 +46,10 @@ github: protected_branches: main: required_status_checks: - strict: true + strict: false Review Comment: `strict: false` means a Fork CI success is not invalidated when `main` moves after the run. The suites do merge the base tip at checkout, but nothing re-runs or fails the check once that tip changes, and the previous protection required the branch to be up to date. If that relaxation is deliberate, please record the tested base SHA on the check and withhold success when it is no longer the tip of the PR base. ########## .github/workflows/fork-ci-status-update.yml: ########## @@ -0,0 +1,299 @@ +# 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: Update Fork CI Status + +on: + schedule: + - cron: '*/5 * * * *' + workflow_dispatch: + +permissions: + actions: read + checks: write + contents: read + pull-requests: read + +concurrency: + group: fork-ci-status-update + cancel-in-progress: false + +jobs: + update: + name: Update fork CI checks + if: github.repository == 'apache/gravitino' + runs-on: ubuntu-slim + steps: + # The state of the previous sweep lets this sweep skip pull requests that have a + # final result and no new activity, without spending API calls on them. + - name: Restore previous sweep state + uses: actions/cache/restore@v4 + with: + path: .fork-ci-status + key: fork-ci-status-${{ github.run_id }}-${{ github.run_attempt }} + restore-keys: fork-ci-status- + + - name: Synchronize fork CI checks + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + retries: 3 + script: | + const fs = require('fs'); + const stateFile = '.fork-ci-status/state.json'; + const HOUR = 60 * 60 * 1000; + // A pull request with activity in this window is always checked, which covers + // the time between a push and the check created by the Fork CI Status workflow. + const RECENT_ACTIVITY = HOUR; + // How long a completed check is still watched for a re-run of the fork workflow. + // action_required is also set when the fork run had not started when the check was created. + const WATCH = { action_required: 24 * HOUR, default: 7 * 24 * HOUR }; + // Pull requests without a running check are checked at most this often, so sweeping + // every 5 minutes spends API calls mainly on the checks that are about to change. + const IDLE_INTERVAL = 15 * 60 * 1000; + const FINAL = ['success', 'neutral', 'skipped']; + + const rateLimit = async () => (await github.rest.rateLimit.get()).data.resources.core; + const budgetBefore = await rateLimit(); + let calls = 0; + github.hook.before('request', () => { calls++; }); + + let previous = {}; + try { + previous = JSON.parse(fs.readFileSync(stateFile, 'utf8')); + } catch (error) { + core.info('No previous sweep state; checking every pull request.'); + } + const next = {}; + const now = Date.now(); + + // The check mirrors the Required CI job rather than the run conclusion: a run whose + // suites were all skipped (no pull request yet, or Fork CI disabled in the fork) + // still concludes as success. Failed jobs are listed while the run is still going, + // so reviewers and contributors can open their logs without waiting for the run. + const mirror = async (owner, repo, run) => { + const jobs = await github.paginate( + 'GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs', + { owner, repo, run_id: run.id, attempt_number: run.run_attempt, per_page: 100 }); + const failed = jobs.filter(job => + job.name !== 'Required CI' && ['failure', 'timed_out'].includes(job.conclusion)); + const failedNote = failed.length + ? `Failed jobs:\n${failed.map(job => `- [${job.name}](${job.html_url})`).join('\n')}` + : ''; + if (run.status !== 'completed') { + // A run stays queued while any of its jobs waits for a runner, even after other + // jobs have finished, so report it as in progress once any job has started. + const started = jobs.some(job => ['in_progress', 'completed'].includes(job.status)); + return { + status: started ? 'in_progress' : 'queued', + title: failed.length ? `Running, ${failed.length} job(s) failed so far` : 'Running', + note: failedNote + }; + } + if (run.conclusion !== 'success') { + // Check runs reject some run conclusions, such as startup_failure. + const conclusion = + ['action_required', 'cancelled', 'failure', 'timed_out'].includes(run.conclusion) + ? run.conclusion + : (['neutral', 'skipped'].includes(run.conclusion) ? 'action_required' : 'failure'); + return { + status: 'completed', conclusion, + title: failed.length ? `${failed.length} job(s) failed` : `Fork CI ${run.conclusion}`, + note: [`The fork workflow run concluded with ${run.conclusion}.`, failedNote] + .filter(Boolean).join('\n\n') + }; + } + const required = jobs.find(job => job.name === 'Required CI'); + if (required && required.conclusion === 'success') { + return { status: 'completed', conclusion: 'success', title: 'All required suites passed' }; + } + return { + status: 'completed', conclusion: 'action_required', + title: 'No suites were verified', + note: `Required CI was ${required ? required.conclusion : 'not run'}, so no suites were verified. ` + + 'Open the pull request first and check that Fork CI is enabled in the fork, then push the branch again.' + }; + }; + const NOT_AVAILABLE = 'Enable GitHub Actions in the fork, merge or rebase the branch onto the latest ' + + 'base branch so it contains fork-ci.yml, and push it again.'; + + const skippable = (pr, state) => { Review Comment: `skippable` treats a successful Fork CI check as final and, once the PR has been idle for an hour, never lists the fork run again. A re-run only changes `run_attempt`, not the head SHA or `updated_at`, so a green check that is re-run and then fails stays green. `workflow_dispatch` uses the same skip path, so running Update Fork CI Status does not refresh a settled PR. Please keep successes in the watch window, or bypass `skippable` on manual dispatch, and adjust the 15-minute / "committers can update immediately" note in CONTRIBUTING.md. ########## .github/workflows/fork-ci.yml: ########## @@ -0,0 +1,310 @@ +# 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. + +# Run the complete CI entry point from branch pushes in the contributor's repository. +name: Fork CI + +on: + push: + branches: ['**'] + +concurrency: + group: fork-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + eligibility: + name: Resolve fork CI eligibility + # Downstream mirrors can set this repository variable to keep the + # inherited workflow completely dormant. + if: vars.DISABLE_FORK_CI != 'true' + runs-on: ubuntu-slim + outputs: + applicable: ${{ steps.resolve.outputs.applicable }} + valid: ${{ steps.resolve.outputs.valid == 'true' || steps.merge.outputs.valid == 'true' }} + steps: + - name: Resolve repository and pull request + id: resolve + env: + GH_TOKEN: ${{ github.token }} + HEAD_BRANCH: ${{ github.ref_name }} + HEAD_OWNER: ${{ github.repository_owner }} + HEAD_REPOSITORY: ${{ github.repository }} + HEAD_SHA: ${{ github.sha }} + REPOSITORY_FORK: ${{ github.event.repository.fork }} + run: | + set -euo pipefail + + echo "applicable=false" >> "${GITHUB_OUTPUT}" + echo "valid=false" >> "${GITHUB_OUTPUT}" + + if [[ "${HEAD_REPOSITORY}" == "apache/gravitino" && \ + ("${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == refs/heads/branch-*) ]]; then + echo "applicable=true" >> "${GITHUB_OUTPUT}" + echo "base_ref=${GITHUB_REF_NAME}" >> "${GITHUB_OUTPUT}" + echo "valid=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ "${HEAD_REPOSITORY}" != "apache/gravitino" && "${REPOSITORY_FORK}" != "true" ]]; then Review Comment: Fork CI returns immediately for a repository that is not `apache/gravitino` and not a fork, and the suite workflows no longer have `push` or `pull_request` triggers. A standalone copy therefore runs neither Fork CI nor the old workflows. Please set eligibility `valid=true` (without syncing to apache) for that case so a push still runs the suites. ########## .github/workflows/fork-ci-status.yml: ########## @@ -0,0 +1,157 @@ +# 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: Fork CI Status + +on: + pull_request_target: + types: [opened, synchronize, reopened] + +permissions: + actions: read + checks: write + contents: read + +jobs: + notify: + name: Notify fork CI + if: github.repository == 'apache/gravitino' + runs-on: ubuntu-slim + steps: + - name: Create fork CI check + uses: actions/github-script@v7 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + retries: 3 + script: | + const pr = context.payload.pull_request; + if (!pr.head.repo) { + core.warning('The pull request head repository no longer exists.'); + return; + } + const owner = pr.head.repo.owner.login; + const repo = pr.head.repo.name; + const branch = pr.head.ref; + const headSha = pr.head.sha; + const workflow = 'fork-ci.yml'; + + // Pull requests to a branch without Fork CI, such as a release branch it has not + // been backported to, still run that branch's own pull_request workflows. + try { + await github.rest.repos.getContent({ + owner: context.repo.owner, repo: context.repo.repo, + path: `.github/workflows/${workflow}`, ref: pr.base.ref }); + } catch (error) { + if (error.status !== 404) throw error; + core.info(`${pr.base.ref} has no Fork CI; no check is created.`); + return; + } + + let run; + for (let attempt = 0; attempt < 3 && !run; attempt++) { + if (attempt > 0) { + await new Promise(resolve => setTimeout(resolve, 3000)); + } + try { + const runs = await github.paginate( + 'GET /repos/{owner}/{repo}/actions/workflows/{workflow_id}/runs', + { owner, repo, workflow_id: workflow, branch, per_page: 100 }); + run = runs.find(candidate => candidate.head_sha === headSha); + } catch (error) { + core.warning(`Unable to read fork Actions: ${error.message}`); + } + } + + // The check mirrors the Required CI job rather than the run conclusion: a run whose + // suites were all skipped (no pull request yet, or Fork CI disabled in the fork) + // still concludes as success. Failed jobs are listed while the run is still going, + // so reviewers and contributors can open their logs without waiting for the run. + const mirror = async (owner, repo, run) => { + const jobs = await github.paginate( + 'GET /repos/{owner}/{repo}/actions/runs/{run_id}/attempts/{attempt_number}/jobs', + { owner, repo, run_id: run.id, attempt_number: run.run_attempt, per_page: 100 }); + const failed = jobs.filter(job => + job.name !== 'Required CI' && ['failure', 'timed_out'].includes(job.conclusion)); + const failedNote = failed.length + ? `Failed jobs:\n${failed.map(job => `- [${job.name}](${job.html_url})`).join('\n')}` + : ''; + if (run.status !== 'completed') { + // A run stays queued while any of its jobs waits for a runner, even after other + // jobs have finished, so report it as in progress once any job has started. + const started = jobs.some(job => ['in_progress', 'completed'].includes(job.status)); + return { + status: started ? 'in_progress' : 'queued', + title: failed.length ? `Running, ${failed.length} job(s) failed so far` : 'Running', + note: failedNote + }; + } + if (run.conclusion !== 'success') { + // Check runs reject some run conclusions, such as startup_failure. + const conclusion = + ['action_required', 'cancelled', 'failure', 'timed_out'].includes(run.conclusion) + ? run.conclusion + : (['neutral', 'skipped'].includes(run.conclusion) ? 'action_required' : 'failure'); + return { + status: 'completed', conclusion, + title: failed.length ? `${failed.length} job(s) failed` : `Fork CI ${run.conclusion}`, + note: [`The fork workflow run concluded with ${run.conclusion}.`, failedNote] + .filter(Boolean).join('\n\n') + }; + } + const required = jobs.find(job => job.name === 'Required CI'); Review Comment: The Fork CI check is now the only required status check, and it goes green whenever the fork run succeeded and some job named "Required CI" succeeded. Push runs the workflow file from the pushed commit, and fork-ci.yml calls the local reusable workflows, so a PR can replace that file with one job named "Required CI" that exits 0 and satisfy branch protection without running the suites. Please compare the blob SHAs of `.github/workflows/**` and `.github/actions/**` at the head with the same paths at `pr.base.sha`, and require the real caller jobs to have succeeded, before reporting success. If those files differ, leave the check `action_required` so a trusted run has to cover the workflow change. (Same logic in `fork-ci-status-update.yml` lines 120-122.) ########## .github/workflows/fork-ci.yml: ########## @@ -0,0 +1,310 @@ +# 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. + +# Run the complete CI entry point from branch pushes in the contributor's repository. +name: Fork CI + +on: + push: + branches: ['**'] + +concurrency: + group: fork-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + eligibility: + name: Resolve fork CI eligibility + # Downstream mirrors can set this repository variable to keep the + # inherited workflow completely dormant. Review Comment: nit: `fork-ci.yml` has no `permissions` block. Pushes to `apache/gravitino` run it with the repository's default token. Please set `permissions: contents: read` so a test process on a same-repo branch does not inherit a write token. ########## .github/workflows/fork-ci.yml: ########## @@ -0,0 +1,310 @@ +# 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. + +# Run the complete CI entry point from branch pushes in the contributor's repository. +name: Fork CI + +on: + push: + branches: ['**'] + +concurrency: + group: fork-ci-${{ github.ref }} + cancel-in-progress: true + +jobs: + eligibility: + name: Resolve fork CI eligibility + # Downstream mirrors can set this repository variable to keep the + # inherited workflow completely dormant. + if: vars.DISABLE_FORK_CI != 'true' + runs-on: ubuntu-slim + outputs: + applicable: ${{ steps.resolve.outputs.applicable }} + valid: ${{ steps.resolve.outputs.valid == 'true' || steps.merge.outputs.valid == 'true' }} + steps: + - name: Resolve repository and pull request + id: resolve + env: + GH_TOKEN: ${{ github.token }} + HEAD_BRANCH: ${{ github.ref_name }} + HEAD_OWNER: ${{ github.repository_owner }} + HEAD_REPOSITORY: ${{ github.repository }} + HEAD_SHA: ${{ github.sha }} + REPOSITORY_FORK: ${{ github.event.repository.fork }} + run: | + set -euo pipefail + + echo "applicable=false" >> "${GITHUB_OUTPUT}" + echo "valid=false" >> "${GITHUB_OUTPUT}" + + if [[ "${HEAD_REPOSITORY}" == "apache/gravitino" && \ + ("${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == refs/heads/branch-*) ]]; then + echo "applicable=true" >> "${GITHUB_OUTPUT}" + echo "base_ref=${GITHUB_REF_NAME}" >> "${GITHUB_OUTPUT}" + echo "valid=true" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + if [[ "${HEAD_REPOSITORY}" != "apache/gravitino" && "${REPOSITORY_FORK}" != "true" ]]; then + echo "This repository is not a fork; it runs its own CI and Fork CI is dormant." + exit 0 + fi + + repository_response="$(curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "${GITHUB_API_URL}/repos/${HEAD_REPOSITORY}")" + source_repository="$(jq -r '.source.full_name // empty' <<< "${repository_response}")" + + if [[ "${HEAD_REPOSITORY}" != "apache/gravitino" && \ + "${source_repository}" != "apache/gravitino" ]]; then + echo "This repository is not in the apache/gravitino fork network; Fork CI is dormant." + exit 0 + fi + + echo "applicable=true" >> "${GITHUB_OUTPUT}" + matches="[]" + match_count=0 + # Give the contributor a few minutes to open the pull request after pushing. + for attempt in $(seq 1 12); do + response="$(curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + --get \ + --data-urlencode "state=open" \ + --data-urlencode "head=${HEAD_OWNER}:${HEAD_BRANCH}" \ + --data-urlencode "per_page=10" \ + "https://api.github.com/repos/apache/gravitino/pulls")" + matches="$(jq -r \ + --arg head_sha "${HEAD_SHA}" \ + --arg head_repository "${HEAD_REPOSITORY}" \ + '[.[] | select(.head.sha == $head_sha and .head.repo.full_name == $head_repository)]' \ + <<< "${response}")" + match_count="$(jq -r 'length' <<< "${matches}")" + if [[ "${match_count}" == "1" ]]; then + break + fi + if [[ "${match_count}" != "0" || "${attempt}" == "12" ]]; then + break + fi + sleep 15 + done + + if [[ "${match_count}" == "0" && "${HEAD_REPOSITORY}" == "apache/gravitino" ]]; then + # Branches in the upstream repository only run for pull requests. + echo "No open pull request for ${HEAD_BRANCH}; Fork CI is dormant." + echo "applicable=false" >> "${GITHUB_OUTPUT}" + exit 0 + fi + + api() { + curl --fail-with-body --silent --show-error --retry 3 --retry-all-errors --retry-delay 5 \ + --header "Accept: application/vnd.github+json" \ + --header "Authorization: Bearer ${GH_TOKEN}" \ + --header "X-GitHub-Api-Version: 2022-11-28" \ + "https://api.github.com/repos/apache/gravitino$1" + } + + if [[ "${match_count}" == "0" ]]; then + # No pull request yet: pick the base the same way checkout-and-sync does, the + # upstream default branch or one of the latest release branches, whichever the + # commit is fewest commits ahead of. The result is mirrored once the pull request + # is opened. + candidates=("$(api "" | jq -r '.default_branch')") + while IFS= read -r branch; do + candidates+=("${branch}") + done < <(api "/branches?per_page=100" \ Review Comment: nit: The pre-PR base guess calls `/branches?per_page=100` once. That currently returns every branch (80), but it will silently ignore release branches once the repository has more than 100. Please paginate, or drop the guess as in the earlier comment. -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected]
