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"

Reply via email to