mirror of https://github.com/renovatebot/renovate
264 lines
8.0 KiB
TypeScript
264 lines
8.0 KiB
TypeScript
import { REPOSITORY_ARCHIVED } from '../../../constants/error-messages';
|
|
import { logger } from '../../../logger';
|
|
import { GerritHttp } from '../../../util/http/gerrit';
|
|
import { regEx } from '../../../util/regex';
|
|
import type {
|
|
GerritAccountInfo,
|
|
GerritBranchInfo,
|
|
GerritChange,
|
|
GerritChangeMessageInfo,
|
|
GerritFindPRConfig,
|
|
GerritMergeableInfo,
|
|
GerritProjectInfo,
|
|
} from './types';
|
|
import { mapPrStateToGerritFilter } from './utils';
|
|
|
|
const QUOTES_REGEX = regEx('"', 'g');
|
|
|
|
class GerritClient {
|
|
private requestDetails = [
|
|
'SUBMITTABLE', //include the submittable field in ChangeInfo, which can be used to tell if the change is reviewed and ready for submit.
|
|
'CHECK', // include potential problems with the change.
|
|
'MESSAGES',
|
|
'DETAILED_ACCOUNTS',
|
|
'LABELS',
|
|
'CURRENT_ACTIONS', //to check if current_revision can be "rebased"
|
|
'CURRENT_REVISION', //get RevisionInfo::ref to fetch
|
|
'CURRENT_COMMIT', // to get the commit message
|
|
] as const;
|
|
|
|
private gerritHttp = new GerritHttp();
|
|
|
|
async getRepos(): Promise<string[]> {
|
|
const res = await this.gerritHttp.getJson<string[]>(
|
|
'a/projects/?type=CODE&state=ACTIVE',
|
|
{},
|
|
);
|
|
return Object.keys(res.body);
|
|
}
|
|
|
|
async getProjectInfo(repository: string): Promise<GerritProjectInfo> {
|
|
const projectInfo = await this.gerritHttp.getJson<GerritProjectInfo>(
|
|
`a/projects/${encodeURIComponent(repository)}`,
|
|
);
|
|
if (projectInfo.body.state !== 'ACTIVE') {
|
|
throw new Error(REPOSITORY_ARCHIVED);
|
|
}
|
|
return projectInfo.body;
|
|
}
|
|
|
|
async getBranchInfo(repository: string): Promise<GerritBranchInfo> {
|
|
const branchInfo = await this.gerritHttp.getJson<GerritBranchInfo>(
|
|
`a/projects/${encodeURIComponent(repository)}/branches/HEAD`,
|
|
);
|
|
return branchInfo.body;
|
|
}
|
|
|
|
async findChanges(
|
|
repository: string,
|
|
findPRConfig: GerritFindPRConfig,
|
|
refreshCache?: boolean,
|
|
): Promise<GerritChange[]> {
|
|
const filters = GerritClient.buildSearchFilters(repository, findPRConfig);
|
|
const changes = await this.gerritHttp.getJson<GerritChange[]>(
|
|
`a/changes/?q=` +
|
|
filters.join('+') +
|
|
this.requestDetails.map((det) => `&o=${det}`).join(''),
|
|
{ memCache: !refreshCache },
|
|
);
|
|
logger.trace(
|
|
`findChanges(${filters.join(', ')}) => ${changes.body.length}`,
|
|
);
|
|
return changes.body;
|
|
}
|
|
|
|
async getChange(changeNumber: number): Promise<GerritChange> {
|
|
const changes = await this.gerritHttp.getJson<GerritChange>(
|
|
`a/changes/${changeNumber}?` +
|
|
this.requestDetails.map((det) => `o=${det}`).join('&'),
|
|
);
|
|
return changes.body;
|
|
}
|
|
|
|
async getMergeableInfo(change: GerritChange): Promise<GerritMergeableInfo> {
|
|
const mergeable = await this.gerritHttp.getJson<GerritMergeableInfo>(
|
|
`a/changes/${change._number}/revisions/current/mergeable`,
|
|
);
|
|
return mergeable.body;
|
|
}
|
|
|
|
async abandonChange(changeNumber: number): Promise<void> {
|
|
await this.gerritHttp.postJson(`a/changes/${changeNumber}/abandon`);
|
|
}
|
|
|
|
async submitChange(changeNumber: number): Promise<GerritChange> {
|
|
const change = await this.gerritHttp.postJson<GerritChange>(
|
|
`a/changes/${changeNumber}/submit`,
|
|
);
|
|
return change.body;
|
|
}
|
|
|
|
async setCommitMessage(changeNumber: number, message: string): Promise<void> {
|
|
await this.gerritHttp.putJson(`a/changes/${changeNumber}/message`, {
|
|
body: { message },
|
|
});
|
|
}
|
|
|
|
async updateChangeSubject(
|
|
number: number,
|
|
currentMessage: string,
|
|
newSubject: string,
|
|
): Promise<void> {
|
|
// Replace first line of the commit message with the new subject
|
|
const newMessage = currentMessage.replace(
|
|
new RegExp(`^.*$`, 'm'),
|
|
newSubject,
|
|
);
|
|
await this.setCommitMessage(number, newMessage);
|
|
}
|
|
|
|
async getMessages(changeNumber: number): Promise<GerritChangeMessageInfo[]> {
|
|
const messages = await this.gerritHttp.getJson<GerritChangeMessageInfo[]>(
|
|
`a/changes/${changeNumber}/messages`,
|
|
{ memCache: false },
|
|
);
|
|
return messages.body;
|
|
}
|
|
|
|
async addMessage(
|
|
changeNumber: number,
|
|
fullMessage: string,
|
|
tag?: string,
|
|
): Promise<void> {
|
|
const message = this.normalizeMessage(fullMessage);
|
|
await this.gerritHttp.postJson(
|
|
`a/changes/${changeNumber}/revisions/current/review`,
|
|
{ body: { message, tag } },
|
|
);
|
|
}
|
|
|
|
async checkForExistingMessage(
|
|
changeNumber: number,
|
|
newMessage: string,
|
|
msgType?: string,
|
|
): Promise<boolean> {
|
|
const messages = await this.getMessages(changeNumber);
|
|
return messages.some(
|
|
(existingMsg) =>
|
|
(msgType === undefined || msgType === existingMsg.tag) &&
|
|
existingMsg.message.includes(newMessage),
|
|
);
|
|
}
|
|
|
|
async addMessageIfNotAlreadyExists(
|
|
changeNumber: number,
|
|
message: string,
|
|
tag?: string,
|
|
): Promise<void> {
|
|
const newMsg = this.normalizeMessage(message);
|
|
if (!(await this.checkForExistingMessage(changeNumber, newMsg, tag))) {
|
|
await this.addMessage(changeNumber, newMsg, tag);
|
|
}
|
|
}
|
|
|
|
async setLabel(
|
|
changeNumber: number,
|
|
label: string,
|
|
value: number,
|
|
): Promise<void> {
|
|
await this.gerritHttp.postJson(
|
|
`a/changes/${changeNumber}/revisions/current/review`,
|
|
{ body: { labels: { [label]: value } } },
|
|
);
|
|
}
|
|
|
|
async addReviewer(changeNumber: number, reviewer: string): Promise<void> {
|
|
await this.gerritHttp.postJson(`a/changes/${changeNumber}/reviewers`, {
|
|
body: { reviewer },
|
|
});
|
|
}
|
|
|
|
async addAssignee(changeNumber: number, assignee: string): Promise<void> {
|
|
await this.gerritHttp.putJson<GerritAccountInfo>(
|
|
`a/changes/${changeNumber}/assignee`,
|
|
{
|
|
body: { assignee },
|
|
},
|
|
);
|
|
}
|
|
|
|
async getFile(
|
|
repo: string,
|
|
branch: string,
|
|
fileName: string,
|
|
): Promise<string> {
|
|
const base64Content = await this.gerritHttp.get(
|
|
`a/projects/${encodeURIComponent(
|
|
repo,
|
|
)}/branches/${encodeURIComponent(branch)}/files/${encodeURIComponent(fileName)}/content`,
|
|
);
|
|
return Buffer.from(base64Content.body, 'base64').toString();
|
|
}
|
|
|
|
async approveChange(changeId: number): Promise<void> {
|
|
const isApproved = await this.checkIfApproved(changeId);
|
|
if (!isApproved) {
|
|
await this.setLabel(changeId, 'Code-Review', +2);
|
|
}
|
|
}
|
|
|
|
async checkIfApproved(changeId: number): Promise<boolean> {
|
|
const change = await client.getChange(changeId);
|
|
const reviewLabels = change?.labels?.['Code-Review'];
|
|
return reviewLabels === undefined || reviewLabels.approved !== undefined;
|
|
}
|
|
|
|
wasApprovedBy(change: GerritChange, username: string): boolean | undefined {
|
|
return (
|
|
change.labels?.['Code-Review'].approved &&
|
|
change.labels['Code-Review'].approved.username === username
|
|
);
|
|
}
|
|
|
|
normalizeMessage(message: string): string {
|
|
//the last \n was removed from gerrit after the comment was added...
|
|
return message.substring(0, 0x4000).trim();
|
|
}
|
|
|
|
private static buildSearchFilters(
|
|
repository: string,
|
|
searchConfig: GerritFindPRConfig,
|
|
): string[] {
|
|
const filterState = mapPrStateToGerritFilter(searchConfig.state);
|
|
const filters = ['owner:self', 'project:' + repository, filterState];
|
|
if (searchConfig.branchName) {
|
|
filters.push(
|
|
...[
|
|
'(',
|
|
`footer:Renovate-Branch=${searchConfig.branchName}`,
|
|
// for backwards compatibility
|
|
'OR',
|
|
`hashtag:sourceBranch-${searchConfig.branchName}`,
|
|
')',
|
|
],
|
|
);
|
|
}
|
|
if (searchConfig.targetBranch) {
|
|
filters.push(`branch:${searchConfig.targetBranch}`);
|
|
}
|
|
if (searchConfig.label) {
|
|
filters.push(`label:Code-Review=${searchConfig.label}`);
|
|
}
|
|
if (searchConfig.prTitle) {
|
|
// escaping support in Gerrit is not great, so we need to remove quotes
|
|
// special characters are ignored anyway in the search so it does not create any issues
|
|
filters.push(
|
|
`message:${encodeURIComponent('"' + searchConfig.prTitle.replace(QUOTES_REGEX, '') + '"')}`,
|
|
);
|
|
}
|
|
return filters;
|
|
}
|
|
}
|
|
|
|
export const client = new GerritClient();
|