4 Commits

Author SHA1 Message Date
Michal Dorner
8b399ed168 Add minimal example to the top part of README 2020-10-01 10:55:29 +02:00
Michal Dorner
ff5bb057bf v2.4.0 - support local execution with act + allow tags (#40)
* Avoid code repetition with exec() and output listeners

* Improve behavior for new branches and when it's running in ACT

* Detect parent commit only if needed

* Fix parent commit detection for initial commit

* Improve logging

* Improve current ref detection

* Fix issue when base is a already fetched tag

* Fix issue when base is a already fetched tag

* Update README

* Document usage with act

* Use `git log` to get changes in latest commit

* Disable other output for `git log`

* get short name from base ref + improve loggig

* update CHANGELOG
2020-09-30 00:32:49 +02:00
Michal Dorner
d9e86af7c0 Small improvement to project description 2020-09-14 18:49:18 +02:00
Michal Dorner
adb239d623 Add hint to use fetch-depth for feature branch workdlow 2020-09-14 18:09:37 +02:00
8 changed files with 317 additions and 152 deletions

View File

@@ -1,5 +1,13 @@
# Changelog
## v2.4.0
- [Support pushes of tags or when tag is used as base](https://github.com/dorny/paths-filter/pull/40)
- [Use git log to detect changes from PRs merge commit if token is not available](https://github.com/dorny/paths-filter/pull/40)
- [Support local execution with act](https://github.com/dorny/paths-filter/pull/40)
- [Improved processing of repository initial push](https://github.com/dorny/paths-filter/pull/40)
- [Improved processing of first push of new branch](https://github.com/dorny/paths-filter/pull/40)
## v2.3.0
- [Improved documentation](https://github.com/dorny/paths-filter/pull/37)
- [Change detection using git "three dot" diff](https://github.com/dorny/paths-filter/pull/35)

View File

@@ -1,8 +1,7 @@
# paths-filter
# Paths Changes Filter
This [Github Action](https://github.com/features/actions) enables conditional execution of workflow steps and jobs,
based on the paths that are modified by pull request or in pushed commits.
based on the files modified by pull request, feature branch or in pushed commits.
It saves time and resources especially in monorepo setups, where you can run slow tasks (e.g. integration tests or deployments) only for changed components.
Github workflows built-in [path filters](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#onpushpull_requestpaths)
@@ -24,17 +23,35 @@ doesn't allow this because they doesn't work on a level of individual jobs or st
- Changes are detected against the most recent commit on the same branch before the push
- Uses git commands to detect changes - repository must be already [checked out](https://github.com/actions/checkout)
## Example
```yaml
- uses: dorny/paths-filter@v2
id: changes
with:
filters: |
src:
- 'src/**'
## Important notes:
# run only if some file in 'src' folder was changed
if: steps.changes.outputs.src == 'true'
run: ...
```
For more scenarios see [examples](#examples) section.
## Notes:
- Paths expressions are evaluated using [minimatch](https://github.com/isaacs/minimatch) 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.
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.
- Use: `act -P ubuntu-latest=nektos/act-environments-ubuntu:18.04`
# What's New
- 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.
- Filter by type of file change:
@@ -68,7 +85,7 @@ For more information see [CHANGELOG](https://github.com/actions/checkout/blob/ma
# Filters syntax is documented by example - see examples section.
filters: ''
# Branch against which the changes will be detected.
# Branch or tag against which the changes will be detected.
# If it references same branch it was pushed to,
# changes are detected against the most recent commit before the push.
# Otherwise it uses git merge-base to find best common ancestor between
@@ -235,6 +252,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
# This may save additional git fetch roundtrip if
# merge-base is found within latest 20 commits
fetch-depth: 20
- uses: dorny/paths-filter@v2
id: filter
with:

View File

@@ -17,19 +17,13 @@ describe('parsing output of the git diff command', () => {
})
describe('git utility function tests (those not invoking git)', () => {
test('Detects if ref references a tag', () => {
expect(git.isTagRef('refs/tags/v1.0')).toBeTruthy()
expect(git.isTagRef('refs/heads/master')).toBeFalsy()
expect(git.isTagRef('master')).toBeFalsy()
})
test('Trims "refs/" from ref', () => {
expect(git.trimRefs('refs/heads/master')).toBe('heads/master')
expect(git.trimRefs('heads/master')).toBe('heads/master')
expect(git.trimRefs('master')).toBe('master')
})
test('Trims "refs/" and "heads/" from ref', () => {
expect(git.trimRefsHeads('refs/heads/master')).toBe('master')
expect(git.trimRefsHeads('heads/master')).toBe('master')
expect(git.trimRefsHeads('master')).toBe('master')
expect(git.getShortName('refs/heads/master')).toBe('master')
expect(git.getShortName('heads/master')).toBe('heads/master')
expect(git.getShortName('master')).toBe('master')
expect(git.getShortName('refs/tags/v1')).toBe('v1')
expect(git.getShortName('tags/v1')).toBe('tags/v1')
expect(git.getShortName('v1')).toBe('v1')
})
})

View File

@@ -1,4 +1,4 @@
name: 'Paths filter'
name: 'Paths Changes Filter'
description: 'Execute your workflow steps only if relevant files are modified.'
author: 'Michal Dorner <dorner.michal@gmail.com>'
inputs:

212
dist/index.js vendored
View File

@@ -3807,27 +3807,20 @@ var __importStar = (this && this.__importStar) || function (mod) {
__setModuleDefault(result, mod);
return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.trimRefsHeads = exports.trimRefs = exports.isTagRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceRef = exports.getChangesAgainstSha = exports.NULL_SHA = void 0;
const exec_1 = __webpack_require__(986);
exports.getShortName = exports.getCurrentRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceMergeBase = exports.getChanges = exports.getChangesInLastCommit = exports.NULL_SHA = void 0;
const exec_1 = __importDefault(__webpack_require__(807));
const core = __importStar(__webpack_require__(470));
const file_1 = __webpack_require__(258);
exports.NULL_SHA = '0000000000000000000000000000000000000000';
async function getChangesAgainstSha(sha) {
// Fetch single commit
core.startGroup(`Fetching ${sha} from origin`);
await exec_1.exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', sha]);
core.endGroup();
// Get differences between sha and HEAD
core.startGroup(`Change detection ${sha}..HEAD`);
async function getChangesInLastCommit() {
core.startGroup(`Change detection in last commit`);
let output = '';
try {
// Two dots '..' change detection - directly compares two versions
await exec_1.exec('git', ['diff', '--no-renames', '--name-status', '-z', `${sha}..HEAD`], {
listeners: {
stdout: (data) => (output += data.toString())
}
});
output = (await exec_1.default('git', ['log', '--format=', '--no-renames', '--name-status', '-z', '-n', '1'])).stdout;
}
finally {
fixStdOutNullTermination();
@@ -3835,23 +3828,52 @@ async function getChangesAgainstSha(sha) {
}
return parseGitDiffOutput(output);
}
exports.getChangesAgainstSha = getChangesAgainstSha;
async function getChangesSinceRef(ref, initialFetchDepth) {
// Fetch and add base branch
core.startGroup(`Fetching ${ref} from origin until merge-base is found`);
await exec_1.exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`]);
exports.getChangesInLastCommit = getChangesInLastCommit;
async function getChanges(ref) {
if (!(await hasCommit(ref))) {
// Fetch single commit
core.startGroup(`Fetching ${ref} from origin`);
await exec_1.default('git', ['fetch', '--depth=1', '--no-tags', '--no-auto-gc', 'origin', ref]);
core.endGroup();
}
// Get differences between ref and HEAD
core.startGroup(`Change detection ${ref}..HEAD`);
let output = '';
try {
// Two dots '..' change detection - directly compares two versions
output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}..HEAD`])).stdout;
}
finally {
fixStdOutNullTermination();
core.endGroup();
}
return parseGitDiffOutput(output);
}
exports.getChanges = getChanges;
async function getChangesSinceMergeBase(ref, initialFetchDepth) {
if (!(await hasCommit(ref))) {
// Fetch and add base branch
core.startGroup(`Fetching ${ref}`);
try {
await exec_1.default('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`]);
}
finally {
core.endGroup();
}
}
async function hasMergeBase() {
return (await exec_1.exec('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })) === 0;
return (await exec_1.default('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })).code === 0;
}
async function countCommits() {
return (await getNumberOfCommits('HEAD')) + (await getNumberOfCommits(ref));
}
core.startGroup(`Searching for merge-base with ${ref}`);
// Fetch more commits until merge-base is found
if (!(await hasMergeBase())) {
let deepen = initialFetchDepth;
let lastCommitsCount = await countCommits();
do {
await exec_1.exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc', '-q']);
await exec_1.default('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc']);
const count = await countCommits();
if (count <= lastCommitsCount) {
core.info('No merge base found - all files will be listed as added');
@@ -3868,11 +3890,7 @@ async function getChangesSinceRef(ref, initialFetchDepth) {
let output = '';
try {
// Three dots '...' change detection - finds merge-base and compares against it
await exec_1.exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`], {
listeners: {
stdout: (data) => (output += data.toString())
}
});
output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout;
}
finally {
fixStdOutNullTermination();
@@ -3880,7 +3898,7 @@ async function getChangesSinceRef(ref, initialFetchDepth) {
}
return parseGitDiffOutput(output);
}
exports.getChangesSinceRef = getChangesSinceRef;
exports.getChangesSinceMergeBase = getChangesSinceMergeBase;
function parseGitDiffOutput(output) {
const tokens = output.split('\u0000').filter(s => s.length > 0);
const files = [];
@@ -3897,11 +3915,7 @@ async function listAllFilesAsAdded() {
core.startGroup('Listing all files tracked by git');
let output = '';
try {
await exec_1.exec('git', ['ls-files', '-z'], {
listeners: {
stdout: (data) => (output += data.toString())
}
});
output = (await exec_1.default('git', ['ls-files', '-z'])).stdout;
}
finally {
fixStdOutNullTermination();
@@ -3916,32 +3930,50 @@ async function listAllFilesAsAdded() {
}));
}
exports.listAllFilesAsAdded = listAllFilesAsAdded;
function isTagRef(ref) {
return ref.startsWith('refs/tags/');
}
exports.isTagRef = isTagRef;
function trimRefs(ref) {
return trimStart(ref, 'refs/');
}
exports.trimRefs = trimRefs;
function trimRefsHeads(ref) {
const trimRef = trimStart(ref, 'refs/');
return trimStart(trimRef, 'heads/');
}
exports.trimRefsHeads = trimRefsHeads;
async function getNumberOfCommits(ref) {
let output = '';
await exec_1.exec('git', ['rev-list', `--count`, ref], {
listeners: {
stdout: (data) => (output += data.toString())
async function getCurrentRef() {
core.startGroup(`Determining current ref`);
try {
const branch = (await exec_1.default('git', ['branch', '--show-current'])).stdout.trim();
if (branch) {
return branch;
}
});
const describe = await exec_1.default('git', ['describe', '--tags', '--exact-match'], { ignoreReturnCode: true });
if (describe.code === 0) {
return describe.stdout.trim();
}
return (await exec_1.default('git', ['rev-parse', 'HEAD'])).stdout.trim();
}
finally {
core.endGroup();
}
}
exports.getCurrentRef = getCurrentRef;
function getShortName(ref) {
if (!ref)
return '';
const heads = 'refs/heads/';
const tags = 'refs/tags/';
if (ref.startsWith(heads))
return ref.slice(heads.length);
if (ref.startsWith(tags))
return ref.slice(tags.length);
return ref;
}
exports.getShortName = getShortName;
async function hasCommit(ref) {
core.startGroup(`Checking if commit for ${ref} is locally available`);
try {
return (await exec_1.default('git', ['cat-file', '-e', `${ref}^{commit}`], { ignoreReturnCode: true })).code === 0;
}
finally {
core.endGroup();
}
}
async function getNumberOfCommits(ref) {
const output = (await exec_1.default('git', ['rev-list', `--count`, ref])).stdout;
const count = parseInt(output);
return isNaN(count) ? 0 : count;
}
function trimStart(ref, start) {
return ref.startsWith(start) ? ref.substr(start.length) : ref;
}
function fixStdOutNullTermination() {
// Previous command uses NULL as delimiters and output is printed to stdout.
// We have to make sure next thing written to stdout will start on new line.
@@ -4641,9 +4673,11 @@ function getConfigFileContent(configPath) {
async function getChangedFiles(token, base, initialFetchDepth) {
if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') {
const pr = github.context.payload.pull_request;
return token
? await getChangedFilesFromApi(token, pr)
: await git.getChangesSinceRef(pr.base.ref, initialFetchDepth);
if (token) {
return await getChangedFilesFromApi(token, pr);
}
core.info('Github token is not available - changes will be detected from PRs merge commit');
return await git.getChangesInLastCommit();
}
else if (github.context.eventName === 'push') {
return getChangedFilesFromPush(base, initialFetchDepth);
@@ -4653,26 +4687,41 @@ async function getChangedFiles(token, base, initialFetchDepth) {
}
}
async function getChangedFilesFromPush(base, initialFetchDepth) {
var _a;
const push = github.context.payload;
// No change detection for pushed tags
if (git.isTagRef(push.ref)) {
core.info('Workflow is triggered by pushing of tag - all files will be listed as added');
return await git.listAllFilesAsAdded();
const defaultRef = (_a = push.repository) === null || _a === void 0 ? void 0 : _a.default_branch;
const pushRef = git.getShortName(push.ref) ||
(core.warning(`'ref' field is missing in PUSH event payload - using current branch, tag or commit SHA`),
await git.getCurrentRef());
const baseRef = git.getShortName(base) || defaultRef;
if (!baseRef) {
throw new Error("This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload");
}
const baseRef = git.trimRefsHeads(base || push.repository.default_branch);
const pushRef = git.trimRefsHeads(push.ref);
// If base references same branch it was pushed to, we will do comparison against the previously pushed commit.
// If base references same branch it was pushed to,
// we will do comparison against the previously pushed commit
if (baseRef === pushRef) {
if (!push.before) {
core.warning(`'before' field is missing in PUSH event payload - changes will be detected from last commit`);
return await git.getChangesInLastCommit();
}
// If there is no previously pushed commit,
// we will do comparison against the default branch or return all as added
if (push.before === git.NULL_SHA) {
core.info('First push of a branch detected - all files will be listed as added');
return await git.listAllFilesAsAdded();
if (defaultRef && baseRef !== defaultRef) {
core.info(`First push of a branch detected - changes will be detected against the default branch ${defaultRef}`);
return await git.getChangesSinceMergeBase(defaultRef, initialFetchDepth);
}
else {
core.info('Initial push detected - all files will be listed as added');
return await git.listAllFilesAsAdded();
}
}
core.info(`Changes will be detected against the last previously pushed commit on same branch (${pushRef})`);
return await git.getChangesAgainstSha(push.before);
return await git.getChanges(push.before);
}
// Changes introduced by current branch against the base branch
core.info(`Changes will be detected against the branch ${baseRef}`);
return await git.getChangesSinceRef(baseRef, initialFetchDepth);
return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth);
}
// Uses github REST api to get list of files changed in PR
async function getChangedFilesFromApi(token, pullRequest) {
@@ -15612,6 +15661,31 @@ exports.getUserAgent = getUserAgent;
//# sourceMappingURL=index.js.map
/***/ }),
/***/ 807:
/***/ (function(__unusedmodule, exports, __webpack_require__) {
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
const exec_1 = __webpack_require__(986);
// Wraps original exec() function
// Returns exit code and whole stdout/stderr
async function exec(commandLine, args, options) {
options = options || {};
let stdout = '';
let stderr = '';
options.listeners = {
stdout: (data) => (stdout += data.toString()),
stderr: (data) => (stderr += data.toString())
};
const code = await exec_1.exec(commandLine, args, options);
return { code, stdout, stderr };
}
exports.default = exec;
/***/ }),
/***/ 809:

21
src/exec.ts Normal file
View File

@@ -0,0 +1,21 @@
import {exec as execImpl, ExecOptions} from '@actions/exec'
// Wraps original exec() function
// Returns exit code and whole stdout/stderr
export default async function exec(commandLine: string, args?: string[], options?: ExecOptions): Promise<ExecResult> {
options = options || {}
let stdout = ''
let stderr = ''
options.listeners = {
stdout: (data: Buffer) => (stdout += data.toString()),
stderr: (data: Buffer) => (stderr += data.toString())
}
const code = await execImpl(commandLine, args, options)
return {code, stdout, stderr}
}
export interface ExecResult {
code: number
stdout: string
stderr: string
}

View File

@@ -1,25 +1,14 @@
import {exec} from '@actions/exec'
import exec from './exec'
import * as core from '@actions/core'
import {File, ChangeStatus} from './file'
export const NULL_SHA = '0000000000000000000000000000000000000000'
export async function getChangesAgainstSha(sha: string): Promise<File[]> {
// Fetch single commit
core.startGroup(`Fetching ${sha} from origin`)
await exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', sha])
core.endGroup()
// Get differences between sha and HEAD
core.startGroup(`Change detection ${sha}..HEAD`)
export async function getChangesInLastCommit(): Promise<File[]> {
core.startGroup(`Change detection in last commit`)
let output = ''
try {
// Two dots '..' change detection - directly compares two versions
await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${sha}..HEAD`], {
listeners: {
stdout: (data: Buffer) => (output += data.toString())
}
})
output = (await exec('git', ['log', '--format=', '--no-renames', '--name-status', '-z', '-n', '1'])).stdout
} finally {
fixStdOutNullTermination()
core.endGroup()
@@ -28,25 +17,54 @@ export async function getChangesAgainstSha(sha: string): Promise<File[]> {
return parseGitDiffOutput(output)
}
export async function getChangesSinceRef(ref: string, initialFetchDepth: number): Promise<File[]> {
// Fetch and add base branch
core.startGroup(`Fetching ${ref} from origin until merge-base is found`)
await exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`])
export async function getChanges(ref: string): Promise<File[]> {
if (!(await hasCommit(ref))) {
// Fetch single commit
core.startGroup(`Fetching ${ref} from origin`)
await exec('git', ['fetch', '--depth=1', '--no-tags', '--no-auto-gc', 'origin', ref])
core.endGroup()
}
// Get differences between ref and HEAD
core.startGroup(`Change detection ${ref}..HEAD`)
let output = ''
try {
// Two dots '..' change detection - directly compares two versions
output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}..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
core.startGroup(`Fetching ${ref}`)
try {
await exec('git', ['fetch', `--depth=${initialFetchDepth}`, '--no-tags', 'origin', `${ref}:${ref}`])
} finally {
core.endGroup()
}
}
async function hasMergeBase(): Promise<boolean> {
return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})) === 0
return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})).code === 0
}
async function countCommits(): Promise<number> {
return (await getNumberOfCommits('HEAD')) + (await getNumberOfCommits(ref))
}
core.startGroup(`Searching for merge-base with ${ref}`)
// Fetch more commits until merge-base is found
if (!(await hasMergeBase())) {
let deepen = initialFetchDepth
let lastCommitsCount = await countCommits()
do {
await exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc', '-q'])
await exec('git', ['fetch', `--deepen=${deepen}`, '--no-tags', '--no-auto-gc'])
const count = await countCommits()
if (count <= lastCommitsCount) {
core.info('No merge base found - all files will be listed as added')
@@ -64,11 +82,7 @@ export async function getChangesSinceRef(ref: string, initialFetchDepth: number)
let output = ''
try {
// Three dots '...' change detection - finds merge-base and compares against it
await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`], {
listeners: {
stdout: (data: Buffer) => (output += data.toString())
}
})
output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout
} finally {
fixStdOutNullTermination()
core.endGroup()
@@ -93,11 +107,7 @@ export async function listAllFilesAsAdded(): Promise<File[]> {
core.startGroup('Listing all files tracked by git')
let output = ''
try {
await exec('git', ['ls-files', '-z'], {
listeners: {
stdout: (data: Buffer) => (output += data.toString())
}
})
output = (await exec('git', ['ls-files', '-z'])).stdout
} finally {
fixStdOutNullTermination()
core.endGroup()
@@ -112,34 +122,52 @@ export async function listAllFilesAsAdded(): Promise<File[]> {
}))
}
export function isTagRef(ref: string): boolean {
return ref.startsWith('refs/tags/')
export async function getCurrentRef(): Promise<string> {
core.startGroup(`Determining current ref`)
try {
const branch = (await exec('git', ['branch', '--show-current'])).stdout.trim()
if (branch) {
return branch
}
const describe = await exec('git', ['describe', '--tags', '--exact-match'], {ignoreReturnCode: true})
if (describe.code === 0) {
return describe.stdout.trim()
}
return (await exec('git', ['rev-parse', 'HEAD'])).stdout.trim()
} finally {
core.endGroup()
}
}
export function trimRefs(ref: string): string {
return trimStart(ref, 'refs/')
export function getShortName(ref: string): string {
if (!ref) return ''
const heads = 'refs/heads/'
const tags = 'refs/tags/'
if (ref.startsWith(heads)) return ref.slice(heads.length)
if (ref.startsWith(tags)) return ref.slice(tags.length)
return ref
}
export function trimRefsHeads(ref: string): string {
const trimRef = trimStart(ref, 'refs/')
return trimStart(trimRef, 'heads/')
async function hasCommit(ref: string): Promise<boolean> {
core.startGroup(`Checking if commit for ${ref} is locally available`)
try {
return (await exec('git', ['cat-file', '-e', `${ref}^{commit}`], {ignoreReturnCode: true})).code === 0
} finally {
core.endGroup()
}
}
async function getNumberOfCommits(ref: string): Promise<number> {
let output = ''
await exec('git', ['rev-list', `--count`, ref], {
listeners: {
stdout: (data: Buffer) => (output += data.toString())
}
})
const output = (await exec('git', ['rev-list', `--count`, ref])).stdout
const count = parseInt(output)
return isNaN(count) ? 0 : count
}
function trimStart(ref: string, start: string): string {
return ref.startsWith(start) ? ref.substr(start.length) : ref
}
function fixStdOutNullTermination(): void {
// Previous command uses NULL as delimiters and output is printed to stdout.
// We have to make sure next thing written to stdout will start on new line.

View File

@@ -57,9 +57,11 @@ function getConfigFileContent(configPath: string): string {
async function getChangedFiles(token: string, base: string, initialFetchDepth: number): Promise<File[]> {
if (github.context.eventName === 'pull_request' || github.context.eventName === 'pull_request_target') {
const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest
return token
? await getChangedFilesFromApi(token, pr)
: await git.getChangesSinceRef(pr.base.ref, initialFetchDepth)
if (token) {
return await getChangedFilesFromApi(token, pr)
}
core.info('Github token is not available - changes will be detected from PRs merge commit')
return await git.getChangesInLastCommit()
} else if (github.context.eventName === 'push') {
return getChangedFilesFromPush(base, initialFetchDepth)
} else {
@@ -69,30 +71,47 @@ async function getChangedFiles(token: string, base: string, initialFetchDepth: n
async function getChangedFilesFromPush(base: string, initialFetchDepth: number): Promise<File[]> {
const push = github.context.payload as Webhooks.WebhookPayloadPush
const defaultRef = push.repository?.default_branch
// No change detection for pushed tags
if (git.isTagRef(push.ref)) {
core.info('Workflow is triggered by pushing of tag - all files will be listed as added')
return await git.listAllFilesAsAdded()
const pushRef =
git.getShortName(push.ref) ||
(core.warning(`'ref' field is missing in PUSH event payload - using current branch, tag or commit SHA`),
await git.getCurrentRef())
const baseRef = git.getShortName(base) || defaultRef
if (!baseRef) {
throw new Error(
"This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload"
)
}
const baseRef = git.trimRefsHeads(base || push.repository.default_branch)
const pushRef = git.trimRefsHeads(push.ref)
// If base references same branch it was pushed to, we will do comparison against the previously pushed commit.
// If base references same branch it was pushed to,
// we will do comparison against the previously pushed commit
if (baseRef === pushRef) {
if (!push.before) {
core.warning(`'before' field is missing in PUSH event payload - changes will be detected from last commit`)
return await git.getChangesInLastCommit()
}
// If there is no previously pushed commit,
// we will do comparison against the default branch or return all as added
if (push.before === git.NULL_SHA) {
core.info('First push of a branch detected - all files will be listed as added')
return await git.listAllFilesAsAdded()
if (defaultRef && baseRef !== defaultRef) {
core.info(`First push of a branch detected - changes will be detected against the default branch ${defaultRef}`)
return await git.getChangesSinceMergeBase(defaultRef, initialFetchDepth)
} else {
core.info('Initial push detected - all files will be listed as added')
return await git.listAllFilesAsAdded()
}
}
core.info(`Changes will be detected against the last previously pushed commit on same branch (${pushRef})`)
return await git.getChangesAgainstSha(push.before)
return await git.getChanges(push.before)
}
// Changes introduced by current branch against the base branch
core.info(`Changes will be detected against the branch ${baseRef}`)
return await git.getChangesSinceRef(baseRef, initialFetchDepth)
return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth)
}
// Uses github REST api to get list of files changed in PR