Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(config/migration): migrate config with a PR #15122

Merged
merged 67 commits into from
Jun 18, 2022
Merged
Show file tree
Hide file tree
Changes from 9 commits
Commits
Show all changes
67 commits
Select commit Hold shift + click to select a range
a9f4663
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 14, 2022
534b461
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Apr 14, 2022
b29fb4f
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 17, 2022
a2265e7
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 17, 2022
9113358
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 17, 2022
15d45ad
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Apr 17, 2022
f395e0f
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 17, 2022
6bd6c6c
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 18, 2022
bb692ca
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 18, 2022
44d7aff
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 18, 2022
814166c
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 18, 2022
c3c7fca
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 19, 2022
8fe5ab3
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 19, 2022
4a9af8b
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 19, 2022
b727bb8
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
d3762a4
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
741abb0
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
5d6e3fd
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
15dbe78
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
bac194e
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
3f44f77
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 20, 2022
b0d4edf
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Apr 24, 2022
ed0877c
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 24, 2022
7aa4d1d
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 24, 2022
7f17942
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 24, 2022
c509a03
Merge branch 'main' into feat-config-migrate
viceice Apr 25, 2022
8d68f0b
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Apr 26, 2022
2c5773b
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Apr 26, 2022
34c570b
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
9693765
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
0f0ec56
Merge branch 'feat-config-migrate' of https://github.com/Gabriel-Ladz…
Gabriel-Ladzaretti May 2, 2022
4d2c0d5
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 2, 2022
f03924b
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
a2a2d37
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
847e19c
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
c311a0a
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 2, 2022
73ff6aa
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 2, 2022
8b02ac0
Update docs/usage/configuration-options.md
Gabriel-Ladzaretti May 3, 2022
a92d157
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 4, 2022
d251c88
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 8, 2022
57470e1
Merge branch 'feat-config-migrate' of https://github.com/Gabriel-Ladz…
Gabriel-Ladzaretti May 8, 2022
a8a6036
Merge branch 'main' into feat-config-migrate
rarkins May 11, 2022
faba41e
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 11, 2022
56deb3b
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 11, 2022
3d8c0d1
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 11, 2022
1985465
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 11, 2022
6b80990
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 12, 2022
4335d29
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 16, 2022
a60701e
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 16, 2022
9a17882
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 16, 2022
f3932d8
Apply suggestions from code review
Gabriel-Ladzaretti May 17, 2022
d1e164e
Apply suggestions from code review
Gabriel-Ladzaretti May 17, 2022
8800917
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 17, 2022
f204391
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 18, 2022
fd05258
Update docs/usage/configuration-options.md
Gabriel-Ladzaretti May 18, 2022
b1f4174
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 22, 2022
d2bd864
Merge branch 'feat-config-migrate' of https://github.com/Gabriel-Ladz…
Gabriel-Ladzaretti May 22, 2022
843dfee
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 25, 2022
445c43e
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti May 31, 2022
4f68a05
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti May 31, 2022
593df89
feat(config/migration): migrate config with a PR
Gabriel-Ladzaretti Jun 12, 2022
29b9f05
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Jun 12, 2022
cf52885
Update lib/workers/repository/config-migration/pr/index.ts
viceice Jun 13, 2022
adef597
Merge branch 'main' into feat-config-migrate
rarkins Jun 13, 2022
81c1437
Merge branch 'main' into feat-config-migrate
Gabriel-Ladzaretti Jun 16, 2022
d39cb33
Merge branch 'main' into feat-config-migrate
rarkins Jun 18, 2022
228b176
Merge branch 'main' into feat-config-migrate
rarkins Jun 18, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion lib/config/options/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,20 @@ const options: RenovateOptions[] = [
'Change this value in order to override the default onboarding PR title.',
type: 'string',
default: 'Configure Renovate',
globalOnly: true,
cli: false,
},
rarkins marked this conversation as resolved.
Show resolved Hide resolved
{
name: 'configMigration',
description: 'Enable this to get Config migration PRs when needed.',
rarkins marked this conversation as resolved.
Show resolved Hide resolved
type: 'boolean',
default: false,
},
{
name: 'configMigrationBranch',
rarkins marked this conversation as resolved.
Show resolved Hide resolved
description:
'Change this value in order to override the default config migration branch name.',
type: 'string',
default: '{{{branchPrefix}}}migrate-config',
cli: false,
},
{
Expand Down
7 changes: 7 additions & 0 deletions lib/config/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ export interface LegacyAdminConfig {
platform?: string;
requireConfig?: boolean;
}

export type ExecutionMode = 'branch' | 'update';

export type PostUpgradeTasks = {
Expand Down Expand Up @@ -171,6 +172,7 @@ export interface RenovateConfig
RenovateSharedConfig,
UpdateConfig<PackageRule>,
AssigneesAndReviewersConfig,
ConfigMigration,
Record<string, unknown> {
depName?: string;
baseBranches?: string[];
Expand Down Expand Up @@ -409,6 +411,11 @@ export interface PackageRuleInputConfig extends Record<string, unknown> {
packageRules?: (PackageRule & PackageRuleInputConfig)[];
}

export interface ConfigMigration {
configMigration?: boolean;
configMigrationBranch?: string;
}

export interface MigratedConfig {
isMigrated: boolean;
migratedConfig: RenovateConfig;
Expand Down
2 changes: 2 additions & 0 deletions lib/util/template/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ const prBodyFields = [
'table',
'notes',
'changelogs',
'errors',
'warnings',
'configDescription',
'controls',
'footer',
Expand Down
40 changes: 40 additions & 0 deletions lib/workers/repository/config-migration/branch/commit-message.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import type { RenovateConfig } from '../../../../config/types';
import * as template from '../../../../util/template';

export class ConfigMigrationCommitMessageFactory {
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
private readonly config: RenovateConfig;

private readonly configFile: string;

constructor(config: RenovateConfig, configFile: string) {
this.config = config;
this.configFile = configFile;
}

create(): string {
const { commitMessagePrefix, commitMessage, semanticCommitType } =
this.config;
let prefix: string | null = null;

if (commitMessagePrefix) {
prefix = (commitMessagePrefix ?? '').trim();
} else if (this.areSemanticCommitsEnabled()) {
prefix = (semanticCommitType ?? '').trim() + '(config)';
}

return template.compile(
commitMessage ?? `Migrate config ${this.configFile}`,
{
...this.config,
commitMessagePrefix: prefix ?? '',
commitMessageAction: 'Migrate',
commitMessageTopic: `config ${this.configFile}`,
commitMessageExtra: '',
}
);
}

private areSemanticCommitsEnabled(): boolean {
return this.config.semanticCommits === 'enabled';
}
}
44 changes: 44 additions & 0 deletions lib/workers/repository/config-migration/branch/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { GlobalConfig } from '../../../../config/global';
import type { RenovateConfig } from '../../../../config/types';
import { logger } from '../../../../logger';
import { commitAndPush } from '../../../../modules/platform/commit';
import { setGitAuthor } from '../../../../util/git';
import { ConfigMigrationCommitMessageFactory } from './commit-message';
import type { MigratedData } from './migrated-data';

export function createConfigMigrationBranch(
config: Partial<RenovateConfig>,
migratedConfigData: MigratedData
): Promise<string | null> {
logger.debug('createConfigMigrationBranch()');
const contents = migratedConfigData.getConfigContent();
const configFileName = migratedConfigData.getConfigFileName();
logger.debug('Creating config migration branch');

const commitMessageFactory = new ConfigMigrationCommitMessageFactory(
config,
configFileName
);

const commitMessage = commitMessageFactory.create();

// istanbul ignore if
if (GlobalConfig.get('dryRun')) {
logger.info('DRY-RUN: Would commit files to config migration branch');
return null;
}

setGitAuthor(config.gitAuthor);
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
return commitAndPush({
branchName: config.configMigrationBranch,
files: [
{
type: 'addition',
path: configFileName,
contents,
},
],
message: commitMessage.toString(),
platformCommit: !!config.platformCommit,
});
}
62 changes: 62 additions & 0 deletions lib/workers/repository/config-migration/branch/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { GlobalConfig } from '../../../../config/global';
import type { RenovateConfig } from '../../../../config/types';
import { logger } from '../../../../logger';
import { platform } from '../../../../modules/platform';
import { checkoutBranch } from '../../../../util/git';
import * as template from '../../../../util/template';
import { createConfigMigrationBranch } from './create';
import { MigratedDataFactory } from './migrated-data';
import { rebaseMigrationBranch } from './rebase';

export async function checkConfigMigrationBranch(
config: RenovateConfig
): Promise<RenovateConfig> {
logger.debug('checkConfigMigrationBranch()');
logger.trace({ config });
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
const migratedConfigData = await MigratedDataFactory.getAsync(config);
if (!migratedConfigData) {
logger.debug('checkConfigMigrationBranch() Error fetching migrated data');
return config;
}
config.configMigrationBranch = template.compile(
config.configMigrationBranch,
config
);
const configMigrationBranch = config.configMigrationBranch;
if (await migrationPrExists(config)) {
logger.debug('Config Migration PR already exists');
const commit = await rebaseMigrationBranch(config, migratedConfigData);
if (commit) {
logger.info({ branch: configMigrationBranch, commit }, 'Branch updated');
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
}
// istanbul ignore if
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
if (platform.refreshPr) {
const configMigrationPr = await platform.getBranchPr(
configMigrationBranch
);
await platform.refreshPr(configMigrationPr.number);
}
} else {
logger.debug('Config Migration PR does not exist');
logger.debug('Need to create migration PR');
const commit = await createConfigMigrationBranch(
config,
migratedConfigData
);
// istanbul ignore if
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
if (commit) {
logger.info({ branch: configMigrationBranch, commit }, 'Branch created');
}
}
if (!GlobalConfig.get('dryRun')) {
viceice marked this conversation as resolved.
Show resolved Hide resolved
await checkoutBranch(configMigrationBranch);
}
MigratedDataFactory.reset();
const branchList = [configMigrationBranch];
return { ...config, configMigrationBranch, branchList };
}

export const migrationPrExists = async (
config: RenovateConfig
): Promise<boolean> =>
!!(await platform.getBranchPr(config.configMigrationBranch));
89 changes: 89 additions & 0 deletions lib/workers/repository/config-migration/branch/migrated-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import detectIndent from 'detect-indent';
import { migrateAndValidate } from '../../../../config/migrate-validate';
import type { RenovateConfig } from '../../../../config/types';
import { logger } from '../../../../logger';
import { readLocalFile } from '../../../../util/fs';
import { detectRepoFileConfig } from '../../init/merge';

export class MigratedData {
constructor(
private readonly _content: string,
private readonly _fileName: string,
private readonly _indent: string
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
) {}

getConfigContent(): string {
return this._content;
}

getConfigFileName(): string {
return this._fileName;
}

getIndent(): string {
return this._indent;
}
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
}

export class MigratedDataFactory {
// singleton
private static _data: MigratedData;
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved

public static async getAsync(config_: RenovateConfig): Promise<MigratedData> {
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
if (this._data) {
return this._data;
}
const d = await this.build(config_);
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
this._data = new MigratedData(d?.content, d?.fileName, d?.indent);
return this._data;
}

public static reset(): void {
this._data = null;
}

// istanbul ignore next: unused constructor for a static factory class
private constructor() {
return;
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
}

private static async build(
config_: RenovateConfig
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
): Promise<IMigratedData | null> {
let res: IMigratedData;
try {
const config = { ...config_ };
rarkins marked this conversation as resolved.
Show resolved Hide resolved
const rc = await detectRepoFileConfig();
const configFileParsed = rc?.configFileParsed || {};

// get migrated config
const migrated = await migrateAndValidate(config, configFileParsed);
delete migrated.errors;
delete migrated.warnings;

const raw = await readLocalFile(rc.configFileName, 'utf8');

// indent defaults to 2 spaces
const indent = detectIndent(raw).indent ?? ' ';
const fileName = rc.configFileName;
let content = JSON.stringify(migrated, undefined, indent);
if (!content.endsWith('\n')) {
content += '\n';
}

res = { content, fileName, indent };
} catch (err) {
logger.debug(
err,
'MigratedDataFactory.getAsync() Error initializing renovate MigratedData'
);
}
return res;
}
}

interface IMigratedData {
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
content: string;
fileName: string;
indent: string;
}
62 changes: 62 additions & 0 deletions lib/workers/repository/config-migration/branch/rebase.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import { GlobalConfig } from '../../../../config/global';
import type { RenovateConfig } from '../../../../config/types';
import { logger } from '../../../../logger';
import { commitAndPush } from '../../../../modules/platform/commit';
import {
getFile,
isBranchModified,
isBranchStale,
setGitAuthor,
} from '../../../../util/git';
import { ConfigMigrationCommitMessageFactory } from './commit-message';
import type { MigratedData } from './migrated-data';

export async function rebaseMigrationBranch(
config: RenovateConfig,
migratedConfigData: MigratedData
): Promise<string | null> {
logger.debug('Checking if onboarding branch needs rebasing');
if (await isBranchModified(config.configMigrationBranch)) {
logger.debug('Onboarding branch has been edited and cannot be rebased');
return null;
}
const configFileName = migratedConfigData.getConfigFileName();
const contents = migratedConfigData.getConfigContent();
const existingContents = await getFile(
configFileName,
config.configMigrationBranch
);
if (
contents === existingContents &&
!(await isBranchStale(config.configMigrationBranch))
) {
logger.debug('Migration branch is up to date');
return null;
}
logger.debug('Rebasing migration branch');

const commitMessageFactory = new ConfigMigrationCommitMessageFactory(
config,
configFileName
);
const commitMessage = commitMessageFactory.create();

if (GlobalConfig.get('dryRun')) {
logger.info('DRY-RUN: Would rebase files in migration branch');
return null;
}
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved

setGitAuthor(config.gitAuthor);
rarkins marked this conversation as resolved.
Show resolved Hide resolved
return commitAndPush({
branchName: config.configMigrationBranch,
files: [
{
type: 'addition',
path: configFileName,
contents,
},
],
message: commitMessage.toString(),
platformCommit: !!config.platformCommit,
});
}
28 changes: 28 additions & 0 deletions lib/workers/repository/config-migration/pr/errors-warnings.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import type { RenovateConfig } from '../../../../config/types';

export function getWarnings(config: RenovateConfig): string {
if (!config?.warnings?.length) {
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
return '';
}
let warningText = `\n# Warnings (${config?.warnings.length})\n\n`;
warningText += `Please correct - or verify that you can safely ignore - these warnings before you merge this PR.\n\n`;
config?.warnings.forEach((w) => {
warningText += `- \`${w.topic}\`: ${w.message}\n`;
});
warningText += '\n---\n';
return warningText;
}

export function getErrors(config: RenovateConfig): string {
let errorText = '';
if (!config?.errors?.length) {
Gabriel-Ladzaretti marked this conversation as resolved.
Show resolved Hide resolved
return '';
}
errorText = `\n# Errors (${config?.errors.length})\n\n`;
errorText += `Renovate has found errors that you should fix (in this branch) before finishing this PR.\n\n`;
config?.errors.forEach((e) => {
errorText += `- \`${e.topic}\`: ${e.message}\n`;
});
errorText += '\n---\n';
return errorText;
}
Loading