Skip to content

Commit 205faac

Browse files
committed
feat: add write-to-files feature to deal with large change sets
This commit introduces a new input named 'write-to-files'. It enables writing the lists of matching files to a corresponding file in addition to the output '<filter-name>_files'. If set, the action will create the specified file with the list of matching files. The file will be written in the format specified by the `list-files` option and named after the filter. The path to the file will be output as a variable named `<filter-name>_files_path`.
1 parent de90cc6 commit 205faac

File tree

4 files changed

+91
-18
lines changed

4 files changed

+91
-18
lines changed

README.md

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,13 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob
142142
# Default: none
143143
list-files: ''
144144
145+
# Enables writing the lists of matching files to a corresponding file.
146+
# If set, the action will create the specified file with the list of matching files.
147+
# The file will be written in the format specified by the `list-files` option and named
148+
# after the filter. The path to the file will be relative to the working directory and
149+
# exported as an output variable named `<filter-name>_files_path`.
150+
write-to-files: ''
151+
145152
# Relative path under $GITHUB_WORKSPACE where the repository was checked out.
146153
working-directory: ''
147154

@@ -154,14 +161,14 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob
154161
# Default: ${{ github.token }}
155162
token: ''
156163

157-
# Optional parameter to override the default behavior of file matching algorithm.
164+
# Optional parameter to override the default behavior of file matching algorithm.
158165
# By default files that match at least one pattern defined by the filters will be included.
159166
# This parameter allows to override the "at least one pattern" behavior to make it so that
160-
# all of the patterns have to match or otherwise the file is excluded.
161-
# An example scenario where this is useful if you would like to match all
162-
# .ts files in a sub-directory but not .md files.
163-
# The filters below will match markdown files despite the exclusion syntax UNLESS
164-
# you specify 'every' as the predicate-quantifier parameter. When you do that,
167+
# all of the patterns have to match or otherwise the file is excluded.
168+
# An example scenario where this is useful if you would like to match all
169+
# .ts files in a sub-directory but not .md files.
170+
# The filters below will match markdown files despite the exclusion syntax UNLESS
171+
# you specify 'every' as the predicate-quantifier parameter. When you do that,
165172
# it will only match the .ts files in the subdirectory as expected.
166173
#
167174
# backend:
@@ -505,6 +512,37 @@ jobs:
505512
506513
</details>
507514
515+
<details>
516+
<summary>Handle large change sets (2000+ files)</summary>
517+
518+
```yaml
519+
- uses: dorny/paths-filter@v3
520+
id: changed
521+
with:
522+
# Enable writing the files matching each filter to the disk in addition to the output '<filter_name>_files'.
523+
# The path for each filter's file is output in the format '<filter_name>_files_path'.
524+
write-to-files: true
525+
list-files: json
526+
filters: |
527+
content:
528+
- 'content/**'
529+
530+
531+
- name: List changed directories relative to the base directory
532+
shell: bash
533+
env:
534+
BASE_DIR: ${{ inputs.base-directory }}
535+
CHANGED_CONTENT_FILES_PATH: ${{ steps.changed.outputs.content_files_path }}
536+
run: |
537+
CHANGED_CONTENT_DIRECTORIES=$(cat "${CHANGED_CONTENT_FILES_PATH}" | xargs -n1 realpath -m --relative-to=${BASE_DIR} | cut -f1 -d / | sort -u)
538+
for d in $CHANGED_CONTENT_DIRECTORIES
539+
do
540+
echo "Content directory change detected: ${d}"
541+
done
542+
```
543+
544+
</details>
545+
508546
### Custom processing of changed files
509547
510548
<details>

action.yml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,13 @@ inputs:
3636
Backslash escapes every potentially unsafe character.
3737
required: false
3838
default: none
39+
write-to-files:
40+
description: |
41+
Enables writing the lists of matching files to a corresponding file in addition to the output '<filter-name>_files'.
42+
If set, the action will create the specified file with the list of matching files.
43+
The file will be written in the format specified by the `list-files` option and named
44+
after the filter. The path to the file will be output as a variable named `<filter-name>_files_path`.
45+
required: false
3946
initial-fetch-depth:
4047
description: |
4148
How many commits are initially fetched from base branch.

