20 Commits

Author SHA1 Message Date
Michal Dorner
ad1ae68cd0 Merge pull request #78 from dorny/issue-77-tag-as-base-broken
Fix change detection when base is a tag
2021-03-26 00:18:14 +01:00
Michal Dorner
5d414b88ab Update CHANGELOG for v2.9.3 2021-03-26 00:14:59 +01:00
Michal Dorner
a6989ad592 Get full ref name without multiple invocations to git show-ref 2021-03-26 00:05:48 +01:00
Michal Dorner
6d8169070c Improve logging 2021-03-25 23:45:44 +01:00
Michal Dorner
3d4a25053b Update dist 2021-03-25 23:39:10 +01:00
Michal Dorner
e59197f91b Fix change detection when base is tag 2021-03-25 23:34:50 +01:00
Michal Dorner
ca8fa4002c Merge pull request #75 from dorny/fix-searching-for-merge-base
Fetch base and search merge-base without creating local branch
2021-03-09 22:05:03 +01:00
Michal Dorner
c64be944bf Update CHANGELOG 2021-03-09 22:01:10 +01:00
Michal Dorner
138368ff4f Use ref instead of HEAD 2021-03-09 21:56:18 +01:00
Michal Dorner
a301a0ad83 Refactor getChangesSinceMergeBase() code 2021-03-09 21:44:15 +01:00
Michal Dorner
0c0d1a854a Fetch base and search merge-base without creating local branch 2021-03-09 21:13:57 +01:00
Michal Dorner
0aa1597c2b Merge pull request #74 from dorny/fix-searching-for-merge-base
Fix fetching git history + fallback to unshallow repo
2021-03-08 17:11:16 +01:00
Michal Dorner
46d2898cef Update CHANGELOG 2021-03-08 17:10:14 +01:00
Michal Dorner
c90ecaa5a1 Increase default value of initial-fetch-depth to 100
For most situation it should be enough to find merge base. Previous value was too slow and overhead of doing fetch was significantly higher than saving of transfer.
2021-03-08 17:06:12 +01:00
Michal Dorner
49abb091ed Use combination of --depth and --deepen 2021-03-08 17:00:52 +01:00
Michal Dorner
8801c887e9 Do not try to update head of current branch 2021-03-08 15:29:27 +01:00
Michal Dorner
68792bf56a Allow single line filters 2021-03-08 15:24:06 +01:00
Michal Dorner
31c576896e Fix lastCommitCount has not been updated 2021-03-08 15:12:58 +01:00
Michal Dorner
3be8c93277 Fix fetching git history + fallback to unshallow repo 2021-03-08 15:09:07 +01:00
Michal Dorner
1cdd3bbdf6 Fix typo in README 2021-02-24 11:24:20 +01:00
6 changed files with 183 additions and 105 deletions

View File

