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(onboarding): skip processing onboarding branch #22490

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
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
2 changes: 2 additions & 0 deletions lib/util/cache/repository/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface OnboardingBranchCache {
onboardingBranchSha: string;
isConflicted: boolean;
isModified: boolean;
configFileName?: string;
configFileParsed?: string;
}

export interface PrCache {
Expand Down
2 changes: 1 addition & 1 deletion lib/workers/repository/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export async function renovateRepository(
addSplit('init');
const performExtract =
config.repoIsOnboarded! ||
!config.onboardingRebaseCheckbox ||
!OnboardingState.onboardingCacheValid ||
OnboardingState.prUpdateRequested;
const { branches, branchList, packageFiles } = performExtract
? await instrument('extract', () => extractDependencies(config))
Expand Down
64 changes: 64 additions & 0 deletions lib/workers/repository/init/merge.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ import {
import { getConfig } from '../../../config/defaults';
import * as _migrateAndValidate from '../../../config/migrate-validate';
import * as _migrate from '../../../config/migration';
import * as memCache from '../../../util/cache/memory';
import * as repoCache from '../../../util/cache/repository';
import { initRepoCache } from '../../../util/cache/repository/init';
import type { RepoCacheData } from '../../../util/cache/repository/types';
import * as _onboardingCache from '../onboarding/branch/onboarding-branch-cache';
import { OnboardingState } from '../onboarding/common';
import {
checkForRepoConfigError,
detectRepoFileConfig,
Expand All @@ -21,13 +24,16 @@ import {

jest.mock('../../../util/fs');
jest.mock('../../../util/git');
jest.mock('../onboarding/branch/onboarding-branch-cache');

const migrate = mocked(_migrate);
const migrateAndValidate = mocked(_migrateAndValidate);
const onboardingCache = mocked(_onboardingCache);

let config: RenovateConfig;

beforeEach(() => {
memCache.init();
jest.resetAllMocks();
config = getConfig();
config.errors = [];
Expand Down Expand Up @@ -64,6 +70,64 @@ describe('workers/repository/init/merge', () => {
);
});

it('returns cache config from onboarding cache - package.json', async () => {
const pJson = JSON.stringify({
schema: 'https://docs.renovate.com',
});
OnboardingState.onboardingCacheValid = true;
onboardingCache.getOnboardingFileNameFromCache.mockReturnValueOnce(
'package.json'
);
onboardingCache.getOnboardingConfigFromCache.mockReturnValueOnce(pJson);
expect(await detectRepoFileConfig()).toEqual({
configFileName: 'package.json',
configFileParsed: { schema: 'https://docs.renovate.com' },
});
});

it('clones, if onboarding cache is valid but parsed config is undefined', async () => {
OnboardingState.onboardingCacheValid = true;
onboardingCache.getOnboardingFileNameFromCache.mockReturnValueOnce(
'package.json'
);
onboardingCache.getOnboardingConfigFromCache.mockReturnValueOnce(
undefined as never
);
scm.getFileList.mockResolvedValueOnce(['package.json']);
const pJson = JSON.stringify({
name: 'something',
renovate: {
prHourlyLimit: 10,
},
});
fs.readLocalFile.mockResolvedValueOnce(pJson);
platform.getRawFile.mockResolvedValueOnce(pJson);
expect(await detectRepoFileConfig()).toEqual({
configFileName: 'package.json',
configFileParsed: { prHourlyLimit: 10 },
});
});

it('returns cache config from onboarding cache - renovate.json', async () => {
const configParsed = JSON.stringify({
schema: 'https://docs.renovate.com',
});
OnboardingState.onboardingCacheValid = true;
onboardingCache.getOnboardingFileNameFromCache.mockReturnValueOnce(
'renovate.json'
);
onboardingCache.getOnboardingConfigFromCache.mockReturnValueOnce(
configParsed
);
expect(await detectRepoFileConfig()).toEqual({
configFileName: 'renovate.json',
configFileParsed: {
schema: 'https://docs.renovate.com',
},
configFileRaw: undefined,
});
});

it('uses package.json config if found', async () => {
scm.getFileList.mockResolvedValue(['package.json']);
const pJson = JSON.stringify({
Expand Down
26 changes: 25 additions & 1 deletion lib/workers/repository/init/merge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,12 @@ import { readLocalFile } from '../../../util/fs';
import * as hostRules from '../../../util/host-rules';
import * as queue from '../../../util/http/queue';
import * as throttle from '../../../util/http/throttle';
import {
getOnboardingConfigFromCache,
getOnboardingFileNameFromCache,
setOnboardingConfigDetails,
} from '../onboarding/branch/onboarding-branch-cache';
import { OnboardingState } from '../onboarding/common';
import type { RepoFileConfig } from './types';

async function detectConfigFile(): Promise<string | null> {
Expand Down Expand Up @@ -74,7 +80,13 @@ export async function detectRepoFileConfig(): Promise<RepoFileConfig> {
delete cache.configFileName;
}
}
configFileName = (await detectConfigFile()) ?? undefined;

if (OnboardingState.onboardingCacheValid) {
configFileName = getOnboardingFileNameFromCache();
} else {
configFileName = (await detectConfigFile()) ?? undefined;
}

if (!configFileName) {
logger.debug('No renovate config file found');
return {};
Expand All @@ -84,6 +96,16 @@ export async function detectRepoFileConfig(): Promise<RepoFileConfig> {
// TODO #7154
let configFileParsed: any;
let configFileRaw: string | undefined | null;

if (OnboardingState.onboardingCacheValid) {
const cachedConfig = getOnboardingConfigFromCache();
const parsedConfig = cachedConfig ? JSON.parse(cachedConfig) : undefined;
if (parsedConfig) {
setOnboardingConfigDetails(configFileName, JSON.stringify(parsedConfig));
return { configFileName, configFileRaw, configFileParsed: parsedConfig };
}
}

if (configFileName === 'package.json') {
// We already know it parses
configFileParsed = JSON.parse(
Expand Down Expand Up @@ -171,6 +193,8 @@ export async function detectRepoFileConfig(): Promise<RepoFileConfig> {
'Repository config'
);
}

setOnboardingConfigDetails(configFileName, JSON.stringify(configFileParsed));
return { configFileName, configFileRaw, configFileParsed };
}

Expand Down
27 changes: 27 additions & 0 deletions lib/workers/repository/onboarding/branch/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,33 @@ describe('workers/repository/onboarding/branch/index', () => {
expect(scm.commitAndPush).toHaveBeenCalledTimes(0);
});

it('skips processing onboarding branch when main/onboarding SHAs have not changed', async () => {
GlobalConfig.set({ platform: 'github' });
const dummyCache = {
onboardingBranchCache: {
defaultBranchSha: 'default-sha',
onboardingBranchSha: 'onboarding-sha',
isConflicted: false,
isModified: false,
configFileParsed: 'raw',
configFileName: 'renovate.json',
},
} satisfies RepoCacheData;
cache.getCache.mockReturnValue(dummyCache);
scm.getFileList.mockResolvedValue(['package.json']);
platform.findPr.mockResolvedValue(null); // finds closed onboarding pr
platform.getBranchPr.mockResolvedValueOnce(
mock<Pr>({ bodyStruct: { rebaseRequested: false } })
); // finds open onboarding pr
git.getBranchCommit
.mockReturnValueOnce('default-sha')
.mockReturnValueOnce('default-sha')
.mockReturnValueOnce('onboarding-sha');
config.onboardingRebaseCheckbox = true;
await checkOnboardingBranch(config);
expect(git.mergeBranch).not.toHaveBeenCalled();
});

it('processes modified onboarding branch and invalidates extract cache', async () => {
const dummyCache = {
scan: {
Expand Down
32 changes: 30 additions & 2 deletions lib/workers/repository/onboarding/branch/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,12 +51,26 @@ export async function checkOnboardingBranch(
// global gitAuthor will need to be used
setGitAuthor(config.gitAuthor);
const onboardingPr = await getOnboardingPr(config);
// TODO #7154
const branchList = [onboardingBranch!];
if (onboardingPr) {
if (config.onboardingRebaseCheckbox) {
handleOnboardingManualRebase(onboardingPr);
}
logger.debug('Onboarding PR already exists');

if (
isOnboardingCacheValid(config.defaultBranch!, config.onboardingBranch!) &&
!(config.onboardingRebaseCheckbox && OnboardingState.prUpdateRequested)
) {
logger.debug(
'Skip processing since the onboarding branch is up to date and default branch has not changed'
);
OnboardingState.onboardingCacheValid = true;
return { ...config, repoIsOnboarded, onboardingBranch, branchList };
}
OnboardingState.onboardingCacheValid = false;

isModified = await isOnboardingBranchModified(config.onboardingBranch!);
if (isModified) {
if (hasOnboardingBranchChanged(config.onboardingBranch!)) {
Expand Down Expand Up @@ -109,8 +123,6 @@ export async function checkOnboardingBranch(
isModified
);

// TODO #7154
const branchList = [onboardingBranch!];
return { ...config, repoIsOnboarded, onboardingBranch, branchList };
}

Expand All @@ -137,3 +149,19 @@ function invalidateExtractCache(baseBranch: string): void {
delete cache.scan[baseBranch];
}
}

function isOnboardingCacheValid(
defaultBranch: string,
onboardingBranch: string
): boolean {
const cache = getCache();
const onboardingBranchCache = cache?.onboardingBranchCache;
return !!(
onboardingBranchCache &&
onboardingBranchCache.defaultBranchSha === getBranchCommit(defaultBranch) &&
onboardingBranchCache.onboardingBranchSha ===
getBranchCommit(onboardingBranch) &&
onboardingBranchCache.configFileName &&
onboardingBranchCache.configFileParsed
);
}
Original file line number Diff line number Diff line change
@@ -1,12 +1,18 @@
import { git, mocked, scm } from '../../../../../test/util';
import { git, mocked, partial, scm } from '../../../../../test/util';
import * as _cache from '../../../../util/cache/repository';
import type { RepoCacheData } from '../../../../util/cache/repository/types';
import type {
OnboardingBranchCache,
RepoCacheData,
} from '../../../../util/cache/repository/types';
import {
deleteOnboardingCache,
getOnboardingConfigFromCache,
getOnboardingFileNameFromCache,
hasOnboardingBranchChanged,
isOnboardingBranchConflicted,
isOnboardingBranchModified,
setOnboardingCache,
setOnboardingConfigDetails,
} from './onboarding-branch-cache';

jest.mock('../../../../util/cache/repository');
Expand Down Expand Up @@ -232,4 +238,61 @@ describe('workers/repository/onboarding/branch/onboarding-branch-cache', () => {
).toBeTrue();
});
});

describe('getOnboardingFileNameFromCache()', () => {
it('returns cached value', () => {
const dummyCache = {
onboardingBranchCache: partial<OnboardingBranchCache>({
configFileName: 'renovate.json',
}),
} satisfies RepoCacheData;
cache.getCache.mockReturnValueOnce(dummyCache);
expect(getOnboardingFileNameFromCache()).toBe('renovate.json');
});

it('returns undefined', () => {
expect(getOnboardingFileNameFromCache()).toBeUndefined();
});
});

describe('getOnboardingConfigFromCache()', () => {
it('returns cached value', () => {
const dummyCache = {
onboardingBranchCache: partial<OnboardingBranchCache>({
configFileParsed: 'parsed',
}),
} satisfies RepoCacheData;
cache.getCache.mockReturnValueOnce(dummyCache);
expect(getOnboardingConfigFromCache()).toBe('parsed');
});

it('returns undefined', () => {
expect(getOnboardingConfigFromCache()).toBeUndefined();
});
});

describe('setOnboardingConfigDetails()', () => {
it('returns cached value', () => {
const dummyCache = {
onboardingBranchCache: {
defaultBranchSha: 'default-sha',
onboardingBranchSha: 'onboarding-sha',
isConflicted: true,
isModified: true,
},
} satisfies RepoCacheData;
cache.getCache.mockReturnValueOnce(dummyCache);
setOnboardingConfigDetails('renovate.json', 'parsed');
expect(dummyCache).toEqual({
onboardingBranchCache: {
defaultBranchSha: 'default-sha',
onboardingBranchSha: 'onboarding-sha',
isConflicted: true,
isModified: true,
configFileName: 'renovate.json',
configFileParsed: 'parsed',
},
});
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,27 @@ export async function isOnboardingBranchModified(
return isModified;
}

export function getOnboardingFileNameFromCache(): string | undefined {
const cache = getCache();
return cache.onboardingBranchCache?.configFileName;
}

export function getOnboardingConfigFromCache(): string | undefined {
const cache = getCache();
return cache.onboardingBranchCache?.configFileParsed;
}

export function setOnboardingConfigDetails(
configFileName: string,
configFileParsed: string
): void {
const cache = getCache();
if (cache.onboardingBranchCache) {
cache.onboardingBranchCache.configFileName = configFileName;
cache.onboardingBranchCache.configFileParsed = configFileParsed;
}
}

export async function isOnboardingBranchConflicted(
defaultBranch: string,
onboardingBranch: string
Expand Down
17 changes: 17 additions & 0 deletions lib/workers/repository/onboarding/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ export function defaultConfigFile(config: RenovateConfig): string {

export class OnboardingState {
private static readonly cacheKey = 'OnboardingState';
private static readonly skipKey = 'OnboardingStateValid';

static get prUpdateRequested(): boolean {
const updateRequested = !!memCache.get<boolean | undefined>(
Expand All @@ -27,4 +28,20 @@ export class OnboardingState {
logger.trace({ value }, 'Set OnboardingState.prUpdateRequested');
memCache.set(OnboardingState.cacheKey, value);
}

static get onboardingCacheValid(): boolean {
const cacheValid = !!memCache.get<boolean | undefined>(
OnboardingState.skipKey
);
logger.trace(
{ value: cacheValid },
'Get OnboardingState.onboardingCacheValid'
);
return cacheValid;
}

static set onboardingCacheValid(value: boolean) {
logger.trace({ value }, 'Set OnboardingState.onboardingCacheValid');
memCache.set(OnboardingState.skipKey, value);
}
}
Loading