diff --git a/CHANGELOG.md b/CHANGELOG.md index 31e166b..a064e6b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,8 @@ # Changelog +## v2.9.1 +- [Fix fetching git history + fallback to unshallow repo](https://github.com/dorny/paths-filter/pull/74) + ## v2.9.0 - [Add list-files: csv format](https://github.com/dorny/paths-filter/pull/68) diff --git a/README.md b/README.md index eaa0bbc..e78b1a0 100644 --- a/README.md +++ b/README.md @@ -117,7 +117,7 @@ For more information see [CHANGELOG](https://github.com/dorny/paths-filter/blob/ # is found or there are no more commits in the history. # This option takes effect only when changes are detected # using git against base branch (feature branch workflow). - # Default: 20 + # Default: 100 initial-fetch-depth: '' # Enables listing of files matching the filter: diff --git a/action.yml b/action.yml index 894b114..b25aa5e 100644 --- a/action.yml +++ b/action.yml @@ -38,7 +38,7 @@ inputs: until the merge-base is found or there are no more commits in the history. This option takes effect only when changes are detected using git against different base branch. required: false - default: '10' + default: '100' outputs: changes: description: JSON array with names of all filters matching any of changed files diff --git a/dist/index.js b/dist/index.js index fcf0faa..71893ad 100644 --- a/dist/index.js +++ b/dist/index.js @@ -3830,19 +3830,19 @@ async function getChangesInLastCommit() { return parseGitDiffOutput(output); } exports.getChangesInLastCommit = getChangesInLastCommit; -async function getChanges(ref) { - if (!(await hasCommit(ref))) { +async function getChanges(baseRef) { + if (!(await hasCommit(baseRef))) { // Fetch single commit - core.startGroup(`Fetching ${ref} from origin`); - await exec_1.default('git', ['fetch', '--depth=1', '--no-tags', 'origin', ref]); + core.startGroup(`Fetching ${baseRef} from origin`); + await exec_1.default('git', ['fetch', '--depth=1', '--no-tags', 'origin', baseRef]); core.endGroup(); } // Get differences between ref and HEAD - core.startGroup(`Change detection ${ref}..HEAD`); + core.startGroup(`Change detection ${baseRef}..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; + output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${baseRef}..HEAD`])).stdout; } finally { fixStdOutNullTermination(); @@ -3865,47 +3865,51 @@ async function getChangesOnHead() { return parseGitDiffOutput(output); } exports.getChangesOnHead = getChangesOnHead; -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 getChangesSinceMergeBase(baseRef, ref, initialFetchDepth) { async function hasMergeBase() { - return (await exec_1.default('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })).code === 0; + return (await exec_1.default('git', ['merge-base', baseRef, ref], { 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.default('git', ['fetch', `--deepen=${deepen}`, '--no-tags']); - const count = await countCommits(); - if (count <= lastCommitsCount) { - core.info('No merge base found - all files will be listed as added'); - core.endGroup(); - return await listAllFilesAsAdded(); + let noMergeBase = false; + core.startGroup(`Searching for merge-base ${baseRef}...${ref}`); + try { + let init = true; + let lastCommitCount = await getCommitCount(); + let depth = Math.max(lastCommitCount * 2, initialFetchDepth); + while (!(await hasMergeBase())) { + if (init) { + await exec_1.default('git', ['fetch', `--depth=${depth}`, 'origin', `${baseRef}:${baseRef}`, `${ref}`]); + init = false; } - lastCommitsCount = count; - deepen = Math.min(deepen * 2, Number.MAX_SAFE_INTEGER); - } while (!(await hasMergeBase())); + else { + await exec_1.default('git', ['fetch', `--deepen=${depth}`, 'origin', baseRef, ref]); + } + const commitCount = await getCommitCount(); + if (commitCount === lastCommitCount) { + core.info('No more commits were fetched'); + core.info('Last attempt will be to fetch full history'); + await exec_1.default('git', ['fetch', '--unshallow']); + if (!(await hasMergeBase())) { + noMergeBase = true; + } + break; + } + depth = Math.min(depth * 2, Number.MAX_SAFE_INTEGER); + lastCommitCount = commitCount; + } + } + finally { + core.endGroup(); + } + if (noMergeBase) { + core.warning('No merge base found - all files will be listed as added'); + return await listAllFilesAsAdded(); } - core.endGroup(); // Get changes introduced on HEAD compared to ref - core.startGroup(`Change detection ${ref}...HEAD`); + core.startGroup(`Change detection ${baseRef}...${ref}`); let output = ''; try { // Three dots '...' change detection - finds merge-base and compares against it - output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout; + output = (await exec_1.default('git', ['diff', '--no-renames', '--name-status', '-z', `${baseRef}...${ref}`])).stdout; } finally { fixStdOutNullTermination(); @@ -3956,7 +3960,7 @@ async function getCurrentRef() { if (describe.code === 0) { return describe.stdout.trim(); } - return (await exec_1.default('git', ['rev-parse', 'HEAD'])).stdout.trim(); + return (await exec_1.default('git', ['rev-parse', exports.HEAD])).stdout.trim(); } finally { core.endGroup(); @@ -3988,8 +3992,8 @@ async function hasCommit(ref) { core.endGroup(); } } -async function getNumberOfCommits(ref) { - const output = (await exec_1.default('git', ['rev-list', `--count`, ref])).stdout; +async function getCommitCount() { + const output = (await exec_1.default('git', ['rev-list', '--count', '--all'])).stdout; const count = parseInt(output); return isNaN(count) ? 0 : count; } @@ -4676,7 +4680,7 @@ async function run() { } } function isPathInput(text) { - return !text.includes('\n'); + return !(text.includes('\n') || text.includes(':')); } function getConfigFileContent(configPath) { if (!fs.existsSync(configPath)) { @@ -4709,7 +4713,7 @@ async function getChangedFilesFromGit(base, initialFetchDepth) { var _a; const defaultRef = (_a = github.context.payload.repository) === null || _a === void 0 ? void 0 : _a.default_branch; const beforeSha = github.context.eventName === 'push' ? github.context.payload.before : null; - const pushRef = git.getShortName(github.context.ref) || + const ref = git.getShortName(github.context.ref) || (core.warning(`'ref' field is missing in event payload - using current branch, tag or commit SHA`), await git.getCurrentRef()); const baseRef = git.getShortName(base) || defaultRef; @@ -4717,10 +4721,10 @@ async function getChangedFilesFromGit(base, initialFetchDepth) { throw new Error("This action requires 'base' input to be configured or 'repository.default_branch' to be set in the event payload"); } const isBaseRefSha = git.isGitSha(baseRef); - const isBaseSameAsPush = baseRef === pushRef; + const isBaseRefSameAsRef = baseRef === ref; // If base is commit SHA we will do comparison against the referenced commit // Or if base references same branch it was pushed to, we will do comparison against the previously pushed commit - if (isBaseRefSha || isBaseSameAsPush) { + if (isBaseRefSha || isBaseRefSameAsRef) { if (!isBaseRefSha && !beforeSha) { core.warning(`'before' field is missing in event payload - changes will be detected from last commit`); return await git.getChangesInLastCommit(); @@ -4731,7 +4735,7 @@ async function getChangedFilesFromGit(base, initialFetchDepth) { if (baseSha === git.NULL_SHA) { 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); + return await git.getChangesSinceMergeBase(defaultRef, ref, initialFetchDepth); } else { core.info('Initial push detected - all files will be listed as added'); @@ -4743,7 +4747,7 @@ async function getChangedFilesFromGit(base, initialFetchDepth) { } // Changes introduced by current branch against the base branch core.info(`Changes will be detected against the branch ${baseRef}`); - return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth); + return await git.getChangesSinceMergeBase(baseRef, ref, initialFetchDepth); } // Uses github REST api to get list of files changed in PR async function getChangedFilesFromApi(token, pullRequest) { diff --git a/src/git.ts b/src/git.ts index f34a5cc..a55faa6 100644 --- a/src/git.ts +++ b/src/git.ts @@ -18,20 +18,20 @@ export async function getChangesInLastCommit(): Promise { return parseGitDiffOutput(output) } -export async function getChanges(ref: string): Promise { - if (!(await hasCommit(ref))) { +export async function getChanges(baseRef: string): Promise { + if (!(await hasCommit(baseRef))) { // Fetch single commit - core.startGroup(`Fetching ${ref} from origin`) - await exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', ref]) + core.startGroup(`Fetching ${baseRef} from origin`) + await exec('git', ['fetch', '--depth=1', '--no-tags', 'origin', baseRef]) core.endGroup() } // Get differences between ref and HEAD - core.startGroup(`Change detection ${ref}..HEAD`) + core.startGroup(`Change detection ${baseRef}..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 + output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${baseRef}..HEAD`])).stdout } finally { fixStdOutNullTermination() core.endGroup() @@ -54,50 +54,56 @@ export async function getChangesOnHead(): Promise { 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() - } - } - +export async function getChangesSinceMergeBase( + baseRef: string, + ref: string, + initialFetchDepth: number +): Promise { async function hasMergeBase(): Promise { - return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})).code === 0 + return (await exec('git', ['merge-base', baseRef, ref], {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']) - const count = await countCommits() - if (count <= lastCommitsCount) { - core.info('No merge base found - all files will be listed as added') - core.endGroup() - return await listAllFilesAsAdded() + let noMergeBase = false + core.startGroup(`Searching for merge-base ${baseRef}...${ref}`) + try { + let init = true + let lastCommitCount = await getCommitCount() + let depth = Math.max(lastCommitCount * 2, initialFetchDepth) + while (!(await hasMergeBase())) { + if (init) { + await exec('git', ['fetch', `--depth=${depth}`, 'origin', `${baseRef}:${baseRef}`, `${ref}`]) + init = false + } else { + await exec('git', ['fetch', `--deepen=${depth}`, 'origin', baseRef, ref]) } - lastCommitsCount = count - deepen = Math.min(deepen * 2, Number.MAX_SAFE_INTEGER) - } while (!(await hasMergeBase())) + const commitCount = await getCommitCount() + if (commitCount === lastCommitCount) { + core.info('No more commits were fetched') + core.info('Last attempt will be to fetch full history') + await exec('git', ['fetch', '--unshallow']) + if (!(await hasMergeBase())) { + noMergeBase = true + } + break + } + depth = Math.min(depth * 2, Number.MAX_SAFE_INTEGER) + lastCommitCount = commitCount + } + } finally { + core.endGroup() + } + + if (noMergeBase) { + core.warning('No merge base found - all files will be listed as added') + return await listAllFilesAsAdded() } - core.endGroup() // Get changes introduced on HEAD compared to ref - core.startGroup(`Change detection ${ref}...HEAD`) + core.startGroup(`Change detection ${baseRef}...${ref}`) let output = '' try { // Three dots '...' change detection - finds merge-base and compares against it - output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${ref}...HEAD`])).stdout + output = (await exec('git', ['diff', '--no-renames', '--name-status', '-z', `${baseRef}...${ref}`])).stdout } finally { fixStdOutNullTermination() core.endGroup() @@ -150,7 +156,7 @@ export async function getCurrentRef(): Promise { return describe.stdout.trim() } - return (await exec('git', ['rev-parse', 'HEAD'])).stdout.trim() + return (await exec('git', ['rev-parse', HEAD])).stdout.trim() } finally { core.endGroup() } @@ -181,8 +187,8 @@ async function hasCommit(ref: string): Promise { } } -async function getNumberOfCommits(ref: string): Promise { - const output = (await exec('git', ['rev-list', `--count`, ref])).stdout +async function getCommitCount(): Promise { + const output = (await exec('git', ['rev-list', '--count', '--all'])).stdout const count = parseInt(output) return isNaN(count) ? 0 : count } diff --git a/src/main.ts b/src/main.ts index a54776f..e3d6ebc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -40,7 +40,7 @@ async function run(): Promise { } function isPathInput(text: string): boolean { - return !text.includes('\n') + return !(text.includes('\n') || text.includes(':')) } function getConfigFileContent(configPath: string): string { @@ -80,7 +80,7 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number): const beforeSha = github.context.eventName === 'push' ? (github.context.payload as Webhooks.WebhookPayloadPush).before : null - const pushRef = + const ref = git.getShortName(github.context.ref) || (core.warning(`'ref' field is missing in event payload - using current branch, tag or commit SHA`), await git.getCurrentRef()) @@ -93,11 +93,11 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number): } const isBaseRefSha = git.isGitSha(baseRef) - const isBaseSameAsPush = baseRef === pushRef + const isBaseRefSameAsRef = baseRef === ref // If base is commit SHA we will do comparison against the referenced commit // Or if base references same branch it was pushed to, we will do comparison against the previously pushed commit - if (isBaseRefSha || isBaseSameAsPush) { + if (isBaseRefSha || isBaseRefSameAsRef) { if (!isBaseRefSha && !beforeSha) { core.warning(`'before' field is missing in event payload - changes will be detected from last commit`) return await git.getChangesInLastCommit() @@ -109,7 +109,7 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number): if (baseSha === git.NULL_SHA) { 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) + return await git.getChangesSinceMergeBase(defaultRef, ref, initialFetchDepth) } else { core.info('Initial push detected - all files will be listed as added') return await git.listAllFilesAsAdded() @@ -122,7 +122,7 @@ async function getChangedFilesFromGit(base: string, initialFetchDepth: number): // Changes introduced by current branch against the base branch core.info(`Changes will be detected against the branch ${baseRef}`) - return await git.getChangesSinceMergeBase(baseRef, initialFetchDepth) + return await git.getChangesSinceMergeBase(baseRef, ref, initialFetchDepth) } // Uses github REST api to get list of files changed in PR