diff --git a/README.md b/README.md index 5d76d95c..244ce95a 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,9 @@ The following merge options are supported: be deleted. This is not the list of GitHub's protected branches, which are never deleted, but an additional list of branches to protect. The default value is `""`. +- `MERGE_ERROR_FAIL`: Set this to `true` to have the action exit with error code `1` + when the pull request could not be merged successfully during a run. + The following update options are supported: - `UPDATE_LABELS`: The labels that need to be present for a pull request to be diff --git a/dist/index.js b/dist/index.js index 17cad705..e23f091b 100755 --- a/dist/index.js +++ b/dist/index.js @@ -663,6 +663,7 @@ function createConfig(env = {}) { const mergeDeleteBranchFilter = parseArray(env.MERGE_DELETE_BRANCH_FILTER); const mergeMethodLabels = parseLabelMethods(env.MERGE_METHOD_LABELS); const mergeMethodLabelRequired = env.MERGE_METHOD_LABEL_REQUIRED === "true"; + const mergeErrorFail = env.MERGE_ERROR_FAIL === "true"; const updateLabels = parseMergeLabels(env.UPDATE_LABELS, "automerge"); const updateMethod = env.UPDATE_METHOD || "merge"; @@ -688,6 +689,7 @@ function createConfig(env = {}) { mergeRequiredApprovals, mergeDeleteBranch, mergeDeleteBranchFilter, + mergeErrorFail, updateLabels, updateMethod, updateRetries, @@ -753,11 +755,6 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -function fail(message) { - logger.error(message); - process.exit(1); -} - module.exports = { ClientError, TimeoutError, @@ -1011,7 +1008,8 @@ async function merge(context, pullRequest, approvalCount) { mergeFilterAuthor, mergeRemoveLabels, mergeRetries, - mergeRetrySleep + mergeRetrySleep, + mergeErrorFail } } = context; @@ -1054,6 +1052,7 @@ async function merge(context, pullRequest, approvalCount) { mergeMethod, mergeRetries, mergeRetrySleep, + mergeErrorFail, commitMessage ); if (!merged) { @@ -1234,7 +1233,7 @@ function skipPullRequest(context, pullRequest, approvalCount) { function waitUntilReady(pullRequest, context) { const { octokit, - config: { mergeRetries, mergeRetrySleep } + config: { mergeRetries, mergeRetrySleep, mergeErrorFail } } = context; return retry( @@ -1245,7 +1244,7 @@ function waitUntilReady(pullRequest, context) { const pr = await getPullRequest(octokit, pullRequest); return checkReady(pr, context); }, - () => fail(`PR not ready to be merged after ${mergeRetries} tries`) + () => failOrInfo(mergeRetries, mergeErrorFail) ); } @@ -1290,6 +1289,7 @@ function tryMerge( mergeMethod, mergeRetries, mergeRetrySleep, + mergeErrorFail, commitMessage ) { return retry( @@ -1310,10 +1310,20 @@ function tryMerge( commitMessage ); }, - () => fail(`PR could not be merged after ${mergeRetries} tries`) + () => failOrInfo(mergeRetries, mergeErrorFail) ); } +function failOrInfo(mergeRetries, mergeErrorFail) { + const message = `PR not ready to be merged after ${mergeRetries} tries`; + if (mergeErrorFail) { + logger.error(message); + process.exit(1); + } else { + logger.info(message); + } +} + function getMergeMethod(defaultMergeMethod, mergeMethodLabels, pullRequest) { const foundMergeMethodLabels = pullRequest.labels.flatMap(l => mergeMethodLabels.filter(ml => ml.label === l.name) diff --git a/it/it.js b/it/it.js index fbb86bec..8c428744 100644 --- a/it/it.js +++ b/it/it.js @@ -19,7 +19,8 @@ async function main() { MERGE_REQUIRED_APPROVALS: "0", MERGE_REMOVE_LABELS: "it-merge", MERGE_RETRIES: "3", - MERGE_RETRY_SLEEP: "2000" + MERGE_RETRY_SLEEP: "2000", + MERGE_ERROR_FAIL: "true" }); const context = { token, octokit, config }; diff --git a/lib/common.js b/lib/common.js index 0aea17be..30eda48b 100644 --- a/lib/common.js +++ b/lib/common.js @@ -43,13 +43,11 @@ const logger = { info: (...str) => log("INFO ", str), error: (...str) => { - if (str.length === 1) { - if (str[0] instanceof Error) { - if (logger.level === "trace" || logger.level === "debug") { - log(null, [str[0].stack || str[0]]); - } else { - log("ERROR", [str[0].message || str[0]]); - } + if (str.length === 1 && str[0] instanceof Error) { + if (logger.level === "trace" || logger.level === "debug") { + log(null, [str[0].stack || str[0]]); + } else { + log("ERROR", [str[0].message || str[0]]); } } else { log("ERROR", str); @@ -173,6 +171,7 @@ function createConfig(env = {}) { const mergeDeleteBranchFilter = parseArray(env.MERGE_DELETE_BRANCH_FILTER); const mergeMethodLabels = parseLabelMethods(env.MERGE_METHOD_LABELS); const mergeMethodLabelRequired = env.MERGE_METHOD_LABEL_REQUIRED === "true"; + const mergeErrorFail = env.MERGE_ERROR_FAIL === "true"; const updateLabels = parseMergeLabels(env.UPDATE_LABELS, "automerge"); const updateMethod = env.UPDATE_METHOD || "merge"; @@ -198,6 +197,7 @@ function createConfig(env = {}) { mergeRequiredApprovals, mergeDeleteBranch, mergeDeleteBranchFilter, + mergeErrorFail, updateLabels, updateMethod, updateRetries, @@ -263,11 +263,6 @@ function sleep(ms) { return new Promise(resolve => setTimeout(resolve, ms)); } -function fail(message) { - logger.error(message); - process.exit(1); -} - module.exports = { ClientError, TimeoutError, diff --git a/lib/merge.js b/lib/merge.js index 7d49ed03..369033b6 100644 --- a/lib/merge.js +++ b/lib/merge.js @@ -35,7 +35,8 @@ async function merge(context, pullRequest, approvalCount) { mergeFilterAuthor, mergeRemoveLabels, mergeRetries, - mergeRetrySleep + mergeRetrySleep, + mergeErrorFail } } = context; @@ -78,6 +79,7 @@ async function merge(context, pullRequest, approvalCount) { mergeMethod, mergeRetries, mergeRetrySleep, + mergeErrorFail, commitMessage ); if (!merged) { @@ -258,7 +260,7 @@ function skipPullRequest(context, pullRequest, approvalCount) { function waitUntilReady(pullRequest, context) { const { octokit, - config: { mergeRetries, mergeRetrySleep } + config: { mergeRetries, mergeRetrySleep, mergeErrorFail } } = context; return retry( @@ -269,7 +271,7 @@ function waitUntilReady(pullRequest, context) { const pr = await getPullRequest(octokit, pullRequest); return checkReady(pr, context); }, - () => fail(`PR not ready to be merged after ${mergeRetries} tries`) + () => failOrInfo(mergeRetries, mergeErrorFail) ); } @@ -314,6 +316,7 @@ function tryMerge( mergeMethod, mergeRetries, mergeRetrySleep, + mergeErrorFail, commitMessage ) { return retry( @@ -334,10 +337,20 @@ function tryMerge( commitMessage ); }, - () => fail(`PR could not be merged after ${mergeRetries} tries`) + () => failOrInfo(mergeRetries, mergeErrorFail) ); } +function failOrInfo(mergeRetries, mergeErrorFail) { + const message = `PR not ready to be merged after ${mergeRetries} tries`; + if (mergeErrorFail) { + logger.error(message); + process.exit(1); + } else { + logger.info(message); + } +} + function getMergeMethod(defaultMergeMethod, mergeMethodLabels, pullRequest) { const foundMergeMethodLabels = pullRequest.labels.flatMap(l => mergeMethodLabels.filter(ml => ml.label === l.name) diff --git a/test/common.test.js b/test/common.test.js index 5d84b228..efaa809d 100644 --- a/test/common.test.js +++ b/test/common.test.js @@ -21,6 +21,7 @@ test("createConfig", () => { mergeCommitMessageRegex: "", mergeDeleteBranch: false, mergeDeleteBranchFilter: [], + mergeErrorFail: false, mergeRetries: 3, mergeRetrySleep: 5000, mergeRequiredApprovals: 0, @@ -59,6 +60,7 @@ test("createConfig with arbitrary pull request (as string)", () => { mergeCommitMessageRegex: "", mergeDeleteBranch: false, mergeDeleteBranchFilter: [], + mergeErrorFail: false, mergeRetries: 3, mergeRetrySleep: 5000, mergeRequiredApprovals: 0, @@ -99,6 +101,7 @@ test("createConfig with arbitrary pull request (as number)", () => { mergeCommitMessageRegex: "", mergeDeleteBranch: false, mergeDeleteBranchFilter: [], + mergeErrorFail: false, mergeRetries: 3, mergeRetrySleep: 5000, mergeRequiredApprovals: 0, @@ -139,6 +142,7 @@ test("createConfig with arbitrary pull request in another repo", () => { mergeCommitMessageRegex: "", mergeDeleteBranch: false, mergeDeleteBranchFilter: [], + mergeErrorFail: false, mergeRetries: 3, mergeRetrySleep: 5000, mergeRequiredApprovals: 0, diff --git a/test/merge.test.js b/test/merge.test.js index 0311bda1..7ee583ec 100644 --- a/test/merge.test.js +++ b/test/merge.test.js @@ -273,7 +273,7 @@ test("Unmergeable pull request fails action with non-zero exit code", async () = // GIVEN const pr = pullRequest(); pr.mergeable_state = "blocked"; - const config = createConfig(); + const config = createConfig({ MERGE_ERROR_FAIL: "true" }); octokit.pulls.get = async () => ({ data: pr }); // Reduce retry wait period to 1ms to prevent test timeout @@ -287,10 +287,14 @@ test("Unmergeable pull request fails action with non-zero exit code", async () = `process.exit was called with status code: ${statusCode}` ); }); + try { await merge({ config, octokit }, pr, 0); } catch (e) { expect(e).toEqual(new Error("process.exit was called with status code: 1")); expect(mockExit).toHaveBeenCalledWith(1); + return; } + + throw new Error("process.exit was not called!"); });