15 Commits

Author SHA1 Message Date
Michal Dorner
eb75a1edc1 Merge pull request #53 from dorny/detect-local-changes
Support local changes
2020-11-22 21:17:54 +01:00
Michal Dorner
181b35e268 Update docs for v2.6.0 2020-11-22 21:17:07 +01:00
Michal Dorner
1934d574ce Support local changes 2020-11-22 20:59:32 +01:00
Michal Dorner
d599443ba5 Fix change detection using Github API (#51) 2020-11-13 20:15:41 +01:00
Michal Dorner
eb8fe2c24b Update dist/index.js and docs after PR #50 2020-11-13 18:55:57 +01:00
Michal Dorner
dec8b8030e Merge pull request #50 from simllll/patch-1
fix: retrieve all changes via api
2020-11-13 18:48:29 +01:00
Simon Tretter
785a14adbe fix: add missing Octokit import 2020-11-13 17:30:27 +01:00
Simon Tretter
e84bc6af29 fix: retrieve all changes via api
the number returned by pullRequest.changed_files doesn't reflect the correct number of real changed files. Therefore lot of filters didn't work in my scenario, as it said nothing has changed. This especially happens if there are more than 100 files changed (first time I experienced it where over 1000 files have changed, and now when there were about 300 files that have changed). 

I changed the detection by querying the api as long as it returns new results. This ensures all pages have been retrieved. It's tested and used in production on our end, but please check again if I didn't miss anything.

Thanks a lot for this awesome github action :)
2020-11-13 17:28:22 +01:00
Michal Dorner
b4eabb6049 Use picomatch + impl followups from #48 2020-11-09 00:45:53 +01:00
Michal Dorner
550eb4925d Merge pull request #48 from getsentry/master
feat: Add support for multiple patterns when using file status
2020-11-09 00:26:00 +01:00
Billy Vong
5282566eab feat: Add support for multiple patterns when using file status
This adds support for using multiple patterns when checking for file status (added, modified, deleted) and as a result also allows you to use YAML anchors.
2020-11-06 17:41:51 -08:00
Billy Vong
804ec66d7a Fix typo in README example (#47)
The example was missing `outputs`
2020-11-03 09:23:07 +01:00
Michal Dorner
b37d4e9e86 Use micromatch instead of minimatch (#46)
* Use micromatch instead of minimatch

micromatch claims to support full Bash 4.3 spec and it actually passes all the tests. 
For example this fixes processing of '!(**/*.tsx|**/*.less)' pattern - needed by #45

* Update CHANGELOG.md
2020-10-23 12:33:44 +02:00
Michal Dorner
7b5334ddb5 Add links to some real world usage examples 2020-10-17 22:38:15 +02:00
Michal Dorner
75cbfb4be9 Support workflows triggered by any event (#44)
* Allow change detection on all event types

* mention commit SHA in docs for base parameter

* improve logging

* update CHANGELOG.md
2020-10-16 12:28:12 +02:00
10 changed files with 2544 additions and 1375 deletions

View File

@@ -71,6 +71,23 @@ jobs:
if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true'
run: exit 1
test-local-changes:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- run: echo "NEW FILE" > local
- run: git add local
- uses: ./
id: filter
with:
base: HEAD
filters: |
local:
- local
- name: filter-test
if: steps.filter.outputs.local != 'true'
run: exit 1
test-change-type:
runs-on: ubuntu-latest
steps:

View File

@@ -1,5 +1,19 @@
# Changelog
## v2.6.0
- [Support local changes](https://github.com/dorny/paths-filter/pull/53)
## v2.5.3
- [Fixed mapping of removed/deleted change status from github API](https://github.com/dorny/paths-filter/pull/51)
- [Fixed retrieval of all changes via Github API when there are 100+ changes](https://github.com/dorny/paths-filter/pull/50)
## v2.5.2
- [Add support for multiple patterns when using file status](https://github.com/dorny/paths-filter/pull/48)
- [Use picomatch directly instead of micromatch wrapper](https://github.com/dorny/paths-filter/pull/49)
## v2.5.1
- [Improved path matching with micromatch](https://github.com/dorny/paths-filter/pull/46)
## v2.5.0
- [Support workflows triggered by any event](https://github.com/dorny/paths-filter/pull/44)

View File

@@ -7,6 +7,10 @@ It saves time and resources especially in monorepo setups, where you can run slo
Github workflows built-in [path filters](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths)
doesn't allow this because they doesn't work on a level of individual jobs or steps.
**Real world usage examples:**
- [sentry.io](https://sentry.io/) - [backend-test-py3.6.yml](https://github.com/getsentry/sentry/blob/ca0e43dc5602a9ab2e06d3f6397cc48fb5a78541/.github/workflows/backend-test-py3.6.yml#L32)
- [GoogleChrome/web.dev](https://web.dev/) - [lint-and-test-workflow.yml](https://github.com/GoogleChrome/web.dev/blob/e1f0c28964e99ce6a996c1e3fd3ee1985a7a04f6/.github/workflows/lint-and-test-workflow.yml#L33)
## Supported workflows:
- **Pull requests:**
@@ -31,6 +35,10 @@ doesn't allow this because they doesn't work on a level of individual jobs or st
when `base` input parameter is same as the branch that triggered the workflow:
- Changes are detected from last commit
- Uses git commands to detect changes - repository must be already [checked out](https://github.com/actions/checkout)
- **Local changes**
- Workflow triggered by any event when `base` input parameter is set to `HEAD`
- Changes are detected against current HEAD
- Untracked files are ignored
## Example
```yaml
@@ -48,9 +56,9 @@ doesn't allow this because they doesn't work on a level of individual jobs or st
For more scenarios see [examples](#examples) section.
## Notes:
- Paths expressions are evaluated using [minimatch](https://github.com/isaacs/minimatch) library.
- Paths expressions are evaluated using [picomatch](https://github.com/micromatch/picomatch) library.
Documentation for path expression format can be found on project github page.
- Minimatch [dot](https://www.npmjs.com/package/minimatch#dot) option is set to true.
- Micromatch [dot](https://github.com/micromatch/picomatch#options) option is set to true.
Globbing will match also paths where file or folder name starts with a dot.
- It's recommended to quote your path expressions with `'` or `"`. Otherwise you will get an error if it starts with `*`.
- Local execution with [act](https://github.com/nektos/act) works only with alternative runner image. Default runner doesn't have `git` binary.
@@ -58,13 +66,16 @@ For more scenarios see [examples](#examples) section.
# What's New
- Support local changes
- Fixed retrieval of all changes via Github API when there are 100+ changes
- Paths expressions are now evaluated using [picomatch](https://github.com/micromatch/picomatch) library
- Support workflows triggered by any event
- Fixed compatibility with older (<2.23) versions of git
- Support for tag pushes and tags as a base reference
- Fixes for various edge cases when event payload is incomplete
- Supports local execution with [act](https://github.com/nektos/act)
- Fixed behavior of feature branch workflow:
- Detects only changes introduced by feature branch. Later modifications on base branch are ignored.
- Detects only changes introduced by feature branch. Later modifications on base branch are ignored
- Filter by type of file change:
- Optionally consider if file was added, modified or deleted
@@ -298,6 +309,35 @@ jobs:
```
</details>
<details>
<summary><b>Local changes:</b> Detect staged and unstaged local changes</summary>
```yaml
on:
push:
branches: # Push to following branches will trigger the workflow
- master
- develop
- release/**
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
# Some action which modifies files tracked by git (e.g. code linter)
- uses: johndoe/some-action@v1
# Filter to detect which files were modified
# Changes could be for example automatically committed
- uses: dorny/paths-filter@v2
id: filter
with:
base: HEAD
filters: ... # Configure your filters
```
</details>
## Advanced options
<details>
@@ -345,10 +385,15 @@ jobs:
# dictionary, where type(s) of change composes the key.
# Multiple change types can be specified using `|` as delimiter.
filters: |
shared: &shared
- common/**
- config/**
addedOrModified:
- added|modified: '**'
allChanges:
- added|deleted|modified: '**'
addedOrModifiedAnchors:
- added|modified: *shared
```
</details>
@@ -400,7 +445,7 @@ jobs:
- name: Lint Markdown
uses: johndoe/some-action@v1
with:
files: ${{ steps.filter.changed_files }}
files: ${{ steps.filter.outputs.changed_files }}
```
</details>

View File

@@ -98,6 +98,25 @@ describe('matching tests', () => {
expect(match.dot).toEqual(files)
})
test('matches all except tsx and less files (negate a group with or-ed parts)', () => {
const yaml = `
backend:
- '!(**/*.tsx|**/*.less)'
`
const filter = new Filter(yaml)
const tsxFiles = modified(['src/ui.tsx'])
const lessFiles = modified(['src/ui.less'])
const pyFiles = modified(['src/server.py'])
const tsxMatch = filter.match(tsxFiles)
const lessMatch = filter.match(lessFiles)
const pyMatch = filter.match(pyFiles)
expect(tsxMatch.backend).toEqual([])
expect(lessMatch.backend).toEqual([])
expect(pyMatch.backend).toEqual(pyFiles)
})
test('matches path based on rules included using YAML anchor', () => {
const yaml = `
shared: &shared
@@ -146,6 +165,20 @@ describe('matching specific change status', () => {
const match = filter.match(files)
expect(match.addOrModify).toEqual(files)
})
test('matches when using an anchor', () => {
const yaml = `
shared: &shared
- common/**/*
- config/**/*
src:
- modified: *shared
`
let filter = new Filter(yaml)
const files = modified(['config/file.js', 'common/anotherFile.js'])
const match = filter.match(files)
expect(match.src).toEqual(files)
})
})
function modified(paths: string[]): File[] {

3378
dist/index.js vendored

File diff suppressed because it is too large Load Diff

362
package-lock.json generated
View File

@@ -3808,6 +3808,12 @@
"integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==",
"dev": true
},
"@types/picomatch": {
"version": "2.2.1",
"resolved": "https://registry.npmjs.org/@types/picomatch/-/picomatch-2.2.1.tgz",
"integrity": "sha512-26/tQcDmJXYHiaWAAIjnTVL5nwrT+IVaqFZIbBImAuKk/r/j1r/1hmZ7uaOzG6IknqP3QHcNNQ6QO8Vp28lUoA==",
"dev": true
},
"@types/prettier": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.0.1.tgz",
@@ -4038,6 +4044,111 @@
"requires": {
"micromatch": "^3.1.4",
"normalize-path": "^2.1.1"
},
"dependencies": {
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
}
}
}
},
"argparse": {
@@ -4778,7 +4889,8 @@
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
"integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
"dev": true
},
"base": {
"version": "0.11.2",
@@ -4853,40 +4965,12 @@
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dev": true,
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"browser-process-hrtime": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz",
@@ -5145,7 +5229,8 @@
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
"dev": true
},
"contains-path": {
"version": "0.1.0",
@@ -6484,29 +6569,6 @@
"flat-cache": "^2.0.1"
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"find-up": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz",
@@ -6768,6 +6830,26 @@
"kind-of": "^4.0.0"
},
"dependencies": {
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"kind-of": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz",
@@ -7121,26 +7203,6 @@
"is-extglob": "^2.1.1"
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
@@ -12325,9 +12387,9 @@
}
},
"kind-of": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
"integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
"integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
"dev": true
},
"kleur": {
@@ -12478,27 +12540,6 @@
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
"dev": true
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"mime-db": {
"version": "1.44.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.44.0.tgz",
@@ -12524,6 +12565,7 @@
"version": "3.0.4",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
"integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
"dev": true,
"requires": {
"brace-expansion": "^1.1.7"
}
@@ -12977,8 +13019,7 @@
"picomatch": {
"version": "2.2.2",
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.2.2.tgz",
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg==",
"dev": true
"integrity": "sha512-q0M/9eZHzmr0AulXyPwNfZjtwZ/RBZlbN3K3CErVrk50T2ASYI7Bye0EvekFY3IP1Nt2DHu0re+V2ZHIpMkuWg=="
},
"pify": {
"version": "2.3.0",
@@ -13506,11 +13547,114 @@
"walker": "~1.0.5"
},
"dependencies": {
"braces": {
"version": "2.3.2",
"resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz",
"integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==",
"dev": true,
"requires": {
"arr-flatten": "^1.1.0",
"array-unique": "^0.3.2",
"extend-shallow": "^2.0.1",
"fill-range": "^4.0.0",
"isobject": "^3.0.1",
"repeat-element": "^1.1.2",
"snapdragon": "^0.8.1",
"snapdragon-node": "^2.0.1",
"split-string": "^3.0.2",
"to-regex": "^3.0.1"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"fill-range": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-4.0.0.tgz",
"integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=",
"dev": true,
"requires": {
"extend-shallow": "^2.0.1",
"is-number": "^3.0.0",
"repeat-string": "^1.6.1",
"to-regex-range": "^2.1.0"
},
"dependencies": {
"extend-shallow": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz",
"integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=",
"dev": true,
"requires": {
"is-extendable": "^0.1.0"
}
}
}
},
"is-number": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-number/-/is-number-3.0.0.tgz",
"integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=",
"dev": true,
"requires": {
"kind-of": "^3.0.2"
},
"dependencies": {
"kind-of": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz",
"integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=",
"dev": true,
"requires": {
"is-buffer": "^1.1.5"
}
}
}
},
"micromatch": {
"version": "3.1.10",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-3.1.10.tgz",
"integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==",
"dev": true,
"requires": {
"arr-diff": "^4.0.0",
"array-unique": "^0.3.2",
"braces": "^2.3.1",
"define-property": "^2.0.2",
"extend-shallow": "^3.0.2",
"extglob": "^2.0.4",
"fragment-cache": "^0.2.1",
"kind-of": "^6.0.2",
"nanomatch": "^1.2.9",
"object.pick": "^1.3.0",
"regex-not": "^1.0.0",
"snapdragon": "^0.8.1",
"to-regex": "^3.0.2"
}
},
"minimist": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
}
}
}
},
@@ -13745,12 +13889,12 @@
"dev": true
},
"source-map-resolve": {
"version": "0.5.2",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.2.tgz",
"integrity": "sha512-MjqsvNwyz1s0k81Goz/9vRBe9SZdB09Bdw+/zYyO+3CuPk6fouTaxscHkgtE8jKvf01kVfl8riHzERQ/kefaSA==",
"version": "0.5.3",
"resolved": "https://registry.npmjs.org/source-map-resolve/-/source-map-resolve-0.5.3.tgz",
"integrity": "sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==",
"dev": true,
"requires": {
"atob": "^2.1.1",
"atob": "^2.1.2",
"decode-uri-component": "^0.2.0",
"resolve-url": "^0.2.1",
"source-map-url": "^0.4.0",
@@ -14214,16 +14358,6 @@
"safe-regex": "^1.1.0"
}
},
"to-regex-range": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-2.1.1.tgz",
"integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=",
"dev": true,
"requires": {
"is-number": "^3.0.0",
"repeat-string": "^1.6.1"
}
},
"tough-cookie": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-3.0.1.tgz",

View File

@@ -29,13 +29,14 @@
"@actions/exec": "^1.0.4",
"@actions/github": "^2.2.0",
"@octokit/webhooks": "^7.6.2",
"minimatch": "^3.0.4"
"picomatch": "^2.2.2"
},
"devDependencies": {
"@types/jest": "^25.2.3",
"@types/js-yaml": "^3.12.4",
"@types/minimatch": "^3.0.3",
"@types/node": "^14.0.5",
"@types/picomatch": "^2.2.1",
"@typescript-eslint/parser": "^3.3.0",
"@zeit/ncc": "^0.22.3",
"eslint": "^7.3.0",

View File

@@ -1,5 +1,5 @@
import * as jsyaml from 'js-yaml'
import * as minimatch from 'minimatch'
import picomatch from 'picomatch'
import {File, ChangeStatus} from './file'
// Type definition of object we expect to load from YAML
@@ -8,11 +8,11 @@ interface FilterYaml {
}
type FilterItemYaml =
| string // Filename pattern, e.g. "path/to/*.js"
| {[changeTypes: string]: string} // Change status and filename, e.g. added|modified: "path/to/*.js"
| {[changeTypes: string]: string | string[]} // Change status and filename, e.g. added|modified: "path/to/*.js"
| FilterItemYaml[] // Supports referencing another rule via YAML anchor
// Minimatch options used in all matchers
const MinimatchOptions: minimatch.IOptions = {
const MatchOptions = {
dot: true
}
@@ -20,7 +20,7 @@ const MinimatchOptions: minimatch.IOptions = {
// Created as simplified form of data in FilterItemYaml
interface FilterRuleItem {
status?: ChangeStatus[] // Required change status of the matched files
matcher: minimatch.IMinimatch // Matches the filename
isMatch: (str: string) => boolean // Matches the filename
}
export interface FilterResults {
@@ -63,7 +63,7 @@ export class Filter {
private isMatch(file: File, patterns: FilterRuleItem[]): boolean {
return patterns.some(
rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.matcher.match(file.filename)
rule => (rule.status === undefined || rule.status.includes(file.status)) && rule.isMatch(file.filename)
)
}
@@ -73,14 +73,14 @@ export class Filter {
}
if (typeof item === 'string') {
return [{status: undefined, matcher: new minimatch.Minimatch(item, MinimatchOptions)}]
return [{status: undefined, isMatch: picomatch(item, MatchOptions)}]
}
if (typeof item === 'object') {
return Object.entries(item).map(([key, pattern]) => {
if (typeof key !== 'string' || typeof pattern !== 'string') {
if (typeof key !== 'string' || (typeof pattern !== 'string' && !Array.isArray(pattern))) {
this.throwInvalidFormatError(
`Expected [key:string]= pattern:string, but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
`Expected [key:string]= pattern:string | string[], but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
)
}
return {
@@ -89,7 +89,7 @@ export class Filter {
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()) as ChangeStatus[],
matcher: new minimatch.Minimatch(pattern, MinimatchOptions)
isMatch: picomatch(pattern, MatchOptions)
}
})
}

View File

@@ -3,6 +3,7 @@ import * as core from '@actions/core'
import {File, ChangeStatus} from './file'
export const NULL_SHA = '0000000000000000000000000000000000000000'
export const HEAD = 'HEAD'
export async function getChangesInLastCommit(): Promise<File[]> {
core.startGroup(`Change detection in last commit`)
@@ -39,6 +40,20 @@ export async function getChanges(ref: string): Promise<File[]> {
return parseGitDiffOutput(output)
}
export async function getChangesOnHead(): Promise<File[]> {
// Get current changes - both staged and unstaged
core.startGroup(`Change detection on HEAD`)
let output = ''
try {
output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', 'HEAD'])).stdout
} finally {
fixStdOutNullTermination()
core.endGroup()
}
return parseGitDiffOutput(output)
}
export async function getChangesSinceMergeBase(ref: string, initialFetchDepth: number): Promise<File[]> {
if (!(await hasCommit(ref))) {
// Fetch and add base branch

View File

@@ -55,6 +55,12 @@ function getConfigFileContent(configPath: string): string {
}
async function getChangedFiles(token: string, base: string, initialFetchDepth: number): Promise<File[]> {
// 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) {
return await git.getChangesOnHead()
}
if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') {
const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest
if (token) {
@@ -75,7 +81,7 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number):
const pushRef =
git.getShortName(github.context.ref) ||
(core.warning(`'ref' field is missing in PUSH event payload - using current branch, tag or commit SHA`),
(core.warning(`'ref' field is missing in event payload - using current branch, tag or commit SHA`),
await git.getCurrentRef())
const baseRef = git.getShortName(base) || defaultRef
@@ -88,11 +94,11 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number):
const isBaseRefSha = git.isGitSha(baseRef)
const isBaseSameAsPush = baseRef === pushRef
// If base is commit SHA will do comparison against the referenced commit
// Or If base references same branch it was pushed to, we will do comparison against the previously pushed commit
// If base is commit SHA we will do comparison against the referenced commit
// Or if base references same branch it was pushed to, we will do comparison against the previously pushed commit
if (isBaseRefSha || isBaseSameAsPush) {
if (!isBaseRefSha && !beforeSha) {
core.warning(`'before' field is missing in PUSH event payload - changes will be detected from last commit`)
core.warning(`'before' field is missing in event payload - changes will be detected from last commit`)
return await git.getChangesInLastCommit()
}
@@ -123,11 +129,13 @@ async function getChangedFilesFromApi(
token: string,
pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest
): Promise<File[]> {
core.info(`Fetching list of changed files for PR#${pullRequest.number} from Github API`)
core.startGroup(`Fetching list of changed files for PR#${pullRequest.number} from Github API`)
core.info(`Number of changed_files is ${pullRequest.changed_files}`)
const client = new github.GitHub(token)
const pageSize = 100
const files: File[] = []
for (let page = 0; page * pageSize < pullRequest.changed_files; page++) {
for (let page = 1; (page - 1) * pageSize < pullRequest.changed_files; page++) {
core.info(`Invoking listFiles(pull_number: ${pullRequest.number}, page: ${page}, per_page: ${pageSize})`)
const response = await client.pulls.listFiles({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
@@ -136,6 +144,7 @@ async function getChangedFilesFromApi(
per_page: pageSize
})
for (const row of response.data) {
core.info(`[${row.status}] ${row.filename}`)
// There's no obvious use-case for detection of renames
// Therefore we treat it as if rename detection in git diff was turned off.
// Rename is replaced by delete of original filename and add of new filename
@@ -150,14 +159,17 @@ async function getChangedFilesFromApi(
status: ChangeStatus.Deleted
})
} else {
// Github status and git status variants are same except for deleted files
const status = row.status === 'removed' ? ChangeStatus.Deleted : (row.status as ChangeStatus)
files.push({
filename: row.filename,
status: row.status as ChangeStatus
status
})
}
}
}
core.endGroup()
return files
}