diff --git a/lib/config/definitions.js b/lib/config/definitions.js index dfd2b35c634ede9b101d2c832cc50268989c09d0..fa062c81e59b3dccd06402daa78617cee1bf0b12 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -48,6 +48,14 @@ const options = [ type: 'boolean', default: false, }, + { + name: 'dryRun', + description: + 'If enabled, Renovate will log messages instead of creating/updating/deleting branches and PRs', + type: 'boolean', + admin: true, + default: false, + }, { name: 'binarySource', description: 'Where to source binaries like `npm` and `yarn` from', diff --git a/lib/workers/branch/commit.js b/lib/workers/branch/commit.js index d5a63e1222fd3d36c9058d6bf271f4622f3aa31e..3fcd4a330c5f5c91100cecfd337991aff7b4292a 100644 --- a/lib/workers/branch/commit.js +++ b/lib/workers/branch/commit.js @@ -11,15 +11,20 @@ async function commitFilesToBranch(config) { if (is.nonEmptyArray(updatedFiles)) { logger.debug(`${updatedFiles.length} file(s) to commit`); - // API will know whether to create new branch or not - const res = await platform.commitFilesToBranch( - config.branchName, - updatedFiles, - config.commitMessage, - config.parentBranch || config.baseBranch || undefined - ); - if (res) { - logger.info({ branch: config.branchName }, `Branch ${res}`); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would commit files to branch ' + config.branchName); + } else { + // API will know whether to create new branch or not + const res = await platform.commitFilesToBranch( + config.branchName, + updatedFiles, + config.commitMessage, + config.parentBranch || config.baseBranch || undefined + ); + if (res) { + logger.info({ branch: config.branchName }, `Branch ${res}`); + } } } else { logger.debug(`No files to commit`); diff --git a/lib/workers/branch/index.js b/lib/workers/branch/index.js index 1fc91dec54156c27b72a471098ab1ecdd6a48871..b7d8c70f906288b764f0354cf064bce0e2588da6 100644 --- a/lib/workers/branch/index.js +++ b/lib/workers/branch/index.js @@ -72,9 +72,22 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) { } content += '\n\nIf this PR was closed by mistake or you changed your mind, you can simply rename this PR and you will soon get a fresh replacement PR opened.'; - await platform.ensureComment(existingPr.number, subject, content); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would ensure closed PR comment in PR #' + + existingPr.number + ); + } else { + await platform.ensureComment(existingPr.number, subject, content); + } if (branchExists) { - await platform.deleteBranch(config.branchName); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would delete branch ' + config.branchName); + } else { + await platform.deleteBranch(config.branchName); + } } } else if (existingPr.state === 'merged') { logger.info( @@ -114,14 +127,27 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) { const labelRebase = branchPr.labels && branchPr.labels.includes(config.rebaseLabel); if (titleRebase || labelRebase) { - await platform.ensureCommentRemoval(branchPr.number, subject); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would ensure PR edited comment removal in PR #' + + branchPr.number + ); + } else { + await platform.ensureCommentRemoval(branchPr.number, subject); + } } else { let content = ':construction_worker: This PR has received other commits, so Renovate will stop updating it to avoid conflicts or other problems.'; content += ` If you wish to abandon your changes and have Renovate start over then you can add the label \`${ config.rebaseLabel }\` to this PR and Renovate will reset/recreate it.`; - await platform.ensureComment(branchPr.number, subject, content); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: ensure comment in PR #' + branchPr.number); + } else { + await platform.ensureComment(branchPr.number, subject, content); + } return 'pr-edited'; } } @@ -227,7 +253,12 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) { logger.info( 'Deleting lock file maintenance branch as master lock file no longer needs updating' ); - await platform.deleteBranch(config.branchName); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would delete lock file maintenance branch'); + } else { + await platform.deleteBranch(config.branchName); + } return 'done'; } if (!(config.committedFiles || branchExists)) { @@ -319,7 +350,14 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) { content += `##### ${error.lockFile}\n\n`; content += `\`\`\`\n${error.stderr}\n\`\`\`\n\n`; }); - await platform.ensureComment(pr.number, topic, content); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would ensure lock file error comment in PR #' + pr.number + ); + } else { + await platform.ensureComment(pr.number, topic, content); + } const context = 'renovate/lock-files'; const description = 'Lock file update failure'; const state = 'failure'; @@ -330,16 +368,30 @@ async function processBranch(branchConfig, prHourlyLimitReached, packageFiles) { // Check if state needs setting if (existingState !== state) { logger.debug(`Updating status check state to failed`); - await platform.setBranchStatus( - config.branchName, - context, - description, - state - ); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would set branch status in ' + config.branchName + ); + } else { + await platform.setBranchStatus( + config.branchName, + context, + description, + state + ); + } } } else { if (config.updatedLockFiles && config.updatedLockFiles.length) { - await platform.ensureCommentRemoval(pr.number, topic); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would ensure comment removal in PR #' + pr.number + ); + } else { + await platform.ensureCommentRemoval(pr.number, topic); + } } const prAutomerged = await prWorker.checkAutoMerge(pr, config); if (prAutomerged) { diff --git a/lib/workers/pr/index.js b/lib/workers/pr/index.js index ca357909bdbbca8d7ff2ad5bcb4201ab37d9b206..a0b7e73a91909e5ce44440e3237d752600c83da3 100644 --- a/lib/workers/pr/index.js +++ b/lib/workers/pr/index.js @@ -210,12 +210,16 @@ async function ensurePr(prConfig) { 'PR body changed' ); } - - await platform.updatePr(existingPr.number, prTitle, prBody); - logger.info( - { committedFiles: config.committedFiles, pr: existingPr.number }, - `PR updated` - ); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would update PR #' + existingPr.number); + } else { + await platform.updatePr(existingPr.number, prTitle, prBody); + logger.info( + { committedFiles: config.committedFiles, pr: existingPr.number }, + `PR updated` + ); + } return existingPr; } logger.debug({ branch: branchName, prTitle }, `Creating PR`); @@ -225,20 +229,31 @@ async function ensurePr(prConfig) { } let pr; try { - pr = await platform.createPr( - branchName, - prTitle, - prBody, - config.labels, - false, - config.statusCheckVerify - ); - logger.info({ branch: branchName, pr: pr.number }, 'PR created'); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would create PR: ' + prTitle); + pr = { number: 0, displayNumber: 'Dry run PR' }; + } else { + pr = await platform.createPr( + branchName, + prTitle, + prBody, + config.labels, + false, + config.statusCheckVerify + ); + logger.info({ branch: branchName, pr: pr.number }, 'PR created'); + } } catch (err) { logger.warn({ err }, `Failed to create PR`); if (err.message === 'Validation Failed (422)') { logger.info({ branch: branchName }, 'Deleting invalid branch'); - await platform.deleteBranch(branchName); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would delete branch: ' + config.branchName); + } else { + await platform.deleteBranch(branchName); + } } // istanbul ignore if if (err.statusCode === 502) { @@ -246,7 +261,12 @@ async function ensurePr(prConfig) { { branch: branchName }, 'Deleting branch due to server error' ); - await platform.deleteBranch(branchName); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would delete branch: ' + config.branchName); + } else { + await platform.deleteBranch(branchName); + } } return null; } @@ -258,7 +278,12 @@ async function ensurePr(prConfig) { content += '\n___\n * Branch has one or more failed status checks'; } logger.info('Adding branch automerge failure message to PR'); - await platform.ensureComment(pr.number, subject, content); + // istanbul ignore if + if (config.dryRun) { + logger.info('Would add comment to PR #' + pr.number); + } else { + await platform.ensureComment(pr.number, subject, content); + } } // Skip assign and review if automerging PR if (config.automerge && (await getBranchStatus()) !== 'failure') { @@ -291,8 +316,13 @@ async function addAssigneesReviewers(config, pr) { assignee => assignee.length && assignee[0] === '@' ? assignee.slice(1) : assignee ); - await platform.addAssignees(pr.number, assignees); - logger.info({ assignees: config.assignees }, 'Added assignees'); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would add assignees to PR #', pr.number); + } else { + await platform.addAssignees(pr.number, assignees); + logger.info({ assignees: config.assignees }, 'Added assignees'); + } } catch (err) { logger.info( { assignees: config.assignees, err }, @@ -306,8 +336,13 @@ async function addAssigneesReviewers(config, pr) { reviewer => reviewer.length && reviewer[0] === '@' ? reviewer.slice(1) : reviewer ); - await platform.addReviewers(pr.number, reviewers); - logger.info({ reviewers: config.reviewers }, 'Added reviewers'); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would add assignees to PR #', pr.number); + } else { + await platform.addReviewers(pr.number, reviewers); + logger.info({ reviewers: config.reviewers }, 'Added reviewers'); + } } catch (err) { logger.info( { assignees: config.assignees, err }, @@ -359,10 +394,22 @@ async function checkAutoMerge(pr, config) { } if (automergeType === 'pr-comment') { logger.info(`Applying automerge comment: ${automergeComment}`); + // istanbul ignore if + if (config.dryRun) { + logger.info( + 'DRY-RUN: Would add PR automerge comment to PR #' + pr.number + ); + return false; + } return platform.ensureComment(pr.number, null, automergeComment); } // Let's merge this logger.debug(`Automerging #${pr.number}`); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would merge PR #' + pr.number); + return false; + } const res = platform.mergePr(pr.number, branchName); logger.info({ pr: pr.number }, 'PR automerged'); return res; diff --git a/lib/workers/repository/onboarding/branch/create.js b/lib/workers/repository/onboarding/branch/create.js index 46fa0f007f5cca37dbd9fa6da9866c227a3e1d17..e60fe8387aaa7972c521d267c368e107b5482b65 100644 --- a/lib/workers/repository/onboarding/branch/create.js +++ b/lib/workers/repository/onboarding/branch/create.js @@ -16,16 +16,21 @@ async function createOnboardingBranch(config) { } else { commitMessage = 'Add renovate.json'; } - await platform.commitFilesToBranch( - `renovate/configure`, - [ - { - name: 'renovate.json', - contents, - }, - ], - commitMessage - ); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would commit files to onboaring branch'); + } else { + await platform.commitFilesToBranch( + `renovate/configure`, + [ + { + name: 'renovate.json', + contents, + }, + ], + commitMessage + ); + } } module.exports = { diff --git a/lib/workers/repository/onboarding/branch/rebase.js b/lib/workers/repository/onboarding/branch/rebase.js index b5caafd7d15a2e62524d89855fe5669842577f49..f8d6785faf82489bdb9c4e8296fe9c5e6a9e8298 100644 --- a/lib/workers/repository/onboarding/branch/rebase.js +++ b/lib/workers/repository/onboarding/branch/rebase.js @@ -30,16 +30,21 @@ async function rebaseOnboardingBranch(config) { } else { commitMessage = 'Add renovate.json'; } - await platform.commitFilesToBranch( - onboardingBranch, - [ - { - name: 'renovate.json', - contents: existingContents || contents, - }, - ], - commitMessage - ); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would rebase files in onboaring branch'); + } else { + await platform.commitFilesToBranch( + onboardingBranch, + [ + { + name: 'renovate.json', + contents: existingContents || contents, + }, + ], + commitMessage + ); + } } module.exports = { diff --git a/lib/workers/repository/onboarding/pr/index.js b/lib/workers/repository/onboarding/pr/index.js index 7f569cbf366a3590e56017023ddde11f32419558..65b03e6683e75eb3d29c00c0adbcf05d110f6970 100644 --- a/lib/workers/repository/onboarding/pr/index.js +++ b/lib/workers/repository/onboarding/pr/index.js @@ -99,14 +99,19 @@ Also, you can post questions about your config in [Renovate's Config Help reposi const labels = []; const useDefaultBranch = true; try { - const pr = await platform.createPr( - onboardingBranch, - onboardingPrTitle, - prBody, - labels, - useDefaultBranch - ); - logger.info({ pr: pr.displayNumber }, 'Created onboarding PR'); + // istanbul ignore if + if (config.dryRun) { + logger.info('DRY-RUN: Would create onboarding PR'); + } else { + const pr = await platform.createPr( + onboardingBranch, + onboardingPrTitle, + prBody, + labels, + useDefaultBranch + ); + logger.info({ pr: pr.displayNumber }, 'Created onboarding PR'); + } } catch (err) /* istanbul ignore next */ { if ( err.statusCode === 422 && diff --git a/test/workers/branch/commit.spec.js b/test/workers/branch/commit.spec.js index 2e9eaf644f11b85e0a4e031a72888b6930326a2c..0f9bd621e5d8967a147034f96607fa8a00eee8d9 100644 --- a/test/workers/branch/commit.spec.js +++ b/test/workers/branch/commit.spec.js @@ -31,5 +31,14 @@ describe('workers/branch/automerge', () => { expect(platform.commitFilesToBranch.mock.calls.length).toBe(1); expect(platform.commitFilesToBranch.mock.calls).toMatchSnapshot(); }); + it('dry runs', async () => { + config.dryRun = true; + config.updatedPackageFiles.push({ + name: 'package.json', + contents: 'some contents', + }); + await commitFilesToBranch(config); + expect(platform.commitFilesToBranch.mock.calls.length).toBe(0); + }); }); }); diff --git a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap index c6fa3104854d1950675bab3f1f14ff540d46d7c7..67d800a83bcfeca3cb2fad63e6e6d450808953ad 100644 --- a/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap +++ b/test/workers/repository/updates/__snapshots__/flatten.spec.js.snap @@ -21,6 +21,7 @@ Array [ "commitMessageTopic": "dependency {{depName}}", "depName": "@org/a", "depNameSanitized": "org-a", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -113,6 +114,7 @@ Array [ "commitMessageTopic": "dependency {{depName}}", "depName": "foo", "depNameSanitized": "foo", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -203,6 +205,7 @@ Array [ "commitMessagePrefix": null, "commitMessageSuffix": null, "commitMessageTopic": null, + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -297,6 +300,7 @@ Array [ "commitMessageTopic": "dependency {{depName}}", "depName": "bar", "depNameSanitized": "bar", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -387,6 +391,7 @@ Array [ "commitMessagePrefix": null, "commitMessageSuffix": null, "commitMessageTopic": null, + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -481,6 +486,7 @@ Array [ "commitMessageTopic": "dependency {{depName}}", "depName": "baz", "depNameSanitized": "baz", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -573,6 +579,7 @@ Array [ "commitMessageTopic": "{{{depName}}} Docker tag", "depName": "amd64/node", "depNameSanitized": "node", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, @@ -665,6 +672,7 @@ Array [ "commitMessageTopic": "{{{depName}}} Docker tag", "depName": "calico/node", "depNameSanitized": "calico-node", + "dryRun": false, "errors": Array [], "gitAuthor": null, "gitPrivateKey": null, diff --git a/website/docs/self-hosted-configuration.md b/website/docs/self-hosted-configuration.md index ac09e1963ef993307b71aae15fc2a4e9f8a17ccb..b9a0eee7f7fafb3364c28c09787cbc29ba7e52af 100644 --- a/website/docs/self-hosted-configuration.md +++ b/website/docs/self-hosted-configuration.md @@ -15,6 +15,8 @@ Be cautious when using this option - it will run Renovate over _every_ repositor Set this to 'global' if you wish Renovate to use globally-installed binaries (`npm`, `yarn`, etc) instead of using its bundled versions. +## dryRun + ## endpoint ## exposeEnv