diff --git a/docs/configuration.md b/docs/configuration.md index c6ca5d55258277aea1ecd1192cf902a230558e0f..dd92a6d47a4c04da376b762e8cd39589d31c3bd0 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -84,6 +84,7 @@ $ node renovate --help --respect-latest [boolean] Ignore versions newer than npm "latest" version --recreate-closed [boolean] Recreate PRs even if same ones were closed previously --rebase-stale-prs [boolean] Rebase stale PRs (GitHub only) + --pr-creation <string> When to create the PR for a branch --maintain-yarn-lock [boolean] Keep yarn.lock files updated in base branch --group-name <string> Human understandable name for the dependency group --group-slug <string> Slug to use for group (e.g. in branch name). Will be calculated from groupName if null @@ -138,6 +139,7 @@ Obviously, you can't set repository or package file location with this method. | `respectLatest` | Ignore versions newer than npm "latest" version | boolean | `true` | `RENOVATE_RESPECT_LATEST` | `--respect-latest` | | `recreateClosed` | Recreate PRs even if same ones were closed previously | boolean | `false` | `RENOVATE_RECREATE_CLOSED` | `--recreate-closed` | | `rebaseStalePrs` | Rebase stale PRs (GitHub only) | boolean | `false` | `RENOVATE_REBASE_STALE_PRS` | `--rebase-stale-prs` | +| `prCreation` | When to create the PR for a branch | string | `"immediate"` | `RENOVATE_PR_CREATION` | `--pr-creation` | | `branchName` | Branch name template | string | `"renovate/{{depName}}-{{newVersionMajor}}.x"` | | | | `commitMessage` | Commit message template | string | `"Update dependency {{depName}} to version {{newVersion}}"` | | | | `prTitle` | Pull Request title template | string | `"{{#if isPin}}Pin{{else}}Update{{/if}} dependency {{depName}} to version {{#if isRange}}{{newVersion}}{{else}}{{#if isMajor}}{{newVersionMajor}}.x{{else}}{{newVersion}}{{/if}}{{/if}}"` | | | diff --git a/lib/api/github.js b/lib/api/github.js index 63cb6de61a69d336f9f125d7a74c4778a19b1487..7853881a80be66686e776413515b73e1e5104b3c 100644 --- a/lib/api/github.js +++ b/lib/api/github.js @@ -10,6 +10,7 @@ module.exports = { // Branch branchExists, getBranchPr, + getBranchStatus, // issue addAssignees, addReviewers, @@ -104,6 +105,15 @@ async function getBranchPr(branchName) { return getPr(prNo); } +// Returns the combined status for a branch. +async function getBranchStatus(branchName) { + logger.debug(`getBranchStatus(${branchName})`); + const gotString = `repos/${config.repoName}/commits/${branchName}/status`; + logger.debug(gotString); + const res = await ghGot(gotString); + return res.body.state; +} + // Issue async function addAssignees(issueNo, assignees) { diff --git a/lib/api/gitlab.js b/lib/api/gitlab.js index ecae60fdd39081e102ebd29851a72720bce5f6dd..24022584a833886e82c4f9fa1575510ac36914e8 100644 --- a/lib/api/gitlab.js +++ b/lib/api/gitlab.js @@ -10,6 +10,7 @@ module.exports = { // Branch branchExists, getBranchPr, + getBranchStatus, // issue addAssignees, addReviewers, @@ -105,6 +106,36 @@ async function getBranchPr(branchName) { return getPr(pr.id); } +// Returns the combined status for a branch. +async function getBranchStatus(branchName) { + logger.debug(`getBranchStatus(${branchName})`); + // First, get the branch to find the commit SHA + let url = `projects/${config.repoName}/repository/branches/${branchName}`; + let res = await glGot(url); + const branchSha = res.body.commit.id; + // Now, check the statuses for that commit + url = `projects/${config.repoName}/repository/commits/${branchSha}/statuses`; + res = await glGot(url); + logger.debug(`Got res with ${res.body.length} results`); + if (res.body.length === 0) { + // Return 'pending' if we have no status checks + return 'pending'; + } + let status = 'success'; + // Return 'success' if all are success + res.body.forEach((check) => { + // If one is failed then don't overwrite that + if (status !== 'failed') { + if (check.status === 'failed') { + status = 'failed'; + } else if (check.status !== 'success') { + status = check.status; + } + } + }); + return status; +} + // Issue async function addAssignees(prNo, assignees) { diff --git a/lib/config/definitions.js b/lib/config/definitions.js index aa0c3f85155f7a1f3a1f764490f972857baceac2..aa2dff75a46bd3ac4ea485b63474ad140d3fb181 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -87,6 +87,12 @@ const options = [ type: 'boolean', default: false, }, + { + name: 'prCreation', + description: 'When to create the PR for a branch', + type: 'string', + default: 'immediate', + }, // String templates { name: 'branchName', diff --git a/lib/workers/branch.js b/lib/workers/branch.js index eb65ba4b01e7107dfc4a47afaa26e19124b2e3b1..a561fd0ff905648efd2927019060841187538c0d 100644 --- a/lib/workers/branch.js +++ b/lib/workers/branch.js @@ -156,5 +156,5 @@ async function ensureBranch(upgrades) { return true; } logger.debug(`No files to commit to branch ${branchName}`); - return false; + return api.branchExists(branchName); } diff --git a/lib/workers/pr.js b/lib/workers/pr.js index 19f31ebb44e732ff1cbaca0e63820801264c97b9..a019e9bfd77cfa74a6c30876200d6c7076d3f0bb 100644 --- a/lib/workers/pr.js +++ b/lib/workers/pr.js @@ -11,10 +11,29 @@ async function ensurePr(upgradeConfig) { const config = Object.assign({}, upgradeConfig); logger.debug('Ensuring PR'); + const branchName = handlebars.compile(config.branchName)(config); + + if (config.prCreation === 'status-success') { + logger.debug('Checking branch combined status'); + const branchStatus = await config.api.getBranchStatus(branchName); + if (branchStatus !== 'success') { + logger.debug(`Branch status is "${branchStatus}" - not creating PR`); + return null; + } + logger.debug('Branch status success'); + } else if (config.prCreation === 'not-pending') { + logger.debug('Checking branch combined status'); + const branchStatus = await config.api.getBranchStatus(branchName); + if (branchStatus === 'pending' || branchStatus === 'running') { + logger.debug(`Branch status is "${branchStatus}" - not creating PR`); + return null; + } + logger.debug('Branch status success'); + } + // Get changelog and then generate template strings config.changelog = await getChangeLog(config.depName, config.changeLogFromVersion, config.changeLogToVersion); - const branchName = handlebars.compile(config.branchName)(config); const prTitle = handlebars.compile(config.prTitle)(config); const prBody = handlebars.compile(config.prBody)(config); diff --git a/readme.md b/readme.md index 24c48f4c4b30489712165bf902aeeb72d87a0adc..3726fe6667b611b5e4a982ef6c7fb23933ff3d7e 100644 --- a/readme.md +++ b/readme.md @@ -53,6 +53,7 @@ $ node renovate --help --respect-latest [boolean] Ignore versions newer than npm "latest" version --recreate-closed [boolean] Recreate PRs even if same ones were closed previously --rebase-stale-prs [boolean] Rebase stale PRs (GitHub only) + --pr-creation <string> When to create the PR for a branch --maintain-yarn-lock [boolean] Keep yarn.lock files updated in base branch --group-name <string> Human understandable name for the dependency group --group-slug <string> Slug to use for group (e.g. in branch name). Will be calculated from groupName if null diff --git a/test/workers/branch.spec.js b/test/workers/branch.spec.js index 116a11a02be5c430d8bebffb0031a285125fc1ed..bb5d769a905b6add18b062b8d233de2a0c381328 100644 --- a/test/workers/branch.spec.js +++ b/test/workers/branch.spec.js @@ -106,6 +106,7 @@ describe('workers/branch', () => { config = Object.assign({}, defaultConfig); config.api = {}; config.api.getFileContent = jest.fn(); + config.api.branchExists = jest.fn(); config.api.commitFilesToBranch = jest.fn(); config.api.getFileContent.mockReturnValueOnce('old content'); config.depName = 'dummy'; @@ -115,6 +116,7 @@ describe('workers/branch', () => { it('returns if new content matches old', async () => { branchWorker.getParentBranch.mockReturnValueOnce('dummy branch'); packageJsonHelper.setNewValue.mockReturnValueOnce('old content'); + config.api.branchExists.mockReturnValueOnce(false); await branchWorker.ensureBranch([config]); expect(branchWorker.getParentBranch.mock.calls.length).toBe(1); expect(packageJsonHelper.setNewValue.mock.calls.length).toBe(1);