From 5266f0ac598cc28b01df67af503dfdaae5e4be85 Mon Sep 17 00:00:00 2001 From: Florent Poinsard Date: Tue, 19 Apr 2022 11:30:13 +0200 Subject: [PATCH 1/7] Compare base and ref when token is empty Signed-off-by: Florent Poinsard --- dist/index.js | 8 ++++++-- src/main.ts | 6 +++++- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/dist/index.js b/dist/index.js index 7f134f1..e2555e3 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4745,6 +4745,7 @@ function getConfigFileContent(configPath) { return fs.readFileSync(configPath, { encoding: 'utf8' }); } async function getChangedFiles(token, base, ref, initialFetchDepth) { + var _a, _b; // if base is 'HEAD' only local uncommitted changes will be detected // This is the simplest case as we don't need to fetch more commits or evaluate current/before refs if (base === git.HEAD) { @@ -4772,7 +4773,10 @@ async function getChangedFiles(token, base, ref, initialFetchDepth) { throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`); } core.info('Github token is not available - changes will be detected from PRs merge commit'); - return await git.getChangesInLastCommit(); + const baseSha = (_a = github.context.payload.pull_request) === null || _a === void 0 ? void 0 : _a.base.sha; + const defaultBranch = (_b = github.context.payload.repository) === null || _b === void 0 ? void 0 : _b.default_branch; + const currentRef = await git.getCurrentRef(); + return await git.getChanges(base || baseSha || defaultBranch, currentRef); } else { return getChangedFilesFromGit(base, ref, initialFetchDepth); @@ -5171,7 +5175,7 @@ module.exports = require("https"); /***/ 215: /***/ (function(module) { -module.exports = {"_args":[["@octokit/rest@16.43.1","C:\\Users\\Michal\\Workspace\\dorny\\pr-changed-files-filter"]],"_from":"@octokit/rest@16.43.1","_id":"@octokit/rest@16.43.1","_inBundle":false,"_integrity":"sha512-gfFKwRT/wFxq5qlNjnW2dh+qh74XgTQ2B179UX5K1HYCluioWj8Ndbgqw2PVqa1NnVJkGHp2ovMpVn/DImlmkw==","_location":"/@octokit/rest","_phantomChildren":{"@types/node":"14.0.5","deprecation":"2.3.1","once":"1.4.0","os-name":"3.1.0"},"_requested":{"type":"version","registry":true,"raw":"@octokit/rest@16.43.1","name":"@octokit/rest","escapedName":"@octokit%2frest","scope":"@octokit","rawSpec":"16.43.1","saveSpec":null,"fetchSpec":"16.43.1"},"_requiredBy":["/@actions/github"],"_resolved":"https://registry.npmjs.org/@octokit/rest/-/rest-16.43.1.tgz","_spec":"16.43.1","_where":"C:\\Users\\Michal\\Workspace\\dorny\\pr-changed-files-filter","author":{"name":"Gregor Martynus","url":"https://github.com/gr2m"},"bugs":{"url":"https://github.com/octokit/rest.js/issues"},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}],"contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"description":"GitHub REST API client for Node.js","devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"files":["index.js","index.d.ts","lib","plugins"],"homepage":"https://github.com/octokit/rest.js#readme","keywords":["octokit","github","rest","api-client"],"license":"MIT","name":"@octokit/rest","nyc":{"ignore":["test"]},"publishConfig":{"access":"public"},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"repository":{"type":"git","url":"git+https://github.com/octokit/rest.js.git"},"scripts":{"build":"npm-run-all build:*","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","build:ts":"npm run -s update-endpoints:typescript","coverage":"nyc report --reporter=html && open coverage/index.html","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","prebuild:browser":"mkdirp dist/","pretest":"npm run -s lint","prevalidate:ts":"npm run -s build:ts","start-fixtures-server":"octokit-fixtures-server","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts"},"types":"index.d.ts","version":"16.43.1"}; +module.exports = {"name":"@octokit/rest","version":"16.43.1","publishConfig":{"access":"public"},"description":"GitHub REST API client for Node.js","keywords":["octokit","github","rest","api-client"],"author":"Gregor Martynus (https://github.com/gr2m)","contributors":[{"name":"Mike de Boer","email":"info@mikedeboer.nl"},{"name":"Fabian Jakobs","email":"fabian@c9.io"},{"name":"Joe Gallo","email":"joe@brassafrax.com"},{"name":"Gregor Martynus","url":"https://github.com/gr2m"}],"repository":"https://github.com/octokit/rest.js","dependencies":{"@octokit/auth-token":"^2.4.0","@octokit/plugin-paginate-rest":"^1.1.1","@octokit/plugin-request-log":"^1.0.0","@octokit/plugin-rest-endpoint-methods":"2.4.0","@octokit/request":"^5.2.0","@octokit/request-error":"^1.0.2","atob-lite":"^2.0.0","before-after-hook":"^2.0.0","btoa-lite":"^1.0.0","deprecation":"^2.0.0","lodash.get":"^4.4.2","lodash.set":"^4.3.2","lodash.uniq":"^4.5.0","octokit-pagination-methods":"^1.1.0","once":"^1.4.0","universal-user-agent":"^4.0.0"},"devDependencies":{"@gimenete/type-writer":"^0.1.3","@octokit/auth":"^1.1.1","@octokit/fixtures-server":"^5.0.6","@octokit/graphql":"^4.2.0","@types/node":"^13.1.0","bundlesize":"^0.18.0","chai":"^4.1.2","compression-webpack-plugin":"^3.1.0","cypress":"^3.0.0","glob":"^7.1.2","http-proxy-agent":"^4.0.0","lodash.camelcase":"^4.3.0","lodash.merge":"^4.6.1","lodash.upperfirst":"^4.3.1","lolex":"^5.1.2","mkdirp":"^1.0.0","mocha":"^7.0.1","mustache":"^4.0.0","nock":"^11.3.3","npm-run-all":"^4.1.2","nyc":"^15.0.0","prettier":"^1.14.2","proxy":"^1.0.0","semantic-release":"^17.0.0","sinon":"^8.0.0","sinon-chai":"^3.0.0","sort-keys":"^4.0.0","string-to-arraybuffer":"^1.0.0","string-to-jsdoc-comment":"^1.0.0","typescript":"^3.3.1","webpack":"^4.0.0","webpack-bundle-analyzer":"^3.0.0","webpack-cli":"^3.0.0"},"types":"index.d.ts","scripts":{"coverage":"nyc report --reporter=html && open coverage/index.html","lint":"prettier --check '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","lint:fix":"prettier --write '{lib,plugins,scripts,test}/**/*.{js,json,ts}' 'docs/*.{js,json}' 'docs/src/**/*' index.js README.md package.json","pretest":"npm run -s lint","test":"nyc mocha test/mocha-node-setup.js \"test/*/**/*-test.js\"","test:browser":"cypress run --browser chrome","build":"npm-run-all build:*","build:ts":"npm run -s update-endpoints:typescript","prebuild:browser":"mkdirp dist/","build:browser":"npm-run-all build:browser:*","build:browser:development":"webpack --mode development --entry . --output-library=Octokit --output=./dist/octokit-rest.js --profile --json > dist/bundle-stats.json","build:browser:production":"webpack --mode production --entry . --plugin=compression-webpack-plugin --output-library=Octokit --output-path=./dist --output-filename=octokit-rest.min.js --devtool source-map","generate-bundle-report":"webpack-bundle-analyzer dist/bundle-stats.json --mode=static --no-open --report dist/bundle-report.html","update-endpoints":"npm-run-all update-endpoints:*","update-endpoints:fetch-json":"node scripts/update-endpoints/fetch-json","update-endpoints:typescript":"node scripts/update-endpoints/typescript","prevalidate:ts":"npm run -s build:ts","validate:ts":"tsc --target es6 --noImplicitAny index.d.ts","postvalidate:ts":"tsc --noEmit --target es6 test/typescript-validate.ts","start-fixtures-server":"octokit-fixtures-server"},"license":"MIT","files":["index.js","index.d.ts","lib","plugins"],"nyc":{"ignore":["test"]},"release":{"publish":["@semantic-release/npm",{"path":"@semantic-release/github","assets":["dist/*","!dist/*.map.gz"]}]},"bundlesize":[{"path":"./dist/octokit-rest.min.js.gz","maxSize":"33 kB"}]}; /***/ }), diff --git a/src/main.ts b/src/main.ts index d2cb678..dc608fe 100644 --- a/src/main.ts +++ b/src/main.ts @@ -8,6 +8,7 @@ import {File, ChangeStatus} from './file' import * as git from './git' import {backslashEscape, shellEscape} from './list-format/shell-escape' import {csvEscape} from './list-format/csv-escape' +import {getChanges} from './git' type ExportFormat = 'none' | 'csv' | 'json' | 'shell' | 'escape' @@ -86,7 +87,10 @@ async function getChangedFiles(token: string, base: string, ref: string, initial throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`) } core.info('Github token is not available - changes will be detected from PRs merge commit') - return await git.getChangesInLastCommit() + const baseSha = github.context.payload.pull_request?.base.sha + const defaultBranch = github.context.payload.repository?.default_branch + const currentRef = await git.getCurrentRef() + return await git.getChanges(base || baseSha || defaultBranch, currentRef) } else { return getChangedFilesFromGit(base, ref, initialFetchDepth) } From 0bc4621a3135347011ad047f9ecf449bf72ce2bd Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Thu, 25 Jan 2024 07:48:07 +0100 Subject: [PATCH 2/7] Bump major version to v3 Node version has been updated to 20 which might be a breaking change. --- CHANGELOG.md | 2 +- README.md | 28 ++++++++++++++-------------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4e59966..4b3a9c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -## v2.12.0 +## v3.0.0 - [Update to Node.js 20 ](https://github.com/dorny/paths-filter/pull/210) - [Update all dependencies](https://github.com/dorny/paths-filter/pull/215) diff --git a/README.md b/README.md index 144a334..557271f 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ don't allow this because they don't work on a level of individual jobs or steps. ## Example ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: changes with: filters: | @@ -83,7 +83,7 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob ## Usage ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 with: # Defines filters applied to detected changed files. # Each filter has a name and a list of rules. @@ -176,7 +176,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: filters: | @@ -220,7 +220,7 @@ jobs: frontend: ${{ steps.filter.outputs.frontend }} steps: # For pull requests it's not necessary to checkout the code - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: filters: | @@ -266,7 +266,7 @@ jobs: packages: ${{ steps.filter.outputs.changes }} steps: # For pull requests it's not necessary to checkout the code - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: filters: | @@ -308,7 +308,7 @@ jobs: pull-requests: read steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: filters: ... # Configure your filters @@ -333,7 +333,7 @@ jobs: # This may save additional git fetch roundtrip if # merge-base is found within latest 20 commits fetch-depth: 20 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: base: develop # Change detection against merge-base with this branch @@ -357,7 +357,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: # Use context to get the branch where commits were pushed. @@ -391,7 +391,7 @@ jobs: # Filter to detect which files were modified # Changes could be, for example, automatically committed - - uses: dorny/paths-filter@v2 + - uses: dorny/paths-filter@v3 id: filter with: base: HEAD @@ -406,7 +406,7 @@ jobs: Define filter rules in own file ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: filter with: # Path to file where filters are defined @@ -419,7 +419,7 @@ jobs: Use YAML anchors to reuse path expression(s) inside another rule ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: filter with: # &shared is YAML anchor, @@ -440,7 +440,7 @@ jobs: Consider if file was added, modified or deleted ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: filter with: # Changed file can be 'added', 'modified', or 'deleted'. @@ -468,7 +468,7 @@ jobs: Passing list of modified files as command line args in Linux shell ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: filter with: # Enable listing of files matching each filter. @@ -494,7 +494,7 @@ jobs: Passing list of modified files as JSON array to another action ```yaml -- uses: dorny/paths-filter@v2 +- uses: dorny/paths-filter@v3 id: filter with: # Enable listing of files matching each filter. From 1441771bbfdd59dcd748680ee64ebd8faab1a242 Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Thu, 25 Jan 2024 08:13:18 +0100 Subject: [PATCH 3/7] Update README.md Add info about v3 release to What's New section --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 557271f..e52115e 100644 --- a/README.md +++ b/README.md @@ -72,6 +72,7 @@ For more scenarios see [examples](#examples) section. ## What's New +- New major release `v3` after update to Node 20 [Breaking change] - Add `ref` input parameter - Add `list-files: csv` format - Configure matrix job to run for each folder with changes using `changes` output From ebc4d7e9ebcb0b1eb21480bb8f43113e996ac77a Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Thu, 15 Feb 2024 09:20:42 +0100 Subject: [PATCH 4/7] Update CHANGELOG for v3.0.1 --- CHANGELOG.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4b3a9c6..9376607 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,10 @@ # Changelog +## v3.0.1 +- [Compare base and ref when token is empty](https://github.com/dorny/paths-filter/pull/133) + ## v3.0.0 -- [Update to Node.js 20 ](https://github.com/dorny/paths-filter/pull/210) +- [Update to Node.js 20](https://github.com/dorny/paths-filter/pull/210) - [Update all dependencies](https://github.com/dorny/paths-filter/pull/215) ## v2.11.1 From f90d5265d6f3c389e3bd0288de3cafc818e26a0f Mon Sep 17 00:00:00 2001 From: Peter Somogyvari Date: Thu, 22 Feb 2024 11:57:39 -0800 Subject: [PATCH 5/7] feat: add config parameter for predicate quantifier Setting the new 'predicate-quantifier' configuration parameter to 'every' makes it so that all the patterns have to match a file for it to be considered changed. This can be leveraged to ensure that you only build & test software changes that have real impact on the behavior of the code, e.g. you can set up your build to run when Typescript/Rust/etc. files are changed but markdown changes in the diff will be ignored and you consume less resources to build. The default behavior does not change by the introduction of this feature so upgrading can be done safely knowing that existing workflows will not break. Signed-off-by: Peter Somogyvari --- README.md | 42 ++++++++++++++++++++++++++++++ __tests__/filter.test.ts | 39 +++++++++++++++++++++++++++- src/filter.ts | 55 +++++++++++++++++++++++++++++++++++++--- src/main.ts | 20 +++++++++++++-- 4 files changed, 149 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index e52115e..b5e0f4c 100644 --- a/README.md +++ b/README.md @@ -153,6 +153,22 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # changes using git commands. # Default: ${{ github.token }} token: '' + + # Optional parameter to override the default behavior of file matching algorithm. + # By default files that match at least one pattern defined by the filters will be included. + # This parameter allows to override the "at least one pattern" behavior to make it so that + # all of the patterns have to match or otherwise the file is excluded. + # An example scenario where this is useful if you would like to match all + # .ts files in a sub-directory but not .md files. + # The filters below will match markdown files despite the exclusion syntax UNLESS + # you specify 'every' as the predicate-quantifier parameter. When you do that, + # it will only match the .ts files in the subdirectory as expected. + # + # backend: + # - 'pkg/a/b/c/**' + # - '!**/*.jpeg' + # - '!**/*.md' + predicate-quantifier: 'some' ``` ## Outputs @@ -463,6 +479,32 @@ jobs: +
+ Detect changes in folder only for some file extensions + +```yaml +- uses: dorny/paths-filter@v3 + id: filter + with: + # This makes it so that all the patterns have to match a file for it to be + # considered changed. Because we have the exclusions for .jpeg and .md files + # the end result is that if those files are changed they will be ignored + # because they don't match the respective rules excluding them. + # + # This can be leveraged to ensure that you only build & test software changes + # that have real impact on the behavior of the code, e.g. you can set up your + # build to run when Typescript/Rust/etc. files are changed but markdown + # changes in the diff will be ignored and you consume less resources to build. + predicate-quantifier: 'every' + filters: | + backend: + - 'pkg/a/b/c/**' + - '!**/*.jpeg' + - '!**/*.md' +``` + +
+ ### Custom processing of changed files
diff --git a/__tests__/filter.test.ts b/__tests__/filter.test.ts index be2a148..7d7da94 100644 --- a/__tests__/filter.test.ts +++ b/__tests__/filter.test.ts @@ -1,4 +1,4 @@ -import {Filter} from '../src/filter' +import {Filter, FilterConfig, PredicateQuantifier} from '../src/filter' import {File, ChangeStatus} from '../src/file' describe('yaml filter parsing tests', () => { @@ -117,6 +117,37 @@ describe('matching tests', () => { expect(pyMatch.backend).toEqual(pyFiles) }) + test('matches only files that are matching EVERY pattern when set to PredicateQuantifier.EVERY', () => { + const yaml = ` + backend: + - 'pkg/a/b/c/**' + - '!**/*.jpeg' + - '!**/*.md' + ` + const filterConfig: FilterConfig = {predicateQuantifier: PredicateQuantifier.EVERY} + const filter = new Filter(yaml, filterConfig) + + const typescriptFiles = modified(['pkg/a/b/c/some-class.ts', 'pkg/a/b/c/src/main/some-class.ts']) + const otherPkgTypescriptFiles = modified(['pkg/x/y/z/some-class.ts', 'pkg/x/y/z/src/main/some-class.ts']) + const otherPkgJpegFiles = modified(['pkg/x/y/z/some-pic.jpeg', 'pkg/x/y/z/src/main/jpeg/some-pic.jpeg']) + const docsFiles = modified([ + 'pkg/a/b/c/some-pics.jpeg', + 'pkg/a/b/c/src/main/jpeg/some-pic.jpeg', + 'pkg/a/b/c/src/main/some-docs.md', + 'pkg/a/b/c/some-docs.md' + ]) + + const typescriptMatch = filter.match(typescriptFiles) + const otherPkgTypescriptMatch = filter.match(otherPkgTypescriptFiles) + const docsMatch = filter.match(docsFiles) + const otherPkgJpegMatch = filter.match(otherPkgJpegFiles) + + expect(typescriptMatch.backend).toEqual(typescriptFiles) + expect(otherPkgTypescriptMatch.backend).toEqual([]) + expect(docsMatch.backend).toEqual([]) + expect(otherPkgJpegMatch.backend).toEqual([]) + }) + test('matches path based on rules included using YAML anchor', () => { const yaml = ` shared: &shared @@ -186,3 +217,9 @@ function modified(paths: string[]): File[] { return {filename, status: ChangeStatus.Modified} }) } + +function renamed(paths: string[]): File[] { + return paths.map(filename => { + return {filename, status: ChangeStatus.Renamed} + }) +} diff --git a/src/filter.ts b/src/filter.ts index d0428e4..2b201fb 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -23,6 +23,48 @@ interface FilterRuleItem { isMatch: (str: string) => boolean // Matches the filename } +/** + * Enumerates the possible logic quantifiers that can be used when determining + * if a file is a match or not with multiple patterns. + * + * The YAML configuration property that is parsed into one of these values is + * 'predicate-quantifier' on the top level of the configuration object of the + * action. + * + * The default is to use 'some' which used to be the hardcoded behavior prior to + * the introduction of the new mechanism. + * + * @see https://en.wikipedia.org/wiki/Quantifier_(logic) + */ +export enum PredicateQuantifier { + /** + * When choosing 'every' in the config it means that files will only get matched + * if all the patterns are satisfied by the path of the file, not just at least one of them. + */ + EVERY = 'every', + /** + * When choosing 'some' in the config it means that files will get matched as long as there is + * at least one pattern that matches them. This is the default behavior if you don't + * specify anything as a predicate quantifier. + */ + SOME = 'some' +} + +/** + * Used to define customizations for how the file filtering should work at runtime. + */ +export type FilterConfig = {readonly predicateQuantifier: PredicateQuantifier} + +/** + * An array of strings (at runtime) that contains the valid/accepted values for + * the configuration parameter 'predicate-quantifier'. + */ +export const SUPPORTED_PREDICATE_QUANTIFIERS = Object.values(PredicateQuantifier) + +export function isPredicateQuantifier(x: unknown): x is PredicateQuantifier { + return SUPPORTED_PREDICATE_QUANTIFIERS.includes(x as PredicateQuantifier) +} + export interface FilterResults { [key: string]: File[] } @@ -31,7 +73,7 @@ export class Filter { rules: {[key: string]: FilterRuleItem[]} = {} // Creates instance of Filter and load rules from YAML if it's provided - constructor(yaml?: string) { + constructor(yaml?: string, public readonly filterConfig?: FilterConfig) { if (yaml) { this.load(yaml) } @@ -62,9 +104,14 @@ export class Filter { } private isMatch(file: File, patterns: FilterRuleItem[]): boolean { - return patterns.some( - rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename) - ) + const aPredicate = (rule: Readonly) => { + return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename) + } + if (this.filterConfig?.predicateQuantifier === 'every') { + return patterns.every(aPredicate) + } else { + return patterns.some(aPredicate) + } } private parseFilterItemYaml(item: FilterItemYaml): FilterRuleItem[] { diff --git a/src/main.ts b/src/main.ts index 18daefb..8320287 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,7 +4,14 @@ import * as github from '@actions/github' import {GetResponseDataTypeFromEndpointMethod} from '@octokit/types' import {PushEvent, PullRequestEvent} from '@octokit/webhooks-types' -import {Filter, FilterResults} from './filter' +import { + isPredicateQuantifier, + Filter, + FilterConfig, + FilterResults, + PredicateQuantifier, + SUPPORTED_PREDICATE_QUANTIFIERS +} from './filter' import {File, ChangeStatus} from './file' import * as git from './git' import {backslashEscape, shellEscape} from './list-format/shell-escape' @@ -26,13 +33,22 @@ async function run(): Promise { const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput const listFiles = core.getInput('list-files', {required: false}).toLowerCase() || 'none' const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', {required: false})) || 10 + const predicateQuantifier = core.getInput('predicate-quantifier', {required: false}) || PredicateQuantifier.SOME if (!isExportFormat(listFiles)) { core.setFailed(`Input parameter 'list-files' is set to invalid value '${listFiles}'`) return } - const filter = new Filter(filtersYaml) + if (!isPredicateQuantifier(predicateQuantifier)) { + const predicateQuantifierInvalidErrorMsg = + `Input parameter 'predicate-quantifier' is set to invalid value ` + + `'${predicateQuantifier}'. Valid values: ${SUPPORTED_PREDICATE_QUANTIFIERS.join(', ')}` + throw new Error(predicateQuantifierInvalidErrorMsg) + } + const filterConfig: FilterConfig = {predicateQuantifier} + + const filter = new Filter(filtersYaml, filterConfig) const files = await getChangedFiles(token, base, ref, initialFetchDepth) core.info(`Detected ${files.length} changed files`) const results = filter.match(files) From de90cc6fb38fc0963ad72b210f1f284cd68cea36 Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Sat, 2 Mar 2024 23:11:12 +0100 Subject: [PATCH 6/7] Update dist and CHANGELOG for v3.0.2 --- CHANGELOG.md | 3 +++ dist/index.js | 61 +++++++++++++++++++++++++++++++++++++++++++++++---- src/filter.ts | 4 ++-- 3 files changed, 62 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9376607..7f3a7af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v3.0.2 +- [Add config parameter for predicate quantifier](https://github.com/dorny/paths-filter/pull/224) + ## v3.0.1 - [Compare base and ref when token is empty](https://github.com/dorny/paths-filter/pull/133) diff --git a/dist/index.js b/dist/index.js index b47bb03..cc7d7d4 100644 --- a/dist/index.js +++ b/dist/index.js @@ -53,16 +53,53 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Filter = void 0; +exports.Filter = exports.isPredicateQuantifier = exports.SUPPORTED_PREDICATE_QUANTIFIERS = exports.PredicateQuantifier = void 0; const jsyaml = __importStar(__nccwpck_require__(1917)); const picomatch_1 = __importDefault(__nccwpck_require__(8569)); // Minimatch options used in all matchers const MatchOptions = { dot: true }; +/** + * Enumerates the possible logic quantifiers that can be used when determining + * if a file is a match or not with multiple patterns. + * + * The YAML configuration property that is parsed into one of these values is + * 'predicate-quantifier' on the top level of the configuration object of the + * action. + * + * The default is to use 'some' which used to be the hardcoded behavior prior to + * the introduction of the new mechanism. + * + * @see https://en.wikipedia.org/wiki/Quantifier_(logic) + */ +var PredicateQuantifier; +(function (PredicateQuantifier) { + /** + * When choosing 'every' in the config it means that files will only get matched + * if all the patterns are satisfied by the path of the file, not just at least one of them. + */ + PredicateQuantifier["EVERY"] = "every"; + /** + * When choosing 'some' in the config it means that files will get matched as long as there is + * at least one pattern that matches them. This is the default behavior if you don't + * specify anything as a predicate quantifier. + */ + PredicateQuantifier["SOME"] = "some"; +})(PredicateQuantifier || (exports.PredicateQuantifier = PredicateQuantifier = {})); +/** + * An array of strings (at runtime) that contains the valid/accepted values for + * the configuration parameter 'predicate-quantifier'. + */ +exports.SUPPORTED_PREDICATE_QUANTIFIERS = Object.values(PredicateQuantifier); +function isPredicateQuantifier(x) { + return exports.SUPPORTED_PREDICATE_QUANTIFIERS.includes(x); +} +exports.isPredicateQuantifier = isPredicateQuantifier; class Filter { // Creates instance of Filter and load rules from YAML if it's provided - constructor(yaml) { + constructor(yaml, filterConfig) { + this.filterConfig = filterConfig; this.rules = {}; if (yaml) { this.load(yaml); @@ -89,7 +126,16 @@ class Filter { return result; } isMatch(file, patterns) { - return patterns.some(rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)); + var _a; + const aPredicate = (rule) => { + return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename); + }; + if (((_a = this.filterConfig) === null || _a === void 0 ? void 0 : _a.predicateQuantifier) === 'every') { + return patterns.every(aPredicate); + } + else { + return patterns.some(aPredicate); + } } parseFilterItemYaml(item) { if (Array.isArray(item)) { @@ -528,11 +574,18 @@ async function run() { const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput; const listFiles = core.getInput('list-files', { required: false }).toLowerCase() || 'none'; const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', { required: false })) || 10; + const predicateQuantifier = core.getInput('predicate-quantifier', { required: false }) || filter_1.PredicateQuantifier.SOME; if (!isExportFormat(listFiles)) { core.setFailed(`Input parameter 'list-files' is set to invalid value '${listFiles}'`); return; } - const filter = new filter_1.Filter(filtersYaml); + if (!(0, filter_1.isPredicateQuantifier)(predicateQuantifier)) { + const predicateQuantifierInvalidErrorMsg = `Input parameter 'predicate-quantifier' is set to invalid value ` + + `'${predicateQuantifier}'. Valid values: ${filter_1.SUPPORTED_PREDICATE_QUANTIFIERS.join(', ')}`; + throw new Error(predicateQuantifierInvalidErrorMsg); + } + const filterConfig = { predicateQuantifier }; + const filter = new filter_1.Filter(filtersYaml, filterConfig); const files = await getChangedFiles(token, base, ref, initialFetchDepth); core.info(`Detected ${files.length} changed files`); const results = filter.match(files); diff --git a/src/filter.ts b/src/filter.ts index 2b201fb..1947ef8 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -73,7 +73,7 @@ export class Filter { rules: {[key: string]: FilterRuleItem[]} = {} // Creates instance of Filter and load rules from YAML if it's provided - constructor(yaml?: string, public readonly filterConfig?: FilterConfig) { + constructor(yaml?: string, readonly filterConfig?: FilterConfig) { if (yaml) { this.load(yaml) } @@ -104,7 +104,7 @@ export class Filter { } private isMatch(file: File, patterns: FilterRuleItem[]): boolean { - const aPredicate = (rule: Readonly) => { + const aPredicate = (rule: Readonly): boolean => { return (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename) } if (this.filterConfig?.predicateQuantifier === 'every') { From 209e61402dbca8aa44f967535da6666b284025ed Mon Sep 17 00:00:00 2001 From: Ward Peeters Date: Fri, 12 Sep 2025 22:58:41 +0200 Subject: [PATCH 7/7] Add missing predicate-quantifier --- action.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/action.yml b/action.yml index e7d24f5..f03ed40 100644 --- a/action.yml +++ b/action.yml @@ -44,6 +44,11 @@ inputs: This option takes effect only when changes are detected using git against different base branch. required: false default: '100' + predicate-quantifier: + description: | + allows to override the "at least one pattern" behavior to make it so that all of the patterns have to match or otherwise the file is excluded. + required: false + default: 'some' outputs: changes: description: JSON array with names of all filters matching any of changed files