mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-12 06:56:24 +00:00
fix(reconfigure/pr): find reconfigure pr separately (#25954)
Co-authored-by: Rhys Arkins <rhys@arkins.net>
This commit is contained in:
parent
831fba0262
commit
5f163552a9
12 changed files with 280 additions and 7 deletions
|
@ -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()', () => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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()', () => {
|
||||||
|
|
|
@ -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) =>
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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()', () => {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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',
|
||||||
|
|
|
@ -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!);
|
||||||
|
|
Loading…
Reference in a new issue