UNPKG

renovate

Version:

Automated dependency updates. Flexible so you don't need to be.

362 lines • 14 kB
"use strict"; 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)(/&#8203;/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