diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index e4f55976..b7f7c22d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -12,3 +12,15 @@ jobs: - run: | npm install npm run all + + self-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./ + id: filter + with: + filters: '.github/filters.yml' + - name: filter-test + if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true' + run: exit 1 diff --git a/LICENSE b/LICENSE index a426ef25..d81689a8 100644 --- a/LICENSE +++ b/LICENSE @@ -1,7 +1,7 @@ The MIT License (MIT) -Copyright (c) 2018 GitHub, Inc. and contributors +Copyright (c) 2020 Michal Dorner and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index 2acef130..b44c8b25 100644 --- a/README.md +++ b/README.md @@ -1,21 +1,22 @@

- typescript-action status + paths-filter status

-> **CAUTION**: This action can only be used in a workflow triggered by `pull_request` event. +# Paths filter -# Pull request changed files filter +With this [Github Action](https://github.com/features/actions) you can execute your workflow steps only if relevant files are modified. -This [Github Action](https://github.com/features/actions) enables conditional execution of workflow job steps considering which files are modified by a pull request. - -It saves time and resources especially in monorepo setups, where you can run slow tasks (e.g. integration tests) only for changed components. -Github workflows built-in -[path filters](https://help.github.com/en/actions/referenceworkflow-syntax-for-github-actions#onpushpull_requestpaths) +It saves time and resources especially in monorepo setups, where you can run slow tasks (e.g. integration tests or deployments) only for changed components. +Github workflows built-in [path filters](https://help.github.com/en/actions/referenceworkflow-syntax-for-github-actions#onpushpull_requestpaths) doesn't allow this because they doesn't work on a level of individual jobs or steps. +Action supports workflows triggered by: +- Pull request: changes are detected against the base branch +- Push: changes are detected against the most recent commit on the same branch before the push + ## Usage -The action accepts filter rules in the YAML format. +Filter rules are defined using YAML format. Each filter rule is a list of [glob expressions](https://github.com/isaacs/minimatch). Corresponding output variable will be created to indicate if there's a changed file matching any of the rule glob expressions. Output variables can be later used in the `if` clause to conditionally run specific steps. @@ -29,16 +30,16 @@ Output variables can be later used in the `if` clause to conditionally run speci - `'true'` - if **any** of changed files matches any of rule patterns - `'false'` - if **none** of changed files matches any of rule patterns - ### Notes - minimatch [dot](https://www.npmjs.com/package/minimatch#dot) option is set to true - therefore globbing will match also paths where file or folder name starts with a dot. -### Sample workflow +### Example ```yaml -name: Build verification - on: + push: + branches: + - master pull_request: types: - opened @@ -50,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: dorny/pr-changed-files-filter@v1.1.0 + - uses: dorny/paths-filter@v2.0.0 id: filter with: # inline YAML or path to separate file (e.g.: .github/filters.yaml) @@ -78,18 +79,19 @@ jobs: ## How it works -1. Required inputs are checked (`filters`) -2. If token was provided, it's used to fetch list of changed files from Github API. -3. If token was not provided, base branch is fetched and changed files are detected using `git diff-index` command. -4. For each filter rule it checks if there is any matching file -5. Output variables are set +1. If action was triggered by pull request: + - If access token was provided it's used to fetch list of changed files from Github API. + - If access token was not provided, top of the base branch is fetched and changed files are detected using `git diff-index` command. +2. If action was triggered by push event + - Last commit before the push is fetched and changed files are detected using `git diff-index` command. +3. For each filter rule it checks if there is any matching file +4. Output variables are set -## Difference from related projects: +## Difference from similar projects: - [Has Changed Path](https://github.com/MarceloPrado/has-changed-path) - detects changes from previous commit - you have to configure `checkout` action to fetch some number of previous commits - - `git diff` is used for change detection - outputs only single `true` / `false` value if any of provided paths contains changes - [Changed Files Exporter](https://github.com/futuratrepadeira/changed-files) - outputs lists with paths of created, updated and deleted files diff --git a/action.yml b/action.yml index 1b8ca9cd..8865f335 100644 --- a/action.yml +++ b/action.yml @@ -1,5 +1,5 @@ -name: 'Pull request changed files filter' -description: 'Enables conditional execution of workflow job steps considering which files are modified by a pull request.' +name: 'Paths filter' +description: 'Execute your workflow steps only if relevant files are modified.' author: 'Michal Dorner ' inputs: token: diff --git a/dist/index.js b/dist/index.js index 09f38c0c..c5cb95e8 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3798,27 +3798,27 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge }); }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.getChangedFiles = exports.fetchBranch = void 0; +exports.getChangedFiles = exports.fetchCommit = void 0; const exec_1 = __webpack_require__(986); -function fetchBranch(base) { +function fetchCommit(sha) { return __awaiter(this, void 0, void 0, function* () { - const exitCode = yield exec_1.exec('git', ['fetch', '--depth=1', 'origin', base]); + const exitCode = yield exec_1.exec('git', ['fetch', '--depth=1', 'origin', sha]); if (exitCode !== 0) { - throw new Error(`Fetching branch ${base} failed, exiting`); + throw new Error(`Fetching commit ${sha} failed`); } }); } -exports.fetchBranch = fetchBranch; -function getChangedFiles(base) { +exports.fetchCommit = fetchCommit; +function getChangedFiles(sha) { return __awaiter(this, void 0, void 0, function* () { let output = ''; - const exitCode = yield exec_1.exec('git', ['diff-index', '--name-only', base], { + const exitCode = yield exec_1.exec('git', ['diff-index', '--name-only', sha], { listeners: { stdout: (data) => (output += data.toString()) } }); if (exitCode !== 0) { - throw new Error(`Couldn't determine changed files, exiting`); + throw new Error(`Couldn't determine changed files`); } return output .split('\n') @@ -4485,13 +4485,8 @@ function run() { const token = core.getInput('token', { required: false }); const filtersInput = core.getInput('filters', { required: true }); const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput; - if (github.context.eventName !== 'pull_request') { - core.setFailed('This action can be triggered only by pull_request event'); - return; - } - const pr = github.context.payload.pull_request; const filter = new filter_1.default(filtersYaml); - const files = token ? yield getChangedFilesFromApi(token, pr) : yield getChangedFilesFromGit(pr); + const files = yield getChangedFiles(token); const result = filter.match(files); for (const key in result) { core.setOutput(key, String(result[key])); @@ -4514,13 +4509,27 @@ function getConfigFileContent(configPath) { } return fs.readFileSync(configPath, { encoding: 'utf8' }); } +function getChangedFiles(token) { + return __awaiter(this, void 0, void 0, function* () { + if (github.context.eventName === 'pull_request') { + const pr = github.context.payload.pull_request; + return token ? yield getChangedFilesFromApi(token, pr) : yield getChangedFilesFromGit(pr.base.sha); + } + else if (github.context.eventName === 'push') { + const push = github.context.payload; + return yield getChangedFilesFromGit(push.before); + } + else { + throw new Error('This action can be triggered only by pull_request or push event'); + } + }); +} // Fetch base branch and use `git diff` to determine changed files -function getChangedFilesFromGit(pullRequest) { +function getChangedFilesFromGit(sha) { return __awaiter(this, void 0, void 0, function* () { core.debug('Fetching base branch and using `git diff-index` to determine changed files'); - const baseRef = pullRequest.base.ref; - yield git.fetchBranch(baseRef); - return yield git.getChangedFiles(pullRequest.base.sha); + yield git.fetchCommit(sha); + return yield git.getChangedFiles(sha); }); } // Uses github REST api to get list of files changed in PR diff --git a/package.json b/package.json index 5bb4e2fa..8aeba1a9 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { - "name": "pr-changed-files-filter", + "name": "paths-filter", "version": "1.0.0", "private": true, - "description": "Enables conditional execution of workflow job steps considering which files are modified by a pull request.", + "description": "Execute your workflow steps only if relevant files are modified.", "main": "lib/main.js", "scripts": { "build": "tsc", diff --git a/src/git.ts b/src/git.ts index 242a1c3d..4cfc5db3 100644 --- a/src/git.ts +++ b/src/git.ts @@ -1,22 +1,22 @@ import {exec} from '@actions/exec' -export async function fetchBranch(base: string): Promise { - const exitCode = await exec('git', ['fetch', '--depth=1', 'origin', base]) +export async function fetchCommit(sha: string): Promise { + const exitCode = await exec('git', ['fetch', '--depth=1', 'origin', sha]) if (exitCode !== 0) { - throw new Error(`Fetching branch ${base} failed, exiting`) + throw new Error(`Fetching commit ${sha} failed`) } } -export async function getChangedFiles(base: string): Promise { +export async function getChangedFiles(sha: string): Promise { let output = '' - const exitCode = await exec('git', ['diff-index', '--name-only', base], { + const exitCode = await exec('git', ['diff-index', '--name-only', sha], { listeners: { stdout: (data: Buffer) => (output += data.toString()) } }) if (exitCode !== 0) { - throw new Error(`Couldn't determine changed files, exiting`) + throw new Error(`Couldn't determine changed files`) } return output diff --git a/src/main.ts b/src/main.ts index f9121b5c..f67d7cb7 100644 --- a/src/main.ts +++ b/src/main.ts @@ -12,14 +12,8 @@ async function run(): Promise { const filtersInput = core.getInput('filters', {required: true}) const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput - if (github.context.eventName !== 'pull_request') { - core.setFailed('This action can be triggered only by pull_request event') - return - } - - const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest const filter = new Filter(filtersYaml) - const files = token ? await getChangedFilesFromApi(token, pr) : await getChangedFilesFromGit(pr) + const files = await getChangedFiles(token) const result = filter.match(files) for (const key in result) { @@ -46,12 +40,23 @@ function getConfigFileContent(configPath: string): string { return fs.readFileSync(configPath, {encoding: 'utf8'}) } +async function getChangedFiles(token: string): Promise { + if (github.context.eventName === 'pull_request') { + const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest + return token ? await getChangedFilesFromApi(token, pr) : await getChangedFilesFromGit(pr.base.sha) + } else if (github.context.eventName === 'push') { + const push = github.context.payload as Webhooks.WebhookPayloadPush + return await getChangedFilesFromGit(push.before) + } else { + throw new Error('This action can be triggered only by pull_request or push event') + } +} + // Fetch base branch and use `git diff` to determine changed files -async function getChangedFilesFromGit(pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest): Promise { +async function getChangedFilesFromGit(sha: string): Promise { core.debug('Fetching base branch and using `git diff-index` to determine changed files') - const baseRef = pullRequest.base.ref - await git.fetchBranch(baseRef) - return await git.getChangedFiles(pullRequest.base.sha) + await git.fetchCommit(sha) + return await git.getChangedFiles(sha) } // Uses github REST api to get list of files changed in PR