Skip to content

Commit

Permalink
feat(onboarding): skip processing onboarding branch (#22490)
Browse files Browse the repository at this point in the history
Co-authored-by: Rhys Arkins <[email protected]>
Co-authored-by: Michael Kriese <[email protected]>
  • Loading branch information
3 people authored Jun 21, 2023
1 parent ae8bb71 commit fa6e5df
Show file tree
Hide file tree
Showing 10 changed files with 253 additions and 6 deletions.
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

0 comments on commit fa6e5df

Please sign in to comment.