mirror of
https://github.com/renovatebot/renovate.git
synced 2025-01-10 05:56:26 +00:00
Compare commits
27 commits
0a3601041e
...
5d640db608
Author | SHA1 | Date | |
---|---|---|---|
|
5d640db608 | ||
|
adede1d309 | ||
|
2eca39ad90 | ||
|
88e2336945 | ||
|
a6d9d910e6 | ||
|
add6ca5720 | ||
|
b93ee7a628 | ||
|
74137a8460 | ||
|
e0b6f8d5ed | ||
|
53743b27bf | ||
|
b1ce02b44b | ||
|
bb317ce921 | ||
|
4dfc817793 | ||
|
c4c81e30fc | ||
|
7923d5b955 | ||
|
539c12f744 | ||
|
ac30f2b204 | ||
|
bd05816dd1 | ||
|
dbdf41a936 | ||
|
c679c28d01 | ||
|
5eb284603f | ||
|
1d8daf5095 | ||
|
7877331cf7 | ||
|
6ffa5b615f | ||
|
6ac7b9e089 | ||
|
107a298054 | ||
|
46f8eed01f |
19 changed files with 134 additions and 216 deletions
|
@ -36,7 +36,7 @@ services:
|
||||||
otel-collector:
|
otel-collector:
|
||||||
# Using the Contrib version to access the spanmetrics connector.
|
# Using the Contrib version to access the spanmetrics connector.
|
||||||
# If you don't need the spanmetrics connector, you can use the standard version
|
# If you don't need the spanmetrics connector, you can use the standard version
|
||||||
image: otel/opentelemetry-collector-contrib:0.116.1
|
image: otel/opentelemetry-collector-contrib:0.117.0
|
||||||
volumes:
|
volumes:
|
||||||
- ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
- ./otel-collector-config.yml:/etc/otelcol-contrib/config.yaml
|
||||||
ports:
|
ports:
|
||||||
|
|
|
@ -102,7 +102,6 @@ describe('modules/platform/gerrit/client', () => {
|
||||||
'footer:Renovate-Branch=dependency-xyz',
|
'footer:Renovate-Branch=dependency-xyz',
|
||||||
{ branchName: 'dependency-xyz' },
|
{ branchName: 'dependency-xyz' },
|
||||||
],
|
],
|
||||||
['hashtag:sourceBranch-dependency-xyz', { branchName: 'dependency-xyz' }], // for backwards compatibility
|
|
||||||
['label:Code-Review=-2', { branchName: 'dependency-xyz', label: '-2' }],
|
['label:Code-Review=-2', { branchName: 'dependency-xyz', label: '-2' }],
|
||||||
[
|
[
|
||||||
'branch:otherTarget',
|
'branch:otherTarget',
|
||||||
|
@ -399,10 +398,20 @@ describe('modules/platform/gerrit/client', () => {
|
||||||
|
|
||||||
describe('approveChange()', () => {
|
describe('approveChange()', () => {
|
||||||
it('already approved - do nothing', async () => {
|
it('already approved - do nothing', async () => {
|
||||||
const change = partial<GerritChange>({});
|
const change = partial<GerritChange>({
|
||||||
|
labels: {
|
||||||
|
'Code-Review': {
|
||||||
|
all: [{ value: 2 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
httpMock
|
httpMock
|
||||||
.scope(gerritEndpointUrl)
|
.scope(gerritEndpointUrl)
|
||||||
.get((url) => url.includes('/a/changes/123456?o='))
|
.get(
|
||||||
|
(url) =>
|
||||||
|
url.includes('/a/changes/123456?o=') &&
|
||||||
|
url.includes('o=DETAILED_LABELS'),
|
||||||
|
)
|
||||||
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
||||||
await expect(client.approveChange(123456)).toResolve();
|
await expect(client.approveChange(123456)).toResolve();
|
||||||
});
|
});
|
||||||
|
@ -411,17 +420,54 @@ describe('modules/platform/gerrit/client', () => {
|
||||||
const change = partial<GerritChange>({ labels: {} });
|
const change = partial<GerritChange>({ labels: {} });
|
||||||
httpMock
|
httpMock
|
||||||
.scope(gerritEndpointUrl)
|
.scope(gerritEndpointUrl)
|
||||||
.get((url) => url.includes('/a/changes/123456?o='))
|
.get(
|
||||||
|
(url) =>
|
||||||
|
url.includes('/a/changes/123456?o=') &&
|
||||||
|
url.includes('o=DETAILED_LABELS'),
|
||||||
|
)
|
||||||
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
||||||
|
|
||||||
await expect(client.approveChange(123456)).toResolve();
|
await expect(client.approveChange(123456)).toResolve();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('not already approved - approve now', async () => {
|
it('not already approved - approve now', async () => {
|
||||||
const change = partial<GerritChange>({ labels: { 'Code-Review': {} } });
|
const change = partial<GerritChange>({
|
||||||
|
labels: { 'Code-Review': { all: [] } },
|
||||||
|
});
|
||||||
httpMock
|
httpMock
|
||||||
.scope(gerritEndpointUrl)
|
.scope(gerritEndpointUrl)
|
||||||
.get((url) => url.includes('/a/changes/123456?o='))
|
.get(
|
||||||
|
(url) =>
|
||||||
|
url.includes('/a/changes/123456?o=') &&
|
||||||
|
url.includes('o=DETAILED_LABELS'),
|
||||||
|
)
|
||||||
|
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
||||||
|
const approveMock = httpMock
|
||||||
|
.scope(gerritEndpointUrl)
|
||||||
|
.post('/a/changes/123456/revisions/current/review', {
|
||||||
|
labels: { 'Code-Review': +2 },
|
||||||
|
notify: 'NONE',
|
||||||
|
})
|
||||||
|
.reply(200, gerritRestResponse(''), jsonResultHeader);
|
||||||
|
await expect(client.approveChange(123456)).toResolve();
|
||||||
|
expect(approveMock.isDone()).toBeTrue();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('not already approved because of +1 - approve now', async () => {
|
||||||
|
const change = partial<GerritChange>({
|
||||||
|
labels: {
|
||||||
|
'Code-Review': {
|
||||||
|
all: [{ value: 1 }],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
httpMock
|
||||||
|
.scope(gerritEndpointUrl)
|
||||||
|
.get(
|
||||||
|
(url) =>
|
||||||
|
url.includes('/a/changes/123456?o=') &&
|
||||||
|
url.includes('o=DETAILED_LABELS'),
|
||||||
|
)
|
||||||
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
.reply(200, gerritRestResponse(change), jsonResultHeader);
|
||||||
const approveMock = httpMock
|
const approveMock = httpMock
|
||||||
.scope(gerritEndpointUrl)
|
.scope(gerritEndpointUrl)
|
||||||
|
@ -434,63 +480,6 @@ describe('modules/platform/gerrit/client', () => {
|
||||||
expect(approveMock.isDone()).toBeTrue();
|
expect(approveMock.isDone()).toBeTrue();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('wasApprovedBy()', () => {
|
|
||||||
it('label not exists', () => {
|
|
||||||
expect(
|
|
||||||
client.wasApprovedBy(partial<GerritChange>({}), 'user'),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('not approved by anyone', () => {
|
|
||||||
expect(
|
|
||||||
client.wasApprovedBy(
|
|
||||||
partial<GerritChange>({
|
|
||||||
labels: {
|
|
||||||
'Code-Review': {},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'user',
|
|
||||||
),
|
|
||||||
).toBeUndefined();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('approved by given user', () => {
|
|
||||||
expect(
|
|
||||||
client.wasApprovedBy(
|
|
||||||
partial<GerritChange>({
|
|
||||||
labels: {
|
|
||||||
'Code-Review': {
|
|
||||||
approved: {
|
|
||||||
_account_id: 1,
|
|
||||||
username: 'user',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'user',
|
|
||||||
),
|
|
||||||
).toBeTrue();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('approved by given other', () => {
|
|
||||||
expect(
|
|
||||||
client.wasApprovedBy(
|
|
||||||
partial<GerritChange>({
|
|
||||||
labels: {
|
|
||||||
'Code-Review': {
|
|
||||||
approved: {
|
|
||||||
_account_id: 1,
|
|
||||||
username: 'other',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
'user',
|
|
||||||
),
|
|
||||||
).toBeFalse();
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function gerritRestResponse(body: any): any {
|
function gerritRestResponse(body: any): any {
|
||||||
|
|
|
@ -2,6 +2,7 @@ import { REPOSITORY_ARCHIVED } from '../../../constants/error-messages';
|
||||||
import { logger } from '../../../logger';
|
import { logger } from '../../../logger';
|
||||||
import { GerritHttp } from '../../../util/http/gerrit';
|
import { GerritHttp } from '../../../util/http/gerrit';
|
||||||
import { regEx } from '../../../util/regex';
|
import { regEx } from '../../../util/regex';
|
||||||
|
import { getQueryString } from '../../../util/url';
|
||||||
import type {
|
import type {
|
||||||
GerritAccountInfo,
|
GerritAccountInfo,
|
||||||
GerritBranchInfo,
|
GerritBranchInfo,
|
||||||
|
@ -72,10 +73,14 @@ class GerritClient {
|
||||||
return changes.body;
|
return changes.body;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getChange(changeNumber: number): Promise<GerritChange> {
|
async getChange(
|
||||||
|
changeNumber: number,
|
||||||
|
extraOptions?: string[],
|
||||||
|
): Promise<GerritChange> {
|
||||||
|
const options = [...this.requestDetails, ...(extraOptions ?? [])];
|
||||||
|
const queryString = getQueryString({ o: options });
|
||||||
const changes = await this.gerritHttp.getJson<GerritChange>(
|
const changes = await this.gerritHttp.getJson<GerritChange>(
|
||||||
`a/changes/${changeNumber}?` +
|
`a/changes/${changeNumber}?${queryString}`,
|
||||||
this.requestDetails.map((det) => `o=${det}`).join('&'),
|
|
||||||
);
|
);
|
||||||
return changes.body;
|
return changes.body;
|
||||||
}
|
}
|
||||||
|
@ -196,15 +201,11 @@ class GerritClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkIfApproved(changeId: number): Promise<boolean> {
|
async checkIfApproved(changeId: number): Promise<boolean> {
|
||||||
const change = await client.getChange(changeId);
|
const change = await client.getChange(changeId, ['DETAILED_LABELS']);
|
||||||
const reviewLabels = change?.labels?.['Code-Review'];
|
const reviewLabel = change?.labels?.['Code-Review'];
|
||||||
return reviewLabels === undefined || reviewLabels.approved !== undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
wasApprovedBy(change: GerritChange, username: string): boolean | undefined {
|
|
||||||
return (
|
return (
|
||||||
change.labels?.['Code-Review'].approved &&
|
reviewLabel === undefined ||
|
||||||
change.labels['Code-Review'].approved.username === username
|
reviewLabel.all?.some((label) => label.value === 2) === true
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -220,16 +221,7 @@ class GerritClient {
|
||||||
const filterState = mapPrStateToGerritFilter(searchConfig.state);
|
const filterState = mapPrStateToGerritFilter(searchConfig.state);
|
||||||
const filters = ['owner:self', 'project:' + repository, filterState];
|
const filters = ['owner:self', 'project:' + repository, filterState];
|
||||||
if (searchConfig.branchName) {
|
if (searchConfig.branchName) {
|
||||||
filters.push(
|
filters.push(`footer:Renovate-Branch=${searchConfig.branchName}`);
|
||||||
...[
|
|
||||||
'(',
|
|
||||||
`footer:Renovate-Branch=${searchConfig.branchName}`,
|
|
||||||
// for backwards compatibility
|
|
||||||
'OR',
|
|
||||||
`hashtag:sourceBranch-${searchConfig.branchName}`,
|
|
||||||
')',
|
|
||||||
],
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
if (searchConfig.targetBranch) {
|
if (searchConfig.targetBranch) {
|
||||||
filters.push(`branch:${searchConfig.targetBranch}`);
|
filters.push(`branch:${searchConfig.targetBranch}`);
|
||||||
|
|
|
@ -187,28 +187,6 @@ describe('modules/platform/gerrit/index', () => {
|
||||||
gerrit.writeToConfig({ labels: {} });
|
gerrit.writeToConfig({ labels: {} });
|
||||||
});
|
});
|
||||||
|
|
||||||
it('updatePr() - auto approve enabled', async () => {
|
|
||||||
const change = partial<GerritChange>({
|
|
||||||
current_revision: 'some-revision',
|
|
||||||
revisions: {
|
|
||||||
'some-revision': partial<GerritRevisionInfo>({
|
|
||||||
commit: {
|
|
||||||
message: 'some message',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
clientMock.getChange.mockResolvedValueOnce(change);
|
|
||||||
await gerrit.updatePr({
|
|
||||||
number: 123456,
|
|
||||||
prTitle: 'subject',
|
|
||||||
platformPrOptions: {
|
|
||||||
autoApprove: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(clientMock.approveChange).toHaveBeenCalledWith(123456);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('updatePr() - closed => abandon the change', async () => {
|
it('updatePr() - closed => abandon the change', async () => {
|
||||||
const change = partial<GerritChange>({});
|
const change = partial<GerritChange>({});
|
||||||
clientMock.getChange.mockResolvedValueOnce(change);
|
clientMock.getChange.mockResolvedValueOnce(change);
|
||||||
|
@ -309,7 +287,7 @@ describe('modules/platform/gerrit/index', () => {
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('createPr() - update body WITHOUT approve', async () => {
|
it('createPr() - update body', async () => {
|
||||||
const pr = await gerrit.createPr({
|
const pr = await gerrit.createPr({
|
||||||
sourceBranch: 'source',
|
sourceBranch: 'source',
|
||||||
targetBranch: 'target',
|
targetBranch: 'target',
|
||||||
|
@ -325,26 +303,6 @@ describe('modules/platform/gerrit/index', () => {
|
||||||
'body',
|
'body',
|
||||||
TAG_PULL_REQUEST_BODY,
|
TAG_PULL_REQUEST_BODY,
|
||||||
);
|
);
|
||||||
expect(clientMock.approveChange).not.toHaveBeenCalled();
|
|
||||||
});
|
|
||||||
|
|
||||||
it('createPr() - update body and approve', async () => {
|
|
||||||
const pr = await gerrit.createPr({
|
|
||||||
sourceBranch: 'source',
|
|
||||||
targetBranch: 'target',
|
|
||||||
prTitle: change.subject,
|
|
||||||
prBody: 'body',
|
|
||||||
platformPrOptions: {
|
|
||||||
autoApprove: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(pr).toHaveProperty('number', 123456);
|
|
||||||
expect(clientMock.addMessageIfNotAlreadyExists).toHaveBeenCalledWith(
|
|
||||||
123456,
|
|
||||||
'body',
|
|
||||||
TAG_PULL_REQUEST_BODY,
|
|
||||||
);
|
|
||||||
expect(clientMock.approveChange).toHaveBeenCalledWith(123456);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -750,6 +708,13 @@ describe('modules/platform/gerrit/index', () => {
|
||||||
//TODO: add some tests for Gerrit-specific replacements..
|
//TODO: add some tests for Gerrit-specific replacements..
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('approvePrForAutomerge()', () => {
|
||||||
|
it('approvePrForAutomerge() - calls approveChange', async () => {
|
||||||
|
await expect(gerrit.approvePrForAutomerge(123456)).toResolve();
|
||||||
|
expect(clientMock.approveChange).toHaveBeenCalledWith(123456);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('currently unused/not-implemented functions', () => {
|
describe('currently unused/not-implemented functions', () => {
|
||||||
it('deleteLabel()', async () => {
|
it('deleteLabel()', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
|
|
|
@ -160,9 +160,6 @@ export async function updatePr(prConfig: UpdatePrConfig): Promise<void> {
|
||||||
TAG_PULL_REQUEST_BODY,
|
TAG_PULL_REQUEST_BODY,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (prConfig.platformPrOptions?.autoApprove) {
|
|
||||||
await client.approveChange(prConfig.number);
|
|
||||||
}
|
|
||||||
if (prConfig.state && prConfig.state === 'closed') {
|
if (prConfig.state && prConfig.state === 'closed') {
|
||||||
await client.abandonChange(prConfig.number);
|
await client.abandonChange(prConfig.number);
|
||||||
}
|
}
|
||||||
|
@ -195,9 +192,6 @@ export async function createPr(prConfig: CreatePRConfig): Promise<Pr | null> {
|
||||||
prConfig.prBody,
|
prConfig.prBody,
|
||||||
TAG_PULL_REQUEST_BODY,
|
TAG_PULL_REQUEST_BODY,
|
||||||
);
|
);
|
||||||
if (prConfig.platformPrOptions?.autoApprove) {
|
|
||||||
await client.approveChange(pr._number);
|
|
||||||
}
|
|
||||||
return getPr(pr._number);
|
return getPr(pr._number);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -442,3 +436,14 @@ export function findIssue(title: string): Promise<Issue | null> {
|
||||||
export function getIssueList(): Promise<Issue[]> {
|
export function getIssueList(): Promise<Issue[]> {
|
||||||
return Promise.resolve([]);
|
return Promise.resolve([]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The Code-Review +2 vote of when the change was created or updated in Gerrit
|
||||||
|
* may have been downgraded by a CI check utilizing the same account as
|
||||||
|
* Renovate (e.g. SonarQube which posts Code-Review +1). This function will
|
||||||
|
* vote with +2 again on the change, if needed, before Renovate attempt to
|
||||||
|
* automerge it.
|
||||||
|
*/
|
||||||
|
export async function approvePrForAutomerge(number: number): Promise<void> {
|
||||||
|
await client.approveChange(number);
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,6 @@ Support for Gerrit is currently _experimental_, meaning that it _might_ still ha
|
||||||
|
|
||||||
Renovate stores its metadata in the _commit message footer_.
|
Renovate stores its metadata in the _commit message footer_.
|
||||||
|
|
||||||
Previously Renovate stored metadata in Gerrit's _hashtags_.
|
|
||||||
To keep backwards compatibility, Renovate still reads metadata from hashtags.
|
|
||||||
But Renovate _always_ puts its metadata in the _commit message footer_!
|
|
||||||
When the Renovate maintainers mark Gerrit support as stable, the maintainers will remove the "read metadata from hashtags" feature.
|
|
||||||
This means changes without metadata in the commit message footer will be "forgotten" by Renovate.
|
|
||||||
|
|
||||||
## Authentication
|
## Authentication
|
||||||
|
|
||||||
<figure markdown>
|
<figure markdown>
|
||||||
|
|
|
@ -369,7 +369,6 @@ describe('modules/platform/gerrit/scm', () => {
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
clientMock.findChanges.mockResolvedValueOnce([existingChange]);
|
clientMock.findChanges.mockResolvedValueOnce([existingChange]);
|
||||||
clientMock.wasApprovedBy.mockReturnValueOnce(true);
|
|
||||||
git.prepareCommit.mockResolvedValueOnce({
|
git.prepareCommit.mockResolvedValueOnce({
|
||||||
commitSha: 'commitSha' as LongCommitSha,
|
commitSha: 'commitSha' as LongCommitSha,
|
||||||
parentCommitSha: 'parentSha' as LongCommitSha,
|
parentCommitSha: 'parentSha' as LongCommitSha,
|
||||||
|
@ -385,6 +384,7 @@ describe('modules/platform/gerrit/scm', () => {
|
||||||
message: 'commit msg',
|
message: 'commit msg',
|
||||||
files: [],
|
files: [],
|
||||||
prTitle: 'pr title',
|
prTitle: 'pr title',
|
||||||
|
autoApprove: true,
|
||||||
}),
|
}),
|
||||||
).toBe('commitSha');
|
).toBe('commitSha');
|
||||||
expect(git.prepareCommit).toHaveBeenCalledWith({
|
expect(git.prepareCommit).toHaveBeenCalledWith({
|
||||||
|
@ -396,19 +396,15 @@ describe('modules/platform/gerrit/scm', () => {
|
||||||
'Renovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
|
'Renovate-Branch: renovate/dependency-1.x\nChange-Id: ...',
|
||||||
],
|
],
|
||||||
prTitle: 'pr title',
|
prTitle: 'pr title',
|
||||||
|
autoApprove: true,
|
||||||
force: true,
|
force: true,
|
||||||
});
|
});
|
||||||
expect(git.fetchRevSpec).toHaveBeenCalledWith('refs/changes/1/2');
|
expect(git.fetchRevSpec).toHaveBeenCalledWith('refs/changes/1/2');
|
||||||
expect(git.pushCommit).toHaveBeenCalledWith({
|
expect(git.pushCommit).toHaveBeenCalledWith({
|
||||||
files: [],
|
files: [],
|
||||||
sourceRef: 'renovate/dependency-1.x',
|
sourceRef: 'renovate/dependency-1.x',
|
||||||
targetRef: 'refs/for/main%notify=NONE',
|
targetRef: 'refs/for/main%notify=NONE,l=Code-Review+2',
|
||||||
});
|
});
|
||||||
expect(clientMock.wasApprovedBy).toHaveBeenCalledWith(
|
|
||||||
existingChange,
|
|
||||||
'user',
|
|
||||||
);
|
|
||||||
expect(clientMock.approveChange).toHaveBeenCalledWith(123456);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -129,19 +129,16 @@ export class GerritScm extends DefaultGitScm {
|
||||||
hasChanges = await git.hasDiff('HEAD', 'FETCH_HEAD'); //avoid empty patchsets
|
hasChanges = await git.hasDiff('HEAD', 'FETCH_HEAD'); //avoid empty patchsets
|
||||||
}
|
}
|
||||||
if (hasChanges || commit.force) {
|
if (hasChanges || commit.force) {
|
||||||
|
const pushOptions = ['notify=NONE'];
|
||||||
|
if (commit.autoApprove) {
|
||||||
|
pushOptions.push('l=Code-Review+2');
|
||||||
|
}
|
||||||
const pushResult = await git.pushCommit({
|
const pushResult = await git.pushCommit({
|
||||||
sourceRef: commit.branchName,
|
sourceRef: commit.branchName,
|
||||||
targetRef: `refs/for/${commit.baseBranch!}%notify=NONE`,
|
targetRef: `refs/for/${commit.baseBranch!}%${pushOptions.join(',')}`,
|
||||||
files: commit.files,
|
files: commit.files,
|
||||||
});
|
});
|
||||||
if (pushResult) {
|
if (pushResult) {
|
||||||
//existingChange was the old change before commit/push. we need to approve again, if it was previously approved from renovate
|
|
||||||
if (
|
|
||||||
existingChange &&
|
|
||||||
client.wasApprovedBy(existingChange, username)
|
|
||||||
) {
|
|
||||||
await client.approveChange(existingChange._number);
|
|
||||||
}
|
|
||||||
return commitSha;
|
return commitSha;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -34,10 +34,6 @@ export type GerritReviewersType = 'REVIEWER' | 'CC' | 'REMOVED';
|
||||||
|
|
||||||
export interface GerritChange {
|
export interface GerritChange {
|
||||||
branch: string;
|
branch: string;
|
||||||
/**
|
|
||||||
* for backwards compatibility
|
|
||||||
*/
|
|
||||||
hashtags?: string[];
|
|
||||||
change_id: string;
|
change_id: string;
|
||||||
subject: string;
|
subject: string;
|
||||||
status: GerritChangeStatus;
|
status: GerritChangeStatus;
|
||||||
|
@ -79,6 +75,8 @@ export interface GerritLabelInfo {
|
||||||
rejected?: GerritAccountInfo;
|
rejected?: GerritAccountInfo;
|
||||||
/** If true, the label blocks submit operation. If not set, the default is false. */
|
/** If true, the label blocks submit operation. If not set, the default is false. */
|
||||||
blocking?: boolean;
|
blocking?: boolean;
|
||||||
|
/** List of votes. Only set when o=DETAILED_LABELS. */
|
||||||
|
all?: GerritApprovalInfo[];
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface GerritActionInfo {
|
export interface GerritActionInfo {
|
||||||
|
@ -101,3 +99,8 @@ export interface GerritMergeableInfo {
|
||||||
| 'CHERRY_PICK';
|
| 'CHERRY_PICK';
|
||||||
mergeable: boolean;
|
mergeable: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface GerritApprovalInfo {
|
||||||
|
value?: number;
|
||||||
|
username?: string;
|
||||||
|
}
|
||||||
|
|
|
@ -186,47 +186,6 @@ describe('modules/platform/gerrit/utils', () => {
|
||||||
});
|
});
|
||||||
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-1.x');
|
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-1.x');
|
||||||
});
|
});
|
||||||
|
|
||||||
// for backwards compatibility
|
|
||||||
it('no commit message but with hashtags', () => {
|
|
||||||
const change = partial<GerritChange>({
|
|
||||||
hashtags: ['sourceBranch-renovate/dependency-1.x'],
|
|
||||||
});
|
|
||||||
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-1.x');
|
|
||||||
});
|
|
||||||
|
|
||||||
// for backwards compatibility
|
|
||||||
it('commit message with no footer but with hashtags', () => {
|
|
||||||
const change = partial<GerritChange>({
|
|
||||||
hashtags: ['sourceBranch-renovate/dependency-1.x'],
|
|
||||||
current_revision: 'abc',
|
|
||||||
revisions: {
|
|
||||||
abc: partial<GerritRevisionInfo>({
|
|
||||||
commit: {
|
|
||||||
message: 'some message...',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-1.x');
|
|
||||||
});
|
|
||||||
|
|
||||||
// for backwards compatibility
|
|
||||||
it('prefers the footer over the hashtags', () => {
|
|
||||||
const change = partial<GerritChange>({
|
|
||||||
hashtags: ['sourceBranch-renovate/dependency-1.x'],
|
|
||||||
current_revision: 'abc',
|
|
||||||
revisions: {
|
|
||||||
abc: partial<GerritRevisionInfo>({
|
|
||||||
commit: {
|
|
||||||
message:
|
|
||||||
'Some change\n\nRenovate-Branch: renovate/dependency-2.x\nChange-Id: ...',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
},
|
|
||||||
});
|
|
||||||
expect(utils.extractSourceBranch(change)).toBe('renovate/dependency-2.x');
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('findPullRequestBody()', () => {
|
describe('findPullRequestBody()', () => {
|
||||||
|
|
|
@ -101,13 +101,6 @@ export function extractSourceBranch(change: GerritChange): string | undefined {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// for backwards compatibility
|
|
||||||
if (!sourceBranch) {
|
|
||||||
sourceBranch = change.hashtags
|
|
||||||
?.find((tag) => tag.startsWith('sourceBranch-'))
|
|
||||||
?.replace('sourceBranch-', '');
|
|
||||||
}
|
|
||||||
|
|
||||||
return sourceBranch ?? undefined;
|
return sourceBranch ?? undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -283,6 +283,11 @@ export interface Platform {
|
||||||
|
|
||||||
maxBodyLength(): number;
|
maxBodyLength(): number;
|
||||||
labelCharLimit?(): number;
|
labelCharLimit?(): number;
|
||||||
|
/**
|
||||||
|
* Platforms that support `autoApprove` can implement this function. It will be
|
||||||
|
* invoked during automerge before the PR branch status is checked.
|
||||||
|
*/
|
||||||
|
approvePrForAutomerge?(number: number): Promise<void>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PlatformScm {
|
export interface PlatformScm {
|
||||||
|
|
|
@ -86,6 +86,8 @@ export interface CommitFilesConfig {
|
||||||
platformCommit?: PlatformCommitOptions;
|
platformCommit?: PlatformCommitOptions;
|
||||||
/** Only needed by Gerrit platform */
|
/** Only needed by Gerrit platform */
|
||||||
prTitle?: string;
|
prTitle?: string;
|
||||||
|
/** Only needed by Gerrit platform */
|
||||||
|
autoApprove?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PushFilesConfig {
|
export interface PushFilesConfig {
|
||||||
|
|
|
@ -62,5 +62,7 @@ export function commitFilesToBranch(
|
||||||
platformCommit: config.platformCommit,
|
platformCommit: config.platformCommit,
|
||||||
// Only needed by Gerrit platform
|
// Only needed by Gerrit platform
|
||||||
prTitle: config.prTitle,
|
prTitle: config.prTitle,
|
||||||
|
// Only needed by Gerrit platform
|
||||||
|
autoApprove: config.autoApprove,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -124,6 +124,18 @@ describe('workers/repository/update/pr/automerge', () => {
|
||||||
expect(platform.mergePr).toHaveBeenCalledTimes(0);
|
expect(platform.mergePr).toHaveBeenCalledTimes(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should attempt to auto-approve if supported', async () => {
|
||||||
|
config.autoApprove = true;
|
||||||
|
config.automerge = true;
|
||||||
|
config.pruneBranchAfterAutomerge = true;
|
||||||
|
platform.getBranchStatus.mockResolvedValueOnce('green');
|
||||||
|
platform.mergePr.mockResolvedValueOnce(true);
|
||||||
|
const res = await prAutomerge.checkAutoMerge(pr, config);
|
||||||
|
expect(res).toEqual({ automerged: true, branchRemoved: true });
|
||||||
|
expect(platform.approvePrForAutomerge).toHaveBeenCalledTimes(1);
|
||||||
|
expect(platform.mergePr).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
|
||||||
it('should not automerge if enabled and pr is unmergeable', async () => {
|
it('should not automerge if enabled and pr is unmergeable', async () => {
|
||||||
config.automerge = true;
|
config.automerge = true;
|
||||||
scm.isBranchConflicted.mockResolvedValueOnce(true);
|
scm.isBranchConflicted.mockResolvedValueOnce(true);
|
||||||
|
|
|
@ -69,6 +69,10 @@ export async function checkAutoMerge(
|
||||||
prAutomergeBlockReason: 'PlatformNotReady',
|
prAutomergeBlockReason: 'PlatformNotReady',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
if (config.autoApprove && platform.approvePrForAutomerge) {
|
||||||
|
logger.debug('Auto-approving PR for automerge');
|
||||||
|
await platform.approvePrForAutomerge(pr.number);
|
||||||
|
}
|
||||||
const branchStatus = await resolveBranchStatus(
|
const branchStatus = await resolveBranchStatus(
|
||||||
branchName,
|
branchName,
|
||||||
!!config.internalChecksAsSuccess,
|
!!config.internalChecksAsSuccess,
|
||||||
|
|
|
@ -336,7 +336,7 @@
|
||||||
"jest-mock-extended": "3.0.7",
|
"jest-mock-extended": "3.0.7",
|
||||||
"jest-snapshot": "29.7.0",
|
"jest-snapshot": "29.7.0",
|
||||||
"markdownlint-cli2": "0.17.1",
|
"markdownlint-cli2": "0.17.1",
|
||||||
"memfs": "4.15.2",
|
"memfs": "4.15.3",
|
||||||
"nock": "13.5.6",
|
"nock": "13.5.6",
|
||||||
"npm-run-all2": "7.0.2",
|
"npm-run-all2": "7.0.2",
|
||||||
"nyc": "17.1.0",
|
"nyc": "17.1.0",
|
||||||
|
|
|
@ -581,8 +581,8 @@ importers:
|
||||||
specifier: 0.17.1
|
specifier: 0.17.1
|
||||||
version: 0.17.1
|
version: 0.17.1
|
||||||
memfs:
|
memfs:
|
||||||
specifier: 4.15.2
|
specifier: 4.15.3
|
||||||
version: 4.15.2
|
version: 4.15.3
|
||||||
nock:
|
nock:
|
||||||
specifier: 13.5.6
|
specifier: 13.5.6
|
||||||
version: 13.5.6
|
version: 13.5.6
|
||||||
|
@ -4555,8 +4555,8 @@ packages:
|
||||||
mdurl@2.0.0:
|
mdurl@2.0.0:
|
||||||
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
resolution: {integrity: sha512-Lf+9+2r+Tdp5wXDXC4PcIBjTDtq4UKjCPMQhKIuzpJNW0b96kVqSwW0bT7FhRSfmAiFYgP+SCRvdrDozfh0U5w==}
|
||||||
|
|
||||||
memfs@4.15.2:
|
memfs@4.15.3:
|
||||||
resolution: {integrity: sha512-n8/qP8AT6CtY6kxCPYgYVusT5rS6axaT66dD3tYi2lm+l1iMH7YYpmW8H/qL5bfV4YvInCCgUDAWIRvrNS7kbQ==}
|
resolution: {integrity: sha512-vR/g1SgqvKJgAyYla+06G4p/EOcEmwhYuVb1yc1ixcKf8o/sh7Zngv63957ZSNd1xrZJoinmNyDf2LzuP8WJXw==}
|
||||||
engines: {node: '>= 4.0.0'}
|
engines: {node: '>= 4.0.0'}
|
||||||
|
|
||||||
memorystream@0.3.1:
|
memorystream@0.3.1:
|
||||||
|
@ -11769,7 +11769,7 @@ snapshots:
|
||||||
|
|
||||||
mdurl@2.0.0: {}
|
mdurl@2.0.0: {}
|
||||||
|
|
||||||
memfs@4.15.2:
|
memfs@4.15.3:
|
||||||
dependencies:
|
dependencies:
|
||||||
'@jsonjoy.com/json-pack': 1.1.1(tslib@2.8.1)
|
'@jsonjoy.com/json-pack': 1.1.1(tslib@2.8.1)
|
||||||
'@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
|
'@jsonjoy.com/util': 1.5.0(tslib@2.8.1)
|
||||||
|
|
|
@ -5,19 +5,19 @@ ARG BASE_IMAGE_TYPE=slim
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
# slim image
|
# slim image
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
FROM ghcr.io/renovatebot/base-image:9.29.0@sha256:10e27273241a0ba63d3a298a7b1e178dbb75b84da6bc2ea7a71db7c9d1a4971c AS slim-base
|
FROM ghcr.io/renovatebot/base-image:9.29.1@sha256:db4b70c00fb197babca9dd92be612bef044d7a35d933d19c668864f84b52d1f8 AS slim-base
|
||||||
|
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
# full image
|
# full image
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
FROM ghcr.io/renovatebot/base-image:9.29.0-full@sha256:7b2353855c0f59b9efdb93ce9356aff5dad7d5102f8947c4ebc906855be9177c AS full-base
|
FROM ghcr.io/renovatebot/base-image:9.29.1-full@sha256:4880c7aae10ed892d49c6c5573418014605ce2824c978dbcc04382a2c26bb0df AS full-base
|
||||||
|
|
||||||
ENV RENOVATE_BINARY_SOURCE=global
|
ENV RENOVATE_BINARY_SOURCE=global
|
||||||
|
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
# build image
|
# build image
|
||||||
# --------------------------------------
|
# --------------------------------------
|
||||||
FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.29.0@sha256:10e27273241a0ba63d3a298a7b1e178dbb75b84da6bc2ea7a71db7c9d1a4971c AS build
|
FROM --platform=$BUILDPLATFORM ghcr.io/renovatebot/base-image:9.29.1@sha256:db4b70c00fb197babca9dd92be612bef044d7a35d933d19c668864f84b52d1f8 AS build
|
||||||
|
|
||||||
# We want a specific node version here
|
# We want a specific node version here
|
||||||
# renovate: datasource=node-version
|
# renovate: datasource=node-version
|
||||||
|
|
Loading…
Reference in a new issue