Adds ignore path functionality

This commit is contained in:
Henry Painter 2022-08-13 15:47:59 +01:00
parent b0bc141e31
commit d748e34e85
10 changed files with 9748 additions and 32251 deletions

View file

@ -6,14 +6,23 @@ import {File, ChangeStatus} from './file'
interface FilterYaml {
[name: string]: FilterItemYaml
}
type FilterItemYaml =
type FilterItemYaml = includesFilter | {paths: includesFilter; paths_ignore: excludesFilter} | FilterItemYaml[] // Supports referencing another rule via YAML anchor
type includesFilter =
| string // Filename pattern, e.g. "path/to/*.js"
| string[] // Array of filename patterns e.g. ["path/to/thing/**", "path/to/another/**"]
| {[changeTypes: string]: string | string[]} // Change status and filename, e.g. added|modified: "path/to/*.js"
| FilterItemYaml[] // Supports referencing another rule via YAML anchor
type excludesFilter = string[] // Filename pattern, e.g. "path/to/*.js"
// Minimatch options used in all matchers
const MatchOptions = {
dot: true
type matchoptions = {
dot: boolean
ignore: string[]
}
const defaultMatchOptions: matchoptions = {
dot: true,
ignore: []
}
// Internal representation of one item in named filter rule
@ -43,7 +52,7 @@ export class Filter {
return
}
const doc = jsyaml.safeLoad(yaml) as FilterYaml
const doc = jsyaml.load(yaml) as FilterYaml
if (typeof doc !== 'object') {
this.throwInvalidFormatError('Root element is not an object')
}
@ -67,9 +76,11 @@ export class Filter {
)
}
private parseFilterItemYaml(item: FilterItemYaml): FilterRuleItem[] {
private parseFilterItemYaml(item: FilterItemYaml, excludes: excludesFilter = []): FilterRuleItem[] {
var MatchOptions: matchoptions = Object.assign(defaultMatchOptions)
MatchOptions.ignore = excludes
if (Array.isArray(item)) {
return flat(item.map(i => this.parseFilterItemYaml(i)))
return flat(item.map(i => this.parseFilterItemYaml(i, excludes)))
}
if (typeof item === 'string') {
@ -77,21 +88,25 @@ export class Filter {
}
if (typeof item === 'object') {
return Object.entries(item).map(([key, pattern]) => {
if (typeof key !== 'string' || (typeof pattern !== 'string' && !Array.isArray(pattern))) {
this.throwInvalidFormatError(
`Expected [key:string]= pattern:string | string[], but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
)
}
return {
status: key
.split('|')
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()) as ChangeStatus[],
isMatch: picomatch(pattern, MatchOptions)
}
})
if (item.paths_ignore && item.paths) {
return this.parseFilterItemYaml(item.paths, item.paths_ignore as excludesFilter)
} else {
return Object.entries(item).map(([key, pattern]) => {
if (typeof key !== 'string' || (typeof pattern !== 'string' && !Array.isArray(pattern))) {
this.throwInvalidFormatError(
`Expected [key:string]= pattern:string | string[], but [${key}:${typeof key}]= ${pattern}:${typeof pattern} found`
)
}
return {
status: key
.split('|')
.map(x => x.trim())
.filter(x => x.length > 0)
.map(x => x.toLowerCase()) as ChangeStatus[],
isMatch: picomatch(pattern, MatchOptions)
}
})
}
}
this.throwInvalidFormatError(`Unexpected element type '${typeof item}'`)

View file

@ -1,16 +1,12 @@
import * as fs from 'fs'
import * as core from '@actions/core'
import * as github from '@actions/github'
import {Webhooks} from '@octokit/webhooks'
import {Filter, FilterResults} from './filter'
import {File, ChangeStatus} from './file'
import * as git from './git'
import {backslashEscape, shellEscape} from './list-format/shell-escape'
import {csvEscape} from './list-format/csv-escape'
type ExportFormat = 'none' | 'csv' | 'json' | 'shell' | 'escape'
async function run(): Promise<void> {
try {
const workingDirectory = core.getInput('working-directory', {required: false})
@ -41,7 +37,11 @@ async function run(): Promise<void> {
const results = filter.match(files)
exportResults(results, listFiles)
} catch (error) {
core.setFailed(error.message)
let message
if (error instanceof Error) message = error.message
else message = String(error)
// we'll proceed, but let's report it
core.setFailed(message)
}
}
@ -101,9 +101,9 @@ async function getChangedFiles(token: string, base: string, ref: string, initial
if (base) {
core.warning(`'base' input parameter is ignored when 'ref' is not also set on PR events.`)
}
const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest
if (token) {
return await getChangedFilesFromApi(token, pr)
const pr = github.context.payload.pull_request
if (token && pr?.number) {
return await getChangedFilesFromApi(token, pr.number)
}
if (github.context.eventName === 'pull_request_target') {
// pull_request_target is executed in context of base branch and GITHUB_SHA points to last commit in base branch
@ -121,8 +121,7 @@ async function getChangedFiles(token: string, base: string, ref: string, initial
async function getChangedFilesFromGit(base: string, head: string, initialFetchDepth: number): Promise<File[]> {
const defaultBranch = github.context.payload.repository?.default_branch
const beforeSha =
github.context.eventName === 'push' ? (github.context.payload as Webhooks.WebhookPayloadPush).before : null
const beforeSha = github.context.eventName === 'push' ? github.context.payload.before : null
const currentRef = await git.getCurrentRef()
@ -182,22 +181,19 @@ async function getChangedFilesFromGit(base: string, head: string, initialFetchDe
}
// Uses github REST api to get list of files changed in PR
async function getChangedFilesFromApi(
token: string,
prNumber: Webhooks.WebhookPayloadPullRequestPullRequest
): Promise<File[]> {
core.startGroup(`Fetching list of changed files for PR#${prNumber.number} from Github API`)
async function getChangedFilesFromApi(token: string, prNumber: number): Promise<File[]> {
core.startGroup(`Fetching list of changed files for PR#${prNumber} from Github API`)
try {
const client = new github.GitHub(token)
const client = github.getOctokit(token)
const per_page = 100
const files: File[] = []
for (let page = 1; ; page++) {
core.info(`Invoking listFiles(pull_number: ${prNumber.number}, page: ${page}, per_page: ${per_page})`)
const response = await client.pulls.listFiles({
core.info(`Invoking listFiles(pull_number: ${prNumber}, page: ${page}, per_page: ${per_page})`)
const response = await client.rest.pulls.listFiles({
owner: github.context.repo.owner,
repo: github.context.repo.repo,
pull_number: prNumber.number,
pull_number: prNumber,
per_page,
page
})