renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
362 lines • 14 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.id = void 0;
exports.writeToConfig = writeToConfig;
exports.initPlatform = initPlatform;
exports.getRepos = getRepos;
exports.initRepo = initRepo;
exports.findPr = findPr;
exports.getPr = getPr;
exports.updatePr = updatePr;
exports.createPr = createPr;
exports.getBranchPr = getBranchPr;
exports.refreshPr = refreshPr;
exports.getPrList = getPrList;
exports.mergePr = mergePr;
exports.getBranchStatus = getBranchStatus;
exports.getBranchStatusCheck = getBranchStatusCheck;
exports.setBranchStatus = setBranchStatus;
exports.getRawFile = getRawFile;
exports.getJsonFile = getJsonFile;
exports.addReviewers = addReviewers;
exports.addAssignees = addAssignees;
exports.ensureComment = ensureComment;
exports.massageMarkdown = massageMarkdown;
exports.maxBodyLength = maxBodyLength;
exports.deleteLabel = deleteLabel;
exports.ensureCommentRemoval = ensureCommentRemoval;
exports.ensureIssueClosing = ensureIssueClosing;
exports.ensureIssue = ensureIssue;
exports.findIssue = findIssue;
exports.getIssueList = getIssueList;
const tslib_1 = require("tslib");
const is_1 = require("@sindresorhus/is");
const luxon_1 = require("luxon");
const logger_1 = require("../../../logger");
const common_1 = require("../../../util/common");
const git = tslib_1.__importStar(require("../../../util/git"));
const gerrit_1 = require("../../../util/http/gerrit");
const regex_1 = require("../../../util/regex");
const url_1 = require("../../../util/url");
const util_1 = require("../util");
const pr_body_1 = require("../utils/pr-body");
const read_only_issue_body_1 = require("../utils/read-only-issue-body");
const client_1 = require("./client");
const scm_1 = require("./scm");
const utils_1 = require("./utils");
exports.id = 'gerrit';
const defaults = {};
let config = {
labels: {},
};
function writeToConfig(newConfig) {
config = { ...config, ...newConfig };
}
function initPlatform({ endpoint, username, password, }) {
logger_1.logger.debug(`initPlatform(${endpoint}, ${username})`);
if (!endpoint) {
throw new Error('Init: You must configure a Gerrit Server endpoint');
}
if (!(username && password)) {
throw new Error('Init: You must configure a Gerrit Server username/password');
}
config.gerritUsername = username;
defaults.endpoint = (0, url_1.ensureTrailingSlash)(endpoint);
(0, gerrit_1.setBaseUrl)(defaults.endpoint);
const platformConfig = {
endpoint: defaults.endpoint,
};
return Promise.resolve(platformConfig);
}
/**
* Get all state="ACTIVE" and type="CODE" repositories from gerrit
*/
async function getRepos() {
logger_1.logger.debug(`getRepos()`);
return await client_1.client.getRepos();
}
/**
* Clone repository to local directory
* @param config
*/
async function initRepo({ repository, gitUrl, }) {
logger_1.logger.debug(`initRepo(${repository}, ${gitUrl})`);
const projectInfo = await client_1.client.getProjectInfo(repository);
const branchInfo = await client_1.client.getBranchInfo(repository);
config = {
...config,
repository,
head: branchInfo.revision,
config: projectInfo,
labels: projectInfo.labels ?? {},
};
const baseUrl = defaults.endpoint;
const url = (0, utils_1.getGerritRepoUrl)(repository, baseUrl);
(0, scm_1.configureScm)(repository, config.gerritUsername);
await git.initRepo({ url });
//abandon "open" and "rejected" changes at startup
const rejectedChanges = await client_1.client.findChanges(config.repository, {
branchName: '',
state: 'open',
label: '-2',
});
for (const change of rejectedChanges) {
await client_1.client.abandonChange(change._number, 'This change has been abandoned as it was voted with Code-Review -2.');
logger_1.logger.info(`Abandoned change ${change._number} with Code-Review -2 in repository ${repository}`);
}
const repoConfig = {
defaultBranch: config.head,
isFork: false,
repoFingerprint: (0, util_1.repoFingerprint)(repository, baseUrl),
};
return repoConfig;
}
async function findPr(findPRConfig) {
const change = (await client_1.client.findChanges(config.repository, {
...findPRConfig,
limit: 1,
requestDetails: utils_1.REQUEST_DETAILS_FOR_PRS,
})).pop();
return change
? (0, utils_1.mapGerritChangeToPr)(change, {
sourceBranch: findPRConfig.branchName,
})
: null;
}
async function getPr(number, refreshCache) {
try {
const change = await client_1.client.getChange(number, refreshCache, utils_1.REQUEST_DETAILS_FOR_PRS);
return (0, utils_1.mapGerritChangeToPr)(change);
}
catch (err) {
if (err.statusCode === 404) {
return null;
}
throw err;
}
}
async function updatePr(prConfig) {
logger_1.logger.debug(`updatePr(${prConfig.number}, ${prConfig.prTitle})`);
if (prConfig.prBody) {
await client_1.client.addMessageIfNotAlreadyExists(prConfig.number, prConfig.prBody, utils_1.TAG_PULL_REQUEST_BODY);
}
if (prConfig.state && prConfig.state === 'closed') {
await client_1.client.abandonChange(prConfig.number);
}
}
async function createPr(prConfig) {
logger_1.logger.debug(`createPr(${prConfig.sourceBranch}, ${prConfig.prTitle}, ${prConfig.labels?.toString() ?? ''})`);
const change = (await client_1.client.findChanges(config.repository, {
branchName: prConfig.sourceBranch,
targetBranch: prConfig.targetBranch,
state: 'open',
limit: 1,
refreshCache: true,
requestDetails: utils_1.REQUEST_DETAILS_FOR_PRS,
})).pop();
if (change === undefined) {
throw new Error(`the change should be created automatically from previous push to refs/for/${prConfig.sourceBranch}`);
}
const created = luxon_1.DateTime.fromISO(change.created.replace(' ', 'T'), {});
const fiveMinutesAgo = luxon_1.DateTime.utc().minus({ minutes: 5 });
if (created < fiveMinutesAgo) {
throw new Error(`the change should have been created automatically from previous push to refs/for/${prConfig.sourceBranch}, but it was not created in the last 5 minutes (${change.created})`);
}
await client_1.client.addMessageIfNotAlreadyExists(change._number, prConfig.prBody, utils_1.TAG_PULL_REQUEST_BODY, change.messages);
return (0, utils_1.mapGerritChangeToPr)(change, {
sourceBranch: prConfig.sourceBranch,
prBody: prConfig.prBody,
});
}
async function getBranchPr(branchName, targetBranch) {
const change = (await client_1.client.findChanges(config.repository, {
branchName,
state: 'open',
targetBranch,
limit: 1,
requestDetails: utils_1.REQUEST_DETAILS_FOR_PRS,
})).pop();
return change
? (0, utils_1.mapGerritChangeToPr)(change, {
sourceBranch: branchName,
})
: null;
}
async function refreshPr(number) {
// refresh cache
await getPr(number, true);
}
async function getPrList() {
const changes = await client_1.client.findChanges(config.repository, {
branchName: '',
requestDetails: utils_1.REQUEST_DETAILS_FOR_PRS,
});
return changes.map((change) => (0, utils_1.mapGerritChangeToPr)(change)).filter(is_1.isTruthy);
}
async function mergePr(config) {
logger_1.logger.debug(`mergePr(${config.id}, ${config.branchName}, ${config.strategy})`);
try {
const change = await client_1.client.submitChange(config.id);
return change.status === 'MERGED';
}
catch (err) {
if (err.statusCode === 409) {
logger_1.logger.warn({ err }, "Can't submit the change, because the submit rule doesn't allow it.");
return false;
}
throw err;
}
}
/**
* BranchStatus for Gerrit assumes that the branchName refers to a change.
* @param branchName
*/
async function getBranchStatus(branchName) {
logger_1.logger.debug(`getBranchStatus(${branchName})`);
const change = (await client_1.client.findChanges(config.repository, {
state: 'open',
branchName,
limit: 1,
refreshCache: true,
requestDetails: ['LABELS', 'SUBMITTABLE', 'CHECK'],
})).pop();
if (change) {
const hasProblems = change.problems && change.problems.length > 0;
if (hasProblems) {
return 'red';
}
const hasBlockingLabels = Object.values(change.labels ?? {}).some((label) => label.blocking);
if (hasBlockingLabels) {
return 'red';
}
if (change.submittable) {
return 'green';
}
}
return 'yellow';
}
/**
* check the gerrit-change for the presence of the corresponding "$context" Gerrit label if configured,
* return 'yellow' if not configured or not set
* @param branchName
* @param context renovate/stability-days || ...
*/
async function getBranchStatusCheck(branchName, context) {
const labelConfig = config.labels[context];
if (labelConfig) {
const change = (await client_1.client.findChanges(config.repository, {
branchName,
state: 'open',
limit: 1,
refreshCache: true,
requestDetails: ['LABELS'],
})).pop();
if (change) {
const label = change.labels[context];
if (label) {
// Check for rejected or blocking first, as a label could have both rejected and approved
if (label.rejected || label.blocking) {
return 'red';
}
if (label.approved) {
return 'green';
}
}
}
}
return 'yellow';
}
/**
* Apply the branch state $context to the corresponding gerrit label (if available)
* context === "renovate/stability-days" / "renovate/merge-confidence" and state === "green"/...
* @param branchStatusConfig
*/
async function setBranchStatus(branchStatusConfig) {
const label = config.labels[branchStatusConfig.context];
const labelValue = label && (0, utils_1.mapBranchStatusToLabel)(branchStatusConfig.state, label);
if (branchStatusConfig.context && labelValue) {
const change = (await client_1.client.findChanges(config.repository, {
branchName: branchStatusConfig.branchName,
state: 'open',
limit: 1,
requestDetails: ['LABELS'],
})).pop();
const labelKey = branchStatusConfig.context;
if (!change?.labels || !Object.hasOwn(change.labels, labelKey)) {
return;
}
await client_1.client.setLabel(change._number, labelKey, labelValue);
}
}
async function getRawFile(fileName, repoName, branchOrTag) {
const repo = repoName ?? config.repository;
if (!repo) {
logger_1.logger.debug('No repo so cannot getRawFile');
return null;
}
const branch = branchOrTag ??
(repo === config.repository ? (config.head ?? 'HEAD') : 'HEAD');
const result = await client_1.client.getFile(repo, branch, fileName);
return result;
}
async function getJsonFile(fileName, repoName, branchOrTag) {
const raw = await getRawFile(fileName, repoName, branchOrTag);
return (0, common_1.parseJson)(raw, fileName);
}
async function addReviewers(number, reviewers) {
await client_1.client.addReviewers(number, reviewers);
}
/**
* add "CC" (only one possible)
*/
async function addAssignees(number, assignees) {
if (assignees.length) {
if (assignees.length > 1) {
logger_1.logger.debug(`addAssignees(${number}, ${assignees.toString()}) called with more then one assignee! Gerrit only supports one assignee! Using the first from list.`);
}
await client_1.client.addAssignee(number, assignees[0]);
}
}
async function ensureComment(ensureComment) {
logger_1.logger.debug(`ensureComment(${ensureComment.number}, ${ensureComment.topic}, ${ensureComment.content})`);
await client_1.client.addMessageIfNotAlreadyExists(ensureComment.number, ensureComment.content, ensureComment.topic ?? undefined);
return true;
}
function massageMarkdown(prBody, rebaseLabel) {
return ((0, pr_body_1.smartTruncate)((0, read_only_issue_body_1.readOnlyIssueBody)(prBody), maxBodyLength())
.replace('Branch creation', 'Change creation')
.replace('close this Pull Request unmerged', 'abandon or vote this change with Code-Review -2')
.replace('Close this PR', 'Abandon or vote this change with Code-Review -2')
.replace('you tick the rebase/retry checkbox', `you add the _${rebaseLabel}_ hashtag to this change`)
.replace('checking the rebase/retry box above', `adding the _${rebaseLabel}_ hashtag to this change`)
.replace((0, regex_1.regEx)(/\b(?:Pull Request|PR)/g), 'change')
// Remove HTML tags not supported in Gerrit markdown
.replace((0, regex_1.regEx)(/<\/?summary>/g), '**')
.replace((0, regex_1.regEx)(/<\/?(details|blockquote)>/g), '')
.replace((0, regex_1.regEx)(`\n---\n\n.*?<!-- rebase-check -->.*?\n`), '')
.replace((0, regex_1.regEx)(/<!--renovate-(?:debug|config-hash):.*?-->/g), '')
// Remove zero-width-space not supported in Gerrit markdown
.replace((0, regex_1.regEx)(/​/g), ''));
}
function maxBodyLength() {
return 16384; //TODO: check the real gerrit limit (max. chars)
}
async function deleteLabel(number, label) {
await client_1.client.deleteHashtag(number, label);
}
function ensureCommentRemoval(ensureCommentRemoval) {
return Promise.resolve();
}
function ensureIssueClosing(title) {
return Promise.resolve();
}
function ensureIssue(issueConfig) {
return Promise.resolve(null);
}
function findIssue(title) {
return Promise.resolve(null);
}
function getIssueList() {
return Promise.resolve([]);
}
//# sourceMappingURL=index.js.map