dist/index.js

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -552,15 +552,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
552552
__setModuleDefault(result, mod);
553553
return result;
554554
};
555+
var __importDefault = (this && this.__importDefault) || function (mod) {
556+
return (mod && mod.__esModule) ? mod : { "default": mod };
557+
};
555558
Object.defineProperty(exports, "__esModule", ({ value: true }));
556559
const fs = __importStar(__nccwpck_require__(7147));
557560
const core = __importStar(__nccwpck_require__(2186));
558561
const github = __importStar(__nccwpck_require__(5438));
562+
const path_1 = __importDefault(__nccwpck_require__(1017));
559563
const filter_1 = __nccwpck_require__(3707);
560564
const file_1 = __nccwpck_require__(4014);
561565
const git = __importStar(__nccwpck_require__(3374));
562566
const shell_escape_1 = __nccwpck_require__(4613);
563567
const csv_escape_1 = __nccwpck_require__(7402);
568+
const fs_1 = __nccwpck_require__(7147);
564569
async function run() {
565570
try {
566571
const workingDirectory = core.getInput('working-directory', { required: false });
@@ -573,6 +578,7 @@ async function run() {
573578
const filtersInput = core.getInput('filters', { required: true });
574579
const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput;
575580
const listFiles = core.getInput('list-files', { required: false }).toLowerCase() || 'none';
581+
const writeToFiles = core.getInput('write-to-files', { required: false }).toLowerCase() === 'true';
576582
const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', { required: false })) || 10;
577583
const predicateQuantifier = core.getInput('predicate-quantifier', { required: false }) || filter_1.PredicateQuantifier.SOME;
578584
if (!isExportFormat(listFiles)) {
@@ -589,7 +595,7 @@ async function run() {
589595
const files = await getChangedFiles(token, base, ref, initialFetchDepth);
590596
core.info(`Detected ${files.length} changed files`);
591597
const results = filter.match(files);
592-
exportResults(results, listFiles);
598+
exportResults(results, listFiles, writeToFiles);
593599
}
594600
catch (error) {
595601
core.setFailed(getErrorMessage(error));
@@ -742,13 +748,14 @@ async function getChangedFilesFromApi(token, pullRequest) {
742748
core.endGroup();
743749
}
744750
}
745-
function exportResults(results, format) {
751+
function exportResults(results, format, writeToFiles) {
752+
const tempDir = (0, fs_1.mkdtempSync)(path_1.default.join(process.cwd(), 'paths-filter-'));
746753
core.info('Results:');
747754
const changes = [];
748755
for (const [key, files] of Object.entries(results)) {
749-
const value = files.length > 0;
750-
core.startGroup(`Filter ${key} = ${value}`);
751-
if (files.length > 0) {
756+
const match = files.length > 0;
757+
core.startGroup(`Filter ${key} = ${match}`);
758+
if (match) {
752759
changes.push(key);
753760
core.info('Matching files:');
754761
for (const file of files) {
@@ -758,11 +765,18 @@ function exportResults(results, format) {
758765
else {
759766
core.info('Matching files: none');
760767
}
761-
core.setOutput(key, value);
768+
core.setOutput(key, match);
762769
core.setOutput(`${key}_count`, files.length);
763770
if (format !== 'none') {
764771
const filesValue = serializeExport(files, format);
765772
core.setOutput(`${key}_files`, filesValue);
773+
if (writeToFiles) {
774+
const ext = format === 'json' ? 'json' : 'txt';
775+
const filePath = path_1.default.join(tempDir, `${key}-files.${ext}`);
776+
fs.writeFileSync(filePath, filesValue);
777+
core.info(`Matching files list for filter '${key}' written to '${filePath}'`);
778+
core.setOutput(`${key}_files_path`, filePath);
779+
}
766780
}
767781
core.endGroup();
768782
}

src/main.ts

Lines changed: 20 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as fs from 'fs'
22
import * as core from '@actions/core'
33
import * as github from '@actions/github'
4+
import path from 'path'
45
import {GetResponseDataTypeFromEndpointMethod} from '@octokit/types'
56
import {PushEvent, PullRequestEvent} from '@octokit/webhooks-types'
67

@@ -16,6 +17,7 @@ import {File, ChangeStatus} from './file'
1617
import * as git from './git'
1718
import {backslashEscape, shellEscape} from './list-format/shell-escape'
1819
import {csvEscape} from './list-format/csv-escape'
20+
import {mkdtempSync} from 'fs'
1921

2022
type ExportFormat = 'none' | 'csv' | 'json' | 'shell' | 'escape'
2123

@@ -32,6 +34,7 @@ async function run(): Promise<void> {
3234
const filtersInput = core.getInput('filters', {required: true})
3335
const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput
3436
const listFiles = core.getInput('list-files', {required: false}).toLowerCase() || 'none'
37+
const writeToFiles = core.getInput('write-to-files', {required: false}).toLowerCase() === 'true'
3538
const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', {required: false})) || 10
3639
const predicateQuantifier = core.getInput('predicate-quantifier', {required: false}) || PredicateQuantifier.SOME
3740

@@ -52,7 +55,7 @@ async function run(): Promise<void> {
5255
const files = await getChangedFiles(token, base, ref, initialFetchDepth)
5356
core.info(`Detected ${files.length} changed files`)
5457
const results = filter.match(files)
55-
exportResults(results, listFiles)
58+
exportResults(results, listFiles, writeToFiles)
5659
} catch (error) {
5760
core.setFailed(getErrorMessage(error))
5861
}
@@ -228,13 +231,15 @@ async function getChangedFilesFromApi(token: string, pullRequest: PullRequestEve
228231
}
229232
}
230233

231-
function exportResults(results: FilterResults, format: ExportFormat): void {
234+
function exportResults(results: FilterResults, format: ExportFormat, writeToFiles: boolean): void {
235+
const tempDir = mkdtempSync(path.join(process.cwd(), 'paths-filter-'))
236+
232237
core.info('Results:')
233238
const changes = []
234239
for (const [key, files] of Object.entries(results)) {
235-
const value = files.length > 0
236-
core.startGroup(`Filter ${key} = ${value}`)
237-
if (files.length > 0) {
240+
const match = files.length > 0
241+
core.startGroup(`Filter ${key} = ${match}`)
242+
if (match) {
238243
changes.push(key)
239244
core.info('Matching files:')
240245
for (const file of files) {
@@ -244,12 +249,21 @@ function exportResults(results: FilterResults, format: ExportFormat): void {
244249
core.info('Matching files: none')
245250
}
246251

247-
core.setOutput(key, value)
252+
core.setOutput(key, match)
248253
core.setOutput(`${key}_count`, files.length)
249254
if (format !== 'none') {
250255
const filesValue = serializeExport(files, format)
251256
core.setOutput(`${key}_files`, filesValue)
257+
258+
if (writeToFiles) {
259+
const ext = format === 'json' ? 'json' : 'txt'
260+
const filePath = path.join(tempDir, `${key}-files.${ext}`)
261+
fs.writeFileSync(filePath, filesValue)
262+
core.info(`Matching files list for filter '${key}' written to '${filePath}'`)
263+
core.setOutput(`${key}_files_path`, filePath)
264+
}
252265
}
266+
253267
core.endGroup()
254268
}
255269

0 commit comments

Comments
 (0)