GitHub Action
Changed Files with filter
A GitHub Action that outputs a list of changed files in pull requests and commits. It is useful when you want to run steps or jobs based on changed files.
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.
Use list of file names from files
output.
- uses: yumemi-inc/changed-files@v3
id: changed
- run: |
for file in ${{ steps.changed.outputs.files }}; do
# do somethihg..
echo "$file"
done
Note: This action only gets a list of file names. If you need access to a file, use actions/checkout to check out the files.
By default, they are separated by spaces, but if you want to change the separator, specify it with separator
input.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
separator: ','
If you want to output in JSON instead of plain text like above, specify it with format
input (default is plain
).
- uses: yumemi-inc/changed-files@v3
id: changed
with:
format: 'json'
The list of files can be filtered by specifying patterns
input.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: |
**/*.{yml,yaml}
!doc/**
To filter by file status, specify statuses
input.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: |
**/*.{yml,yaml}
!doc/**
statuses: 'added'
about file status
There are four statuses for changed files: added
, modified
, renamed
, and removed
.
File statuses are displayed as an icon in pull requests:
Note that renamed files will have renamed
status even if edited.
To specify multiple statuses, separate them with non-alphabetic characters, such as a space, ,
, and |
.
statuses: 'added|modified|renamed'
Alternatively, you can specify statuses to exclude with exclude-statuses
input.
exclude-statuses: 'removed'
Often we are only interested in whether a particular file is included, not the list of files.
You can check it like steps.<id>.outputs.files != null
(for JSON, '[]'
instead of null
), but you can also use exists
output.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '!**/*.md'
- if: steps.changed.outputs.exists == 'true' # or fromJSON(steps.changed.outputs.exists)
run: # do something..
This is useful for controlling step execution.
examples
- uses: actions/checkout@v4
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '**/*.js'
- if: steps.changed.outputs.exists == 'true'
run: npm run test
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: |
**/*.js
!server/**
- env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.number }} ${{ steps.changed.outputs.exists == 'true' && '--add-label' || '--remove-label' }} 'frontend'
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '**/*.xml'
statuses: 'added'
- if: steps.changed.outputs.exists == 'true'
run: |
for file in ${{ steps.changed.outputs.files }}; do
echo "::notice file=$file::New XML file added. Please check .."
done
For more information on workflow commands, see Workflow commands for GitHub Actions.
- uses: yumemi-inc/changed-files@v3
id: changed-src
with:
patterns: |
**/*.{js,ts}
package.json
- uses: yumemi-inc/changed-files@v3
id: changed-build
with:
patterns: 'dist/**'
- if: steps.changed-src.outputs.exists == 'true' && steps.changed-build.outputs.exists != 'true'
uses: yumemi-inc/comment-pull-request@v1
with:
comment: ':warning: Please check if you forgot to build.'
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: 'CHANGELOG.md'
exclude-statuses: 'removed'
- if: steps.changed.outputs.exists != 'true' && github.base_ref == 'main'
run: |
echo "::error::CHANGELOG.md is not updated."
exit 1
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/changed-files@v3
with:
patterns: '!**/*.md'
run: # do something..
changes
output is the total number of changed lines.
This can be used in comparison expressions.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '!doc/**'
exclude-statuses: 'removed'
- if: 100 < steps.changed.outputs.changes
run: # do something..
additions
output and deletions
output can also be used in the same way (additions + deletions = changes
).
examples
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '!doc/**'
exclude-statuses: 'removed'
- env:
GH_REPO: ${{ github.repository }}
GH_TOKEN: ${{ github.token }}
run: |
gh pr edit ${{ github.event.number }} ${{ 100 < steps.changed.outputs.changes && '--add-label' || '--remove-label' }} 'large PR'
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '!doc/**'
exclude-statuses: 'removed'
- if: 100 < steps.changed.outputs.changes
uses: yumemi-inc/comment-pull-request@v1
with:
comment: ':warning: Changes have exceeded 100 lines.'
Simply, the change files are determined between head-ref
input and base-ref
input references.
The behavior is the same as yumemi-inc/path-filter, so refer to it for details, but this Changed Files action has the following limitations:
- The upper limit for the number of files is 3,000 when used with the default value in
pull_request
,pull_request_target
,push
, andmerge_group
events (there is a head commit immediately after the base commit, like a merge commit), and 300 otherwise. - Always performs three-dot comparison, does not support two-dot.
These limitations are because this aciton uses GitHub API. If these limitations are a problem, use yumem-inc/path-filter. Although there are no functions such as filter by status or output the number of changed lines, but since it does not have the above limitations, it works as a complete path filter.
My recommendation is to use this Changed Files action, which has functions useful for checking pull requests, in pull_request
, pull_request_target
and merge_group
events.
There is no problem unless it is a large pull request with 3,000 files.
For other events, you don't need many functions, so use yumem-inc/path-filter.
Set this action's exists
output to the job's output, and reference it in subsequent jobs.
outputs:
exists: ${{ steps.changed.outputs.exists }}
steps:
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '**/*.{kt,kts}'
examples
jobs:
changed:
runs-on: ubuntu-latest
permissions:
contents: read
outputs:
exists-src: ${{ steps.changed-src.outputs.exists }}
exists-doc: ${{ steps.changed-doc.outputs.exists }}
steps:
- uses: yumemi-inc/changed-files@v3
id: changed-src
with:
patterns: 'src/**'
- uses: yumemi-inc/changed-files@v3
id: changed-doc
with:
patterns: 'doc/**'
job-src:
needs: [changed]
if: needs.changed.outputs.exists-src == 'true'
runs-on: ubuntu-latest
steps:
...
job-doc:
needs: [changed]
if: needs.changed.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:
...
Use JSON format output and actions/github-script.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
format: 'json'
- uses: actions/github-script@v6
env:
FILES: ${{ steps.changed.outputs.files }}
with:
script: |
const { FILES } = process.env;
const files = JSON.parse(FILES);
files.forEach(file => {
// do something..
console.log(file);
});
Characters after #
are treated as comments.
Therefore, you can write an explanation for the pattern as a comment.
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: |
# sorces
**/*.{js,css,png}
!dist/** # exclude built files
# documents
doc/**
!doc/**/*.png # exclude image files
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 other inputs of filtering conditions.
For example, display them in the job summary like this:
- uses: yumemi-inc/changed-files@v3
id: changed
with:
patterns: '!**/*.md'
- run: |
{
echo '### files before filtering'
echo '```json'
cat '${{ steps.changed.outputs.action-path }}/files.json' | jq
echo '```'
echo '### files after filtering'
echo '```json'
cat '${{ steps.changed.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.