@@ -1,5 +1,14 @@
# Changelog
## v2.9.3
- [Fix change detection when base is a tag](https://github.com/dorny/paths-filter/pull/78)
## v2.9.2
- [Fix fetching git history](https://github.com/dorny/paths-filter/pull/75)
## 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)

View File

@@ -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:
@@ -262,7 +262,7 @@ jobs:
matrix:
# Parse JSON array containing names of all filters matching any of changed files
# e.g. ['package1', 'package2'] if both package folders contains changes
package: ${{ fromJson(needs.changes.outputs.packages) }}
package: ${{ fromJSON(needs.changes.outputs.packages) }}
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2

View File

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

131
dist/index.js vendored
View File

@@ -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,60 @@ 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(base, ref, initialFetchDepth) {
let baseRef;
async function hasMergeBase() {
return (await exec_1.default('git', ['merge-base', ref, 'HEAD'], { ignoreReturnCode: true })).code === 0;
return (baseRef !== undefined && (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 ${base}...${ref}`);
try {
baseRef = await getFullRef(base);
if (!(await hasMergeBase())) {
await exec_1.default('git', ['fetch', '--no-tags', `--depth=${initialFetchDepth}`, 'origin', base, ref]);
if (baseRef === undefined) {
baseRef = await getFullRef(base);
if (baseRef === undefined) {
await exec_1.default('git', ['fetch', '--tags', `--depth=1`, 'origin', base, ref]);
baseRef = await getFullRef(base);
if (baseRef === undefined) {
throw new Error(`Could not determine what is ${base} - fetch works but it's not a branch or tag`);
}
}
}
lastCommitsCount = count;
deepen = Math.min(deepen * 2, Number.MAX_SAFE_INTEGER);
} while (!(await hasMergeBase()));
let depth = initialFetchDepth;
let lastCommitCount = await getCommitCount();
while (!(await hasMergeBase())) {
depth = Math.min(depth * 2, Number.MAX_SAFE_INTEGER);
await exec_1.default('git', ['fetch', `--deepen=${depth}`, 'origin', base, 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']);
if (!(await hasMergeBase())) {
noMergeBase = true;
}
break;
}
lastCommitCount = commitCount;
}
}
}
core.endGroup();
// Get changes introduced on HEAD compared to ref
core.startGroup(`Change detection ${ref}...HEAD`);
finally {
core.endGroup();
}
let diffArg = `${baseRef}...${ref}`;
if (noMergeBase) {
core.warning('No merge base found - change detection will use direct <commit>..<commit> comparison');
diffArg = `${baseRef}..${ref}`;
}
// Get changes introduced on ref compared to base
core.startGroup(`Change detection ${diffArg}`);
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', diffArg])).stdout;
}
finally {
fixStdOutNullTermination();
@@ -3956,7 +3969,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,11 +4001,29 @@ 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;
}
async function getFullRef(shortName) {
if (isGitSha(shortName)) {
return shortName;
}
const output = (await exec_1.default('git', ['show-ref', shortName], { ignoreReturnCode: true })).stdout;
const refs = output
.split(/\r?\n/g)
.map(l => { var _a, _b; return (_b = (_a = l.match(/refs\/.*$/)) === null || _a === void 0 ? void 0 : _a[0]) !== null && _b !== void 0 ? _b : ''; })
.filter(l => l !== '');
if (refs.length === 0) {
return undefined;
}
const remoteRef = refs.find(ref => ref.startsWith('refs/remotes/origin/'));
if (remoteRef) {
return remoteRef;
}
return refs[0];
}
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.
@@ -4676,7 +4707,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 +4740,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 +4748,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 +4762,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');
@@ -4742,8 +4773,8 @@ async function getChangedFilesFromGit(base, initialFetchDepth) {
return await git.getChanges(baseSha);
}
// 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);
core.info(`Changes will be detected against ${baseRef}`);
return await git.getChangesSinceMergeBase(baseRef, ref, initialFetchDepth);
}
// Uses github REST api to get list of files changed in PR
async function getChangedFilesFromApi(token, pullRequest) {

View File

@@ -18,20 +18,20 @@ export async function getChangesInLastCommit(): Promise<File[]> {
return parseGitDiffOutput(output)
}
export async function getChanges(ref: string): Promise<File[]> {
if (!(await hasCommit(ref))) {
export async function getChanges(baseRef: string): Promise<File[]> {
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,65 @@ export async function getChangesOnHead(): Promise<File[]> {
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()
}
}
export async function getChangesSinceMergeBase(base: string, ref: string, initialFetchDepth: number): Promise<File[]> {
let baseRef: string | undefined
async function hasMergeBase(): Promise<boolean> {
return (await exec('git', ['merge-base', ref, 'HEAD'], {ignoreReturnCode: true})).code === 0
return (
baseRef !== undefined && (await exec('git', ['merge-base', baseRef, ref], {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'])
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 ${base}...${ref}`)
try {
baseRef = await getFullRef(base)
if (!(await hasMergeBase())) {
await exec('git', ['fetch', '--no-tags', `--depth=${initialFetchDepth}`, 'origin', base, ref])
if (baseRef === undefined) {
baseRef = await getFullRef(base)
if (baseRef === undefined) {
await exec('git', ['fetch', '--tags', `--depth=1`, 'origin', base, ref])
baseRef = await getFullRef(base)
if (baseRef === undefined) {
throw new Error(`Could not determine what is ${base} - fetch works but it's not a branch or tag`)
}
}
}
lastCommitsCount = count
deepen = Math.min(deepen * 2, Number.MAX_SAFE_INTEGER)
} while (!(await hasMergeBase()))
}
core.endGroup()
// Get changes introduced on HEAD compared to ref
core.startGroup(`Change detection ${ref}...HEAD`)
let depth = initialFetchDepth
let lastCommitCount = await getCommitCount()
while (!(await hasMergeBase())) {
depth = Math.min(depth * 2, Number.MAX_SAFE_INTEGER)
await exec('git', ['fetch', `--deepen=${depth}`, 'origin', base, 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('git', ['fetch'])
if (!(await hasMergeBase())) {
noMergeBase = true
}
break
}
lastCommitCount = commitCount
}
}
} finally {
core.endGroup()
}
let diffArg = `${baseRef}...${ref}`
if (noMergeBase) {
core.warning('No merge base found - change detection will use direct <commit>..<commit> comparison')
diffArg = `${baseRef}..${ref}`
}
// Get changes introduced on ref compared to base
core.startGroup(`Change detection ${diffArg}`)
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', diffArg])).stdout
} finally {
fixStdOutNullTermination()
core.endGroup()
@@ -150,7 +165,7 @@ export async function getCurrentRef(): Promise<string> {
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,12 +196,35 @@ async function hasCommit(ref: string): Promise<boolean> {
}
}
async function getNumberOfCommits(ref: string): Promise<number> {
const output = (await exec('git', ['rev-list', `--count`, ref])).stdout
async function getCommitCount(): Promise<number> {
const output = (await exec('git', ['rev-list', '--count', '--all'])).stdout
const count = parseInt(output)
return isNaN(count) ? 0 : count
}
async function getFullRef(shortName: string): Promise<string | undefined> {
if (isGitSha(shortName)) {
return shortName
}
const output = (await exec('git', ['show-ref', shortName], {ignoreReturnCode: true})).stdout
const refs = output
.split(/\r?\n/g)
.map(l => l.match(/refs\/.*$/)?.[0] ?? '')
.filter(l => l !== '')
if (refs.length === 0) {
return undefined
}
const remoteRef = refs.find(ref => ref.startsWith('refs/remotes/origin/'))
if (remoteRef) {
return remoteRef
}
return refs[0]
}
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

@@ -40,7 +40,7 @@ async function run(): Promise<void> {
}
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()
@@ -121,8 +121,8 @@ 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)
core.info(`Changes will be detected against ${baseRef}`)
return await git.getChangesSinceMergeBase(baseRef, ref, initialFetchDepth)
}
// Uses github REST api to get list of files changed in PR