Export changed files grouped by change status (#23)

This commit is contained in:
Michal Dorner 2020-07-11 23:33:11 +02:00
parent 1ff702da35
commit f34047f516
No known key found for this signature in database
GPG Key ID: 9EEE04B48DA36786
5 changed files with 130 additions and 32 deletions

View File

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

View File

@ -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
icon: filter

68
dist/index.js vendored
View File

@ -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/<NAME> 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');

View File

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

View File

@ -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<void> {
try {
const workingDirectory = core.getInput('working-directory', {required: false})
@ -20,19 +27,21 @@ async function run(): Promise<void> {
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<File[] | null> {
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/<NAME> doesn't work)
@ -81,18 +93,22 @@ async function getChangedFilesFromPush(): Promise<File[] | null> {
// 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<File[]> {
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<File[]> {
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()