-
Notifications
You must be signed in to change notification settings - Fork 8.1k
Add reusable get-changed-files action and refactor existing actions #26355
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
TravisEz13
merged 7 commits into
PowerShell:master
from
TravisEz13:refactor/add-reusable-get-changed-files-action
Oct 30, 2025
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
eadba38
Add reusable get-changed-files action and refactor existing actions
TravisEz13 da5608d
Update .github/actions/infrastructure/get-changed-files/README.md
TravisEz13 e5843b2
Update .github/actions/infrastructure/path-filters/action.yml
TravisEz13 b8482dd
Update .github/actions/infrastructure/path-filters/action.yml
TravisEz13 be0df09
Update .github/actions/infrastructure/path-filters/action.yml
TravisEz13 87f62ae
Merge remote-tracking branch 'upstream/master' into refactor/add-reus…
TravisEz13 354c5c4
Merge branch 'refactor/add-reusable-get-changed-files-action' of http…
TravisEz13 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
122 changes: 122 additions & 0 deletions
122
.github/actions/infrastructure/get-changed-files/README.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,122 @@ | ||
| # Get Changed Files Action | ||
|
|
||
| A reusable composite action that retrieves the list of files changed in a pull request or push event. | ||
|
|
||
| ## Features | ||
|
|
||
| - Supports both `pull_request` and `push` events | ||
| - Optional filtering by file pattern | ||
| - Returns files as JSON array for easy consumption | ||
| - Filters out deleted files (only returns added, modified, or renamed files) | ||
| - Handles up to 100 changed files per request | ||
|
|
||
| ## Usage | ||
|
|
||
| ### Basic Usage (Pull Requests Only) | ||
|
|
||
| ```yaml | ||
| - name: Get changed files | ||
| id: changed-files | ||
| uses: "./.github/actions/infrastructure/get-changed-files" | ||
|
|
||
| - name: Process files | ||
| run: | | ||
| echo "Changed files: ${{ steps.changed-files.outputs.files }}" | ||
| echo "Count: ${{ steps.changed-files.outputs.count }}" | ||
| ``` | ||
| ### With Filtering | ||
| ```yaml | ||
| # Get only markdown files | ||
| - name: Get changed markdown files | ||
| id: changed-md | ||
| uses: "./.github/actions/infrastructure/get-changed-files" | ||
| with: | ||
| filter: '*.md' | ||
|
|
||
| # Get only GitHub workflow/action files | ||
| - name: Get changed GitHub files | ||
| id: changed-github | ||
| uses: "./.github/actions/infrastructure/get-changed-files" | ||
| with: | ||
| filter: '.github/' | ||
| ``` | ||
| ### Support Both PR and Push Events | ||
| ```yaml | ||
| - name: Get changed files | ||
| id: changed-files | ||
| uses: "./.github/actions/infrastructure/get-changed-files" | ||
| with: | ||
| event-types: 'pull_request,push' | ||
| ``` | ||
| ## Inputs | ||
| | Name | Description | Required | Default | | ||
| |------|-------------|----------|---------| | ||
| | `filter` | Optional filter pattern (e.g., `*.md` for markdown files, `.github/` for GitHub files) | No | `''` | | ||
| | `event-types` | Comma-separated list of event types to support (`pull_request`, `push`) | No | `pull_request` | | ||
|
|
||
| ## Outputs | ||
|
|
||
| | Name | Description | | ||
| |------|-------------| | ||
| | `files` | JSON array of changed file paths | | ||
| | `count` | Number of changed files | | ||
|
|
||
| ## Filter Patterns | ||
|
|
||
| The action supports simple filter patterns: | ||
|
|
||
| - **Extension matching**: Use `*.ext` to match files with a specific extension | ||
| - Example: `*.md` matches all markdown files | ||
| - Example: `*.yml` matches all YAML files | ||
|
|
||
| - **Path prefix matching**: Use a path prefix to match files in a directory | ||
| - Example: `.github/` matches all files in the `.github` directory | ||
| - Example: `tools/` matches all files in the `tools` directory | ||
|
|
||
| ## Example: Processing Changed Files | ||
|
|
||
| ```yaml | ||
| - name: Get changed files | ||
| id: changed-files | ||
| uses: "./.github/actions/infrastructure/get-changed-files" | ||
| - name: Process each file | ||
| shell: pwsh | ||
| env: | ||
| CHANGED_FILES: ${{ steps.changed-files.outputs.files }} | ||
| run: | | ||
| $changedFilesJson = $env:CHANGED_FILES | ||
| $changedFiles = $changedFilesJson | ConvertFrom-Json | ||
| foreach ($file in $changedFiles) { | ||
| Write-Host "Processing: $file" | ||
| # Your processing logic here | ||
| } | ||
| ``` | ||
|
|
||
| ## Limitations | ||
|
|
||
| - Simple filter patterns only (no complex glob or regex patterns) | ||
|
|
||
| ## Pagination | ||
|
|
||
| The action automatically handles pagination to fetch **all** changed files in a PR, regardless of how many files were changed: | ||
|
|
||
| - Fetches files in batches of 100 per page | ||
| - Continues fetching until all files are retrieved | ||
| - Logs a note when pagination occurs, showing the total file count | ||
| - **No file limit** - all changed files will be processed, even in very large PRs | ||
|
|
||
| This ensures that critical workflows (such as merge conflict checking, link validation, etc.) don't miss files due to pagination limits. | ||
|
|
||
| ## Related Actions | ||
|
|
||
| - **markdownlinks**: Uses this pattern to get changed markdown files | ||
| - **merge-conflict-checker**: Uses this pattern to get changed files for conflict detection | ||
| - **path-filters**: Similar functionality but with more complex filtering logic |
117 changes: 117 additions & 0 deletions
117
.github/actions/infrastructure/get-changed-files/action.yml
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| name: 'Get Changed Files' | ||
| description: 'Gets the list of files changed in a pull request or push event' | ||
| inputs: | ||
| filter: | ||
| description: 'Optional filter pattern (e.g., "*.md" for markdown files, ".github/" for GitHub files)' | ||
| required: false | ||
| default: '' | ||
| event-types: | ||
| description: 'Comma-separated list of event types to support (pull_request, push)' | ||
| required: false | ||
| default: 'pull_request' | ||
| outputs: | ||
| files: | ||
| description: 'JSON array of changed file paths' | ||
| value: ${{ steps.get-files.outputs.files }} | ||
| count: | ||
| description: 'Number of changed files' | ||
| value: ${{ steps.get-files.outputs.count }} | ||
| runs: | ||
| using: 'composite' | ||
| steps: | ||
| - name: Get changed files | ||
| id: get-files | ||
| uses: actions/github-script@v7 | ||
| with: | ||
| script: | | ||
| const eventTypes = '${{ inputs.event-types }}'.split(',').map(t => t.trim()); | ||
| const filter = '${{ inputs.filter }}'; | ||
| let changedFiles = []; | ||
| if (eventTypes.includes('pull_request') && context.eventName === 'pull_request') { | ||
| console.log(`Getting files changed in PR #${context.payload.pull_request.number}`); | ||
| // Fetch all files changed in the PR with pagination | ||
| let allFiles = []; | ||
| let page = 1; | ||
| let fetchedCount; | ||
| do { | ||
| const { data: files } = await github.rest.pulls.listFiles({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| pull_number: context.payload.pull_request.number, | ||
| per_page: 100, | ||
| page: page | ||
| }); | ||
| allFiles = allFiles.concat(files); | ||
| fetchedCount = files.length; | ||
| page++; | ||
| } while (fetchedCount === 100); | ||
| if (allFiles.length >= 100) { | ||
| console.log(`Note: This PR has ${allFiles.length} changed files. All files fetched using pagination.`); | ||
| } | ||
| changedFiles = allFiles | ||
| .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') | ||
| .map(file => file.filename); | ||
| } else if (eventTypes.includes('push') && context.eventName === 'push') { | ||
| console.log(`Getting files changed in push to ${context.ref}`); | ||
| const { data: comparison } = await github.rest.repos.compareCommits({ | ||
| owner: context.repo.owner, | ||
| repo: context.repo.repo, | ||
| base: context.payload.before, | ||
| head: context.payload.after, | ||
| }); | ||
| changedFiles = comparison.files | ||
| .filter(file => file.status === 'added' || file.status === 'modified' || file.status === 'renamed') | ||
| .map(file => file.filename); | ||
| } else { | ||
| core.setFailed(`Unsupported event type: ${context.eventName}. Supported types: ${eventTypes.join(', ')}`); | ||
| return; | ||
| } | ||
| // Apply filter if provided | ||
| if (filter) { | ||
| const filterLower = filter.toLowerCase(); | ||
| const beforeFilter = changedFiles.length; | ||
| changedFiles = changedFiles.filter(file => { | ||
| const fileLower = file.toLowerCase(); | ||
| // Support simple patterns like "*.md" or ".github/" | ||
| if (filterLower.startsWith('*.')) { | ||
| const ext = filterLower.substring(1); | ||
| return fileLower.endsWith(ext); | ||
| } else { | ||
| return fileLower.startsWith(filterLower); | ||
| } | ||
| }); | ||
| console.log(`Filter '${filter}' applied: ${beforeFilter} → ${changedFiles.length} files`); | ||
| } | ||
| // Calculate simple hash for verification | ||
| const crypto = require('crypto'); | ||
| const filesJson = JSON.stringify(changedFiles.sort()); | ||
| const hash = crypto.createHash('sha256').update(filesJson).digest('hex').substring(0, 8); | ||
| // Log changed files in a collapsible group | ||
| core.startGroup(`Changed Files (${changedFiles.length} total, hash: ${hash})`); | ||
| if (changedFiles.length > 0) { | ||
| changedFiles.forEach(file => console.log(` - ${file}`)); | ||
| } else { | ||
| console.log(' (no files changed)'); | ||
| } | ||
| core.endGroup(); | ||
| console.log(`Found ${changedFiles.length} changed files`); | ||
| core.setOutput('files', JSON.stringify(changedFiles)); | ||
| core.setOutput('count', changedFiles.length); | ||
| branding: | ||
| icon: 'file-text' | ||
| color: 'blue' | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.