diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f3a7af..4e59966 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,13 +1,7 @@ # 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) - -## v3.0.0 -- [Update to Node.js 20](https://github.com/dorny/paths-filter/pull/210) +## v2.12.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) ## v2.11.1 diff --git a/README.md b/README.md index b5e0f4c..144a334 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@v3 +- uses: dorny/paths-filter@v2 id: changes with: filters: | @@ -72,7 +72,6 @@ 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 @@ -84,7 +83,7 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob ## Usage ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 with: # Defines filters applied to detected changed files. # Each filter has a name and a list of rules. @@ -153,22 +152,6 @@ 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 @@ -193,7 +176,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v2 id: filter with: filters: | @@ -237,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@v3 + - uses: dorny/paths-filter@v2 id: filter with: filters: | @@ -283,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@v3 + - uses: dorny/paths-filter@v2 id: filter with: filters: | @@ -325,7 +308,7 @@ jobs: pull-requests: read steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v2 id: filter with: filters: ... # Configure your filters @@ -350,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@v3 + - uses: dorny/paths-filter@v2 id: filter with: base: develop # Change detection against merge-base with this branch @@ -374,7 +357,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v2 id: filter with: # Use context to get the branch where commits were pushed. @@ -408,7 +391,7 @@ jobs: # Filter to detect which files were modified # Changes could be, for example, automatically committed - - uses: dorny/paths-filter@v3 + - uses: dorny/paths-filter@v2 id: filter with: base: HEAD @@ -423,7 +406,7 @@ jobs: Define filter rules in own file ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 id: filter with: # Path to file where filters are defined @@ -436,7 +419,7 @@ jobs: Use YAML anchors to reuse path expression(s) inside another rule ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 id: filter with: # &shared is YAML anchor, @@ -457,7 +440,7 @@ jobs: Consider if file was added, modified or deleted ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 id: filter with: # Changed file can be 'added', 'modified', or 'deleted'. @@ -479,39 +462,13 @@ 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
Passing list of modified files as command line args in Linux shell ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 id: filter with: # Enable listing of files matching each filter. @@ -537,7 +494,7 @@ jobs: Passing list of modified files as JSON array to another action ```yaml -- uses: dorny/paths-filter@v3 +- uses: dorny/paths-filter@v2 id: filter with: # Enable listing of files matching each filter. diff --git a/__tests__/filter.test.ts b/__tests__/filter.test.ts index 7d7da94..be2a148 100644 --- a/__tests__/filter.test.ts +++ b/__tests__/filter.test.ts @@ -1,4 +1,4 @@ -import {Filter, FilterConfig, PredicateQuantifier} from '../src/filter' +import {Filter} from '../src/filter' import {File, ChangeStatus} from '../src/file' describe('yaml filter parsing tests', () => { @@ -117,37 +117,6 @@ 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 @@ -217,9 +186,3 @@ 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/action.yml b/action.yml index f03ed40..e7d24f5 100644 --- a/action.yml +++ b/action.yml @@ -44,11 +44,6 @@ 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 diff --git a/dist/index.js b/dist/index.js index cc7d7d4..4a35e5b 100644 --- a/dist/index.js +++ b/dist/index.js @@ -53,53 +53,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.Filter = exports.isPredicateQuantifier = exports.SUPPORTED_PREDICATE_QUANTIFIERS = exports.PredicateQuantifier = void 0; +exports.Filter = 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, filterConfig) { - this.filterConfig = filterConfig; + constructor(yaml) { this.rules = {}; if (yaml) { this.load(yaml); @@ -126,16 +89,7 @@ class Filter { return result; } isMatch(file, patterns) { - 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); - } + return patterns.some(rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)); } parseFilterItemYaml(item) { if (Array.isArray(item)) { @@ -574,18 +528,11 @@ 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; } - 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 filter = new filter_1.Filter(filtersYaml); const files = await getChangedFiles(token, base, ref, initialFetchDepth); core.info(`Detected ${files.length} changed files`); const results = filter.match(files); @@ -608,7 +555,6 @@ 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) { @@ -635,11 +581,8 @@ async function getChangedFiles(token, base, ref, initialFetchDepth) { // At the same time we don't want to fetch any code from forked repository 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 using git diff'); - 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); + core.info('Github token is not available - changes will be detected from PRs merge commit'); + return await git.getChangesInLastCommit(); } else { return getChangedFilesFromGit(base, ref, initialFetchDepth); diff --git a/src/filter.ts b/src/filter.ts index 1947ef8..d0428e4 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -23,48 +23,6 @@ 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[] } @@ -73,7 +31,7 @@ export class Filter { rules: {[key: string]: FilterRuleItem[]} = {} // Creates instance of Filter and load rules from YAML if it's provided - constructor(yaml?: string, readonly filterConfig?: FilterConfig) { + constructor(yaml?: string) { if (yaml) { this.load(yaml) } @@ -104,14 +62,9 @@ export class Filter { } private isMatch(file: File, patterns: FilterRuleItem[]): boolean { - const aPredicate = (rule: Readonly): boolean => { - 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) - } + return patterns.some( + rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename) + ) } private parseFilterItemYaml(item: FilterItemYaml): FilterRuleItem[] { diff --git a/src/main.ts b/src/main.ts index 8320287..6f5fe6a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -4,14 +4,7 @@ import * as github from '@actions/github' import {GetResponseDataTypeFromEndpointMethod} from '@octokit/types' import {PushEvent, PullRequestEvent} from '@octokit/webhooks-types' -import { - isPredicateQuantifier, - Filter, - FilterConfig, - FilterResults, - PredicateQuantifier, - SUPPORTED_PREDICATE_QUANTIFIERS -} from './filter' +import {Filter, FilterResults} from './filter' import {File, ChangeStatus} from './file' import * as git from './git' import {backslashEscape, shellEscape} from './list-format/shell-escape' @@ -33,22 +26,13 @@ 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 } - 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 filter = new Filter(filtersYaml) const files = await getChangedFiles(token, base, ref, initialFetchDepth) core.info(`Detected ${files.length} changed files`) const results = filter.match(files) @@ -102,11 +86,8 @@ async function getChangedFiles(token: string, base: string, ref: string, initial // At the same time we don't want to fetch any code from forked repository 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 using git diff') - 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) + core.info('Github token is not available - changes will be detected from PRs merge commit') + return await git.getChangesInLastCommit() } else { return getChangedFilesFromGit(base, ref, initialFetchDepth) }