This is an automated email from the ASF dual-hosted git repository.
yihua pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/hudi.git
The following commit(s) were added to refs/heads/master by this push:
new 1c54bd382ad9 chore(ci): run all validation in one
validate-pr-and-source job (#20421)
1c54bd382ad9 is described below
commit 1c54bd382ad9b37a004ba7ec785ca3ebc3c70a84
Author: voonhous <[email protected]>
AuthorDate: Fri Oct 23 10:56:31 2026 +0800
chore(ci): run all validation in one validate-pr-and-source job (#20421)
---
.github/workflows/README.md | 3 +-
.github/workflows/pr_validation.yml | 128 ----------------------
.github/workflows/validate_source.yml | 134 +++++++++++++++++++++---
.gitignore | 2 +-
scripts/release/validate_source_binary_files.sh | 7 +-
5 files changed, 125 insertions(+), 149 deletions(-)
diff --git a/.github/workflows/README.md b/.github/workflows/README.md
index d496b1a205bb..ad4360531ecd 100644
--- a/.github/workflows/README.md
+++ b/.github/workflows/README.md
@@ -5,5 +5,4 @@ When updating the pr template, you must consider if updates
need to be made to s
## What are the files in workflows?
- bot.yml: Java CI. Spark lanes (JDK 11 on Spark 3.5, JDK 17 on Spark 4.2).
Path-filtered at the trigger; no job depends on another. Dropped lanes are
commented in place and marked `[CI-TRIM]`
- java_ci_engines.yml: Java CI Engines. Flink, bundle validation and
integration tests. Same trigger filter and layout rules as bot.yml
-- validate_source.yml: Source Validation. The two required status checks
(validate-source, validate-ci-baseline). Never path-filtered, because a
required check must report on every PR
-- pr_validation.yml: one job (`validate-pr`) that checks the PR description
with scripts/pr_compliance.py, runs that script's own tests, validates the PR
title, and rejects commits with a Claude co-author trailer
+- validate_source.yml: PR and Source Validation. One job,
`validate-pr-and-source`, the only required status check: it checks the PR
description with scripts/pr_compliance.py, runs that script's own tests,
validates the PR title, rejects commits with a Claude co-author trailer, and
runs the source release checks (RAT, binary files, copyright). Never
path-filtered, because a required check must report on every PR
diff --git a/.github/workflows/pr_validation.yml
b/.github/workflows/pr_validation.yml
deleted file mode 100644
index 17afe511e192..000000000000
--- a/.github/workflows/pr_validation.yml
+++ /dev/null
@@ -1,128 +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.
-#
-
-name: PR Validation
-
-on:
- pull_request:
- types: [opened, edited, reopened, synchronize]
- branches:
- - master
- - branch-0.x
-
-concurrency:
- group: pr-validation-${{ github.ref }}
- cancel-in-progress: ${{ !contains(github.ref, 'master') &&
!contains(github.ref, 'branch-0.x') }}
-
-jobs:
- # All PR metadata checks share one job: each takes seconds, so a job per
check spent more on
- # runner startup than on the check itself. Every check step runs even when
an earlier one
- # fails, so one run reports every problem; the failed step's name says which
check it was.
- validate-pr:
- runs-on: ubuntu-24.04
- steps:
- - uses: actions/checkout@v5
-
- - name: Check PR description
- env:
- REQUEST_BODY: ${{ github.event.pull_request.body }}
- REQUEST_TITLE: ${{ github.event.pull_request.title }}
- run: python3 scripts/pr_compliance.py
-
- # Seconds of stdlib-only unit tests, so they run on every PR instead of
path-filtering
- # on scripts/pr_compliance.py.
- - name: Test pr_compliance.py
- if: ${{ !cancelled() }}
- run: python3 scripts/pr_compliance.py run-tests > test.log || { echo
"::error::pr_compliance.py $(cat test.log)" && exit 1; }
-
- - name: Check for legacy PR title format using JIRA or MINOR
- id: check-legacy-format
- if: ${{ !cancelled() }}
- env:
- PR_TITLE: ${{ github.event.pull_request.title }}
- run: |
- title="$PR_TITLE"
- echo "Checking PR title: $title"
-
- # Check for HUDI JIRA format: [HUDI-1234] description
- if [[ "$title" =~ ^\[HUDI-[0-9]+\]\ .+ ]]; then
- echo "Title matches HUDI JIRA format"
- echo "skip_conventional=true" >> $GITHUB_OUTPUT
- exit 0
- fi
-
- # Check for MINOR format: [MINOR] description
- if [[ "$title" =~ ^\[MINOR\]\ .+ ]]; then
- echo "Title matches MINOR format"
- echo "skip_conventional=true" >> $GITHUB_OUTPUT
- exit 0
- fi
-
- echo "Title does not match JIRA/MINOR format, will validate as
conventional commit"
- echo "skip_conventional=false" >> $GITHUB_OUTPUT
-
- - name: Validate PR title with Conventional Commits spec
- if: ${{ !cancelled() &&
steps.check-legacy-format.outputs.skip_conventional == 'false' }}
- uses:
amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 #
v6
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- with:
- types: |
- feat
- fix
- docs
- style
- refactor
- perf
- test
- chore
- requireScope: false
- # Disable subject case validation to be more permissive
- subjectPattern: ^.+$
- subjectPatternError: |
- The subject "{subject}" found in the pull request title "{title}"
- is empty. Please provide a meaningful description.
- # Allow ignoring certain labels
- ignoreLabels: |
- bot
- ignore-semantic-pull-request
- validateSingleCommit: false
- headerPattern: '^(\w+)(?:\(([^)]+)\))?!?: (.+)$'
- headerPatternCorrespondence: type,scope,subject
-
- - name: Check commits for Claude co-author trailers
- if: ${{ !cancelled() }}
- env:
- GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- REPO: ${{ github.repository }}
- PR_NUMBER: ${{ github.event.pull_request.number }}
- run: |
- echo "Checking commits of PR #${PR_NUMBER} for Claude co-author
trailers..."
- flagged=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/commits"
--paginate \
- --jq '.[] | select(.commit.message |
test("(^|\\n)co-authored-by:\\s*(claude\\s+(opus|sonnet|haiku|fable|mythos|instant|code|[0-9])|[^\\n]*<noreply@anthropic\\.com>)";
"i")) | .sha')
-
- if [[ -n "$flagged" ]]; then
- echo "The following commits contain a Claude co-author trailer:"
- echo "$flagged"
- echo ""
- echo "Commits must not credit Claude as a co-author."
- echo "Remove the 'Co-authored-by: Claude ...' trailer from the
commit message(s),"
- echo "e.g., by amending the commit or rebasing, and force-push the
branch."
- exit 1
- fi
-
- echo "No Claude co-author trailers found."
diff --git a/.github/workflows/validate_source.yml
b/.github/workflows/validate_source.yml
index 51c36fb51f09..ba60a804da59 100644
--- a/.github/workflows/validate_source.yml
+++ b/.github/workflows/validate_source.yml
@@ -1,8 +1,8 @@
-name: Source Validation
+name: PR and Source Validation
-# Both jobs here are required status checks in .asf.yaml. A required check
must report on every PR,
-# including docs-only ones, and a path-filtered workflow is never instantiated
for PRs that miss the
-# filter, so these jobs cannot live in bot.yml once it is path-filtered.
+# The one job here is the only required status check in .asf.yaml. A required
check must report on
+# every PR, including docs-only ones, and a path-filtered workflow is never
instantiated for PRs that
+# miss the filter, so this workflow is never path-filtered.
on:
push:
branches:
@@ -10,6 +10,8 @@ on:
- 'release-*'
- branch-0.x
pull_request:
+ # `edited` re-runs the PR metadata checks when the title or description
changes.
+ types: [opened, edited, reopened, synchronize]
branches:
- master
- 'release-*'
@@ -20,21 +22,118 @@ concurrency:
cancel-in-progress: ${{ !contains(github.ref, 'master') &&
!contains(github.ref, 'branch-0.x') && !contains(github.ref, 'release-') }}
jobs:
- # [CI-TRIM] to revisit for CI improvement
- # This only blocks PRs whose checks predate the matrix trim (#19514). Since
bot.yml and
- # java_ci_engines.yml are path-filtered, a green result here no longer means
any Java CI
- # job ran; reviewers gate on the checks the PR shows. Delete under #19535.
- validate-ci-baseline:
- runs-on: ubuntu-24.04
- steps:
- - name: Confirm CI ran against the reduced matrix
- run: echo "Ran against the reduced CI matrix."
-
- validate-source:
+ # The only required status check in .asf.yaml. It runs every short check (PR
metadata, source
+ # release) so each PR event spends one runner. It replaces
validate-ci-baseline: PRs whose last
+ # run predates this job never reported its name, so they cannot merge on
stale checks until a new
+ # push or a close and reopen runs it. A re-run of an old run does not help,
as it replays the old
+ # workflow file.
+ validate-pr-and-source:
runs-on: ubuntu-24.04
+ env:
+ # The metadata checks only apply to PRs. release-* PRs (backports, "[DO
NOT MERGE] Validate
+ # rc bundles") were never subject to them.
+ CHECK_PR_METADATA: ${{ github.event_name == 'pull_request' &&
!startsWith(github.base_ref, 'release-') }}
steps:
- uses: actions/checkout@v5
+
+ # PR metadata checks. Each takes seconds, so they share this job instead
of spending a runner
+ # of their own. Every step from here on runs even when an earlier one
fails, so one run
+ # reports every problem; the failed step's name says which check it was.
+ - name: Check PR description
+ if: ${{ env.CHECK_PR_METADATA == 'true' }}
+ env:
+ REQUEST_BODY: ${{ github.event.pull_request.body }}
+ REQUEST_TITLE: ${{ github.event.pull_request.title }}
+ run: python3 scripts/pr_compliance.py
+
+ # Seconds of stdlib-only unit tests, so they run on every PR instead of
path-filtering
+ # on scripts/pr_compliance.py. The log goes to the runner temp dir: the
source checks below
+ # copy and scan the workspace, and a stray test.log has no license
header.
+ - name: Test pr_compliance.py
+ if: ${{ !cancelled() && env.CHECK_PR_METADATA == 'true' }}
+ run: python3 scripts/pr_compliance.py run-tests >
"$RUNNER_TEMP/test.log" || { echo "::error::pr_compliance.py $(cat
"$RUNNER_TEMP/test.log")" && exit 1; }
+
+ - name: Check for legacy PR title format using JIRA or MINOR
+ id: check-legacy-format
+ if: ${{ !cancelled() && env.CHECK_PR_METADATA == 'true' }}
+ env:
+ PR_TITLE: ${{ github.event.pull_request.title }}
+ run: |
+ title="$PR_TITLE"
+ echo "Checking PR title: $title"
+
+ # Check for HUDI JIRA format: [HUDI-1234] description
+ if [[ "$title" =~ ^\[HUDI-[0-9]+\]\ .+ ]]; then
+ echo "Title matches HUDI JIRA format"
+ echo "skip_conventional=true" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ # Check for MINOR format: [MINOR] description
+ if [[ "$title" =~ ^\[MINOR\]\ .+ ]]; then
+ echo "Title matches MINOR format"
+ echo "skip_conventional=true" >> $GITHUB_OUTPUT
+ exit 0
+ fi
+
+ echo "Title does not match JIRA/MINOR format, will validate as
conventional commit"
+ echo "skip_conventional=false" >> $GITHUB_OUTPUT
+
+ - name: Validate PR title with Conventional Commits spec
+ if: ${{ !cancelled() && env.CHECK_PR_METADATA == 'true' &&
steps.check-legacy-format.outputs.skip_conventional == 'false' }}
+ uses:
amannn/action-semantic-pull-request@48f256284bd46cdaab1048c3721360e808335d50 #
v6
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ types: |
+ feat
+ fix
+ docs
+ style
+ refactor
+ perf
+ test
+ chore
+ requireScope: false
+ # Disable subject case validation to be more permissive
+ subjectPattern: ^.+$
+ subjectPatternError: |
+ The subject "{subject}" found in the pull request title "{title}"
+ is empty. Please provide a meaningful description.
+ # Allow ignoring certain labels
+ ignoreLabels: |
+ bot
+ ignore-semantic-pull-request
+ validateSingleCommit: false
+ headerPattern: '^(\w+)(?:\(([^)]+)\))?!?: (.+)$'
+ headerPatternCorrespondence: type,scope,subject
+
+ - name: Check commits for Claude co-author trailers
+ if: ${{ !cancelled() && env.CHECK_PR_METADATA == 'true' }}
+ env:
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ REPO: ${{ github.repository }}
+ PR_NUMBER: ${{ github.event.pull_request.number }}
+ run: |
+ echo "Checking commits of PR #${PR_NUMBER} for Claude co-author
trailers..."
+ flagged=$(gh api "repos/${REPO}/pulls/${PR_NUMBER}/commits"
--paginate \
+ --jq '.[] | select(.commit.message |
test("(^|\\n)co-authored-by:\\s*(claude\\s+(opus|sonnet|haiku|fable|mythos|instant|code|[0-9])|[^\\n]*<noreply@anthropic\\.com>)";
"i")) | .sha')
+
+ if [[ -n "$flagged" ]]; then
+ echo "The following commits contain a Claude co-author trailer:"
+ echo "$flagged"
+ echo ""
+ echo "Commits must not credit Claude as a co-author."
+ echo "Remove the 'Co-authored-by: Claude ...' trailer from the
commit message(s),"
+ echo "e.g., by amending the commit or rebasing, and force-push the
branch."
+ exit 1
+ fi
+
+ echo "No Claude co-author trailers found."
+
+ # Source checks. They run even when a metadata check above fails.
- name: Set up JDK 11
+ if: ${{ !cancelled() }}
uses: actions/setup-java@v5
with:
java-version: '11'
@@ -59,14 +158,19 @@ jobs:
# upstream trinodb/trino build; nothing in this repo runs it since
RFC-105 dropped the
# trino-root parent.
- name: Create source release directory
+ id: source-dir
+ if: ${{ !cancelled() }}
run: ./scripts/release/create_source_directory.sh "${{ runner.temp
}}/hudi-tmp-repo"
- name: Check Binary Files
+ if: ${{ !cancelled() && steps.source-dir.outcome == 'success' }}
run: |
cd "${{ runner.temp }}/hudi-tmp-repo"
./scripts/release/validate_source_binary_files.sh
- name: Check Copyright
+ if: ${{ !cancelled() && steps.source-dir.outcome == 'success' }}
run: |
cd "${{ runner.temp }}/hudi-tmp-repo"
./scripts/release/validate_source_copyright.sh
- name: RAT check
+ if: ${{ !cancelled() }}
run: ./scripts/release/validate_source_rat.sh
diff --git a/.gitignore b/.gitignore
index f4850db9b2f5..b739a18af1ab 100644
--- a/.gitignore
+++ b/.gitignore
@@ -9,7 +9,7 @@ metastore_db/
# Keep the un-anchored `.mvn/` entry. A pattern containing a slash is anchored
to this file's
# directory, so narrowing it to `.mvn/*` would stop matching nested .mvn
directories -- and
# apache-rat reads .gitignore, so that un-hides
hudi-trino/.mvn/modernizer/*.xml, which are tracked
-# without ASF headers, and validate-source fails.
+# without ASF headers, and the RAT check in validate_source.yml fails.
.mvn/
!/.mvn/
/.mvn/*
diff --git a/scripts/release/validate_source_binary_files.sh
b/scripts/release/validate_source_binary_files.sh
index 607015e0422f..2f8848e1f7cc 100755
--- a/scripts/release/validate_source_binary_files.sh
+++ b/scripts/release/validate_source_binary_files.sh
@@ -20,9 +20,10 @@
#
# Run this from the root of a source release tree, i.e. the output of
-# create_source_directory.sh (see the validate-source job in
validate_source.yml) or an
-# extracted source tarball (see validate_staged_release.sh). Paths that never
-# reach the source release are excluded by create_source_directory.sh, not
here.
+# create_source_directory.sh (see the validate-pr-and-source job in
+# validate_source.yml) or an extracted source tarball (see
+# validate_staged_release.sh). Paths that never reach the source release are
+# excluded by create_source_directory.sh, not here.
# fail immediately; pipefail so that a broken `file` invocation can never again
# yield an empty pipeline that reads as "no binary files found"