A GitHub Action to filter changed files in pull requests and commits. It is useful when you want to run steps or jobs based on changed files.
Compared to GitHub's path filter, this action allows detailed control with steps and jobs, and there is no limit to the number of files. The behavior of this action is simple, just compare head ref and base ref. Works fast and can handle differences between thousands of files. Additionally, do not require code checkout in previous steps.
Note
For pull_request
events, yumemi-inc/changed-files can also be used.
There are limitations due to using GitHub API, but in pull_request
events, there are no problems and it has more functions.
See action.yml for available action inputs and outputs.
Note that this action requires contents: read
permission.
Works on any event. Basically it works as is, but if you want to customize it, refer to the Specify comparison targets section.
If there are changes to the files specified by pattern
input, exists
output will be 'true'
.
This is useful for controlling step execution.
- uses: yumemi-inc/path-filter@v2
id: filter
with:
patterns: |
**/*.{js,ts}
!tools/**
- if: steps.filter.outputs.exists == 'true' # or fromJSON(steps.filter.outputs.exists)
run: npm run test
Note: In
pattern
input, characters after#
are treated as comments.
If you just want to run a Bash script, you can use run
input.
In this case, there is no need to define id:
, since exists
output is not used.
- uses: yumemi-inc/path-filter@v2
with:
patterns: |
**/*.{js,ts}
!tools/**
run: npm run test
There is also run-if-not
input and either will be executed.
examples of other uses
- uses: yumemi-inc/path-filter@v2
id: filter
with:
patterns: |
**/*.js
!server/**
- env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.number }} ${{ steps.filter.outputs.exists == 'true' && '--add-label' || '--remove-label' }} 'frontend'
- uses: yumemi-inc/path-filter@v2
id: filter-src
with:
patterns: |
**/*.ts
package.json
- uses: yumemi-inc/path-filter@v2
id: filter-build
with:
patterns: 'dist/**'
- if: steps.filter-src.outputs.exists == 'true' && steps.filter-build.outputs.exists != 'true'
run: |
echo "::error::Please check if you forgot to build."
exit 1
- uses: yumemi-inc/path-filter@v2
id: filter
with:
patterns: 'CHANGELOG.md'
- if: github.base_ref == 'main' && steps.filter.outputs.exists != 'true'
run: |
echo "::error::CHANGELOG.md is not updated."
exit 1
Set this action's exists
output to the job's output, and reference it in subsequent jobs.
outputs:
exists: ${{ steps.filter.outputs.exists }}
steps:
- uses: yumemi-inc/path-filter@v2
id: filter
with:
patterns: '**/*.{kt,kts}'
examples
jobs:
filter:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
exists-src: ${{ steps.filter-src.outputs.exists }}
exists-doc: ${{ steps.filter-doc.outputs.exists }}
steps:
- uses: yumemi-inc/path-filter@v2
id: filter-src
with:
patterns: 'src/**'
- uses: yumemi-inc/path-filter@v2
id: filter-doc
with:
patterns: 'doc/**'
job-src:
needs: [filter]
if: needs.filter.outputs.exists-src == 'true'
runs-on: ubuntu-latest
steps:
...
job-doc:
needs: [filter]
if: needs.filter.outputs.exists-doc == 'true'
runs-on: ubuntu-latest
steps:
...
job-common:
needs: [job-src, job-doc]
# treat skipped jobs as successful
if: cancelled() != true && contains(needs.*.result, 'failure') == false
runs-on: ubuntu-latest
steps:
...
Simply, the change files are determined between head-ref
input and base-ref
input references.
The default values for each workflow trigger event are as follows:
event | head-ref | base-ref |
---|---|---|
pull_request | github.sha | github.base_ref |
pull_request_target | refs/pull/<pr number>/merge | github.base_ref |
push | github.sha | github.event.before1 / default branch |
merge_group | github.sha | github.event.merge_group.base_sha |
other events | github.sha | default branch |
Specify these inputs explicitly if necessary. Any branch, tag, or commit SHA can be specified for tease inputs2.
- uses: yumemi-inc/path-filter@v2
with:
head-ref: 'main' # branch to be released
base-ref: 'release-x.x.x' # previous release tag
patterns: '**/*.js'
run: |
...
npm run deploy
By default, two-dot comparison is performed.
If you want to compare with three-dot, set use-merge-base
input to 'true'
.
They are output to a file in JSON format and can be accessed as follows:
${{ steps.<id>.outputs.action-path }}/files.json
${{ steps.<id>.outputs.action-path }}/filtered_files.json
.
more
Refer to these files when debugging head-ref
, base-ref
, and patterns
inputs.
For example, display them in the job summary like this:
- uses: yumemi-inc/path-filter@v2
id: filter
with:
patterns: '!**/*.md'
- run: |
{
echo '### files before filtering'
echo '```json'
cat '${{ steps.filter.outputs.action-path }}/files.json' | jq
echo '```'
echo '### files after filtering'
echo '```json'
cat '${{ steps.filter.outputs.action-path }}/filtered_files.json' | jq
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
You may use these files for purposes other than debugging, but note that these files will be overwritten if you use this action multiple times in the same job.
And, in this action's run
input, access them with Bash variables like $GITHUB_ACTION_PATH/files.json
, but note that the Bash script in run
input will not be executed if there are no files after filtering.
Basically, it complies with the minimatch library used in this action. Please refer to the implementation in action.yml for the specified options.