diff --git a/README.md b/README.md index 62456c8..a4b1035 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,11 @@ don't allow this because they don't work on a level of individual jobs or steps. - The `base` input parameter must not be the same as the branch that triggered the workflow - Changes are detected against the merge-base with the configured base branch or the default branch - Uses git commands to detect changes - repository must be already [checked out](https://github.com/actions/checkout) +- **[Merge queue](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/configuring-pull-request-merges/managing-a-merge-queue):** + - Workflow triggered by **[merge_group](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#merge_group)** + - The `base` and `ref` input parameters default to commit hashes from the event + unless explicitly specified. + - Uses git commands to detect changes - repository must be already [checked out](https://github.com/actions/checkout) - **Master, Release, or other long-lived branches:** - Workflow triggered by **[push](https://docs.github.com/en/actions/reference/events-that-trigger-workflows#push)** event when `base` input parameter is the same as the branch that triggered the workflow: @@ -104,6 +109,8 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # Branch, tag, or commit SHA against which the changes will be detected. # If it references the same branch it was pushed to, # changes are detected against the most recent commit before the push. + # If it is empty and action is triggered by merge_group event, + # the base commit in the event will be used. # Otherwise, it uses git merge-base to find the best common ancestor between # current branch (HEAD) and base. # When merge-base is found, it's used for change detection - only changes @@ -117,6 +124,8 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # 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. + # If this is empty and action is triggered by merge_group event, + # the head commit in the event will be used. # This option is ignored if action is triggered by pull_request event. # default: ${{ github.ref }} ref: @@ -154,14 +163,14 @@ For more information, see [CHANGELOG](https://github.com/dorny/paths-filter/blob # Default: ${{ github.token }} token: '' - # Optional parameter to override the default behavior of file matching algorithm. + # Optional parameter to override the default behavior of file matching algorithm. # By default files that match at least one pattern defined by the filters will be included. # This parameter allows to override the "at least one pattern" behavior to make it so that - # all of the patterns have to match or otherwise the file is excluded. - # An example scenario where this is useful if you would like to match all - # .ts files in a sub-directory but not .md files. - # The filters below will match markdown files despite the exclusion syntax UNLESS - # you specify 'every' as the predicate-quantifier parameter. When you do that, + # all of the patterns have to match or otherwise the file is excluded. + # An example scenario where this is useful if you would like to match all + # .ts files in a sub-directory but not .md files. + # The filters below will match markdown files despite the exclusion syntax UNLESS + # you specify 'every' as the predicate-quantifier parameter. When you do that, # it will only match the .ts files in the subdirectory as expected. # # backend: @@ -317,6 +326,12 @@ on: branches: # PRs to the following branches will trigger the workflow - master - develop + # Optionally you can use the action in the merge queue + # if your repository enables the feature. + merge_group: + branches: + - master + - develop jobs: build: runs-on: ubuntu-latest diff --git a/dist/index.js b/dist/index.js index cc7d7d4..88b476a 100644 --- a/dist/index.js +++ b/dist/index.js @@ -42,18 +42,29 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __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.Filter = exports.isPredicateQuantifier = exports.SUPPORTED_PREDICATE_QUANTIFIERS = exports.PredicateQuantifier = void 0; +exports.Filter = exports.SUPPORTED_PREDICATE_QUANTIFIERS = exports.PredicateQuantifier = void 0; +exports.isPredicateQuantifier = isPredicateQuantifier; const jsyaml = __importStar(__nccwpck_require__(1917)); const picomatch_1 = __importDefault(__nccwpck_require__(8569)); // Minimatch options used in all matchers @@ -95,7 +106,6 @@ exports.SUPPORTED_PREDICATE_QUANTIFIERS = Object.values(PredicateQuantifier); function isPredicateQuantifier(x) { return exports.SUPPORTED_PREDICATE_QUANTIFIERS.includes(x); } -exports.isPredicateQuantifier = isPredicateQuantifier; class Filter { // Creates instance of Filter and load rules from YAML if it's provided constructor(yaml, filterConfig) { @@ -196,15 +206,34 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); 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.HEAD = exports.NULL_SHA = void 0; +exports.getChangesInLastCommit = getChangesInLastCommit; +exports.getChanges = getChanges; +exports.getChangesOnHead = getChangesOnHead; +exports.getChangesSinceMergeBase = getChangesSinceMergeBase; +exports.parseGitDiffOutput = parseGitDiffOutput; +exports.listAllFilesAsAdded = listAllFilesAsAdded; +exports.getCurrentRef = getCurrentRef; +exports.getShortName = getShortName; +exports.isGitSha = isGitSha; const exec_1 = __nccwpck_require__(1514); const core = __importStar(__nccwpck_require__(2186)); const file_1 = __nccwpck_require__(4014); @@ -222,7 +251,6 @@ async function getChangesInLastCommit() { } return parseGitDiffOutput(output); } -exports.getChangesInLastCommit = getChangesInLastCommit; async function getChanges(base, head) { const baseRef = await ensureRefAvailable(base); const headRef = await ensureRefAvailable(head); @@ -240,7 +268,6 @@ async function getChanges(base, head) { } return parseGitDiffOutput(output); } -exports.getChanges = getChanges; async function getChangesOnHead() { // Get current changes - both staged and unstaged core.startGroup(`Change detection on HEAD`); @@ -254,7 +281,6 @@ async function getChangesOnHead() { } return parseGitDiffOutput(output); } -exports.getChangesOnHead = getChangesOnHead; async function getChangesSinceMergeBase(base, head, initialFetchDepth) { let baseRef; let headRef; @@ -328,7 +354,6 @@ async function getChangesSinceMergeBase(base, head, initialFetchDepth) { } return parseGitDiffOutput(output); } -exports.getChangesSinceMergeBase = getChangesSinceMergeBase; function parseGitDiffOutput(output) { const tokens = output.split('\u0000').filter(s => s.length > 0); const files = []; @@ -340,7 +365,6 @@ function parseGitDiffOutput(output) { } return files; } -exports.parseGitDiffOutput = parseGitDiffOutput; async function listAllFilesAsAdded() { core.startGroup('Listing all files tracked by git'); let output = ''; @@ -359,7 +383,6 @@ async function listAllFilesAsAdded() { filename: path })); } -exports.listAllFilesAsAdded = listAllFilesAsAdded; async function getCurrentRef() { core.startGroup(`Get current git ref`); try { @@ -377,7 +400,6 @@ async function getCurrentRef() { core.endGroup(); } } -exports.getCurrentRef = getCurrentRef; function getShortName(ref) { if (!ref) return ''; @@ -389,11 +411,9 @@ function getShortName(ref) { return ref.slice(tags.length); return ref; } -exports.getShortName = getShortName; function isGitSha(ref) { return /^[a-z0-9]{40}$/.test(ref); } -exports.isGitSha = isGitSha; async function hasCommit(ref) { return (await (0, exec_1.getExecOutput)('git', ['cat-file', '-e', `${ref}^{commit}`], { ignoreReturnCode: true })).exitCode === 0; } @@ -466,7 +486,7 @@ const statusMap = { "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.csvEscape = void 0; +exports.csvEscape = csvEscape; // Returns filename escaped for CSV // Wraps file name into "..." only when it contains some potentially unsafe character function csvEscape(value) { @@ -482,7 +502,6 @@ function csvEscape(value) { // another double quote return `"${value.replace(/"/g, '""')}"`; } -exports.csvEscape = csvEscape; /***/ }), @@ -493,12 +512,12 @@ exports.csvEscape = csvEscape; "use strict"; Object.defineProperty(exports, "__esModule", ({ value: true })); -exports.shellEscape = exports.backslashEscape = void 0; +exports.backslashEscape = backslashEscape; +exports.shellEscape = shellEscape; // Backslash escape every character except small subset of definitely safe characters function backslashEscape(value) { return value.replace(/([^a-zA-Z0-9,._+:@%/-])/gm, '\\$1'); } -exports.backslashEscape = backslashEscape; // Returns filename escaped for usage as shell argument. // Applies "human readable" approach with as few escaping applied as possible function shellEscape(value) { @@ -519,7 +538,6 @@ function shellEscape(value) { // Contains some unsafe characters but no single quote return `'${value}'`; } -exports.shellEscape = shellEscape; /***/ }), @@ -545,13 +563,23 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( }) : function(o, v) { o["default"] = v; }); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; +var __importStar = (this && this.__importStar) || (function () { + var ownKeys = function(o) { + ownKeys = Object.getOwnPropertyNames || function (o) { + var ar = []; + for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; + return ar; + }; + return ownKeys(o); + }; + return function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); + __setModuleDefault(result, mod); + return result; + }; +})(); Object.defineProperty(exports, "__esModule", ({ value: true })); const fs = __importStar(__nccwpck_require__(7147)); const core = __importStar(__nccwpck_require__(2186)); @@ -617,33 +645,49 @@ 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 (ref) { - core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`); + switch (github.context.eventName) { + // To keep backward compatibility, commits in GitHub pull request event + // take precedence over manual inputs. + case 'pull_request': + case 'pull_request_review': + case 'pull_request_review_comment': + case 'pull_request_target': { + if (ref) { + core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`); + } + if (base) { + core.warning(`'base' input parameter is ignored when action is triggered by pull request event`); + } + const pr = github.context.payload.pull_request; + if (token) { + return await getChangedFilesFromApi(token, pr); + } + if (github.context.eventName === 'pull_request_target') { + // pull_request_target is executed in context of base branch and GITHUB_SHA points to last commit in base branch + // Therefore it's not possible to look at changes in last commit + // At the same time we don't want to fetch any code from forked repository + throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`); + } + core.info('Github token is not available - changes will be detected using git diff'); + const baseSha = (_a = github.context.payload.pull_request) === null || _a === void 0 ? void 0 : _a.base.sha; + const defaultBranch = (_b = github.context.payload.repository) === null || _b === void 0 ? void 0 : _b.default_branch; + const currentRef = await git.getCurrentRef(); + return await git.getChanges(base || baseSha || defaultBranch, currentRef); } - if (base) { - core.warning(`'base' input parameter is ignored when action is triggered by pull request event`); + // To keep backward compatibility, manual inputs take precedence over + // commits in GitHub merge queue event. + case 'merge_group': { + const mergeGroup = github.context.payload; + if (!base) { + base = mergeGroup.merge_group.base_sha; + } + if (!ref) { + ref = mergeGroup.merge_group.head_sha; + } + break; } - const pr = github.context.payload.pull_request; - if (token) { - return await getChangedFilesFromApi(token, pr); - } - if (github.context.eventName === 'pull_request_target') { - // pull_request_target is executed in context of base branch and GITHUB_SHA points to last commit in base branch - // Therefor it's not possible to look at changes in last commit - // At the same time we don't want to fetch any code from forked repository - throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`); - } - core.info('Github token is not available - changes will be detected using git diff'); - const baseSha = (_a = github.context.payload.pull_request) === null || _a === void 0 ? void 0 : _a.base.sha; - const defaultBranch = (_b = github.context.payload.repository) === null || _b === void 0 ? void 0 : _b.default_branch; - const currentRef = await git.getCurrentRef(); - return await git.getChanges(base || baseSha || defaultBranch, currentRef); - } - else { - return getChangedFilesFromGit(base, ref, initialFetchDepth); } + return getChangedFilesFromGit(base, ref, initialFetchDepth); } async function getChangedFilesFromGit(base, head, initialFetchDepth) { var _a; diff --git a/src/main.ts b/src/main.ts index 8320287..0aefbb0 100644 --- a/src/main.ts +++ b/src/main.ts @@ -2,7 +2,7 @@ import * as fs from 'fs' import * as core from '@actions/core' import * as github from '@actions/github' import {GetResponseDataTypeFromEndpointMethod} from '@octokit/types' -import {PushEvent, PullRequestEvent} from '@octokit/webhooks-types' +import {MergeGroupEvent, PullRequest, PushEvent} from '@octokit/webhooks-types' import { isPredicateQuantifier, @@ -84,32 +84,50 @@ async function getChangedFiles(token: string, base: string, ref: string, initial 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 (ref) { - core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`) + switch (github.context.eventName) { + // To keep backward compatibility, commits in GitHub pull request event + // take precedence over manual inputs. + case 'pull_request': + case 'pull_request_review': + case 'pull_request_review_comment': + case 'pull_request_target': { + if (ref) { + core.warning(`'ref' input parameter is ignored when 'base' is set to HEAD`) + } + if (base) { + core.warning(`'base' input parameter is ignored when action is triggered by pull request event`) + } + const pr = github.context.payload.pull_request as PullRequest + if (token) { + return await getChangedFilesFromApi(token, pr) + } + if (github.context.eventName === 'pull_request_target') { + // pull_request_target is executed in context of base branch and GITHUB_SHA points to last commit in base branch + // Therefore it's not possible to look at changes in last commit + // At the same time we don't want to fetch any code from forked repository + throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`) + } + core.info('Github token is not available - changes will be detected using git diff') + const baseSha = github.context.payload.pull_request?.base.sha + const defaultBranch = github.context.payload.repository?.default_branch + const currentRef = await git.getCurrentRef() + return await git.getChanges(base || baseSha || defaultBranch, currentRef) } - if (base) { - core.warning(`'base' input parameter is ignored when action is triggered by pull request event`) + // To keep backward compatibility, manual inputs take precedence over + // commits in GitHub merge queue event. + case 'merge_group': { + const mergeGroup = github.context.payload as MergeGroupEvent + if (!base) { + base = mergeGroup.merge_group.base_sha + } + if (!ref) { + ref = mergeGroup.merge_group.head_sha + } + break } - const pr = github.context.payload.pull_request as PullRequestEvent - if (token) { - return await getChangedFilesFromApi(token, pr) - } - if (github.context.eventName === 'pull_request_target') { - // pull_request_target is executed in context of base branch and GITHUB_SHA points to last commit in base branch - // Therefor it's not possible to look at changes in last commit - // At the same time we don't want to fetch any code from forked repository - throw new Error(`'token' input parameter is required if action is triggered by 'pull_request_target' event`) - } - core.info('Github token is not available - changes will be detected using git diff') - const baseSha = github.context.payload.pull_request?.base.sha - const defaultBranch = github.context.payload.repository?.default_branch - const currentRef = await git.getCurrentRef() - return await git.getChanges(base || baseSha || defaultBranch, currentRef) - } else { - return getChangedFilesFromGit(base, ref, initialFetchDepth) } + + return getChangedFilesFromGit(base, ref, initialFetchDepth) } async function getChangedFilesFromGit(base: string, head: string, initialFetchDepth: number): Promise { @@ -175,7 +193,7 @@ async function getChangedFilesFromGit(base: string, head: string, initialFetchDe } // Uses github REST api to get list of files changed in PR -async function getChangedFilesFromApi(token: string, pullRequest: PullRequestEvent): Promise { +async function getChangedFilesFromApi(token: string, pullRequest: PullRequest): Promise { core.startGroup(`Fetching list of changed files for PR#${pullRequest.number} from Github API`) try { const client = github.getOctokit(token)