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(cache): pr fingerprint implementation #18850

Merged
merged 68 commits into from
Feb 21, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
68 commits
Select commit Hold shift + click to select a range
1d893a1
first implementation
RahulGautamSingh Nov 8, 2022
cd181c4
refactor code
RahulGautamSingh Nov 8, 2022
80b8806
refactor: add fn to compute get elapsed hours
RahulGautamSingh Nov 9, 2022
80df5e3
setPrCache when with-pr and pr is updated/created
RahulGautamSingh Nov 10, 2022
1061209
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Nov 10, 2022
8ff8337
refactor code
RahulGautamSingh Nov 10, 2022
1dc2e65
revert changes to .gitignore
RahulGautamSingh Nov 10, 2022
941911f
apply suggested changes
RahulGautamSingh Nov 10, 2022
061f236
refactor: remove unnecessary optional chaining
RahulGautamSingh Nov 10, 2022
b2075c8
move getElapsedHours to util/date
RahulGautamSingh Nov 10, 2022
2710240
update comments
RahulGautamSingh Nov 16, 2022
21bc91e
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Nov 22, 2022
6486b64
add tests'
RahulGautamSingh Nov 23, 2022
76134e4
Apply suggestions from code review
RahulGautamSingh Nov 23, 2022
ba33c96
Update lib/util/cache/repository/types.ts
RahulGautamSingh Nov 23, 2022
32e74d7
convert type Date->string + update debug logs
RahulGautamSingh Nov 24, 2022
30fd556
Merge branch 'feat/pr-caching' of https://github.com/RahulGautamSingh…
RahulGautamSingh Nov 24, 2022
628caeb
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Dec 11, 2022
cb86f69
remove isCloned check
RahulGautamSingh Dec 12, 2022
d201966
- refactor logs
RahulGautamSingh Jan 8, 2023
7d78cb1
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 8, 2023
86a0fa4
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 8, 2023
cc0e82f
refactor: update log messages
RahulGautamSingh Jan 9, 2023
4e72018
ensure repoCache:enabled before using prCache
RahulGautamSingh Jan 9, 2023
0dd6343
use luxon
RahulGautamSingh Jan 10, 2023
b4baced
update test
RahulGautamSingh Jan 11, 2023
0de8553
update logic
RahulGautamSingh Jan 12, 2023
e275c6b
refactor: remove extra file
RahulGautamSingh Jan 12, 2023
ffd4fac
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 12, 2023
10bf02c
apply suggestions
RahulGautamSingh Jan 13, 2023
4ee9e56
filter config
RahulGautamSingh Jan 14, 2023
d507a5f
update tests
RahulGautamSingh Jan 14, 2023
9c10632
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 14, 2023
37de2d9
refactor
RahulGautamSingh Jan 14, 2023
9cf6377
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 14, 2023
58c8b38
rename files
RahulGautamSingh Jan 18, 2023
923d9a2
move fingerprint logic to pr-fingerprint.ts
RahulGautamSingh Jan 18, 2023
f0e4571
update set pr-cache logic
RahulGautamSingh Jan 18, 2023
70dd247
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 18, 2023
a3edb54
fix coverage
RahulGautamSingh Jan 18, 2023
5a37612
fix: update pr cache if pr not updated
RahulGautamSingh Jan 20, 2023
c9a02ca
fix(test): add/modify tests
RahulGautamSingh Jan 20, 2023
5be8186
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 20, 2023
f9980de
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Jan 30, 2023
9aef1ed
update tests
RahulGautamSingh Jan 30, 2023
033d445
Update lib/workers/repository/update/pr/index.ts
RahulGautamSingh Feb 2, 2023
85c37b6
refactor(util/date): rename time->date
RahulGautamSingh Feb 2, 2023
6a4d217
Update lib/workers/repository/update/pr/index.ts
RahulGautamSingh Feb 2, 2023
cabe559
Merge branch 'feat/pr-caching' of https://github.com/RahulGautamSingh…
RahulGautamSingh Feb 2, 2023
5556ade
add sensible tests
RahulGautamSingh Feb 2, 2023
7853e86
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 2, 2023
1bc3932
setPrCache(): make lastEdited update optional
RahulGautamSingh Feb 2, 2023
e47c5d8
feat: add pendingVersions to PrFingerprintConnfig
RahulGautamSingh Feb 4, 2023
ab3e3e1
refactor: format
RahulGautamSingh Feb 4, 2023
f110be6
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 4, 2023
b32cd60
fix tests
RahulGautamSingh Feb 5, 2023
64791a0
Merge branch 'main' into feat/pr-caching
rarkins Feb 11, 2023
c2c35d9
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 13, 2023
a5af9b1
apply suggestions
RahulGautamSingh Feb 13, 2023
f2d733f
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 13, 2023
22498b1
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 16, 2023
667350f
update test + move log msg
RahulGautamSingh Feb 16, 2023
07ceb04
Update types.ts
RahulGautamSingh Feb 16, 2023
4f5385d
Update types.ts
RahulGautamSingh Feb 16, 2023
487633c
fix lint issue
RahulGautamSingh Feb 16, 2023
3f5351e
Update lib/util/cache/repository/types.ts
RahulGautamSingh Feb 20, 2023
e1d7e4b
Merge branch 'main' into feat/pr-caching
RahulGautamSingh Feb 20, 2023
fcda03c
prettier-fix
RahulGautamSingh Feb 20, 2023
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
16 changes: 14 additions & 2 deletions lib/util/cache/repository/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,17 @@ export interface BranchUpgradeCache {
sourceUrl?: string;
}

