diff --git a/.github/workflows/pull-request-verification.yml b/.github/workflows/pull-request-verification.yml index b3f6952..1f28193 100644 --- a/.github/workflows/pull-request-verification.yml +++ b/.github/workflows/pull-request-verification.yml @@ -135,3 +135,82 @@ jobs: || steps.filter.outputs.modified_files != 'LICENSE' || steps.filter.outputs.deleted_files != 'README.md' run: exit 1 + + test-baseref-changes: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - uses: ./ + id: filter + with: + base: ${{ github.base_ref }} + ref: ${{ github.sha }} + filters: | + error: + - not_existing_path/**/* + any: + - "**/*" + - name: filter-test + if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true' + run: exit 1 + - name: changes-test + if: contains(fromJSON(steps.filter.outputs.changes), 'error') || !contains(fromJSON(steps.filter.outputs.changes), 'any') + run: exit 1 + + test-custom-nostatus: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - run: | + base=${{ github.base_ref }} + compare=${{ github.sha }} + git fetch origin $base:$base + echo 'customchanges<> $GITHUB_ENV + git diff --name-only $base..$compare >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + - uses: ./ + id: filter + with: + files: ${{ env.customchanges }} + filters: | + error: + - not_existing_path/**/* + any: + - "**/*" + - name: filter-test + if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true' + run: exit 1 + - name: changes-test + if: contains(fromJSON(steps.filter.outputs.changes), 'error') || !contains(fromJSON(steps.filter.outputs.changes), 'any') + run: exit 1 + + test-custom-withstatus: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + with: + fetch-depth: 0 + - run: | + base=${{ github.base_ref }} + compare=${{ github.sha }} + git fetch origin $base:$base + echo 'customchanges<> $GITHUB_ENV + git diff --name-status $base..$compare >> $GITHUB_ENV + echo 'EOF' >> $GITHUB_ENV + - uses: ./ + id: filter + with: + files: ${{ env.customchanges }} + filters: | + error: + - not_existing_path/**/* + any: + - "**/*" + - name: filter-test + if: steps.filter.outputs.any != 'true' || steps.filter.outputs.error == 'true' + run: exit 1 + - name: changes-test + if: contains(fromJSON(steps.filter.outputs.changes), 'error') || !contains(fromJSON(steps.filter.outputs.changes), 'any') + run: exit 1 \ No newline at end of file diff --git a/README.md b/README.md index 106b471..7dcffb8 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ don't allow this because they don't work on a level of individual jobs or steps. - Workflow triggered by **[pull_request](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request)** or **[pull_request_target](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#pull_request_target)** event - Changes are detected against the pull request base branch - - Uses GitHub REST API to fetch a list of modified files + - Uses GitHub REST API to fetch a list of modified files, if base or ref are not provided. - **Feature branches:** - Workflow triggered by **[push](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push)** or any other **[event](https://docs.github.com/en/free-pro-team@latest/actions/reference/events-that-trigger-workflows)** @@ -40,6 +40,8 @@ don't allow this because they don't work on a level of individual jobs or steps. - Workflow triggered by any event when `base` input parameter is set to `HEAD` - Changes are detected against the current HEAD - Untracked files are ignored +- **Input Files** + - Input list of string to `customfiles` input parameter and get the filtered results. In case you want to generate list of files elsewhere. ## Example @@ -107,14 +109,12 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # introduced by the current branch are considered. # All files are considered as added if there is no common ancestor with # base branch or no previous commit. - # This option is ignored if action is triggered by pull_request event. # Default: repository default branch (e.g. master) base: '' # Git reference (e.g. branch name) from which the changes will be detected. # Useful when workflow can be triggered only on the default branch (e.g. repository_dispatch event) # but you want to get changes on a different branch. - # This option is ignored if action is triggered by pull_request event. # default: ${{ github.ref }} ref: @@ -150,6 +150,11 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # changes using git commands. # Default: ${{ github.token }} token: '' + + # Optionally provide a list of files you ahve generated elsewhere. + # Performs the glob matching against these files instead. Negates + # all other inputs less filters & list-files. + customfiles: '' ``` ## Outputs diff --git a/action.yml b/action.yml index dc51528..2063621 100644 --- a/action.yml +++ b/action.yml @@ -36,6 +36,12 @@ inputs: Backslash escapes every potentially unsafe character. required: true default: none + files: + description: | + Custom input for files changed if you do your diff elsewhere. + Accepts a newline separated list of files, with optional leading status code, as per output of git diff --name-only or git diff --name-status + If no status supplied sets status to unmerged. Is set then only filers and list-files inputs are effective. + required: false initial-fetch-depth: description: | How many commits are initially fetched from base branch. diff --git a/dist/index.js b/dist/index.js index bf428c1..99699ec 100644 --- a/dist/index.js +++ b/dist/index.js @@ -178,7 +178,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.isGitSha = exports.getShortName = exports.getCurrentRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceMergeBase = exports.getChangesOnHead = exports.getChanges = exports.getChangesInLastCommit = exports.HEAD = exports.NULL_SHA = void 0; +exports.statusMap = exports.isGitSha = exports.getShortName = exports.getCurrentRef = exports.listAllFilesAsAdded = exports.parseGitDiffOutput = exports.getChangesSinceMergeBase = exports.getChangesOnHead = exports.getChanges = exports.getChangesInLastCommit = exports.HEAD = exports.NULL_SHA = void 0; const exec_1 = __importDefault(__nccwpck_require__(7757)); const core = __importStar(__nccwpck_require__(2186)); const file_1 = __nccwpck_require__(4014); @@ -307,7 +307,7 @@ function parseGitDiffOutput(output) { const files = []; for (let i = 0; i + 1 < tokens.length; i += 2) { files.push({ - status: statusMap[tokens[i]], + status: exports.statusMap[tokens[i]], filename: tokens[i + 1] }); } @@ -421,7 +421,7 @@ function fixStdOutNullTermination() { // Otherwise things like ::set-output wouldn't work. core.info(''); } -const statusMap = { +exports.statusMap = { A: file_1.ChangeStatus.Added, C: file_1.ChangeStatus.Copied, D: file_1.ChangeStatus.Deleted, @@ -536,6 +536,7 @@ async function run() { if (workingDirectory) { process.chdir(workingDirectory); } + const customfiles = core.getInput('files', { required: false }); const token = core.getInput('token', { required: false }); const ref = core.getInput('ref', { required: false }); const base = core.getInput('base', { required: false }); @@ -543,12 +544,14 @@ async function run() { const filtersYaml = isPathInput(filtersInput) ? getConfigFileContent(filtersInput) : filtersInput; const listFiles = core.getInput('list-files', { required: false }).toLowerCase() || 'none'; const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', { required: false })) || 10; + const files = customfiles + ? parseFilesInput(customfiles.split(/\r?\n/)) + : await getChangedFiles(token, base, ref, initialFetchDepth); if (!isExportFormat(listFiles)) { core.setFailed(`Input parameter 'list-files' is set to invalid value '${listFiles}'`); return; } const filter = new filter_1.Filter(filtersYaml); - const files = await getChangedFiles(token, base, ref, initialFetchDepth); core.info(`Detected ${files.length} changed files`); const results = filter.match(files); exportResults(results, listFiles); @@ -557,6 +560,28 @@ async function run() { core.setFailed(error.message); } } +function parseFilesInput(customfiles) { + const files = []; + for (let i = 0; i + 1 < customfiles.length; i += 1) { + var filearray = customfiles[i].split(/\s+/); + if (filearray.length == 1) { + var filestatus = 'U'; + var filename = filearray[0]; + } + else if (filearray.length == 2) { + var filestatus = filearray[0]; + var filename = filearray[1]; + } + else { + throw new Error(`Line '${i + 1}' in custom file: '${customfiles[i]}' is not parseable.`); + } + files.push({ + status: git.statusMap[filestatus], + filename: filename + }); + } + return files; +} function isPathInput(text) { return !(text.includes('\n') || text.includes(':')); } @@ -579,12 +604,12 @@ async function getChangedFiles(token, base, ref, initialFetchDepth) { return await git.getChangesOnHead(); } const prEvents = ['pull_request', 'pull_request_review', 'pull_request_review_comment', 'pull_request_target']; - if (prEvents.includes(github.context.eventName)) { + if (prEvents.includes(github.context.eventName) && !ref && !base) { if (ref) { - core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`); + core.warning(`'ref' input parameter is ignored when 'base' is not also set on PR events.`); } if (base) { - core.warning(`'base' input parameter is ignored when action is triggered by pull request event`); + core.warning(`'base' input parameter is ignored when 'ref' is not also set on PR events.`); } const pr = github.context.payload.pull_request; if (token) { diff --git a/src/git.ts b/src/git.ts index 3d6d3be..f19e31e 100644 --- a/src/git.ts +++ b/src/git.ts @@ -260,7 +260,7 @@ function fixStdOutNullTermination(): void { core.info('') } -const statusMap: {[char: string]: ChangeStatus} = { +export const statusMap: {[char: string]: ChangeStatus} = { A: ChangeStatus.Added, C: ChangeStatus.Copied, D: ChangeStatus.Deleted, diff --git a/src/main.ts b/src/main.ts index d2cb678..d4761f8 100644 --- a/src/main.ts +++ b/src/main.ts @@ -18,6 +18,7 @@ async function run(): Promise { process.chdir(workingDirectory) } + const customfiles = core.getInput('files', {required: false}) const token = core.getInput('token', {required: false}) const ref = core.getInput('ref', {required: false}) const base = core.getInput('base', {required: false}) @@ -26,13 +27,16 @@ async function run(): Promise { const listFiles = core.getInput('list-files', {required: false}).toLowerCase() || 'none' const initialFetchDepth = parseInt(core.getInput('initial-fetch-depth', {required: false})) || 10 + const files = customfiles + ? parseFilesInput(customfiles.split(/\r?\n/)) + : await getChangedFiles(token, base, ref, initialFetchDepth) + if (!isExportFormat(listFiles)) { core.setFailed(`Input parameter 'list-files' is set to invalid value '${listFiles}'`) return } const filter = new Filter(filtersYaml) - const files = await getChangedFiles(token, base, ref, initialFetchDepth) core.info(`Detected ${files.length} changed files`) const results = filter.match(files) exportResults(results, listFiles) @@ -41,6 +45,28 @@ async function run(): Promise { } } +function parseFilesInput(customfiles: string[]): File[] { + const files: File[] = [] + for (let i = 0; i + 1 < customfiles.length; i += 1) { + var filearray = customfiles[i].split(/\s+/) + if (filearray.length == 1) { + var filestatus = 'U' + var filename = filearray[0] + } else if (filearray.length == 2) { + var filestatus = filearray[0] + var filename = filearray[1] + } else { + throw new Error(`Line '${i + 1}' in custom file: '${customfiles[i]}' is not parseable.`) + } + + files.push({ + status: git.statusMap[filestatus], + filename: filename + }) + } + return files +} + function isPathInput(text: string): boolean { return !(text.includes('\n') || text.includes(':')) } @@ -68,12 +94,12 @@ async function getChangedFiles(token: string, base: string, ref: string, initial } const prEvents = ['pull_request', 'pull_request_review', 'pull_request_review_comment', 'pull_request_target'] - if (prEvents.includes(github.context.eventName)) { + if (prEvents.includes(github.context.eventName) && !ref && !base) { if (ref) { - core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`) + core.warning(`'ref' input parameter is ignored when 'base' is not also set on PR events.`) } if (base) { - core.warning(`'base' input parameter is ignored when action is triggered by pull request event`) + core.warning(`'base' input parameter is ignored when 'ref' is not also set on PR events.`) } const pr = github.context.payload.pull_request as Webhooks.WebhookPayloadPullRequestPullRequest if (token) {