8 Commits

Author SHA1 Message Date
Michal Dorner
affb29871a Support push event (#10)
* Support triggering from push event
* Add self-test to build workflow
* Update action metadata
2020-06-15 21:49:10 +02:00
Tonye Jack
910e8b1235 Update README.md (#11)
* Update README.md

* Update version
2020-06-14 23:04:35 +02:00
Michal Dorner
1cbb925a17 Change detection via git + rename githubToken to token (#9) 2020-05-26 17:16:09 +02:00
Michal Dorner
a2e5f9f7bb Filters input accepts path to external yaml file (#8)
* Filters input accepts path to external yaml file

* Fix formatting and eslint issues

* Fix file ext of filters yaml file

* Update documentation in README file
2020-05-24 22:50:33 +02:00
Michal Dorner
0612377665 Do not require user provided githubToken input (configure default) (#7)
* Do not require user provided `githubToken` input  (configure default)

* Remove `edited` from workflow example

Idea of this example was to show setup where CI runs only when particular files are changed. Triggering workflow when name or description of PR is edited is not needed for this use case.
2020-05-24 21:17:51 +02:00
Michal Dorner
0c9e16cc6d Update dependencies (#6) 2020-05-23 14:23:31 +02:00
Michal Dorner
9b321c4b3a Fix status badge 2020-05-21 14:56:45 +02:00
Michal Dorner
25327a213e Fix README & status badge (#5) 2020-05-21 14:53:14 +02:00
11 changed files with 11348 additions and 2007 deletions

4
.github/filters.yml vendored Normal file
View File

@@ -0,0 +1,4 @@
error:
- not_existing_path/**/*
any:
- "**/*"

View File

@@ -1,9 +1,6 @@
name: "build-test"
name: "Build"
on:
pull_request:
types:
- opened
- synchronize
push:
branches:
- master
@@ -16,21 +13,14 @@ jobs:
npm install
npm run all
test:
self-test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
id: filter
with:
githubToken: ${{ github.token }}
filters: |
src:
- src/**/*
tests:
- __tests__/**/*
any:
- "**/*"
filters: '.github/filters.yml'
- name: filter-test
if: steps.filter.outputs.any != 'true'
if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true'
run: exit 1

View File

@@ -0,0 +1,58 @@
name: "Pull Request Verification"
on:
pull_request:
types:
- opened
- synchronize
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
npm install
npm run all
test-inline:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
id: filter
with:
filters: |
error:
- not_existing_path/**/*
any:
- "**/*"
- name: filter-test
if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true'
run: exit 1
test-external:
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
test-without-token:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
id: filter
with:
token: ''
filters: '.github/filters.yml'
- name: filter-test
if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true'
run: exit 1

View File

@@ -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

View File

@@ -1,49 +1,48 @@
<p align="center">
<a href="https://github.com/dorny/pr-changed-files-filter/actions"><img alt="typescript-action status" src="https://github.com/dorny/pr-changed-files-filter/workflows/build-test/badge.svg"></a>
<a href="https://github.com/dorny/paths-filter/actions"><img alt="paths-filter status" src="https://github.com/dorny/paths-filter/workflows/Build/badge.svg"></a>
</p>
**CAUTION**: This action can be only 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.
### Inputs
- **`githubToken`**: GitHub Access Token - use `${{ github.token }}`
- **`filters`**: YAML dictionary where keys specifies rule names and values are lists of file path patterns
- **`token`**: GitHub Access Token - defaults to `${{ github.token }}`
- **`filters`**: Path to the configuration file or directly embedded string in YAML format. Filter configuration is a dictionary, where keys specifies rule names and values are lists of file path patterns.
### Outputs
- For each rule it sets output variable named by the rule to text:
- `'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
- edited
- synchronize
branches:
- master
@@ -52,10 +51,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: dorny/pr-changed-files-filter@v1
- uses: dorny/paths-filter@v2.0.0
id: filter
with:
githubToken: ${{ github.token }}
# inline YAML or path to separate file (e.g.: .github/filters.yaml)
filters: |
backend:
- 'backend/**/*'
@@ -80,17 +79,19 @@ jobs:
## How it works
1. Required inputs are checked (`githubToken` & `filters`)
2. Provided access token is used to fetch list of changed files.
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
@@ -98,4 +99,4 @@ jobs:
- [Changed File Filter](https://github.com/tony84727/changed-file-filter)
- allows change detection between any refs or commits
- fetches whole history of your git repository
- might have negative performance impact on big repositories (github by default fetches only single commit)
- might have negative performance impact on big repositories (github by default fetches only single commit)

View File

@@ -1,12 +1,13 @@
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 <dorner.michal@gmail.com>'
inputs:
githubToken:
token:
description: 'GitHub Access Token'
required: true
required: false
default: ${{ github.token }}
filters:
description: 'YAML dictionary where keys specifies rule names and values are lists of (globbing) file path patterns'
description: 'Path to the configuration file or YAML string with filters definition'
required: true
runs:
using: 'node12'

1499
dist/index.js vendored

File diff suppressed because it is too large Load Diff

11598
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -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",
@@ -25,27 +25,28 @@
"author": "YourNameOrOrganization",
"license": "MIT",
"dependencies": {
"@actions/core": "^1.2.0",
"@actions/core": "^1.2.4",
"@actions/exec": "^1.0.4",
"@actions/github": "^2.2.0",
"@octokit/webhooks": "^7.6.1",
"@octokit/webhooks": "^7.6.2",
"minimatch": "^3.0.4"
},
"devDependencies": {
"@types/jest": "^24.0.23",
"@types/jest": "^25.2.3",
"@types/js-yaml": "^3.12.4",
"@types/minimatch": "^3.0.3",
"@types/node": "^12.7.12",
"@typescript-eslint/parser": "^2.8.0",
"@zeit/ncc": "^0.22.2",
"@types/node": "^14.0.5",
"@typescript-eslint/parser": "^3.0.0",
"@zeit/ncc": "^0.22.3",
"eslint": "^5.16.0",
"eslint-plugin-github": "^2.0.0",
"eslint-plugin-jest": "^22.21.0",
"jest": "^24.9.0",
"jest-circus": "^24.9.0",
"js-yaml": "^3.13.1",
"prettier": "^1.19.1",
"ts-jest": "^24.2.0",
"typescript": "^3.6.4"
"jest": "^26.0.1",
"jest-circus": "^26.0.1",
"js-yaml": "^3.14.0",
"prettier": "^2.0.5",
"ts-jest": "^26.0.0",
"typescript": "^3.9.3"
},
"jest": {
"testEnvironment": "node"

26
src/git.ts Normal file
View File

@@ -0,0 +1,26 @@
import {exec} from '@actions/exec'
export async function fetchCommit(sha: string): Promise<void> {
const exitCode = await exec('git', ['fetch', '--depth=1', 'origin', sha])
if (exitCode !== 0) {
throw new Error(`Fetching commit ${sha} failed`)
}
}
export async function getChangedFiles(sha: string): Promise<string[]> {
let output = ''
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`)
}
return output
.split('\n')
.map(s => s.trim())
.filter(s => s.length > 0)
}

View File

@@ -1,23 +1,19 @@
import * as fs from 'fs'
import * as core from '@actions/core'
import * as github from '@actions/github'
import {Webhooks} from '@octokit/webhooks'
import Filter from './filter'
import * as git from './git'
async function run(): Promise<void> {
try {
const token = core.getInput('githubToken', {required: true})
const filterYaml = core.getInput('filters', {required: true})
const client = new github.GitHub(token)
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 as Webhooks.WebhookPayloadPullRequestPullRequest
const filter = new Filter(filterYaml)
const files = await getChangedFiles(client, pr)
const filter = new Filter(filtersYaml)
const files = await getChangedFiles(token)
const result = filter.match(files)
for (const key in result) {
@@ -28,11 +24,48 @@ async function run(): Promise<void> {
}
}
function isPathInput(text: string): boolean {
return !text.includes('\n')
}
function getConfigFileContent(configPath: string): string {
if (!fs.existsSync(configPath)) {
throw new Error(`Configuration file '${configPath}' not found`)
}
if (!fs.lstatSync(configPath).isFile()) {
throw new Error(`'${configPath}' is not a file.`)
}
return fs.readFileSync(configPath, {encoding: 'utf8'})
}
async function getChangedFiles(token: string): Promise<string[]> {
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(sha: string): Promise<string[]> {
core.debug('Fetching base branch and using `git diff-index` to determine changed files')
await git.fetchCommit(sha)
return await git.getChangedFiles(sha)
}
// Uses github REST api to get list of files changed in PR
async function getChangedFiles(
client: github.GitHub,
async function getChangedFilesFromApi(
token: string,
pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest
): Promise<string[]> {
core.debug('Fetching list of modified files from Github API')
const client = new github.GitHub(token)
const pageSize = 100
const files: string[] = []
for (let page = 0; page * pageSize < pullRequest.changed_files; page++) {