export interface PrCache {
fingerprint: string;
/**
* last PR modified ISO timestamp
*/
lastEdited: string;
}

export interface BranchCache {
/**
*Whether this branch has automerge enabled
* Whether this branch has automerge enabled
*/
automerge: boolean;
/**
Expand All @@ -40,7 +48,7 @@ export interface BranchCache {
*/
baseBranchSha: string | null;
/**
* Hash of the manager fingerprints and the update branch config
* Hash of the manager fingerprints and the filtered update branch config
*/
branchFingerprint?: string;
/**
Expand Down Expand Up @@ -75,6 +83,10 @@ export interface BranchCache {
* Details on the dependency upgrades that have been applied in this branch
*/
upgrades: BranchUpgradeCache[];
/**
* Object that has PR info
*/
prCache?: PrCache | null;
}

export interface RepoCacheData {
Expand Down
2 changes: 1 addition & 1 deletion lib/util/date.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ describe('util/date', () => {
expect(getElapsedHours(Jan1)).toBe(elapsedHours); // JS Date
});

it('throws when invalid date is passed', () => {
it('returns zero when date passed is invalid', () => {
expect(getElapsedHours(new Date('invalid_date_string'))).toBe(0);
});
});
Expand Down
6 changes: 3 additions & 3 deletions lib/util/date.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ export function getElapsedMinutes(date: Date): number {
}

export function getElapsedHours(date: Date | string): number {
const lastDate =
const pastDate =
typeof date === 'string'
? DateTime.fromISO(date)
: DateTime.fromJSDate(date);

if (!lastDate.isValid) {
if (!pastDate.isValid) {
return 0;
}

const diff = DateTime.now().diff(lastDate, 'hours');
const diff = DateTime.now().diff(pastDate, 'hours');
return Math.floor(diff.hours);
}
3 changes: 3 additions & 0 deletions lib/workers/repository/cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
} from '../../util/git';
import { getCachedPristineResult } from '../../util/git/pristine';
import type { BranchConfig, BranchUpgradeConfig } from '../types';
import { getPrCache } from './update/pr/pr-cache';

function generateBranchUpgradeCache(
upgrade: BranchUpgradeConfig
Expand Down Expand Up @@ -73,6 +74,7 @@ async function generateBranchCache(
? branch.upgrades.map(generateBranchUpgradeCache)
: [];
const branchFingerprint = branch.branchFingerprint;
const prCache = getPrCache(branchName);
return {
automerge,
baseBranchSha,
Expand All @@ -83,6 +85,7 @@ async function generateBranchCache(
isConflicted,
isModified,
pristine,
prCache,
prNo,
sha,
upgrades,
Expand Down
140 changes: 140 additions & 0 deletions lib/workers/repository/update/pr/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@ import * as _comment from '../../../../modules/platform/comment';
import { getPrBodyStruct } from '../../../../modules/platform/pr-body';
import type { Pr } from '../../../../modules/platform/types';
import { ExternalHostError } from '../../../../types/errors/external-host-error';
import type { PrCache } from '../../../../util/cache/repository/types';
import { fingerprint } from '../../../../util/fingerprint';
import * as _limits from '../../../global/limits';
import type { BranchConfig, BranchUpgradeConfig } from '../../../types';
import { embedChangelog } from '../../changelog';
import * as _statusChecks from '../branch/status-checks';
import * as _prBody from './body';
import type { ChangeLogChange, ChangeLogRelease } from './changelog/types';
import * as _participants from './participants';
import * as _prCache from './pr-cache';
import { generatePrFingerprintConfig } from './pr-fingerprint';
import { ensurePr } from '.';

jest.mock('../../../../util/git');
Expand All @@ -36,6 +41,9 @@ const participants = mocked(_participants);
jest.mock('../../../../modules/platform/comment');
const comment = mocked(_comment);

jest.mock('./pr-cache');
const prCache = mocked(_prCache);

describe('workers/repository/update/pr/index', () => {
describe('ensurePr', () => {
const number = 123;
Expand Down Expand Up @@ -79,6 +87,7 @@ describe('workers/repository/update/pr/index', () => {
{ pr: pr.number, prTitle },
'PR created'
);
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('aborts PR creation once limit is exceeded', async () => {
Expand All @@ -91,6 +100,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'without-pr', prBlockedBy: 'RateLimited' });
expect(platform.createPr).not.toHaveBeenCalled();
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('ignores PR limits on vulnerability alert', async () => {
Expand All @@ -101,6 +111,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'with-pr', pr });
expect(platform.createPr).toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('creates rollback PR', async () => {
Expand All @@ -110,6 +121,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'with-pr', pr });
expect(logger.logger.info).toHaveBeenCalledWith('Creating Rollback PR');
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('skips PR creation due to non-green branch check', async () => {
Expand All @@ -121,6 +133,7 @@ describe('workers/repository/update/pr/index', () => {
type: 'without-pr',
prBlockedBy: 'AwaitingTests',
});
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('creates PR for green branch checks', async () => {
Expand All @@ -131,6 +144,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'with-pr', pr });
expect(platform.createPr).toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('skips PR creation for unapproved dependencies', async () => {
Expand All @@ -142,6 +156,7 @@ describe('workers/repository/update/pr/index', () => {
type: 'without-pr',
prBlockedBy: 'NeedsApproval',
});
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('skips PR creation before prNotPendingHours is hit', async () => {
Expand All @@ -161,6 +176,7 @@ describe('workers/repository/update/pr/index', () => {
type: 'without-pr',
prBlockedBy: 'AwaitingTests',
});
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('skips PR creation due to stabilityStatus', async () => {
Expand All @@ -180,6 +196,7 @@ describe('workers/repository/update/pr/index', () => {
type: 'without-pr',
prBlockedBy: 'AwaitingTests',
});
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('creates PR after prNotPendingHours is hit', async () => {
Expand All @@ -197,6 +214,7 @@ describe('workers/repository/update/pr/index', () => {
});

expect(res).toEqual({ type: 'with-pr', pr });
expect(prCache.setPrCache).toHaveBeenCalled();
});

describe('Error handling', () => {
Expand All @@ -207,6 +225,7 @@ describe('workers/repository/update/pr/index', () => {
const res = await ensurePr(config);

expect(res).toEqual({ type: 'without-pr', prBlockedBy: 'Error' });
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('handles error for PR that already exists', async () => {
Expand All @@ -223,6 +242,7 @@ describe('workers/repository/update/pr/index', () => {
expect(logger.logger.warn).toHaveBeenCalledWith(
'A pull requests already exists'
);
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('deletes branch on 502 error', async () => {
Expand All @@ -233,6 +253,7 @@ describe('workers/repository/update/pr/index', () => {
const res = await ensurePr(config);

expect(res).toEqual({ type: 'without-pr', prBlockedBy: 'Error' });
expect(prCache.setPrCache).not.toHaveBeenCalled();
expect(git.deleteBranch).toHaveBeenCalledWith('renovate-branch');
});
});
Expand All @@ -252,6 +273,7 @@ describe('workers/repository/update/pr/index', () => {
{ pr: changedPr.number, prTitle },
`PR updated`
);
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('updates PR due to body change', async () => {
Expand All @@ -266,6 +288,11 @@ describe('workers/repository/update/pr/index', () => {
expect(res).toEqual({ type: 'with-pr', pr: changedPr });
expect(platform.updatePr).toHaveBeenCalled();
expect(platform.createPr).not.toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
expect(logger.logger.info).toHaveBeenCalledWith(
{ pr: changedPr.number, prTitle },
`PR updated`
);
});

it('ignores reviewable content ', async () => {
Expand All @@ -284,6 +311,10 @@ describe('workers/repository/update/pr/index', () => {
expect(res).toEqual({ type: 'with-pr', pr: changedPr });
expect(platform.updatePr).not.toHaveBeenCalled();
expect(platform.createPr).not.toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
expect(logger.logger.debug).toHaveBeenCalledWith(
'Pull Request #123 does not need updating'
);
});
});

Expand Down Expand Up @@ -353,6 +384,7 @@ describe('workers/repository/update/pr/index', () => {
});
expect(platform.updatePr).not.toHaveBeenCalled();
expect(platform.createPr).not.toHaveBeenCalled();
expect(prCache.setPrCache).not.toHaveBeenCalled();
});

it('adds assignees for PR automerge with red status', async () => {
Expand All @@ -372,6 +404,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'with-pr', pr: changedPr });
expect(participants.addParticipants).toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('adds reviewers for PR automerge with red status and existing ignorable reviewers that can be ignored', async () => {
Expand Down Expand Up @@ -408,6 +441,7 @@ describe('workers/repository/update/pr/index', () => {
expect(res).toEqual({ type: 'with-pr', pr });
expect(platform.createPr).toHaveBeenCalled();
expect(participants.addParticipants).not.toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('skips branch automerge and forces PR creation due to prNotPendingHours exceeded', async () => {
Expand All @@ -428,6 +462,7 @@ describe('workers/repository/update/pr/index', () => {

expect(res).toEqual({ type: 'with-pr', pr });
expect(platform.createPr).toHaveBeenCalled();
expect(prCache.setPrCache).toHaveBeenCalled();
});

it('automerges branch when prNotPendingHours are not exceeded', async () => {
Expand Down Expand Up @@ -711,5 +746,110 @@ describe('workers/repository/update/pr/index', () => {
});
});
});

describe('prCache', () => {
const existingPr: Pr = {
...pr,
};
let cachedPr: PrCache | null = null;

it('adds pr-cache when not present', async () => {
platform.getBranchPr.mockResolvedValue(existingPr);
cachedPr = null;
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr(config);
expect(res).toEqual({
type: 'with-pr',
pr: existingPr,
});
expect(logger.logger.debug).toHaveBeenCalledWith(
'Pull Request #123 does not need updating'
);
expect(prCache.setPrCache).toHaveBeenCalledTimes(1);
});

it('does not update lastEdited pr-cache when pr fingerprint is same but pr was edited within 24hrs', async () => {
platform.getBranchPr.mockResolvedValue(existingPr);
cachedPr = {
fingerprint: fingerprint(generatePrFingerprintConfig(config)),
lastEdited: new Date().toISOString(),
};
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr(config);
expect(res).toEqual({
type: 'with-pr',
pr: existingPr,
});
expect(logger.logger.debug).toHaveBeenCalledWith(
'Pull Request #123 does not need updating'
);
expect(logger.logger.debug).toHaveBeenCalledWith(
'PR cache matches but it has been edited in the past 24hrs, so processing PR'
);
expect(prCache.setPrCache).toHaveBeenCalledWith(
sourceBranch,
cachedPr.fingerprint,
false
);
});

it('updates pr-cache when pr fingerprint is different', async () => {
platform.getBranchPr.mockResolvedValue({
...existingPr,
title: 'Another title',
});
cachedPr = {
fingerprint: 'old',
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
};
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr(config);
expect(res).toEqual({
type: 'with-pr',
pr: { ...existingPr, title: 'Another title' },
});
expect(logger.logger.debug).toHaveBeenCalledWith(
'PR fingerprints mismatch, processing PR'
);
expect(prCache.setPrCache).toHaveBeenCalledTimes(1);
});

it('skips fetching changelogs when cache is valid and pr was lastEdited before 24hrs', async () => {
config.repositoryCache = 'enabled';
platform.getBranchPr.mockResolvedValue(existingPr);
cachedPr = {
fingerprint: fingerprint(generatePrFingerprintConfig(config)),
lastEdited: new Date('2020-01-20T00:00:00Z').toISOString(),
};
prCache.getPrCache.mockReturnValueOnce(cachedPr);
const res = await ensurePr(config);
expect(res).toEqual({
type: 'with-pr',
pr: existingPr,
});
expect(logger.logger.debug).toHaveBeenCalledWith(
'PR cache matches and no PR changes in last 24hrs, so skipping PR body check'
);
expect(embedChangelog).toHaveBeenCalledTimes(0);
});

it('logs when cache is enabled but pr-cache is absent', async () => {
config.repositoryCache = 'enabled';
platform.getBranchPr.mockResolvedValue(existingPr);
prCache.getPrCache.mockReturnValueOnce(null);
await ensurePr(config);
expect(logger.logger.debug).toHaveBeenCalledWith('PR cache not found');
});

it('does not log when cache is disabled and pr-cache is absent', async () => {
config.repositoryCache = 'disabled';
platform.getBranchPr.mockResolvedValue(existingPr);
prCache.getPrCache.mockReturnValueOnce(null);
await ensurePr(config);
expect(logger.logger.debug).not.toHaveBeenCalledWith(
'PR cache not found'
);
});
});
});
});
Loading