From e1467950a23f5c7d3ee75297c87d8d289b9f80dc Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 27 Feb 2026 01:27:39 -0500 Subject: [PATCH 1/4] feat: add CI trigger patterns file Extract the CI-skip glob patterns into eng/testing/github-ci-trigger-patterns.txt so that pattern maintenance is decoupled from the workflow definition. Changing this file alone will not trigger CI, avoiding the chicken-and-egg problem of the patterns being inlined in ci.yml. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- eng/testing/github-ci-trigger-patterns.txt | 49 ++++++++++++++++++++++ 1 file changed, 49 insertions(+) create mode 100644 eng/testing/github-ci-trigger-patterns.txt diff --git a/eng/testing/github-ci-trigger-patterns.txt b/eng/testing/github-ci-trigger-patterns.txt new file mode 100644 index 00000000000..0d0ae248697 --- /dev/null +++ b/eng/testing/github-ci-trigger-patterns.txt @@ -0,0 +1,49 @@ +# CI trigger patterns +# +# This file lists glob patterns for files whose changes do NOT require the full CI +# to run (e.g. documentation, non-build pipeline scripts, or specific workflow files +# that are unrelated to the build and test process). +# +# When all files changed in a pull request match at least one pattern here, the CI +# workflow is skipped. +# +# Pattern syntax: +# ** matches any path including directory separators (recursive) +# * matches any characters except a directory separator +# . is treated as a literal dot (no escaping needed) +# All other characters are treated as literals. +# +# Lines starting with '#' and blank lines are ignored. + +# This file itself - changing CI-skip patterns doesn't require a CI run. +# Note: this also means a syntax error introduced here won't be caught by CI, +# so take care when editing. Pattern conversion is validated by the +# check-changed-files action at runtime. +eng/testing/github-ci-trigger-patterns.txt + +# Documentation +**.md + +# Engineering pipeline scripts (Azure DevOps, not used in the GitHub CI build) +eng/pipelines/** +eng/test-configuration.json + +.github/instructions/** +.github/skills/** + +# GitHub workflow files that do not affect the CI build or test process +.github/workflows/apply-test-attributes.yml +.github/workflows/backmerge-release.yml +.github/workflows/backport.yml +.github/workflows/dogfood-comment.yml +.github/workflows/generate-api-diffs.yml +.github/workflows/generate-ats-diffs.yml +.github/workflows/labeler-*.yml +.github/workflows/markdownlint*.yml +.github/workflows/pr-review-needed.yml +.github/workflows/refresh-manifests.yml +.github/workflows/reproduce-flaky-tests.yml +.github/workflows/specialized-test-runner.yml +.github/workflows/tests-outerloop.yml +.github/workflows/tests-quarantine.yml +.github/workflows/update-*.yml From f697db80cb15b7eb59a34dc97e1b450e0cf2e155 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 27 Feb 2026 01:27:46 -0500 Subject: [PATCH 2/4] refactor: read glob patterns from file in check-changed-files action Replace the inline regex patterns input with a patterns_file input that points to a file of glob patterns. Add a glob_to_regex() function that converts glob syntax (**, *, literal dot) to anchored ERE regexes. The action now: - reads patterns from a file, skipping comments and blank lines - converts each glob to an anchored regex before matching - escapes regex metacharacters (. + ? [ ] ( ) |) in glob literals Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../actions/check-changed-files/action.yml | 92 +++++++++++++------ 1 file changed, 65 insertions(+), 27 deletions(-) diff --git a/.github/actions/check-changed-files/action.yml b/.github/actions/check-changed-files/action.yml index 4e44385e177..f012557ea88 100644 --- a/.github/actions/check-changed-files/action.yml +++ b/.github/actions/check-changed-files/action.yml @@ -1,12 +1,19 @@ name: 'Check Changed Files' description: | - Check if all changed files in a PR match provided regex patterns. + Check if all changed files in a PR match provided glob patterns. - This action compares changed files in a pull request against one or more regex patterns + This action compares changed files in a pull request against one or more glob patterns and determines if all changed files match at least one of the provided patterns. + It only supports pull_request events. Inputs: - - patterns: List of regex patterns (multiline string) to match against changed file paths + - patterns_file: Path to a file containing glob patterns (relative to repository root). + Lines starting with '#' and blank lines are ignored. + + Pattern syntax: + - ** matches any path including directory separators (recursive) + - * matches any characters except a directory separator + - . is treated as a literal dot (no escaping needed) Outputs: - only_changed: Boolean indicating if all changed files matched the patterns @@ -15,8 +22,8 @@ description: | - matched_files: JSON array of files that matched at least one pattern - unmatched_files: JSON array of files that didn't match any pattern inputs: - patterns: - description: 'List of regex patterns to match against changed files' + patterns_file: + description: 'Path to a file containing glob patterns (relative to repository root)' required: true outputs: @@ -57,12 +64,60 @@ runs: exit 1 fi - # Read patterns from input (multiline string) - PATTERNS_INPUT="${{ inputs.patterns }}" + # Convert a glob pattern to an anchored ERE regex pattern. + # Glob syntax supported: + # ** matches any path including directory separators + # * matches any characters except a directory separator + # . is treated as a literal dot + # All other characters are treated as literals. + glob_to_regex() { + local glob="$1" + local result="$glob" + # Replace ** and * with placeholders before escaping + result="${result//\*\*/__DOUBLESTAR__}" + result="${result//\*/__STAR__}" + # Escape regex metacharacters that could appear in file paths. + # Note: { } ^ $ are not escaped because they are either not special + # in ERE mid-pattern or cannot appear in file paths. + result="${result//\\/\\\\}" + result="${result//./\\.}" + result="${result//+/\\+}" + result="${result//\?/\\?}" + result="${result//\[/\\[}" + result="${result//\]/\\]}" + result="${result//\(/\\(}" + result="${result//\)/\\)}" + result="${result//|/\\|}" + # Restore glob placeholders as regex + result="${result//__STAR__/[^/]*}" + result="${result//__DOUBLESTAR__/.*}" + # Anchor to full path + echo "^${result}$" + } + + PATTERNS=() + + PATTERNS_FILE="${{ inputs.patterns_file }}" - # Validate patterns input - if [ -z "$PATTERNS_INPUT" ]; then - echo "Error: patterns input is required" + # Read glob patterns from file, skip comments and blank lines + FULL_PATH="${GITHUB_WORKSPACE}/${PATTERNS_FILE}" + if [ ! -f "$FULL_PATH" ]; then + echo "Error: patterns_file '$FULL_PATH' not found" + exit 1 + fi + while IFS= read -r line; do + # Remove leading/trailing whitespace + line=$(echo "$line" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') + # Skip blank lines and comments + if [ -z "$line" ] || [[ "$line" == \#* ]]; then + continue + fi + PATTERNS+=("$(glob_to_regex "$line")") + done < "$FULL_PATH" + + # Check if we have any valid patterns + if [ ${#PATTERNS[@]} -eq 0 ]; then + echo "Error: No valid patterns provided" exit 1 fi @@ -77,23 +132,6 @@ runs: echo "Changed files:" echo "$CHANGED_FILES" - # Convert patterns to array and filter out empty lines - PATTERNS=() - while IFS= read -r pattern; do - # Remove leading/trailing whitespace - pattern=$(echo "$pattern" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//') - # Skip empty patterns - if [ -n "$pattern" ]; then - PATTERNS+=("$pattern") - fi - done <<< "$PATTERNS_INPUT" - - # Check if we have any valid patterns - if [ ${#PATTERNS[@]} -eq 0 ]; then - echo "Error: No valid patterns provided" - exit 1 - fi - # Initialize arrays MATCHED_FILES=() UNMATCHED_FILES=() From 92970652489dde398a795d2925288cda68424a83 Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 27 Feb 2026 01:27:50 -0500 Subject: [PATCH 3/4] chore: use patterns_file in CI workflow Replace the inline regex patterns block in ci.yml with a single patterns_file reference to eng/testing/github-ci-trigger-patterns.txt. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/ci.yml | 19 +------------------ 1 file changed, 1 insertion(+), 18 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a0007e8030f..bc39f93bf69 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -37,24 +37,7 @@ jobs: if: ${{ github.event_name == 'pull_request' }} uses: ./.github/actions/check-changed-files with: - # Patterns that do NOT require CI to run - patterns: | - \.md$ - eng/pipelines/.* - eng/test-configuration.json - \.github/workflows/apply-test-attributes.yml - \.github/workflows/backport.yml - \.github/workflows/dogfood-comment.yml - \.github/workflows/generate-api-diffs.yml - \.github/workflows/generate-ats-diffs.yml - \.github/workflows/labeler-*.yml - \.github/workflows/markdownlint*.yml - \.github/workflows/refresh-manifests.yml - \.github/workflows/pr-review-needed.yml - \.github/workflows/specialized-test-runner.yml - \.github/workflows/tests-outerloop.yml - \.github/workflows/tests-quarantine.yml - \.github/workflows/update-*.yml + patterns_file: eng/testing/github-ci-trigger-patterns.txt - id: compute_version_suffix name: Compute version suffix for PRs From 95f5efc3ca86166eb75cf548a935404769799cfc Mon Sep 17 00:00:00 2001 From: Ankit Jain Date: Fri, 27 Feb 2026 01:27:54 -0500 Subject: [PATCH 4/4] docs: add CI trigger patterns documentation Explain the patterns file, its glob syntax, how to add new patterns, and how the check-changed-files action converts globs to ERE regexes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- docs/ci/ci-trigger-patterns.md | 71 ++++++++++++++++++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 docs/ci/ci-trigger-patterns.md diff --git a/docs/ci/ci-trigger-patterns.md b/docs/ci/ci-trigger-patterns.md new file mode 100644 index 00000000000..ab7f4cbc67d --- /dev/null +++ b/docs/ci/ci-trigger-patterns.md @@ -0,0 +1,71 @@ +# CI Trigger Patterns + +## Overview + +The file `eng/testing/github-ci-trigger-patterns.txt` lists glob patterns for files whose changes do **not** require the full CI to run. + +When a pull request is opened or updated, the CI workflow (`ci.yml`) checks whether **all** changed files match at least one pattern in the file. If they do, the workflow is skipped (no build or test jobs run). This keeps CI fast for changes that only affect documentation, pipeline configuration, or unrelated workflow files. + +> **Note:** This mechanism applies only to **pull requests**. Pushes to `main` or `release/*` branches always run the full CI pipeline. The `check-changed-files` action explicitly rejects non-`pull_request` events. + +## Why a Separate File? + +Previously the patterns were inlined in `.github/workflows/ci.yml`. Any change to that file (even just adding a new pattern to skip CI) would trigger CI on itself. Moving the patterns to `eng/testing/github-ci-trigger-patterns.txt` decouples pattern maintenance from the workflow definition. + +## Pattern Syntax + +Patterns use a simple **glob** style: + +| Syntax | Meaning | +|--------|---------| +| `**` | Matches any path including directory separators (recursive) | +| `*` | Matches any characters except a directory separator | +| `.` | Treated as a literal dot — no backslash escaping needed | + +All other characters (letters, digits, `-`, `_`, `/`, etc.) are treated as literals. + +Lines starting with `#` and blank lines are ignored. + +### Examples + +```text +# All Markdown files anywhere in the repo +**.md + +# All files under eng/pipelines/ recursively +eng/pipelines/** + +# A specific file +eng/test-configuration.json + +# Workflow files matching a glob (e.g. labeler-promote.yml, labeler-train.yml) +.github/workflows/labeler-*.yml +``` + +## How to Add a New Pattern + +To add files whose changes should not trigger CI: + +1. Open `eng/testing/github-ci-trigger-patterns.txt`. +2. Add one pattern per line, optionally preceded by a comment. +3. Submit a PR — CI will not run for that PR if all changed files match the patterns. + +> **Tip:** Changing the patterns file itself is listed as a skippable change (`eng/testing/github-ci-trigger-patterns.txt`), so a PR that only updates this file will not trigger CI. + +## How It Works + +The `.github/actions/check-changed-files` composite action: + +1. Reads `eng/testing/github-ci-trigger-patterns.txt` from the checked-out repository. +2. Converts each glob pattern to an anchored ERE (Extended Regular Expression) regex: + - `**` → `.*` + - `*` → `[^/]*` + - `.` and other regex metacharacters (`+`, `?`, `[`, `]`, `(`, `)`, `|`) → escaped with `\` +3. For every file changed in the PR, checks whether the file path matches at least one of the converted regexes. +4. Outputs `only_changed=true` when every changed file matched, allowing the calling workflow to skip further jobs. + +## Related Files + +- `eng/testing/github-ci-trigger-patterns.txt` — the patterns file described on this page +- `.github/actions/check-changed-files/action.yml` — the composite action that reads and evaluates the patterns +- `.github/workflows/ci.yml` — the CI workflow that calls the action