// TODO #22198 import { GlobalConfig } from '../../../config/global.ts'; import { mergeChildConfig } from '../../../config/index.ts'; import { migrateAndValidate } from '../../../config/migrate-validate.ts'; import { resolveConfigPresets } from '../../../config/presets/index.ts'; import type { RenovateConfig } from '../../../config/types.ts'; import { CONFIG_VALIDATION } from '../../../constants/error-messages.ts'; import { instrument } from '../../../instrumentation/index.ts'; import { ATTR_RENOVATE_SPLIT } from '../../../instrumentation/types.ts'; import { addMeta, logger, removeMeta } from '../../../logger/index.ts'; import type { PackageFile } from '../../../modules/manager/types.ts'; import { platform } from '../../../modules/platform/index.ts'; import { scm } from '../../../modules/platform/scm.ts'; import { getCache } from '../../../util/cache/repository/index.ts'; import { clone } from '../../../util/clone.ts'; import { getBranchList } from '../../../util/git/index.ts'; import { addSplit } from '../../../util/split.ts'; import { getRegexPredicate } from '../../../util/string-match.ts'; import type { BranchConfig } from '../../types.ts'; import { readDashboardBody } from '../dependency-dashboard.ts'; import type { ExtractResult } from './extract-update.ts'; import { extract, lookup, update } from './extract-update.ts'; import type { WriteUpdateResult } from './write.ts'; export async function getBaseBranchConfig( baseBranch: string, config: RenovateConfig, ): Promise { logger.debug(`baseBranch: ${baseBranch}`); let baseBranchConfig: RenovateConfig = clone(config); if ( config.useBaseBranchConfig === 'merge' && baseBranch !== config.defaultBranch ) { logger.debug( { baseBranch }, `Merging config from base branch because useBaseBranchConfig=merge`, ); // Retrieve config file name autodetected for this repo const cache = getCache(); // TODO: types (#22198) const configFileName = cache.configFileName!; try { baseBranchConfig = await platform.getJsonFile( configFileName, config.repository, baseBranch, ); logger.debug({ config: baseBranchConfig }, 'Base branch config raw'); } catch { logger.error( { configFileName, baseBranch }, `Error fetching config file from base branch - possible config name mismatch between branches?`, ); const error = new Error(CONFIG_VALIDATION); error.validationSource = 'config'; error.validationError = 'Error fetching config file'; error.validationMessage = `Error fetching config file \`${configFileName}\` from branch \`${baseBranch}\``; throw error; } baseBranchConfig = await migrateAndValidate(config, baseBranchConfig); if (baseBranchConfig.errors?.length) { const error = new Error(CONFIG_VALIDATION); error.validationSource = configFileName; error.validationError = `The renovate configuration file of branch ${baseBranch} contains some invalid settings`; error.validationMessage = baseBranchConfig.errors .map((e) => e.message) .join(', '); throw error; } ({ config: baseBranchConfig } = await resolveConfigPresets( baseBranchConfig, config, )); baseBranchConfig = mergeChildConfig(config, baseBranchConfig); // istanbul ignore if if (config.printConfig) { logger.info( { config: baseBranchConfig }, 'Base branch config after merge', ); } // baseBranches value should be based off the default branch baseBranchConfig.baseBranchPatterns = config.baseBranchPatterns; baseBranchConfig.baseBranches = config.baseBranches; } if (isMultiBaseBranch(config)) { baseBranchConfig.branchPrefix += `${baseBranch}-`; baseBranchConfig.hasBaseBranches = true; } baseBranchConfig = mergeChildConfig(baseBranchConfig, { baseBranch }); return baseBranchConfig; } function unfoldBaseBranches( defaultBranch: string, baseBranchPatterns: string[], ): string[] { const unfoldedList: string[] = []; const allBranches = getBranchList(); for (const baseBranchPattern of baseBranchPatterns) { const isAllowedPred = getRegexPredicate(baseBranchPattern); if (isAllowedPred) { const matchingBranches = allBranches.filter(isAllowedPred); logger.debug( `baseBranchePatterns regex "${baseBranchPattern}" matches [${matchingBranches.join()}]`, ); unfoldedList.push(...matchingBranches); } else if (baseBranchPattern === '$default') { logger.debug(`baseBranchPatterns "$default" matches "${defaultBranch}"`); unfoldedList.push(defaultBranch); } else { unfoldedList.push(baseBranchPattern); } } return [...new Set(unfoldedList)]; } export function isMultiBaseBranch(config: RenovateConfig): boolean { if (!config.baseBranchPatterns?.length) { return false; } return ( config.baseBranchPatterns.length > 1 || config.baseBranchPatterns[0].startsWith('/') ); } export async function extractDependencies( config: RenovateConfig, overwriteCache = true, ): Promise { await readDashboardBody(config); let res: ExtractResult = { branches: [], branchList: [], packageFiles: {}, }; if ( GlobalConfig.get('platform') !== 'local' && config.baseBranchPatterns?.length ) { config.baseBranches = unfoldBaseBranches( config.defaultBranch!, config.baseBranchPatterns, ); logger.debug({ baseBranches: config.baseBranches }, 'baseBranches'); const extracted: Record> = {}; await instrument( 'extract', async () => { for (const baseBranch of config.baseBranches!) { addMeta({ baseBranch }); if (scm.syncForkWithUpstream) { await scm.syncForkWithUpstream(baseBranch); } if (await scm.branchExists(baseBranch)) { const baseBranchConfig = await getBaseBranchConfig( baseBranch, config, ); extracted[baseBranch] = await extract( baseBranchConfig, overwriteCache, ); } else { logger.warn( { baseBranch }, 'Base branch does not exist - skipping', ); } } }, { attributes: { [ATTR_RENOVATE_SPLIT]: 'extract', }, }, ); addSplit('extract'); await instrument( 'lookup', async () => { for (const baseBranch of config.baseBranches!) { if (await scm.branchExists(baseBranch)) { addMeta({ baseBranch }); const baseBranchConfig = await getBaseBranchConfig( baseBranch, config, ); const packageFiles = extracted[baseBranch]; const baseBranchRes = await lookup(baseBranchConfig, packageFiles); res.branches = res.branches.concat(baseBranchRes?.branches); res.branchList = res.branchList.concat(baseBranchRes?.branchList); if (!res.packageFiles || !Object.keys(res.packageFiles).length) { // Use the first branch res.packageFiles = baseBranchRes?.packageFiles; } } } }, { attributes: { [ATTR_RENOVATE_SPLIT]: 'lookup', }, }, ); removeMeta(['baseBranch']); } else { logger.debug('No baseBranches'); const packageFiles = await instrument( 'extract', async () => await extract(config, overwriteCache), { attributes: { [ATTR_RENOVATE_SPLIT]: 'extract', }, }, ); addSplit('extract'); if (GlobalConfig.get('dryRun') === 'extract') { res.packageFiles = packageFiles; logger.info({ packageFiles }, 'Extracted dependencies'); return res; } res = await instrument( 'lookup', async () => await lookup(config, packageFiles), { attributes: { [ATTR_RENOVATE_SPLIT]: 'lookup', }, }, ); } addSplit('lookup'); return res; } export function updateRepo( config: RenovateConfig, branches: BranchConfig[], ): Promise { logger.debug('processRepo()'); return update(config, branches); }