diff --git a/lib/workers/repository/index.js b/lib/workers/repository/index.js index 59410ac512b3e4b809b31992df7ea46ffd285bd7..ca886116c59fc45bb59adf2a84b295e95865ff2e 100644 --- a/lib/workers/repository/index.js +++ b/lib/workers/repository/index.js @@ -24,130 +24,140 @@ function pinDependenciesFirst(a, b) { return a.branchName > b.branchName; } -async function renovateRepository(repoConfig, token) { +async function renovateRepositoryInner(repoConfig, token, count = 1) { let config = { ...repoConfig }; const { logger } = config; - config.tmpDir = tmp.dirSync({ unsafeCleanup: true }); - config.errors = []; - config.warnings = []; - logger.trace({ config }, 'renovateRepository'); - try { - let branchList; - let baseBranchUpdated; - let loopCount = 1; - do { - logger.debug(`renovateRepository loop ${loopCount}`); - baseBranchUpdated = false; - config = await apis.initApis(config, token); - config = await apis.mergeRenovateJson(config); - if (config.enabled === false) { - logger.debug('repository is disabled'); - await cleanup.pruneStaleBranches(config, []); - return; - } - if (config.isFork && !config.renovateJsonPresent) { - logger.debug('repository is a fork and not manually configured'); - return; - } - if (config.baseBranch) { - // Renovate should read content and target PRs here - if (await config.api.branchExists(config.baseBranch)) { - config.api.setBaseBranch(config.baseBranch); - } else { - // Warn and ignore setting (use default branch) - const message = `The configured baseBranch "${config.baseBranch}" is not present. Ignoring`; - config.errors.push({ - depName: 'baseBranch', - message, - }); - logger.warn(message); - } - } - config = await onboarding.getOnboardingStatus(config); - // Detect package files in default branch if not manually provisioned - if (config.packageFiles.length === 0) { - logger.debug('Detecting package files'); - config = await apis.detectPackageFiles(config); - // If we can't detect any package.json then return - if (config.packageFiles.length === 0) { - logger.info('Cannot detect package files'); - // istanbul ignore if - if (config.repoIsOnboarded === false) { - logger.warn('Need to delete onboarding PR'); - const pr = await config.api.getBranchPr(config.onboardingBranch); - if (pr) { - logger.info('Found onboarding PR'); - await config.api.updatePr( - pr.number, - 'Configure Renovate - canceled', - 'This PR was created in error and is now being deleted automatically. Sorry for the inconvenience.' - ); - await config.api.deleteBranch(config.onboardingBranch); - throw new Error('no package files'); - } - } - return; - } - logger.debug( - `Detected ${config.packageFiles - .length} package files: ${config.packageFiles}` - ); - } - logger.debug('Resolving package files and content'); - config = await apis.resolvePackageFiles(config); - config = await apis.checkMonorepos(config); - logger.trace({ config }, 'post-packageFiles config'); - // TODO: why is this fix needed?! - config.logger = logger; - config = decryptConfig(config); - logger.trace({ config }, 'post-decrypt config'); - const allUpgrades = await upgrades.determineRepoUpgrades(config); - const res = await upgrades.branchifyUpgrades(allUpgrades, logger); - config.errors = config.errors.concat(res.errors); - config.warnings = config.warnings.concat(res.warnings); - const branchUpgrades = res.upgrades; - logger.debug(`Updating ${branchUpgrades.length} branch(es)`); - logger.trace({ config: branchUpgrades }, 'branchUpgrades'); - if (config.repoIsOnboarded) { - logger.info(`Processing ${branchUpgrades.length} branch(es)`); - // eslint-disable-next-line no-loop-function - branchUpgrades.sort(pinDependenciesFirst); - const branchStartTime = process.hrtime(); - for (const branchUpgrade of branchUpgrades) { - const branchResult = await branchWorker.processBranch( - branchUpgrade, - config.errors, - config.warnings + // istanbul ignore if + if (count > 5) { + // This is an arbitrary number added in to cut short any unintended infinite recursion + throw new Error('Existing renovateRepositoryInner after 5 loops'); + } + logger.info(`renovateRepository loop ${count}`); + let branchList = []; + config = await apis.initApis(config, token); + config = await apis.mergeRenovateJson(config); + if (config.enabled === false) { + logger.debug('repository is disabled'); + await cleanup.pruneStaleBranches(config, []); + return null; + } + if (config.isFork && !config.renovateJsonPresent) { + logger.debug('repository is a fork and not manually configured'); + return null; + } + if (config.baseBranch) { + // Renovate should read content and target PRs here + if (await config.api.branchExists(config.baseBranch)) { + config.api.setBaseBranch(config.baseBranch); + } else { + // Warn and ignore setting (use default branch) + const message = `The configured baseBranch "${config.baseBranch}" is not present. Ignoring`; + config.errors.push({ + depName: 'baseBranch', + message, + }); + logger.warn(message); + } + } + config = await onboarding.getOnboardingStatus(config); + // Detect package files in default branch if not manually provisioned + if (config.packageFiles.length === 0) { + logger.debug('Detecting package files'); + config = await apis.detectPackageFiles(config); + // If we can't detect any package.json then return + if (config.packageFiles.length === 0) { + logger.info('Cannot detect package files'); + // istanbul ignore if + if (config.repoIsOnboarded === false) { + logger.warn('Need to delete onboarding PR'); + const pr = await config.api.getBranchPr(config.onboardingBranch); + if (pr) { + logger.info('Found onboarding PR'); + await config.api.updatePr( + pr.number, + 'Configure Renovate - canceled', + 'This PR was created in error and is now being deleted automatically. Sorry for the inconvenience.' ); - if (branchResult === 'automerged') { - // Stop procesing other branches because base branch has been changed by an automerge - logger.info('Restarting repo renovation after automerge'); - baseBranchUpdated = true; - break; - } else if (branchResult === 'lockFileError') { - logger.info('Lock file error - stopping branch updates'); - break; - } else if (branchUpgrade.type === 'pin') { - logger.info( - 'Stopping branch processing until Pin Dependencies is merged' - ); - break; - } + await config.api.deleteBranch(config.onboardingBranch); + throw new Error('no package files'); } + } + return null; + } + logger.info( + { + packageFiles: config.packageFiles, + count: config.packageFiles.length, + }, + `Detected package files` + ); + } + logger.debug('Resolving package files and content'); + config = await apis.resolvePackageFiles(config); + config = await apis.checkMonorepos(config); + logger.trace({ config }, 'post-packageFiles config'); + // TODO: why is this fix needed?! + config.logger = logger; + config = decryptConfig(config); + logger.trace({ config }, 'post-decrypt config'); + const allUpgrades = await upgrades.determineRepoUpgrades(config); + const res = await upgrades.branchifyUpgrades(allUpgrades, logger); + config.errors = config.errors.concat(res.errors); + config.warnings = config.warnings.concat(res.warnings); + const branchUpgrades = res.upgrades; + logger.debug(`Updating ${branchUpgrades.length} branch(es)`); + logger.trace({ config: branchUpgrades }, 'branchUpgrades'); + if (config.repoIsOnboarded) { + logger.info(`Processing ${branchUpgrades.length} branch(es)`); + // eslint-disable-next-line no-loop-function + branchUpgrades.sort(pinDependenciesFirst); + const branchStartTime = process.hrtime(); + branchList = branchUpgrades.map(upgrade => upgrade.branchName); + for (const branchUpgrade of branchUpgrades) { + const branchResult = await branchWorker.processBranch( + branchUpgrade, + config.errors, + config.warnings + ); + if (branchResult === 'automerged') { + // Stop procesing other branches because base branch has been changed by an automerge + logger.info('Restarting repo renovation after automerge'); + return renovateRepositoryInner(repoConfig, token, count + 1); + } else if (branchResult === 'lockFileError') { + logger.info('Lock file error - stopping branch updates'); + return branchList; + } else if (branchUpgrade.type === 'pin') { logger.info( - { seconds: convertHrTime(process.hrtime(branchStartTime)).seconds }, - 'Finished updating branches' + 'Stopping branch processing until Pin Dependencies is merged' ); - branchList = branchUpgrades.map(upgrade => upgrade.branchName); - logger.debug(`branchList=${branchList}`); - } else { - await onboarding.ensurePr(config, branchUpgrades); - logger.info('"Configure Renovate" PR needs to be closed first'); - branchList = [`${config.branchPrefix}configure`]; + return branchList; } - loopCount += 1; - } while (baseBranchUpdated); - await cleanup.pruneStaleBranches(config, branchList); + } + logger.info( + { seconds: convertHrTime(process.hrtime(branchStartTime)).seconds }, + 'Finished updating branches' + ); + } else { + await onboarding.ensurePr(config, branchUpgrades); + logger.info('"Configure Renovate" PR needs to be closed first'); + branchList = [`${config.branchPrefix}configure`]; + } + logger.debug(`branchList=${branchList}`); + return branchList; +} + +async function renovateRepository(repoConfig, token) { + const config = { ...repoConfig }; + const { logger } = config; + logger.trace({ config }, 'renovateRepository'); + config.tmpDir = tmp.dirSync({ unsafeCleanup: true }); + config.errors = []; + config.warnings = []; + try { + const branchList = await renovateRepositoryInner(config, token); + if (branchList) { + await cleanup.pruneStaleBranches(config, branchList); + } } catch (err) { // Swallow this error so that other repositories can be processed if (err.message === 'uninitiated') {