fix(reconfigure/pr): find reconfigure pr separately (#25954)

Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
RahulGautamSingh 2023-12-29 18:40:11 +05:45 committed by GitHub
parent 831fba0262
commit 5f163552a9
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 280 additions and 7 deletions

View file

@ -1306,6 +1306,50 @@ describe('modules/platform/bitbucket-server/index', () => {
}), }),
).toBeNull(); ).toBeNull();
}); });
it('finds pr from other authors', async () => {
const scope = await initRepo();
scope
.get(
`${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=OPEN&direction=outgoing&at=refs/heads/branch&limit=1`,
)
.reply(200, {
isLastPage: true,
values: [prMock(url, 'SOME', 'repo')],
});
expect(
await bitbucket.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
}),
).toMatchObject({
number: 5,
sourceBranch: 'userName1/pullRequest5',
targetBranch: 'master',
title: 'title',
state: 'open',
});
});
it('returns null if no pr found - (includeOtherAuthors)', async () => {
const scope = await initRepo();
scope
.get(
`${urlPath}/rest/api/1.0/projects/SOME/repos/repo/pull-requests?state=OPEN&direction=outgoing&at=refs/heads/branch&limit=1`,
)
.reply(200, {
isLastPage: true,
values: [],
});
const pr = await bitbucket.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
});
expect(pr).toBeNull();
});
}); });
describe('createPr()', () => { describe('createPr()', () => {

View file

@ -324,8 +324,34 @@ export async function findPr({
prTitle, prTitle,
state = 'all', state = 'all',
refreshCache, refreshCache,
includeOtherAuthors,
}: FindPRConfig): Promise<Pr | null> { }: FindPRConfig): Promise<Pr | null> {
logger.debug(`findPr(${branchName}, "${prTitle!}", "${state}")`); logger.debug(`findPr(${branchName}, "${prTitle!}", "${state}")`);
if (includeOtherAuthors) {
// PR might have been created by anyone, so don't use the cached Renovate PR list
const searchParams: Record<string, string> = {
state: 'OPEN',
};
searchParams['direction'] = 'outgoing';
searchParams['at'] = `refs/heads/${branchName}`;
const query = getQueryString(searchParams);
const prs = await utils.accumulateValues(
`./rest/api/1.0/projects/${config.projectKey}/repos/${config.repositorySlug}/pull-requests?${query}`,
'get',
{},
1, // only fetch the latest pr
);
if (!prs.length) {
logger.debug(`No PR found for branch ${branchName}`);
return null;
}
return utils.prInfo(prs[0]);
}
const prList = await getPrList(refreshCache); const prList = await getPrList(refreshCache);
const pr = prList.find(isRelevantPr(branchName, prTitle, state)); const pr = prList.find(isRelevantPr(branchName, prTitle, state));
if (pr) { if (pr) {

View file

@ -954,6 +954,46 @@ describe('modules/platform/bitbucket/index', () => {
}); });
expect(pr?.number).toBe(5); expect(pr?.number).toBe(5);
}); });
it('finds pr from other authors', async () => {
const scope = await initRepoMock();
scope
.get(
'/2.0/repositories/some/repo/pullrequests?q=source.branch.name="branch"&state=open',
)
.reply(200, { values: [pr] });
expect(
await bitbucket.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
}),
).toMatchObject({
number: 5,
sourceBranch: 'branch',
targetBranch: 'master',
title: 'title',
state: 'open',
});
});
it('returns null if no open pr exists - (includeOtherAuthors)', async () => {
const scope = await initRepoMock();
scope
.get(
'/2.0/repositories/some/repo/pullrequests?q=source.branch.name="branch"&state=open',
)
.reply(200, {
values: [],
});
const pr = await bitbucket.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
});
expect(pr).toBeNull();
});
}); });
describe('createPr()', () => { describe('createPr()', () => {

View file

@ -301,8 +301,26 @@ export async function findPr({
branchName, branchName,
prTitle, prTitle,
state = 'all', state = 'all',
includeOtherAuthors,
}: FindPRConfig): Promise<Pr | null> { }: FindPRConfig): Promise<Pr | null> {
logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`); logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
if (includeOtherAuthors) {
// PR might have been created by anyone, so don't use the cached Renovate PR list
const prs = (
await bitbucketHttp.getJson<PagedResult<PrResponse>>(
`/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`,
)
).body.values;
if (prs.length === 0) {
logger.debug(`No PR found for branch ${branchName}`);
return null;
}
return utils.prInfo(prs[0]);
}
const prList = await getPrList(); const prList = await getPrList();
const pr = prList.find( const pr = prList.find(
(p) => (p) =>

View file

@ -13,6 +13,7 @@ import type { BranchStatus } from '../../../types';
import { parseJson } from '../../../util/common'; import { parseJson } from '../../../util/common';
import * as git from '../../../util/git'; import * as git from '../../../util/git';
import { setBaseUrl } from '../../../util/http/gitea'; import { setBaseUrl } from '../../../util/http/gitea';
import { regEx } from '../../../util/regex';
import { sanitize } from '../../../util/sanitize'; import { sanitize } from '../../../util/sanitize';
import { ensureTrailingSlash } from '../../../util/url'; import { ensureTrailingSlash } from '../../../util/url';
import { getPrBodyStruct, hashBody } from '../pr-body'; import { getPrBodyStruct, hashBody } from '../pr-body';
@ -70,6 +71,7 @@ interface GiteaRepoConfig {
export const id = 'gitea'; export const id = 'gitea';
const DRAFT_PREFIX = 'WIP: '; const DRAFT_PREFIX = 'WIP: ';
const reconfigurePrRegex = regEx(/reconfigure$/g);
const defaults = { const defaults = {
hostType: 'gitea', hostType: 'gitea',
@ -109,7 +111,12 @@ function toRenovatePR(data: PR): Pr | null {
} }
const createdBy = data.user?.username; const createdBy = data.user?.username;
if (createdBy && botUserName && createdBy !== botUserName) { if (
createdBy &&
botUserName &&
!reconfigurePrRegex.test(data.head.label) &&
createdBy !== botUserName
) {
return null; return null;
} }
@ -493,6 +500,7 @@ const platform: Platform = {
branchName, branchName,
prTitle: title, prTitle: title,
state = 'all', state = 'all',
includeOtherAuthors,
}: FindPRConfig): Promise<Pr | null> { }: FindPRConfig): Promise<Pr | null> {
logger.debug(`findPr(${branchName}, ${title!}, ${state})`); logger.debug(`findPr(${branchName}, ${title!}, ${state})`);
const prList = await platform.getPrList(); const prList = await platform.getPrList();

View file

@ -2428,6 +2428,51 @@ describe('modules/platform/github/index', () => {
res = await github.findPr({ branchName: 'branch-b' }); res = await github.findPr({ branchName: 'branch-b' });
expect(res).toBeNull(); expect(res).toBeNull();
}); });
it('finds pr from other authors', async () => {
const scope = httpMock.scope(githubApiHost);
initRepoMock(scope, 'some/repo');
scope
.get('/repos/some/repo/pulls?head=some/repo:branch&state=open')
.reply(200, [
{
number: 1,
head: { ref: 'branch-a', repo: { full_name: 'some/repo' } },
title: 'branch a pr',
state: 'open',
},
]);
await github.initRepo({ repository: 'some/repo' });
expect(
await github.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
}),
).toMatchObject({
number: 1,
sourceBranch: 'branch-a',
sourceRepo: 'some/repo',
state: 'open',
title: 'branch a pr',
updated_at: undefined,
});
});
it('returns null if no pr found - (includeOtherAuthors)', async () => {
const scope = httpMock.scope(githubApiHost);
initRepoMock(scope, 'some/repo');
scope
.get('/repos/some/repo/pulls?head=some/repo:branch&state=open')
.reply(200, []);
await github.initRepo({ repository: 'some/repo' });
const pr = await github.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
});
expect(pr).toBeNull();
});
}); });
describe('createPr()', () => { describe('createPr()', () => {

View file

@ -798,8 +798,26 @@ export async function findPr({
branchName, branchName,
prTitle, prTitle,
state = 'all', state = 'all',
includeOtherAuthors,
}: FindPRConfig): Promise<GhPr | null> { }: FindPRConfig): Promise<GhPr | null> {
logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`); logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
if (includeOtherAuthors) {
const repo = config.parentRepo ?? config.repository;
// PR might have been created by anyone, so don't use the cached Renovate PR list
const response = await githubApi.getJson<GhRestPr[]>(
`repos/${repo}/pulls?head=${repo}:${branchName}&state=open`,
);
const { body: prList } = response;
if (!prList.length) {
logger.debug(`No PR found for branch ${branchName}`);
return null;
}
return coerceRestPr(prList[0]);
}
const prList = await getPrList(); const prList = await getPrList();
const pr = prList.find((p) => { const pr = prList.find((p) => {
if (p.sourceBranch !== branchName) { if (p.sourceBranch !== branchName) {

View file

@ -1678,6 +1678,49 @@ describe('modules/platform/gitlab/index', () => {
}); });
expect(res).toBeDefined(); expect(res).toBeDefined();
}); });
it('finds pr from other authors', async () => {
httpMock
.scope(gitlabApiHost)
.get(
'/api/v4/projects/undefined/merge_requests?source_branch=branch&state=opened',
)
.reply(200, [
{
iid: 1,
source_branch: 'branch',
title: 'branch a pr',
state: 'opened',
},
]);
expect(
await gitlab.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
}),
).toMatchObject({
number: 1,
sourceBranch: 'branch',
state: 'open',
title: 'branch a pr',
});
});
it('returns null if no pr found - (includeOtherAuthors)', async () => {
httpMock
.scope(gitlabApiHost)
.get(
'/api/v4/projects/undefined/merge_requests?source_branch=branch&state=opened',
)
.reply(200, []);
const pr = await gitlab.findPr({
branchName: 'branch',
state: 'open',
includeOtherAuthors: true,
});
expect(pr).toBeNull();
});
}); });
async function initPlatform(gitlabVersion: string) { async function initPlatform(gitlabVersion: string) {

View file

@ -863,8 +863,36 @@ export async function findPr({
branchName, branchName,
prTitle, prTitle,
state = 'all', state = 'all',
includeOtherAuthors,
}: FindPRConfig): Promise<Pr | null> { }: FindPRConfig): Promise<Pr | null> {
logger.debug(`findPr(${branchName}, ${prTitle!}, ${state})`); logger.debug(`findPr(${branchName}, ${prTitle!}, ${state})`);
if (includeOtherAuthors) {
// PR might have been created by anyone, so don't use the cached Renovate MR list
const response = await gitlabApi.getJson<GitLabMergeRequest[]>(
`projects/${config.repository}/merge_requests?source_branch=${branchName}&state=opened`,
);
const { body: mrList } = response;
if (!mrList.length) {
logger.debug(`No MR found for branch ${branchName}`);
return null;
}
// return the latest merge request
const mr = mrList[0];
// only pass necessary info
const pr: GitlabPr = {
sourceBranch: mr.source_branch,
number: mr.iid,
state: 'open',
title: mr.title,
};
return massagePr(pr);
}
const prList = await getPrList(); const prList = await getPrList();
return ( return (
prList.find( prList.find(

View file

@ -143,6 +143,7 @@ export interface FindPRConfig {
state?: 'open' | 'closed' | '!open' | 'all'; state?: 'open' | 'closed' | '!open' | 'all';
refreshCache?: boolean; refreshCache?: boolean;
targetBranch?: string | null; targetBranch?: string | null;
includeOtherAuthors?: boolean;
} }
export interface MergePRConfig { export interface MergePRConfig {
branchName?: string; branchName?: string;

View file

@ -8,6 +8,7 @@ import {
platform, platform,
scm, scm,
} from '../../../../test/util'; } from '../../../../test/util';
import { GlobalConfig } from '../../../config/global';
import { logger } from '../../../logger'; import { logger } from '../../../logger';
import type { Pr } from '../../../modules/platform/types'; import type { Pr } from '../../../modules/platform/types';
import * as _cache from '../../../util/cache/repository'; import * as _cache from '../../../util/cache/repository';
@ -39,8 +40,8 @@ describe('workers/repository/reconfigure/index', () => {
cache.getCache.mockReturnValue({}); cache.getCache.mockReturnValue({});
git.getBranchCommit.mockReturnValue('sha' as LongCommitSha); git.getBranchCommit.mockReturnValue('sha' as LongCommitSha);
fs.readLocalFile.mockResolvedValue(null); fs.readLocalFile.mockResolvedValue(null);
platform.getBranchPr.mockResolvedValue(null);
platform.getBranchStatusCheck.mockResolvedValue(null); platform.getBranchStatusCheck.mockResolvedValue(null);
GlobalConfig.reset();
}); });
it('no effect on repo with no reconfigure branch', async () => { it('no effect on repo with no reconfigure branch', async () => {
@ -126,7 +127,7 @@ describe('workers/repository/reconfigure/index', () => {
"enabledManagers": ["docker"] "enabledManagers": ["docker"]
} }
`); `);
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>({ number: 1 })); platform.findPr.mockResolvedValueOnce(mock<Pr>({ number: 1 }));
await validateReconfigureBranch(config); await validateReconfigureBranch(config);
expect(logger.debug).toHaveBeenCalledWith( expect(logger.debug).toHaveBeenCalledWith(
{ errors: expect.any(String) }, { errors: expect.any(String) },
@ -229,7 +230,6 @@ describe('workers/repository/reconfigure/index', () => {
"enabledManagers": ["npm",] "enabledManagers": ["npm",]
} }
`); `);
platform.getBranchPr.mockResolvedValueOnce(mock<Pr>({ number: 1 }));
await validateReconfigureBranch(config); await validateReconfigureBranch(config);
expect(platform.setBranchStatus).toHaveBeenCalledWith({ expect(platform.setBranchStatus).toHaveBeenCalledWith({
branchName: 'prefix/reconfigure', branchName: 'prefix/reconfigure',

View file

@ -161,10 +161,11 @@ export async function validateReconfigureBranch(
); );
// add comment to reconfigure PR if it exists // add comment to reconfigure PR if it exists
const branchPr = await platform.getBranchPr( const branchPr = await platform.findPr({
branchName, branchName,
config.defaultBranch, state: 'open',
); includeOtherAuthors: true,
});
if (branchPr) { if (branchPr) {
let body = `There is an error with this repository's Renovate configuration that needs to be fixed.\n\n`; let body = `There is an error with this repository's Renovate configuration that needs to be fixed.\n\n`;
body += `Location: \`${configFileName}\`\n`; body += `Location: \`${configFileName}\`\n`;
@ -179,6 +180,7 @@ export async function validateReconfigureBranch(
content: body, content: body,
}); });
} }
await setBranchStatus(branchName, 'Validation Failed', 'red', context); await setBranchStatus(branchName, 'Validation Failed', 'red', context);
setReconfigureBranchCache(branchSha, false); setReconfigureBranchCache(branchSha, false);
await scm.checkoutBranch(config.baseBranch!); await scm.checkoutBranch(config.baseBranch!);