Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 65 additions & 27 deletions .github/actions/check-changed-files/action.yml
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -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

Expand All @@ -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=()
Expand Down
19 changes: 1 addition & 18 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
71 changes: 71 additions & 0 deletions docs/ci/ci-trigger-patterns.md
Original file line number Diff line number Diff line change
@@ -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
49 changes: 49 additions & 0 deletions eng/testing/github-ci-trigger-patterns.txt
Original file line number Diff line number Diff line change
@@ -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
Loading