diff --git a/lib/config/definitions.js b/lib/config/definitions.js index c85207bce7b7cfcf2bb8e2c0aff8cefcf3898b84..eb416efdfb4d5fbdcce4c8a2e1b6334c239214dc 100644 --- a/lib/config/definitions.js +++ b/lib/config/definitions.js @@ -197,11 +197,10 @@ const options = [ cli: false, }, { - name: 'baseBranch', + name: 'baseBranches', description: - 'Base branch to target for Pull Requests. Otherwise default branch is used', - stage: 'repository', - type: 'string', + 'An array of one or more custom base branches to be renovated. If left empty, the default branch will be renovate', + type: 'list', cli: false, env: false, }, diff --git a/lib/config/migration.js b/lib/config/migration.js index 84f8a280ea9ea32ce46e68d1143671575ad7b790..2bb1908bf40854e92f5b519add27eda28ece36d1 100644 --- a/lib/config/migration.js +++ b/lib/config/migration.js @@ -118,6 +118,10 @@ function migrateConfig(config) { isMigrated = true; migratedConfig.packagePatterns = [val]; delete migratedConfig.packagePattern; + } else if (key === 'baseBranch') { + isMigrated = true; + migratedConfig.baseBranches = [val]; + delete migratedConfig.baseBranch; } else if (key === 'schedule' && !val) { isMigrated = true; migratedConfig.schedule = []; diff --git a/lib/workers/branch/commit.js b/lib/workers/branch/commit.js index bce9d94d3a017096f5ee1e9357a0f72644633404..9b2fb152b0a93f7f4dbbd29629fc5337c466f802 100644 --- a/lib/workers/branch/commit.js +++ b/lib/workers/branch/commit.js @@ -30,7 +30,7 @@ async function commitFilesToBranch(config) { config.branchName, updatedFiles, commitMessage, - config.parentBranch, + config.parentBranch || config.baseBranch || undefined, config.gitAuthor, config.gitPrivateKey ); diff --git a/lib/workers/branch/index.js b/lib/workers/branch/index.js index fccbffcb26f3bb8654ef199a1d4a24c833c8033a..1079a085f4cdf9f933b1a3abeac6eb3285270c82 100644 --- a/lib/workers/branch/index.js +++ b/lib/workers/branch/index.js @@ -27,6 +27,7 @@ async function processBranch(branchConfig) { }); logger.debug('processBranch()'); logger.trace({ config }); + await platform.setBaseBranch(config.baseBranch); const branchExists = await platform.branchExists(config.branchName); logger.debug(`branchExists=${branchExists}`); try { diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js index e59a3a220c50bfd9c907ef315b7f58c600605155..2d7f0b63199a9e0004a02d6cdbca6816b5b827ff 100644 --- a/lib/workers/repository/index.js +++ b/lib/workers/repository/index.js @@ -23,8 +23,48 @@ async function renovateRepository(repoConfig, token, loop = 1) { } config = await initApis(config, token); config = await initRepo(config); - config = await resolvePackageFiles(config); - config = await determineUpdates(config); + + if (config.baseBranches && config.baseBranches.length) { + // At this point we know if we have multiple branches + // Do the following for every branch + const commonConfig = JSON.parse(JSON.stringify(config)); + const configs = []; + logger.info({ baseBranches: config.baseBranches }, 'baseBranches'); + for (const [index, baseBranch] of commonConfig.baseBranches.entries()) { + config = JSON.parse(JSON.stringify(commonConfig)); + config.baseBranch = baseBranch; + config.branchPrefix += + config.baseBranches.length > 1 ? `${baseBranch}-` : ''; + platform.setBaseBranch(baseBranch); + config = await resolvePackageFiles(config); + config = await determineUpdates(config); + configs[index] = config; + } + // Combine all the results into one + for (const [index, res] of configs.entries()) { + if (index === 0) { + config = res; + } else { + config.branches = config.branches.concat(res.branches); + } + } + } else { + config = await resolvePackageFiles(config); + config = await determineUpdates(config); + } + + // Sort branches + const sortOrder = [ + 'digest', + 'pin', + 'patch', + 'minor', + 'major', + 'lockFileMaintenance', + ]; + config.branches.sort( + (a, b) => sortOrder.indexOf(a.type) - sortOrder.indexOf(b.type) + ); const res = config.repoIsOnboarded ? await writeUpdates(config) : await ensureOnboardingPr(config); diff --git a/lib/workers/repository/onboarding/pr/pr-list.js b/lib/workers/repository/onboarding/pr/pr-list.js index a58f8f1a86e8dd5c9e69f0ec88b9f510be5d0b97..d6973fa75060159e7b314646aa869d9e0e5a3a18 100644 --- a/lib/workers/repository/onboarding/pr/pr-list.js +++ b/lib/workers/repository/onboarding/pr/pr-list.js @@ -21,6 +21,9 @@ function getPrList(config) { prDesc += ` - Schedule: ${JSON.stringify(branch.schedule)}\n`; } prDesc += ` - Branch name: \`${branch.branchName}\`\n`; + prDesc += config.baseBranch + ? ` - Merge into: \`${branch.baseBranch}\`\n` + : ''; for (const upgrade of branch.upgrades) { if (upgrade.type === 'lockFileMaintenance') { prDesc += ' - Regenerates lock file to use latest dependency versions'; diff --git a/lib/workers/repository/updates/branchify.js b/lib/workers/repository/updates/branchify.js index be5e22758d923ade741ab66920eef38f4b4e2756..ebea4b4ca45bfe055ff2c50daec61b7dd2c2ac76 100644 --- a/lib/workers/repository/updates/branchify.js +++ b/lib/workers/repository/updates/branchify.js @@ -56,10 +56,6 @@ function branchifyUpgrades(config) { const branchList = config.repoIsOnboarded ? branches.map(upgrade => upgrade.branchName) : config.branchList; - const sortOrder = ['digest', 'pin', 'minor', 'major', 'lockFileMaintenance']; - branches.sort( - (a, b) => sortOrder.indexOf(a.type) - sortOrder.indexOf(b.type) - ); return { ...config, errors: config.errors.concat(errors), diff --git a/lib/workers/repository/updates/generate.js b/lib/workers/repository/updates/generate.js index 1b28e47c6ac6bb487cbaff5b80579f71f2096fff..d7ee80534a73a85c213a1cbf20b5202995ad0e55 100644 --- a/lib/workers/repository/updates/generate.js +++ b/lib/workers/repository/updates/generate.js @@ -46,6 +46,10 @@ function generateBranchConfig(branchUpgrades) { 'Compiling branchName and prTitle' ); upgrade.branchName = handlebars.compile(upgrade.branchName)(upgrade); + upgrade.prTitle += + upgrade.baseBranches && upgrade.baseBranches.length > 1 + ? ' ({{baseBranch}})' + : ''; upgrade.prTitle = handlebars.compile(upgrade.prTitle)(upgrade); if (upgrade.semanticCommits) { logger.debug('Upgrade has semantic commits enabled'); diff --git a/test/config/__snapshots__/migration.spec.js.snap b/test/config/__snapshots__/migration.spec.js.snap index 9792e8a55727a01975beaa469a700887b0f030cf..3014e833dc2ad161539cfbaf4f87bee13f0cd2c1 100644 --- a/test/config/__snapshots__/migration.spec.js.snap +++ b/test/config/__snapshots__/migration.spec.js.snap @@ -10,6 +10,9 @@ exports[`config/migration migrateConfig(config, parentConfig) it migrates config Object { "autodiscover": true, "automerge": false, + "baseBranches": Array [ + "next", + ], "commitMessage": "some commit message", "devDependencies": Object { "major": Object { diff --git a/test/config/migration.spec.js b/test/config/migration.spec.js index dfba2b3283f2d1674c362bdfa270537a9261de46..17da0dc988cd3226f25ecc1ec2ea9a338642fb7b 100644 --- a/test/config/migration.spec.js +++ b/test/config/migration.spec.js @@ -13,6 +13,7 @@ describe('config/migration', () => { automergeMajor: false, automergeMinor: true, automergePatch: true, + baseBranch: 'next', ignoreNodeModules: true, meteor: true, autodiscover: 'true', diff --git a/test/workers/repository/__snapshots__/index.spec.js.snap b/test/workers/repository/__snapshots__/index.spec.js.snap index 2b844f24c33cb85a0ac463b522d0488e9b301fbb..d617b64e2b85551bd8d40f896d0e7cc0229e123c 100644 --- a/test/workers/repository/__snapshots__/index.spec.js.snap +++ b/test/workers/repository/__snapshots__/index.spec.js.snap @@ -4,4 +4,6 @@ exports[`workers/repository renovateRepository() ensures onboarding pr 1`] = `"o exports[`workers/repository renovateRepository() exits after 6 loops 1`] = `"loops>5"`; +exports[`workers/repository renovateRepository() handles baseBranches 1`] = `"onboarded"`; + exports[`workers/repository renovateRepository() writes 1`] = `"onboarded"`; diff --git a/test/workers/repository/index.spec.js b/test/workers/repository/index.spec.js index 2c39859c8c292a94a9854b4f87c6e383173cd899..2dd664e9bdb7ea601162c0cb009d8fa4e819b0e0 100644 --- a/test/workers/repository/index.spec.js +++ b/test/workers/repository/index.spec.js @@ -1,3 +1,4 @@ +const { initRepo } = require('../../../lib/workers/repository/init'); const { determineUpdates } = require('../../../lib/workers/repository/updates'); const { writeUpdates } = require('../../../lib/workers/repository/write'); const { @@ -26,17 +27,36 @@ describe('workers/repository', () => { expect(res).toMatchSnapshot(); }); it('writes', async () => { - determineUpdates.mockReturnValue({ repoIsOnboarded: true }); + initRepo.mockReturnValue({}); + determineUpdates.mockReturnValue({ + repoIsOnboarded: true, + branches: [{ type: 'minor' }, { type: 'pin' }], + }); writeUpdates.mockReturnValueOnce('automerged'); writeUpdates.mockReturnValueOnce('onboarded'); const res = await renovateRepository(config, 'some-token'); expect(res).toMatchSnapshot(); }); it('ensures onboarding pr', async () => { - determineUpdates.mockReturnValue({ repoIsOnboarded: false }); + initRepo.mockReturnValue({}); + determineUpdates.mockReturnValue({ + repoIsOnboarded: false, + branches: [], + }); ensureOnboardingPr.mockReturnValue('onboarding'); const res = await renovateRepository(config, 'some-token'); expect(res).toMatchSnapshot(); }); + it('handles baseBranches', async () => { + initRepo.mockReturnValue({ baseBranches: ['master', 'next'] }); + determineUpdates.mockReturnValue({ + repoIsOnboarded: true, + branches: [], + }); + writeUpdates.mockReturnValueOnce('automerged'); + writeUpdates.mockReturnValueOnce('onboarded'); + const res = await renovateRepository(config, 'some-token'); + expect(res).toMatchSnapshot(); + }); }); }); diff --git a/website/docs/_posts/2017-10-05-configuration-options.md b/website/docs/_posts/2017-10-05-configuration-options.md index cdf87ce07fb4e609e36bfe8a9895c48cfc61a712..19f2364ea8a0a0f7b6c9ba761f3542896cbbcb4e 100644 --- a/website/docs/_posts/2017-10-05-configuration-options.md +++ b/website/docs/_posts/2017-10-05-configuration-options.md @@ -71,20 +71,22 @@ Merge commits will employ the standard GitHub "merge commit" API, just like when Branch push employs GitHub's low-level `git` API to push the Renovate upgrade directly to the head of the base branch (e.g. `master`) to maintain a "clean" history. The downside of this approach is that it implicitly enables the `rebaseStalePrs` setting because otherwise we would risk pushing a bad commit to master. i.e. Renovate won't push the commit to base branch unless the branch is completely up-to-date with `master` and has passed tests, which means that if the default branch is getting updated regularly then it might take several rebases from Renovate until it has a branch commit that is safe to push to `master`. -## baseBranch +## baseBranches -A custom base branch to target for pull requests. +An array of one or more custom base branches to be renovated. Default behaviour is to renovate the default repository branch. -| name | value | -| ------- | ------ | -| type | string | -| default | `''` | +| name | value | +| ------- | ----- | +| type | list | +| default | [] | If left default (empty) then the default branch of the repository is used. For most projects, this should be left as default. An example use case for using this setting is a project who uses the default `master` branch for releases and a separate branch `next` for preparing for the next release. In that case, the project may prefer for Pull Requests from Renovate to be opened against the `next` branch instead of `master`. -You also may add this setting into the `renovate.json` file as part of the "Configure Renovate" onboarding PR. If so then Renovate will reflect this setting in its description and use package file contents from the custom base branch instead of default. +If instead the project needs _both_ `master` and `next` to be renovated, then both should be put into the `baseBranches` array. + +It's possible to add this setting into the `renovate.json` file as part of the "Configure Renovate" onboarding PR. If so then Renovate will reflect this setting in its description and use package file contents from the custom base branch instead of default. ## bazel