6 Commits

Author SHA1 Message Date
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 11311 additions and 1998 deletions

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

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

14
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,14 @@
name: "Build"
on:
push:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: |
npm install
npm run all

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,36 +0,0 @@
name: "build-test"
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:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: ./
id: filter
with:
githubToken: ${{ github.token }}
filters: |
src:
- src/**/*
tests:
- __tests__/**/*
any:
- "**/*"
- name: filter-test
if: steps.filter.outputs.any != 'true'
run: exit 1

View File

@@ -1,5 +1,5 @@
<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/pr-changed-files-filter/actions"><img alt="typescript-action status" src="https://github.com/dorny/pr-changed-files-filter/workflows/Build/badge.svg"></a>
</p>
**CAUTION**: This action can be only used in a workflow triggered by `pull_request` event.
@@ -21,8 +21,8 @@ Corresponding output variable will be created to indicate if there's a changed f
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:
@@ -36,14 +36,12 @@ Output variables can be later used in the `if` clause to conditionally run speci
### Sample workflow
```yaml
...
name: Build verification
on:
pull_request:
types:
- opened
- edited
- synchronize
branches:
- master
@@ -55,7 +53,7 @@ jobs:
- uses: dorny/pr-changed-files-filter@v1
id: filter
with:
githubToken: ${{ github.token }}
# inline YAML or path to separate file (e.g.: .github/filters.yaml)
filters: |
backend:
- 'backend/**/*'
@@ -80,10 +78,11 @@ jobs:
## How it works
1. Required inputs are checked (`githubToken` & `filters`)
2. Provided access token is used to fetch list of changed files.
3. For each filter rule it checks if there is any matching file
4. Output variables are set
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
## Difference from related projects:

View File

@@ -2,11 +2,12 @@ name: 'Pull request changed files filter'
description: 'Enables conditional execution of workflow job steps considering which files are modified by a pull request.'
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'

1480
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

@@ -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 fetchBranch(base: string): Promise<void> {
const exitCode = await exec('git', ['fetch', '--depth=1', 'origin', base])
if (exitCode !== 0) {
throw new Error(`Fetching branch ${base} failed, exiting`)
}
}
export async function getChangedFiles(base: string): Promise<string[]> {
let output = ''
const exitCode = await exec('git', ['diff-index', '--name-only', base], {
listeners: {
stdout: (data: Buffer) => (output += data.toString())
}
})
if (exitCode !== 0) {
throw new Error(`Couldn't determine changed files, exiting`)
}
return output
.split('\n')
.map(s => s.trim())
.filter(s => s.length > 0)
}

View File

@@ -1,14 +1,16 @@
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')
@@ -16,8 +18,8 @@ async function run(): Promise<void> {
}
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 = token ? await getChangedFilesFromApi(token, pr) : await getChangedFilesFromGit(pr)
const result = filter.match(files)
for (const key in result) {
@@ -28,11 +30,37 @@ 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'})
}
// Fetch base branch and use `git diff` to determine changed files
async function getChangedFilesFromGit(pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest): Promise<string[]> {
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)
}
// 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++) {