From f34047f516870a4eb02b82e528eca01a20d78b72 Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Sat, 11 Jul 2020 23:33:11 +0200 Subject: [PATCH] Export changed files grouped by change status (#23) --- .../workflows/pull-request-verification.yml | 5 ++ action.yml | 7 +- dist/index.js | 68 ++++++++++++---- src/filter.ts | 4 + src/main.ts | 78 +++++++++++++++---- 5 files changed, 130 insertions(+), 32 deletions(-) diff --git a/.github/workflows/pull-request-verification.yml b/.github/workflows/pull-request-verification.yml index 46ba5ff..660f26e 100644 --- a/.github/workflows/pull-request-verification.yml +++ b/.github/workflows/pull-request-verification.yml @@ -89,10 +89,15 @@ jobs: - modified: "LICENSE" any: - added|deleted|modified: "*" + - name: Print changed files + run: echo '${{steps.filter.outputs.files}}' | jq . - name: filter-test if: | steps.filter.outputs.add != 'true' || steps.filter.outputs.rm != 'true' || steps.filter.outputs.modified != 'true' || steps.filter.outputs.any != 'true' + || !contains(fromJSON(steps.filter.outputs.files).added,'add.txt') + || !contains(fromJSON(steps.filter.outputs.files).modified,'LICENSE') + || !contains(fromJSON(steps.filter.outputs.files).deleted,'README.md') run: exit 1 diff --git a/action.yml b/action.yml index 990c420..072036b 100644 --- a/action.yml +++ b/action.yml @@ -17,10 +17,13 @@ inputs: required: false filters: description: 'Path to the configuration file or YAML string with filters definition' - required: true + required: false +outputs: + files: + description: 'Changed files grouped by status - added, deleted or modified.' runs: using: 'node12' main: 'dist/index.js' branding: color: blue - icon: filter \ No newline at end of file + icon: filter diff --git a/dist/index.js b/dist/index.js index 842fa33..fb24b6c 100644 --- a/dist/index.js +++ b/dist/index.js @@ -4548,19 +4548,20 @@ function run() { const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput; const filter = new filter_1.default(filtersYaml); const files = yield getChangedFiles(token); + let results; if (files === null) { // Change detection was not possible - // Set all filter keys to true (i.e. changed) - for (const key in filter.rules) { - core.setOutput(key, String(true)); + core.info('All filters will be set to true.'); + results = {}; + for (const key of Object.keys(filter.rules)) { + results[key] = true; } } else { - const result = filter.match(files); - for (const key in result) { - core.setOutput(key, String(result[key])); - } + results = filter.match(files); } + exportFiles(files !== null && files !== void 0 ? files : []); + exportResults(results); } catch (error) { core.setFailed(error.message); @@ -4597,8 +4598,10 @@ function getChangedFilesFromPush() { return __awaiter(this, void 0, void 0, function* () { const push = github.context.payload; // No change detection for pushed tags - if (git.isTagRef(push.ref)) + if (git.isTagRef(push.ref)) { + core.info('Workflow is triggered by pushing of tag. Change detection will not run.'); return null; + } // Get base from input or use repo default branch. // It it starts with 'refs/', it will be trimmed (git fetch refs/heads/ doesn't work) const baseInput = git.trimRefs(core.getInput('base', { required: false }) || push.repository.default_branch); @@ -4607,25 +4610,28 @@ function getChangedFilesFromPush() { const base = git.trimRefsHeads(baseInput) === git.trimRefsHeads(push.ref) ? push.before : baseInput; // There is no previous commit for comparison // e.g. change detection against previous commit of just pushed new branch - if (base === git.NULL_SHA) + if (base === git.NULL_SHA) { + core.info('There is no previous commit for comparison. Change detection will not run.'); return null; + } return yield getChangedFilesFromGit(base); }); } // Fetch base branch and use `git diff` to determine changed files function getChangedFilesFromGit(ref) { return __awaiter(this, void 0, void 0, function* () { - core.debug('Fetching base branch and using `git diff-index` to determine changed files'); - yield git.fetchCommit(ref); - // FETCH_HEAD will always point to the just fetched commit - // No matter if ref is SHA, branch or tag name or full git ref - return yield git.getChangedFiles(git.FETCH_HEAD); + return core.group(`Fetching base and using \`git diff-index\` to determine changed files`, () => __awaiter(this, void 0, void 0, function* () { + yield git.fetchCommit(ref); + // FETCH_HEAD will always point to the just fetched commit + // No matter if ref is SHA, branch or tag name or full git ref + return yield git.getChangedFiles(git.FETCH_HEAD); + })); }); } // Uses github REST api to get list of files changed in PR function getChangedFilesFromApi(token, pullRequest) { return __awaiter(this, void 0, void 0, function* () { - core.debug('Fetching list of modified files from Github API'); + core.info(`Fetching list of changed files for PR#${pullRequest.number} from Github API`); const client = new github.GitHub(token); const pageSize = 100; const files = []; @@ -4663,6 +4669,35 @@ function getChangedFilesFromApi(token, pullRequest) { return files; }); } +function exportFiles(files) { + var _a; + const output = {}; + output[file_1.ChangeStatus.Added] = []; + output[file_1.ChangeStatus.Deleted] = []; + output[file_1.ChangeStatus.Modified] = []; + for (const file of files) { + const arr = (_a = output[file.status]) !== null && _a !== void 0 ? _a : []; + arr.push(file.filename); + output[file.status] = arr; + } + core.setOutput('files', output); + // Files grouped by status + for (const [status, paths] of Object.entries(output)) { + core.startGroup(`${status.toUpperCase()} files:`); + for (const filename of paths) { + core.info(filename); + } + core.endGroup(); + } +} +function exportResults(results) { + core.startGroup('Filters results:'); + for (const [key, value] of Object.entries(results)) { + core.info(`${key}: ${value}`); + core.setOutput(key, value); + } + core.endGroup(); +} run(); @@ -4766,6 +4801,9 @@ class Filter { } // Load rules from YAML string load(yaml) { + if (!yaml) { + return; + } const doc = jsyaml.safeLoad(yaml); if (typeof doc !== 'object') { this.throwInvalidFormatError('Root element is not an object'); diff --git a/src/filter.ts b/src/filter.ts index f951749..e5d354f 100644 --- a/src/filter.ts +++ b/src/filter.ts @@ -35,6 +35,10 @@ export default class Filter { // Load rules from YAML string load(yaml: string): void { + if (!yaml) { + return + } + const doc = jsyaml.safeLoad(yaml) as FilterYaml if (typeof doc !== 'object') { this.throwInvalidFormatError('Root element is not an object') diff --git a/src/main.ts b/src/main.ts index 0f2c878..9b005e0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -7,6 +7,13 @@ import Filter from './filter' import {File, ChangeStatus} from './file' import * as git from './git' +interface FilterResults { + [key: string]: boolean +} +interface ActionOutput { + [key: string]: string[] +} + async function run(): Promise { try { const workingDirectory = core.getInput('working-directory', {required: false}) @@ -20,19 +27,21 @@ async function run(): Promise { const filter = new Filter(filtersYaml) const files = await getChangedFiles(token) + let results: FilterResults if (files === null) { // Change detection was not possible - // Set all filter keys to true (i.e. changed) - for (const key in filter.rules) { - core.setOutput(key, String(true)) + core.info('All filters will be set to true.') + results = {} + for (const key of Object.keys(filter.rules)) { + results[key] = true } } else { - const result = filter.match(files) - for (const key in result) { - core.setOutput(key, String(result[key])) - } + results = filter.match(files) } + + exportFiles(files ?? []) + exportResults(results) } catch (error) { core.setFailed(error.message) } @@ -69,7 +78,10 @@ async function getChangedFilesFromPush(): Promise { const push = github.context.payload as Webhooks.WebhookPayloadPush // No change detection for pushed tags - if (git.isTagRef(push.ref)) return null + if (git.isTagRef(push.ref)) { + core.info('Workflow is triggered by pushing of tag. Change detection will not run.') + return null + } // Get base from input or use repo default branch. // It it starts with 'refs/', it will be trimmed (git fetch refs/heads/ doesn't work) @@ -81,18 +93,22 @@ async function getChangedFilesFromPush(): Promise { // There is no previous commit for comparison // e.g. change detection against previous commit of just pushed new branch - if (base === git.NULL_SHA) return null + if (base === git.NULL_SHA) { + core.info('There is no previous commit for comparison. Change detection will not run.') + return null + } return await getChangedFilesFromGit(base) } // Fetch base branch and use `git diff` to determine changed files async function getChangedFilesFromGit(ref: string): Promise { - core.debug('Fetching base branch and using `git diff-index` to determine changed files') - await git.fetchCommit(ref) - // FETCH_HEAD will always point to the just fetched commit - // No matter if ref is SHA, branch or tag name or full git ref - return await git.getChangedFiles(git.FETCH_HEAD) + return core.group(`Fetching base and using \`git diff-index\` to determine changed files`, async () => { + await git.fetchCommit(ref) + // FETCH_HEAD will always point to the just fetched commit + // No matter if ref is SHA, branch or tag name or full git ref + return await git.getChangedFiles(git.FETCH_HEAD) + }) } // Uses github REST api to get list of files changed in PR @@ -100,7 +116,7 @@ async function getChangedFilesFromApi( token: string, pullRequest: Webhooks.WebhookPayloadPullRequestPullRequest ): Promise { - core.debug('Fetching list of modified files from Github API') + core.info(`Fetching list of changed files for PR#${pullRequest.number} from Github API`) const client = new github.GitHub(token) const pageSize = 100 const files: File[] = [] @@ -138,4 +154,36 @@ async function getChangedFilesFromApi( return files } +function exportFiles(files: File[]): void { + const output: ActionOutput = {} + output[ChangeStatus.Added] = [] + output[ChangeStatus.Deleted] = [] + output[ChangeStatus.Modified] = [] + + for (const file of files) { + const arr = output[file.status] ?? [] + arr.push(file.filename) + output[file.status] = arr + } + core.setOutput('files', output) + + // Files grouped by status + for (const [status, paths] of Object.entries(output)) { + core.startGroup(`${status.toUpperCase()} files:`) + for (const filename of paths) { + core.info(filename) + } + core.endGroup() + } +} + +function exportResults(results: FilterResults): void { + core.startGroup('Filters results:') + for (const [key, value] of Object.entries(results)) { + core.info(`${key}: ${value}`) + core.setOutput(key, value) + } + core.endGroup() +} + run()