Skip to content

Commit

Permalink
feat(core/onboarding): support manual rebase/retry (#17633)
Browse files Browse the repository at this point in the history
Co-authored-by: Michael Kriese <michael.kriese@visualon.de>
  • Loading branch information
Gabriel-Ladzaretti and viceice committed Jan 11, 2023
1 parent 218ac84 commit de289bb
Show file tree
Hide file tree
Showing 13 changed files with 675 additions and 148 deletions.
2 changes: 2 additions & 0 deletions docs/usage/self-hosted-configuration.md
Expand Up @@ -519,6 +519,8 @@ Otherwise, Renovate skips onboarding a repository if it finds no dependencies in

Similarly to `onboardingBranch`, if you have an existing Renovate installation and you change `onboardingPrTitle` then it's possible that you'll get onboarding PRs for repositories that had previously closed the onboarding PR unmerged.

## onboardingRebaseCheckbox

## optimizeForDisabled

When this option is `true`, Renovate will do the following during repository initialization:
Expand Down
11 changes: 11 additions & 0 deletions lib/config/options/index.ts
Expand Up @@ -399,6 +399,17 @@ const options: RenovateOptions[] = [
globalOnly: true,
mergeable: true,
},
{
name: 'onboardingRebaseCheckbox',
description:
'Set to enable rebase/retry markdown checkbox for onboarding PRs.',
type: 'boolean',
default: false,
supportedPlatforms: ['github', 'gitlab', 'gitea'],
globalOnly: true,
experimental: true,
experimentalIssues: [17633],
},
{
name: 'includeForks',
description:
Expand Down
1 change: 1 addition & 0 deletions lib/config/types.ts
Expand Up @@ -142,6 +142,7 @@ export interface LegacyAdminConfig {
onboardingBranch?: string;
onboardingCommitMessage?: string;
onboardingNoDeps?: boolean;
onboardingRebaseCheckbox?: boolean;
onboardingPrTitle?: string;
onboardingConfig?: RenovateSharedConfig;
onboardingConfigFileName?: string;
Expand Down
5 changes: 5 additions & 0 deletions lib/util/hasha.ts
@@ -0,0 +1,5 @@
import hasha from 'hasha';

export function toSha256(input: string): string {
return hasha(input, { algorithm: 'sha256' });
}
26 changes: 21 additions & 5 deletions lib/workers/repository/index.ts
Expand Up @@ -19,8 +19,10 @@ import { ensureDependencyDashboard } from './dependency-dashboard';
import handleError from './error';
import { finaliseRepo } from './finalise';
import { initRepo } from './init';
import { OnboardingState } from './onboarding/common';
import { ensureOnboardingPr } from './onboarding/pr';
import { extractDependencies, updateRepo } from './process';
import type { ExtractResult } from './process/extract-update';
import { ProcessResult, processResult } from './result';
import { printRequestStats } from './stats';

Expand All @@ -46,10 +48,13 @@ export async function renovateRepository(
logger.debug('Using localDir: ' + localDir);
config = await initRepo(config);
addSplit('init');
const { branches, branchList, packageFiles } = await instrument(
'extract',
() => extractDependencies(config)
);
const performExtract =
config.repoIsOnboarded! ||
!config.onboardingRebaseCheckbox ||
OnboardingState.prUpdateRequested;
const { branches, branchList, packageFiles } = performExtract
? await instrument('extract', () => extractDependencies(config))
: emptyExtract(config);
if (config.semanticCommits === 'auto') {
config.semanticCommits = await detectSemanticCommits();
}
Expand All @@ -67,7 +72,9 @@ export async function renovateRepository(
);
setMeta({ repository: config.repository });
addSplit('update');
await setBranchCache(branches);
if (performExtract) {
await setBranchCache(branches); // update branch cache if performed extraction
}
if (res === 'automerged') {
if (canRetry) {
logger.info('Renovating repository again after automerge result');
Expand Down Expand Up @@ -109,3 +116,12 @@ export async function renovateRepository(
logger.info({ cloned, durationMs: splits.total }, 'Repository finished');
return repoResult;
}

// istanbul ignore next: renovateRepository is ignored
function emptyExtract(config: RenovateConfig): ExtractResult {
return {
branches: [],
branchList: [config.onboardingBranch!], // to prevent auto closing
packageFiles: {},
};
}
121 changes: 101 additions & 20 deletions lib/workers/repository/onboarding/branch/index.spec.ts
Expand Up @@ -8,14 +8,17 @@ import {
platform,
} from '../../../../../test/util';
import { configFileNames } from '../../../../config/app-strings';
import { GlobalConfig } from '../../../../config/global';
import {
REPOSITORY_FORKED,
REPOSITORY_NO_PACKAGE_FILES,
} from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import type { Pr } from '../../../../modules/platform';
import * as memCache from '../../../../util/cache/memory';
import * as _cache from '../../../../util/cache/repository';
import type { FileAddition } from '../../../../util/git/types';
import { OnboardingState } from '../common';
import * as _config from './config';
import * as _rebase from './rebase';
import { checkOnboardingBranch } from '.';
Expand All @@ -36,9 +39,11 @@ describe('workers/repository/onboarding/branch/index', () => {
let config: RenovateConfig;

beforeEach(() => {
memCache.init();
jest.resetAllMocks();
config = getConfig();
config.repository = 'some/repo';
OnboardingState.prUpdateRequested = false;
git.getFileList.mockResolvedValue([]);
cache.getCache.mockReturnValue({});
});
Expand All @@ -63,26 +68,36 @@ describe('workers/repository/onboarding/branch/index', () => {
);
});

it('has default onboarding config', async () => {
configModule.getOnboardingConfig.mockResolvedValue(
config.onboardingConfig
);
configModule.getOnboardingConfigContents.mockResolvedValue(
'{\n' +
' "$schema": "https://docs.renovatebot.com/renovate-schema.json"\n' +
'}\n'
);
git.getFileList.mockResolvedValue(['package.json']);
fs.readLocalFile.mockResolvedValue('{}');
await checkOnboardingBranch(config);
const file = git.commitFiles.mock.calls[0][0].files[0] as FileAddition;
const contents = file.contents?.toString();
expect(contents).toBeJsonString();
// TODO #7154
expect(JSON.parse(contents!)).toEqual({
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
});
});
it.each`
checkboxEnabled | expected
${true} | ${true}
${false} | ${false}
`(
'has default onboarding config' +
'(config.onboardingRebaseCheckbox="$checkboxEnabled")',
async ({ checkboxEnabled, expected }) => {
config.onboardingRebaseCheckbox = checkboxEnabled;
configModule.getOnboardingConfig.mockResolvedValue(
config.onboardingConfig
);
configModule.getOnboardingConfigContents.mockResolvedValue(
'{\n' +
' "$schema": "https://docs.renovatebot.com/renovate-schema.json"\n' +
'}\n'
);
git.getFileList.mockResolvedValue(['package.json']);
fs.readLocalFile.mockResolvedValue('{}');
await checkOnboardingBranch(config);
const file = git.commitFiles.mock.calls[0][0].files[0] as FileAddition;
const contents = file.contents?.toString();
expect(contents).toBeJsonString();
// TODO #7154
expect(JSON.parse(contents!)).toEqual({
$schema: 'https://docs.renovatebot.com/renovate-schema.json',
});
expect(OnboardingState.prUpdateRequested).toBe(expected);
}
);

it('uses discovered onboarding config', async () => {
configModule.getOnboardingConfig.mockResolvedValue({
Expand Down Expand Up @@ -244,5 +259,71 @@ describe('workers/repository/onboarding/branch/index', () => {
expect(git.checkoutBranch).toHaveBeenCalledTimes(1);
expect(git.commitFiles).toHaveBeenCalledTimes(0);
});

describe('tests onboarding rebase/retry checkbox handling', () => {
beforeEach(() => {
GlobalConfig.set({ platform: 'github' });
config.onboardingRebaseCheckbox = true;
OnboardingState.prUpdateRequested = false;
git.getFileList.mockResolvedValueOnce(['package.json']);
platform.findPr.mockResolvedValueOnce(null);
rebase.rebaseOnboardingBranch.mockResolvedValueOnce(null);
});

it('detects unsupported platfom', async () => {
const pl = 'bitbucket';
GlobalConfig.set({ platform: pl });
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>({}));

await checkOnboardingBranch(config);

expect(logger.trace).toHaveBeenCalledWith(
`Platform '${pl}' does not support extended markdown`
);
expect(OnboardingState.prUpdateRequested).toBeTrue();
expect(git.checkoutBranch).toHaveBeenCalledTimes(1);
expect(git.commitFiles).toHaveBeenCalledTimes(0);
});

it('detects missing rebase checkbox', async () => {
const pr = { bodyStruct: { rebaseRequested: undefined } };
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>(pr));

await checkOnboardingBranch(config);

expect(logger.debug).toHaveBeenCalledWith(
`No rebase checkbox was found in the onboarding PR`
);
expect(OnboardingState.prUpdateRequested).toBeTrue();
expect(git.checkoutBranch).toHaveBeenCalledTimes(1);
expect(git.commitFiles).toHaveBeenCalledTimes(0);
});

it('detects manual pr update requested', async () => {
const pr = { bodyStruct: { rebaseRequested: true } };
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>(pr));

await checkOnboardingBranch(config);

expect(logger.debug).toHaveBeenCalledWith(
`Manual onboarding PR update requested`
);
expect(OnboardingState.prUpdateRequested).toBeTrue();
``;
expect(git.checkoutBranch).toHaveBeenCalledTimes(1);
expect(git.commitFiles).toHaveBeenCalledTimes(0);
});

it('handles unchecked rebase checkbox', async () => {
const pr = { bodyStruct: { rebaseRequested: false } };
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>(pr));

await checkOnboardingBranch(config);

expect(OnboardingState.prUpdateRequested).toBeFalse();
expect(git.checkoutBranch).toHaveBeenCalledTimes(1);
expect(git.commitFiles).toHaveBeenCalledTimes(0);
});
});
});
});
29 changes: 26 additions & 3 deletions lib/workers/repository/onboarding/branch/index.ts
@@ -1,3 +1,4 @@
import is from '@sindresorhus/is';
import { mergeChildConfig } from '../../../../config';
import { GlobalConfig } from '../../../../config/global';
import type { RenovateConfig } from '../../../../config/types';
Expand All @@ -6,10 +7,11 @@ import {
REPOSITORY_NO_PACKAGE_FILES,
} from '../../../../constants/error-messages';
import { logger } from '../../../../logger';
import { platform } from '../../../../modules/platform';
import { Pr, platform } from '../../../../modules/platform';
import { checkoutBranch, setGitAuthor } from '../../../../util/git';
import { extractAllDependencies } from '../../extract';
import { mergeRenovateConfig } from '../../init/merge';
import { OnboardingState } from '../common';
import { getOnboardingPr, isOnboarded } from './check';
import { getOnboardingConfig } from './config';
import { createOnboardingBranch } from './create';
Expand All @@ -34,8 +36,12 @@ export async function checkOnboardingBranch(
setGitAuthor(config.gitAuthor);
const onboardingPr = await getOnboardingPr(config);
if (onboardingPr) {
if (config.onboardingRebaseCheckbox) {
handleOnboardingManualRebase(onboardingPr);
}
logger.debug('Onboarding PR already exists');
const commit = await rebaseOnboardingBranch(config);
const { rawConfigHash } = onboardingPr.bodyStruct ?? {};
const commit = await rebaseOnboardingBranch(config, rawConfigHash);
if (commit) {
logger.info(
{ branch: config.onboardingBranch, commit, onboarding: true },
Expand All @@ -44,7 +50,6 @@ export async function checkOnboardingBranch(
}
// istanbul ignore if
if (platform.refreshPr) {
// TODO #7154
await platform.refreshPr(onboardingPr.number);
}
} else {
Expand All @@ -62,6 +67,9 @@ export async function checkOnboardingBranch(
}
}
logger.debug('Need to create onboarding PR');
if (config.onboardingRebaseCheckbox) {
OnboardingState.prUpdateRequested = true;
}
const commit = await createOnboardingBranch(mergedConfig);
// istanbul ignore if
if (commit) {
Expand All @@ -80,3 +88,18 @@ export async function checkOnboardingBranch(
const branchList = [onboardingBranch!];
return { ...config, repoIsOnboarded, onboardingBranch, branchList };
}

function handleOnboardingManualRebase(onboardingPr: Pr): void {
const pl = GlobalConfig.get('platform')!;
const { rebaseRequested } = onboardingPr.bodyStruct ?? {};
if (!['github', 'gitlab', 'gitea'].includes(pl)) {
logger.trace(`Platform '${pl}' does not support extended markdown`);
OnboardingState.prUpdateRequested = true;
} else if (is.nullOrUndefined(rebaseRequested)) {
logger.debug('No rebase checkbox was found in the onboarding PR');
OnboardingState.prUpdateRequested = true;
} else if (rebaseRequested) {
logger.debug('Manual onboarding PR update requested');
OnboardingState.prUpdateRequested = true;
}
}

0 comments on commit de289bb

Please sign in to comment.