From ff5bb057bf62b684107d110563da97d79c1b1845 Mon Sep 17 00:00:00 2001 From: Michal Dorner Date: Wed, 30 Sep 2020 00:32:49 +0200 Subject: [PATCH] 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 --- CHANGELOG.md | 8 ++ README.md | 8 +- __tests__/git.test.ts | 20 ++-- dist/index.js | 212 ++++++++++++++++++++++++++++-------------- src/exec.ts | 21 +++++ src/git.ts | 124 ++++++++++++++---------- src/main.ts | 49 +++++++--- 7 files changed, 295 insertions(+), 147 deletions(-) create mode 100644 src/exec.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index c0b6701..9e78221 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -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) diff --git a/README.md b/README.md index 6fd8ffe..01c32b7 100644 --- a/README.md +++ b/README.md @@ -31,10 +31,14 @@ doesn't allow this because they doesn't work on a level of individual jobs or st - 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 +72,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 diff --git a/__tests__/git.test.ts b/__tests__/git.test.ts index ce46d6e..5f36ec1 100644 --- a/__tests__/git.test.ts +++ b/__tests__/git.test.ts @@ -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') }) }) diff --git a/dist/index.js b/dist/index.js index 418489d..ac24d3d 100644 --- a/dist/index.js +++ b/dist/index.js @@ -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: diff --git a/src/exec.ts b/src/exec.ts new file mode 100644 index 0000000..d0abd03 --- /dev/null +++ b/src/exec.ts @@ -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 { + 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 +} diff --git a/src/git.ts b/src/git.ts index 831ab6a..3433116 100644 --- a/src/git.ts +++ b/src/git.ts @@ -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 { - // 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 { + 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 { return parseGitDiffOutput(output) } -export async function getChangesSinceRef(ref: string, initialFetchDepth: number): Promise { - // 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 { + 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 { + 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 { - 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 { 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 { 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 { })) } -export function isTagRef(ref: string): boolean { - return ref.startsWith('refs/tags/') +export async function getCurrentRef(): Promise { + 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 { + 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 { - 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. diff --git a/src/main.ts b/src/main.ts index f2a20d3..12408cd 100644 --- a/src/main.ts +++ b/src/main.ts @@ -57,9 +57,11 @@ function getConfigFileContent(configPath: string): string { async function getChangedFiles(token: string, base: string, initialFetchDepth: number): Promise { 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 { 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