From 11393afa0f62be7210ed889095c4b71ed202b01f Mon Sep 17 00:00:00 2001 From: Dan Adajian Date: Wed, 14 Dec 2022 12:48:05 -0600 Subject: [PATCH] fix(check-merge-safety): improve error and success logging (#308) --- dist/180.index.js | 39 ++++++++++++++---------- dist/180.index.js.map | 2 +- src/helpers/check-merge-safety.ts | 40 ++++++++++++++++--------- test/helpers/check-merge-safety.test.ts | 14 ++++----- 4 files changed, 58 insertions(+), 37 deletions(-) diff --git a/dist/180.index.js b/dist/180.index.js index 48074502c..3d28b539d 100644 --- a/dist/180.index.js +++ b/dist/180.index.js @@ -9,8 +9,7 @@ exports.modules = { __webpack_require__.r(__webpack_exports__); /* harmony export */ __webpack_require__.d(__webpack_exports__, { /* harmony export */ "CheckMergeSafety": () => (/* binding */ CheckMergeSafety), -/* harmony export */ "checkMergeSafety": () => (/* binding */ checkMergeSafety), -/* harmony export */ "safeMessage": () => (/* binding */ safeMessage) +/* harmony export */ "checkMergeSafety": () => (/* binding */ checkMergeSafety) /* harmony export */ }); /* harmony import */ var _types_generated__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(3476); /* harmony import */ var _actions_github__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(5438); @@ -63,21 +62,20 @@ const checkMergeSafety = (inputs) => __awaiter(void 0, void 0, void 0, function* const { data: pullRequest } = yield _octokit__WEBPACK_IMPORTED_MODULE_1__/* .octokit.pulls.get */ .K.pulls.get(Object.assign({ pull_number: _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.issue.number }, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo)); return setMergeSafetyStatus(pullRequest, inputs); }); -const safeMessage = 'This branch is safe to merge!'; const setMergeSafetyStatus = (pullRequest, inputs) => __awaiter(void 0, void 0, void 0, function* () { - const message = yield getMergeSafetyMessage(pullRequest, inputs); - const isSafeToMerge = message === safeMessage; - yield (0,_set_commit_status__WEBPACK_IMPORTED_MODULE_5__.setCommitStatus)(Object.assign({ sha: pullRequest.head.sha, state: isSafeToMerge ? 'success' : 'failure', context: 'Merge Safety', description: message }, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo)); + const { state, message } = yield getMergeSafetyStateAndMessage(pullRequest, inputs); + yield (0,_set_commit_status__WEBPACK_IMPORTED_MODULE_5__.setCommitStatus)(Object.assign({ sha: pullRequest.head.sha, state, context: 'Merge Safety', description: message }, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo)); }); const handlePushWorkflow = (inputs) => __awaiter(void 0, void 0, void 0, function* () { const pullRequests = yield (0,_utils_paginate_open_pull_requests__WEBPACK_IMPORTED_MODULE_3__/* .paginateAllOpenPullRequests */ .P)(); const filteredPullRequests = pullRequests.filter(({ base, draft }) => !draft && base.ref === base.repo.default_branch); return (0,bluebird__WEBPACK_IMPORTED_MODULE_4__.map)(filteredPullRequests, pullRequest => setMergeSafetyStatus(pullRequest, inputs)); }); -const getMergeSafetyMessage = (pullRequest, { paths, override_filter_paths, override_filter_globs }) => __awaiter(void 0, void 0, void 0, function* () { +const getMergeSafetyStateAndMessage = (pullRequest, { paths, override_filter_paths, override_filter_globs }) => __awaiter(void 0, void 0, void 0, function* () { var _a; const { base: { repo: { default_branch, owner: { login: baseOwner } } }, head: { ref, user: { login: username } } } = pullRequest; - const { data: { files: filesWhichBranchIsBehindOn } } = yield _octokit__WEBPACK_IMPORTED_MODULE_1__/* .octokit.repos.compareCommitsWithBasehead */ .K.repos.compareCommitsWithBasehead(Object.assign(Object.assign({}, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo), { basehead: `${username}:${ref}...${baseOwner}:${default_branch}` })); + const branchName = `${username}:${ref}`; + const { data: { files: filesWhichBranchIsBehindOn } } = yield _octokit__WEBPACK_IMPORTED_MODULE_1__/* .octokit.repos.compareCommitsWithBasehead */ .K.repos.compareCommitsWithBasehead(Object.assign(Object.assign({}, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo), { basehead: `${branchName}...${baseOwner}:${default_branch}` })); const fileNamesWhichBranchIsBehindOn = (_a = filesWhichBranchIsBehindOn === null || filesWhichBranchIsBehindOn === void 0 ? void 0 : filesWhichBranchIsBehindOn.map(file => file.filename)) !== null && _a !== void 0 ? _a : []; const globalFilesOutdatedOnBranch = override_filter_globs ? micromatch__WEBPACK_IMPORTED_MODULE_2___default()(fileNamesWhichBranchIsBehindOn, override_filter_globs.split('\n')) @@ -85,25 +83,36 @@ const getMergeSafetyMessage = (pullRequest, { paths, override_filter_paths, over ? fileNamesWhichBranchIsBehindOn.filter(changedFile => override_filter_paths.split(/[\n,]/).includes(changedFile)) : []; if (globalFilesOutdatedOnBranch.length) { - _actions_core__WEBPACK_IMPORTED_MODULE_6__.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', `${username}:${ref}`)); - return `This branch has one or more outdated global files. Please update with ${default_branch}.`; + _actions_core__WEBPACK_IMPORTED_MODULE_6__.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', branchName)); + return { + state: 'failure', + message: `This branch has one or more outdated global files. Please update with ${default_branch}.` + }; } - const { data: { files: changedFiles } } = yield _octokit__WEBPACK_IMPORTED_MODULE_1__/* .octokit.repos.compareCommitsWithBasehead */ .K.repos.compareCommitsWithBasehead(Object.assign(Object.assign({}, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo), { basehead: `${baseOwner}:${default_branch}...${username}:${ref}` })); + const { data: { files: changedFiles } } = yield _octokit__WEBPACK_IMPORTED_MODULE_1__/* .octokit.repos.compareCommitsWithBasehead */ .K.repos.compareCommitsWithBasehead(Object.assign(Object.assign({}, _actions_github__WEBPACK_IMPORTED_MODULE_0__.context.repo), { basehead: `${baseOwner}:${default_branch}...${branchName}` })); const changedFileNames = changedFiles === null || changedFiles === void 0 ? void 0 : changedFiles.map(file => file.filename); const allProjectDirectories = paths === null || paths === void 0 ? void 0 : paths.split(/[\n,]/); const changedProjectsOutdatedOnBranch = allProjectDirectories === null || allProjectDirectories === void 0 ? void 0 : allProjectDirectories.filter(dir => fileNamesWhichBranchIsBehindOn.some(file => file.includes(dir)) && (changedFileNames === null || changedFileNames === void 0 ? void 0 : changedFileNames.some(file => file.includes(dir)))); if (changedProjectsOutdatedOnBranch === null || changedProjectsOutdatedOnBranch === void 0 ? void 0 : changedProjectsOutdatedOnBranch.length) { - _actions_core__WEBPACK_IMPORTED_MODULE_6__.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', `${username}:${ref}`)); - return `This branch has one or more outdated projects. Please update with ${default_branch}.`; + _actions_core__WEBPACK_IMPORTED_MODULE_6__.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', branchName)); + return { + state: 'failure', + message: `This branch has one or more outdated projects. Please update with ${default_branch}.` + }; } + const safeMessage = buildSuccessMessage(branchName); _actions_core__WEBPACK_IMPORTED_MODULE_6__.info(safeMessage); - return safeMessage; + return { + state: 'success', + message: safeMessage + }; }); const buildErrorMessage = (paths, pathType, branchName) => ` -The following ${pathType} are outdated on branch ${branchName}: +The following ${pathType} are outdated on branch ${branchName} ${paths.map(path => `* ${path}`).join('\n')} `; +const buildSuccessMessage = (branchName) => `Branch ${branchName} is safe to merge!`; /***/ }), diff --git a/dist/180.index.js.map b/dist/180.index.js.map index b23420b66..abc064674 100644 --- a/dist/180.index.js.map +++ b/dist/180.index.js.map @@ -1 +1 @@ -{"version":3,"file":"180.index.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AAEA;AACA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAIA;AAYA;AAMA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAEA;AAIA;AACA;AACA;AACA;AAEA;AACA;AACA;AAEA;AAEA;;AAEA;AACA;;;;;;;;;;;;;;;;;;;ACzHA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AACA;AAGA;AAAA;AAEA;AACA;AAGA;AAGA;AAIA;;;;;;;;;;;;;;;;;;ACtCA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AAoCA;;;;;;;;;;;;;;ACjDA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA","sources":["webpack://github-helpers/./src/helpers/check-merge-safety.ts","webpack://github-helpers/./src/helpers/set-commit-status.ts","webpack://github-helpers/./src/octokit.ts","webpack://github-helpers/./src/types/generated.ts","webpack://github-helpers/./src/utils/paginate-open-pull-requests.ts"],"sourcesContent":["/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport micromatch from 'micromatch';\nimport { PullRequest } from '../types/github';\nimport { paginateAllOpenPullRequests } from '../utils/paginate-open-pull-requests';\nimport { map } from 'bluebird';\nimport { setCommitStatus } from './set-commit-status';\nimport * as core from '@actions/core';\n\nexport class CheckMergeSafety extends HelperInputs {\n paths?: string;\n override_filter_paths?: string;\n override_filter_globs?: string;\n}\n\nexport const checkMergeSafety = async (inputs: CheckMergeSafety) => {\n const isPrWorkflow = Boolean(context.issue.number);\n if (!isPrWorkflow) {\n return handlePushWorkflow(inputs);\n }\n const { data: pullRequest } = await octokit.pulls.get({ pull_number: context.issue.number, ...context.repo });\n\n return setMergeSafetyStatus(pullRequest, inputs);\n};\n\nexport const safeMessage = 'This branch is safe to merge!';\n\nconst setMergeSafetyStatus = async (pullRequest: PullRequest, inputs: CheckMergeSafety) => {\n const message = await getMergeSafetyMessage(pullRequest, inputs);\n const isSafeToMerge = message === safeMessage;\n await setCommitStatus({\n sha: pullRequest.head.sha,\n state: isSafeToMerge ? 'success' : 'failure',\n context: 'Merge Safety',\n description: message,\n ...context.repo\n });\n};\n\nconst handlePushWorkflow = async (inputs: CheckMergeSafety) => {\n const pullRequests = await paginateAllOpenPullRequests();\n const filteredPullRequests = pullRequests.filter(({ base, draft }) => !draft && base.ref === base.repo.default_branch);\n return map(filteredPullRequests, pullRequest => setMergeSafetyStatus(pullRequest as PullRequest, inputs));\n};\n\nconst getMergeSafetyMessage = async (\n pullRequest: PullRequest,\n { paths, override_filter_paths, override_filter_globs }: CheckMergeSafety\n) => {\n const {\n base: {\n repo: {\n default_branch,\n owner: { login: baseOwner }\n }\n },\n head: {\n ref,\n user: { login: username }\n }\n } = pullRequest;\n const {\n data: { files: filesWhichBranchIsBehindOn }\n } = await octokit.repos.compareCommitsWithBasehead({\n ...context.repo,\n basehead: `${username}:${ref}...${baseOwner}:${default_branch}`\n });\n const fileNamesWhichBranchIsBehindOn = filesWhichBranchIsBehindOn?.map(file => file.filename) ?? [];\n\n const globalFilesOutdatedOnBranch = override_filter_globs\n ? micromatch(fileNamesWhichBranchIsBehindOn, override_filter_globs.split('\\n'))\n : override_filter_paths\n ? fileNamesWhichBranchIsBehindOn.filter(changedFile => override_filter_paths.split(/[\\n,]/).includes(changedFile))\n : [];\n\n if (globalFilesOutdatedOnBranch.length) {\n core.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', `${username}:${ref}`));\n return `This branch has one or more outdated global files. Please update with ${default_branch}.`;\n }\n\n const {\n data: { files: changedFiles }\n } = await octokit.repos.compareCommitsWithBasehead({\n ...context.repo,\n basehead: `${baseOwner}:${default_branch}...${username}:${ref}`\n });\n const changedFileNames = changedFiles?.map(file => file.filename);\n const allProjectDirectories = paths?.split(/[\\n,]/);\n\n const changedProjectsOutdatedOnBranch = allProjectDirectories?.filter(\n dir => fileNamesWhichBranchIsBehindOn.some(file => file.includes(dir)) && changedFileNames?.some(file => file.includes(dir))\n );\n\n if (changedProjectsOutdatedOnBranch?.length) {\n core.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', `${username}:${ref}`));\n return `This branch has one or more outdated projects. Please update with ${default_branch}.`;\n }\n\n core.info(safeMessage);\n return safeMessage;\n};\n\nconst buildErrorMessage = (paths: string[], pathType: 'projects' | 'global files', branchName: string) =>\n `\nThe following ${pathType} are outdated on branch ${branchName}:\n\n${paths.map(path => `* ${path}`).join('\\n')}\n`;\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { PipelineState } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\n\nexport class SetCommitStatus extends HelperInputs {\n sha = '';\n context = '';\n state = '';\n description?: string;\n target_url?: string;\n}\n\nexport const setCommitStatus = async ({ sha, context, state, description, target_url }: SetCommitStatus) => {\n await map(context.split('\\n').filter(Boolean), context =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\n })\n );\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport * as fetch from '@adobe/node-fetch-retry';\nimport { getOctokit } from '@actions/github';\n\nconst githubToken = core.getInput('github_token', { required: true });\nexport const { rest: octokit, graphql: octokitGraphql } = getOctokit(githubToken, { request: { fetch } });\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport class HelperInputs {\n helper?: string;\n github_token?: string;\n body?: string;\n project_name?: string;\n project_destination_column_name?: string;\n note?: string;\n project_origin_column_name?: string;\n sha?: string;\n context?: string;\n state?: string;\n description?: string;\n target_url?: string;\n environment?: string;\n environment_url?: string;\n label?: string;\n labels?: string;\n paths?: string;\n extensions?: string;\n override_filter_paths?: string;\n batches?: string;\n pattern?: string;\n teams?: string;\n login?: string;\n paths_no_filter?: string;\n slack_webhook_url?: string;\n number_of_assignees?: string;\n globs?: string;\n override_filter_globs?: string;\n title?: string;\n seconds?: string;\n pull_number?: string;\n base?: string;\n head?: string;\n days?: string;\n no_evict_upon_conflict?: string;\n}\n","/*\nCopyright 2022 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { PullRequestList } from '../types/github';\nimport { octokit } from '../octokit';\nimport { context } from '@actions/github';\n\nexport const paginateAllOpenPullRequests = async (page = 1): Promise => {\n const response = await octokit.pulls.list({\n state: 'open',\n sort: 'updated',\n direction: 'desc',\n per_page: 100,\n page,\n ...context.repo\n });\n if (!response.data.length) {\n return [];\n }\n return response.data.concat(await paginateAllOpenPullRequests(page + 1));\n};\n"],"names":[],"sourceRoot":""} \ No newline at end of file +{"version":3,"file":"180.index.js","mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAAA;;;;;;;;;;;AAWA;;;;;;;;;;AAEA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AAEA;AAIA;AAEA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AAEA;AACA;AACA;AAOA;AAEA;AACA;AACA;AACA;AACA;AAEA;;AAIA;AAaA;AAEA;AAMA;AAEA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAMA;AACA;AAEA;AAIA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AAEA;AAEA;;AAEA;AACA;AAEA;;;;;;;;;;;;;;;;;;;ACrIA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AACA;AACA;AAEA;AAAA;;AACA;AACA;AACA;AAGA;AAAA;AAEA;AACA;AAGA;AAGA;AAIA;;;;;;;;;;;;;;;;;;ACtCA;;;;;;;;;;;AAWA;AAEA;AACA;AACA;AAEA;AACA;;;;;;;;;;;AClBA;;;;;;;;;;;AAWA;AAEA;AAoCA;;;;;;;;;;;;;;ACjDA;;;;;;;;;;;AAWA;;;;;;;;;;AAGA;AACA;AAEA;AACA;AAQA;AACA;AACA;AACA;AACA","sources":["webpack://github-helpers/./src/helpers/check-merge-safety.ts","webpack://github-helpers/./src/helpers/set-commit-status.ts","webpack://github-helpers/./src/octokit.ts","webpack://github-helpers/./src/types/generated.ts","webpack://github-helpers/./src/utils/paginate-open-pull-requests.ts"],"sourcesContent":["/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { HelperInputs } from '../types/generated';\nimport { context } from '@actions/github';\nimport { octokit } from '../octokit';\nimport micromatch from 'micromatch';\nimport { PullRequest } from '../types/github';\nimport { paginateAllOpenPullRequests } from '../utils/paginate-open-pull-requests';\nimport { map } from 'bluebird';\nimport { setCommitStatus } from './set-commit-status';\nimport * as core from '@actions/core';\n\nexport class CheckMergeSafety extends HelperInputs {\n paths?: string;\n override_filter_paths?: string;\n override_filter_globs?: string;\n}\n\nexport const checkMergeSafety = async (inputs: CheckMergeSafety) => {\n const isPrWorkflow = Boolean(context.issue.number);\n if (!isPrWorkflow) {\n return handlePushWorkflow(inputs);\n }\n const { data: pullRequest } = await octokit.pulls.get({ pull_number: context.issue.number, ...context.repo });\n\n return setMergeSafetyStatus(pullRequest, inputs);\n};\n\nconst setMergeSafetyStatus = async (pullRequest: PullRequest, inputs: CheckMergeSafety) => {\n const { state, message } = await getMergeSafetyStateAndMessage(pullRequest, inputs);\n await setCommitStatus({\n sha: pullRequest.head.sha,\n state,\n context: 'Merge Safety',\n description: message,\n ...context.repo\n });\n};\n\nconst handlePushWorkflow = async (inputs: CheckMergeSafety) => {\n const pullRequests = await paginateAllOpenPullRequests();\n const filteredPullRequests = pullRequests.filter(({ base, draft }) => !draft && base.ref === base.repo.default_branch);\n return map(filteredPullRequests, pullRequest => setMergeSafetyStatus(pullRequest as PullRequest, inputs));\n};\n\nconst getMergeSafetyStateAndMessage = async (\n pullRequest: PullRequest,\n { paths, override_filter_paths, override_filter_globs }: CheckMergeSafety\n) => {\n const {\n base: {\n repo: {\n default_branch,\n owner: { login: baseOwner }\n }\n },\n head: {\n ref,\n user: { login: username }\n }\n } = pullRequest;\n\n const branchName = `${username}:${ref}`;\n\n const {\n data: { files: filesWhichBranchIsBehindOn }\n } = await octokit.repos.compareCommitsWithBasehead({\n ...context.repo,\n basehead: `${branchName}...${baseOwner}:${default_branch}`\n });\n const fileNamesWhichBranchIsBehindOn = filesWhichBranchIsBehindOn?.map(file => file.filename) ?? [];\n\n const globalFilesOutdatedOnBranch = override_filter_globs\n ? micromatch(fileNamesWhichBranchIsBehindOn, override_filter_globs.split('\\n'))\n : override_filter_paths\n ? fileNamesWhichBranchIsBehindOn.filter(changedFile => override_filter_paths.split(/[\\n,]/).includes(changedFile))\n : [];\n\n if (globalFilesOutdatedOnBranch.length) {\n core.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', branchName));\n return {\n state: 'failure',\n message: `This branch has one or more outdated global files. Please update with ${default_branch}.`\n };\n }\n\n const {\n data: { files: changedFiles }\n } = await octokit.repos.compareCommitsWithBasehead({\n ...context.repo,\n basehead: `${baseOwner}:${default_branch}...${branchName}`\n });\n const changedFileNames = changedFiles?.map(file => file.filename);\n const allProjectDirectories = paths?.split(/[\\n,]/);\n\n const changedProjectsOutdatedOnBranch = allProjectDirectories?.filter(\n dir => fileNamesWhichBranchIsBehindOn.some(file => file.includes(dir)) && changedFileNames?.some(file => file.includes(dir))\n );\n\n if (changedProjectsOutdatedOnBranch?.length) {\n core.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', branchName));\n return {\n state: 'failure',\n message: `This branch has one or more outdated projects. Please update with ${default_branch}.`\n };\n }\n\n const safeMessage = buildSuccessMessage(branchName);\n core.info(safeMessage);\n return {\n state: 'success',\n message: safeMessage\n };\n};\n\nconst buildErrorMessage = (paths: string[], pathType: 'projects' | 'global files', branchName: string) =>\n `\nThe following ${pathType} are outdated on branch ${branchName}\n\n${paths.map(path => `* ${path}`).join('\\n')}\n`;\n\nconst buildSuccessMessage = (branchName: string) => `Branch ${branchName} is safe to merge!`;\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { PipelineState } from '../types/github';\nimport { HelperInputs } from '../types/generated';\nimport { context as githubContext } from '@actions/github';\nimport { map } from 'bluebird';\nimport { octokit } from '../octokit';\n\nexport class SetCommitStatus extends HelperInputs {\n sha = '';\n context = '';\n state = '';\n description?: string;\n target_url?: string;\n}\n\nexport const setCommitStatus = async ({ sha, context, state, description, target_url }: SetCommitStatus) => {\n await map(context.split('\\n').filter(Boolean), context =>\n octokit.repos.createCommitStatus({\n sha,\n context,\n state: state as PipelineState,\n description,\n target_url,\n ...githubContext.repo\n })\n );\n};\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport * as core from '@actions/core';\nimport * as fetch from '@adobe/node-fetch-retry';\nimport { getOctokit } from '@actions/github';\n\nconst githubToken = core.getInput('github_token', { required: true });\nexport const { rest: octokit, graphql: octokitGraphql } = getOctokit(githubToken, { request: { fetch } });\n","/*\nCopyright 2021 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nexport class HelperInputs {\n helper?: string;\n github_token?: string;\n body?: string;\n project_name?: string;\n project_destination_column_name?: string;\n note?: string;\n project_origin_column_name?: string;\n sha?: string;\n context?: string;\n state?: string;\n description?: string;\n target_url?: string;\n environment?: string;\n environment_url?: string;\n label?: string;\n labels?: string;\n paths?: string;\n extensions?: string;\n override_filter_paths?: string;\n batches?: string;\n pattern?: string;\n teams?: string;\n login?: string;\n paths_no_filter?: string;\n slack_webhook_url?: string;\n number_of_assignees?: string;\n globs?: string;\n override_filter_globs?: string;\n title?: string;\n seconds?: string;\n pull_number?: string;\n base?: string;\n head?: string;\n days?: string;\n no_evict_upon_conflict?: string;\n}\n","/*\nCopyright 2022 Expedia, Inc.\nLicensed under the Apache License, Version 2.0 (the \"License\");\nyou may not use this file except in compliance with the License.\nYou may obtain a copy of the License at\n https://www.apache.org/licenses/LICENSE-2.0\nUnless required by applicable law or agreed to in writing, software\ndistributed under the License is distributed on an \"AS IS\" BASIS,\nWITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\nSee the License for the specific language governing permissions and\nlimitations under the License.\n*/\n\nimport { PullRequestList } from '../types/github';\nimport { octokit } from '../octokit';\nimport { context } from '@actions/github';\n\nexport const paginateAllOpenPullRequests = async (page = 1): Promise => {\n const response = await octokit.pulls.list({\n state: 'open',\n sort: 'updated',\n direction: 'desc',\n per_page: 100,\n page,\n ...context.repo\n });\n if (!response.data.length) {\n return [];\n }\n return response.data.concat(await paginateAllOpenPullRequests(page + 1));\n};\n"],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/src/helpers/check-merge-safety.ts b/src/helpers/check-merge-safety.ts index a7e89e9f4..255d5e515 100644 --- a/src/helpers/check-merge-safety.ts +++ b/src/helpers/check-merge-safety.ts @@ -37,14 +37,11 @@ export const checkMergeSafety = async (inputs: CheckMergeSafety) => { return setMergeSafetyStatus(pullRequest, inputs); }; -export const safeMessage = 'This branch is safe to merge!'; - const setMergeSafetyStatus = async (pullRequest: PullRequest, inputs: CheckMergeSafety) => { - const message = await getMergeSafetyMessage(pullRequest, inputs); - const isSafeToMerge = message === safeMessage; + const { state, message } = await getMergeSafetyStateAndMessage(pullRequest, inputs); await setCommitStatus({ sha: pullRequest.head.sha, - state: isSafeToMerge ? 'success' : 'failure', + state, context: 'Merge Safety', description: message, ...context.repo @@ -57,7 +54,7 @@ const handlePushWorkflow = async (inputs: CheckMergeSafety) => { return map(filteredPullRequests, pullRequest => setMergeSafetyStatus(pullRequest as PullRequest, inputs)); }; -const getMergeSafetyMessage = async ( +const getMergeSafetyStateAndMessage = async ( pullRequest: PullRequest, { paths, override_filter_paths, override_filter_globs }: CheckMergeSafety ) => { @@ -73,11 +70,14 @@ const getMergeSafetyMessage = async ( user: { login: username } } } = pullRequest; + + const branchName = `${username}:${ref}`; + const { data: { files: filesWhichBranchIsBehindOn } } = await octokit.repos.compareCommitsWithBasehead({ ...context.repo, - basehead: `${username}:${ref}...${baseOwner}:${default_branch}` + basehead: `${branchName}...${baseOwner}:${default_branch}` }); const fileNamesWhichBranchIsBehindOn = filesWhichBranchIsBehindOn?.map(file => file.filename) ?? []; @@ -88,15 +88,18 @@ const getMergeSafetyMessage = async ( : []; if (globalFilesOutdatedOnBranch.length) { - core.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', `${username}:${ref}`)); - return `This branch has one or more outdated global files. Please update with ${default_branch}.`; + core.error(buildErrorMessage(globalFilesOutdatedOnBranch, 'global files', branchName)); + return { + state: 'failure', + message: `This branch has one or more outdated global files. Please update with ${default_branch}.` + }; } const { data: { files: changedFiles } } = await octokit.repos.compareCommitsWithBasehead({ ...context.repo, - basehead: `${baseOwner}:${default_branch}...${username}:${ref}` + basehead: `${baseOwner}:${default_branch}...${branchName}` }); const changedFileNames = changedFiles?.map(file => file.filename); const allProjectDirectories = paths?.split(/[\n,]/); @@ -106,17 +109,26 @@ const getMergeSafetyMessage = async ( ); if (changedProjectsOutdatedOnBranch?.length) { - core.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', `${username}:${ref}`)); - return `This branch has one or more outdated projects. Please update with ${default_branch}.`; + core.error(buildErrorMessage(changedProjectsOutdatedOnBranch, 'projects', branchName)); + return { + state: 'failure', + message: `This branch has one or more outdated projects. Please update with ${default_branch}.` + }; } + const safeMessage = buildSuccessMessage(branchName); core.info(safeMessage); - return safeMessage; + return { + state: 'success', + message: safeMessage + }; }; const buildErrorMessage = (paths: string[], pathType: 'projects' | 'global files', branchName: string) => ` -The following ${pathType} are outdated on branch ${branchName}: +The following ${pathType} are outdated on branch ${branchName} ${paths.map(path => `* ${path}`).join('\n')} `; + +const buildSuccessMessage = (branchName: string) => `Branch ${branchName} is safe to merge!`; diff --git a/test/helpers/check-merge-safety.test.ts b/test/helpers/check-merge-safety.test.ts index 258c13384..21de23ca8 100644 --- a/test/helpers/check-merge-safety.test.ts +++ b/test/helpers/check-merge-safety.test.ts @@ -13,7 +13,7 @@ limitations under the License. import { Mocktokit } from '../types'; import { context } from '@actions/github'; -import { checkMergeSafety, safeMessage } from '../../src/helpers/check-merge-safety'; +import { checkMergeSafety } from '../../src/helpers/check-merge-safety'; import { octokit } from '../../src/octokit'; import { setCommitStatus } from '../../src/helpers/set-commit-status'; import { paginateAllOpenPullRequests } from '../../src/utils/paginate-open-pull-requests'; @@ -90,7 +90,7 @@ describe('checkMergeSafety', () => { sha, state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', repo: 'repo', owner: 'owner' }); @@ -109,7 +109,7 @@ describe('checkMergeSafety', () => { sha, state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', repo: 'repo', owner: 'owner' }); @@ -205,28 +205,28 @@ describe('checkMergeSafety', () => { sha: '123', state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', ...context.repo }); expect(setCommitStatus).toHaveBeenCalledWith({ sha: '456', state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', ...context.repo }); expect(setCommitStatus).not.toHaveBeenCalledWith({ sha: '789', state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', ...context.repo }); expect(setCommitStatus).not.toHaveBeenCalledWith({ sha: '000', state: 'success', context: 'Merge Safety', - description: safeMessage, + description: 'Branch username:some-branch-name is safe to merge!', ...context.repo }); });