renovate
Version:
Automated dependency updates. Flexible so you don't need to be.
773 lines • 32.7 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.id = void 0;
exports.resetPlatform = resetPlatform;
exports.initPlatform = initPlatform;
exports.getRepos = getRepos;
exports.getRawFile = getRawFile;
exports.getJsonFile = getJsonFile;
exports.initRepo = initRepo;
exports.getPrList = getPrList;
exports.findPr = findPr;
exports.getPr = getPr;
exports.getBranchPr = getBranchPr;
exports.getBranchStatus = getBranchStatus;
exports.getBranchStatusCheck = getBranchStatusCheck;
exports.setBranchStatus = setBranchStatus;
exports.findIssue = findIssue;
exports.massageMarkdown = massageMarkdown;
exports.maxBodyLength = maxBodyLength;
exports.ensureIssue = ensureIssue;
exports.getIssueList = getIssueList;
exports.ensureIssueClosing = ensureIssueClosing;
exports.addAssignees = addAssignees;
exports.addReviewers = addReviewers;
exports.deleteLabel = deleteLabel;
exports.ensureComment = ensureComment;
exports.ensureCommentRemoval = ensureCommentRemoval;
exports.createPr = createPr;
exports.updatePr = updatePr;
exports.mergePr = mergePr;
const tslib_1 = require("tslib");
const node_url_1 = tslib_1.__importDefault(require("node:url"));
const is_1 = tslib_1.__importDefault(require("@sindresorhus/is"));
const error_messages_1 = require("../../../constants/error-messages");
const logger_1 = require("../../../logger");
const common_1 = require("../../../util/common");
const git = tslib_1.__importStar(require("../../../util/git"));
const hostRules = tslib_1.__importStar(require("../../../util/host-rules"));
const bitbucket_1 = require("../../../util/http/bitbucket");
const memory_http_cache_provider_1 = require("../../../util/http/cache/memory-http-cache-provider");
const repository_http_cache_provider_1 = require("../../../util/http/cache/repository-http-cache-provider");
const regex_1 = require("../../../util/regex");
const sanitize_1 = require("../../../util/sanitize");
const string_match_1 = require("../../../util/string-match");
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 comments = tslib_1.__importStar(require("./comments"));
const pr_cache_1 = require("./pr-cache");
const schema_1 = require("./schema");
const utils = tslib_1.__importStar(require("./utils"));
const utils_1 = require("./utils");
exports.id = 'bitbucket';
const bitbucketHttp = new bitbucket_1.BitbucketHttp();
const BITBUCKET_PROD_ENDPOINT = 'https://api.bitbucket.org/';
let config = {};
function resetPlatform() {
config = {};
renovateUserUuid = null;
}
const defaults = { endpoint: BITBUCKET_PROD_ENDPOINT };
const pathSeparator = '/';
let renovateUserUuid = null;
async function initPlatform({ endpoint, username, password, token, }) {
if (!(username && password) && !token) {
throw new Error('Init: You must configure either a Bitbucket token or username and password');
}
if (endpoint && endpoint !== BITBUCKET_PROD_ENDPOINT) {
logger_1.logger.warn(`Init: Bitbucket Cloud endpoint should generally be ${BITBUCKET_PROD_ENDPOINT} but is being configured to a different value. Did you mean to use Bitbucket Server?`);
defaults.endpoint = endpoint;
}
(0, bitbucket_1.setBaseUrl)(defaults.endpoint);
renovateUserUuid = null;
const options = { memCache: false };
if (token) {
options.token = token;
}
else {
options.username = username;
options.password = password;
}
try {
const { uuid } = (await bitbucketHttp.getJsonUnchecked('/2.0/user', options)).body;
renovateUserUuid = uuid;
}
catch (err) {
if (err.statusCode === 403 &&
err.body?.error?.detail?.required?.includes('account')) {
logger_1.logger.warn(`Bitbucket: missing 'account' scope for password`);
}
else {
logger_1.logger.debug({ err }, 'Unknown error fetching Bitbucket user identity');
}
}
// TODO: Add a connection check that endpoint/username/password combination are valid (#9594)
const platformConfig = {
endpoint: endpoint ?? BITBUCKET_PROD_ENDPOINT,
};
return Promise.resolve(platformConfig);
}
// Get all repositories that the user has access to
async function getRepos(config) {
logger_1.logger.debug('Autodiscovering Bitbucket Cloud repositories');
try {
let { body: repos } = await bitbucketHttp.getJson(`/2.0/repositories/?role=contributor`, { paginate: true }, schema_1.Repositories);
// if autodiscoverProjects is configured
// filter the repos list
const autodiscoverProjects = config.projects;
if (is_1.default.nonEmptyArray(autodiscoverProjects)) {
logger_1.logger.debug({ autodiscoverProjects: config.projects }, 'Applying autodiscoverProjects filter');
repos = repos.filter((repo) => repo.projectName &&
(0, string_match_1.matchRegexOrGlobList)(repo.projectName, autodiscoverProjects));
}
return repos.map(({ owner, name }) => `${owner}/${name}`);
}
catch (err) /* v8 ignore start */ {
logger_1.logger.error({ err }, `bitbucket getRepos error`);
throw err;
} /* v8 ignore stop */
}
async function getRawFile(fileName, repoName, branchOrTag) {
// See: https://developer.atlassian.com/bitbucket/api/2/reference/resource/repositories/%7Bworkspace%7D/%7Brepo_slug%7D/src/%7Bcommit%7D/%7Bpath%7D
const repo = repoName ?? config.repository;
const path = fileName;
let finalBranchOrTag = branchOrTag;
if (branchOrTag?.includes(pathSeparator)) {
// Branch name contains slash, so we have to replace branch name with SHA1 of the head commit; otherwise the API will not work.
finalBranchOrTag = await getBranchCommit(branchOrTag);
}
const url = `/2.0/repositories/${repo}/src/` +
(finalBranchOrTag ?? `HEAD`) +
`/${path}`;
const res = await bitbucketHttp.getText(url, {
cacheProvider: repository_http_cache_provider_1.repoCacheProvider,
});
return res.body;
}
async function getJsonFile(fileName, repoName, branchOrTag) {
// TODO #22198
const raw = await getRawFile(fileName, repoName, branchOrTag);
return (0, common_1.parseJson)(raw, fileName);
}
// Initialize bitbucket by getting base branch and SHA
async function initRepo({ repository, cloneSubmodules, cloneSubmodulesFilter, ignorePrAuthor, bbUseDevelopmentBranch, }) {
logger_1.logger.debug(`initRepo("${repository}")`);
const opts = hostRules.find({
hostType: 'bitbucket',
url: defaults.endpoint,
});
config = {
repository,
ignorePrAuthor,
};
let info;
let mainBranch;
try {
const { body: repoInfo } = await bitbucketHttp.getJson(`/2.0/repositories/${repository}`, schema_1.RepoInfo);
info = repoInfo;
mainBranch = info.mainbranch;
if (bbUseDevelopmentBranch) {
// Fetch Bitbucket development branch
const developmentBranch = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${repository}/branching-model`)).body.development?.branch?.name;
if (developmentBranch) {
mainBranch = developmentBranch;
}
}
config.defaultBranch = mainBranch;
config = {
...config,
owner: info.owner,
mergeMethod: info.mergeMethod,
has_issues: info.has_issues,
is_private: info.is_private,
};
logger_1.logger.debug(`${repository} owner = ${config.owner}`);
}
catch (err) /* v8 ignore start */ {
if (err.statusCode === 404) {
throw new Error(error_messages_1.REPOSITORY_NOT_FOUND);
}
logger_1.logger.debug({ err }, 'Unknown Bitbucket initRepo error');
throw err;
} /* v8 ignore stop */
const { hostname } = node_url_1.default.parse(defaults.endpoint);
// Converts API hostnames to their respective HTTP git hosts:
// `api.bitbucket.org` to `bitbucket.org`
// `api-staging.<host>` to `staging.<host>`
// TODO #22198
const hostnameWithoutApiPrefix = (0, regex_1.regEx)(/api[.|-](.+)/).exec(hostname)?.[1];
const auth = opts.token
? `x-token-auth:${opts.token}`
: `${opts.username}:${opts.password}`;
const url = git.getUrl({
protocol: 'https',
auth,
hostname: hostnameWithoutApiPrefix,
repository,
});
await git.initRepo({
...config,
url,
cloneSubmodules,
cloneSubmodulesFilter,
});
const repoConfig = {
defaultBranch: mainBranch,
isFork: info.isFork,
repoFingerprint: (0, util_1.repoFingerprint)(info.uuid, defaults.endpoint),
};
return repoConfig;
}
/* v8 ignore start */
function matchesState(state, desiredState) {
if (desiredState === 'all') {
return true;
}
if (desiredState.startsWith('!')) {
return state !== desiredState.substring(1);
}
return state === desiredState;
} /* v8 ignore stop */
async function getPrList() {
logger_1.logger.trace('getPrList()');
return await pr_cache_1.BitbucketPrCache.getPrs(bitbucketHttp, config.repository, renovateUserUuid);
}
async function findPr({ branchName, prTitle, state = 'all', includeOtherAuthors, }) {
logger_1.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.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests?q=source.branch.name="${branchName}"&state=open`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body.values;
if (prs.length === 0) {
logger_1.logger.debug(`No PR found for branch ${branchName}`);
return null;
}
return utils.prInfo(prs[0]);
}
const prList = await getPrList();
const pr = prList.find((p) => p.sourceBranch === branchName &&
(!prTitle || p.title.toUpperCase() === prTitle.toUpperCase()) &&
matchesState(p.state, state));
if (!pr) {
return null;
}
logger_1.logger.debug(`Found PR #${pr.number}`);
/**
* Bitbucket doesn't support renaming or reopening declined PRs.
* Instead, we have to use comment-driven signals.
*/
if (pr.state === 'closed') {
const reopenComments = await comments.reopenComments(config, pr.number);
if (is_1.default.nonEmptyArray(reopenComments)) {
if (config.is_private) {
// Only workspace members could have commented on a private repository
logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`);
return null;
}
for (const comment of reopenComments) {
if (await isAccountMemberOfWorkspace(comment.user, config.repository)) {
logger_1.logger.debug(`Found '${comments.REOPEN_PR_COMMENT_KEYWORD}' comment from workspace member. Renovate will reopen PR ${pr.number} as a new PR`);
return null;
}
}
}
}
return pr;
}
// Gets details for a PR
async function getPr(prNo) {
const pr = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body;
/* v8 ignore start */
if (!pr) {
return null;
} /* v8 ignore stop */
const res = {
...utils.prInfo(pr),
};
if (is_1.default.nonEmptyArray(pr.reviewers)) {
res.reviewers = pr.reviewers
.map(({ uuid }) => uuid)
.filter(is_1.default.nonEmptyString);
}
return res;
}
const escapeHash = (input) => input?.replace((0, regex_1.regEx)(/#/g), '%23');
// Return the commit SHA for a branch
async function getBranchCommit(branchName) {
try {
const branch = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/refs/branches/${escapeHash(branchName)}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body;
return branch.target.hash;
}
catch (err) /* v8 ignore start */ {
logger_1.logger.debug({ err }, `getBranchCommit('${branchName}') failed'`);
return undefined;
} /* v8 ignore stop */
}
// Returns the Pull Request for a branch. Null if not exists.
async function getBranchPr(branchName) {
logger_1.logger.debug(`getBranchPr(${branchName})`);
const existingPr = await findPr({
branchName,
state: 'open',
});
return existingPr ? getPr(existingPr.number) : null;
}
async function getStatus(branchName, memCache = true) {
const sha = await getBranchCommit(branchName);
const opts = { paginate: true };
/* v8 ignore start: temporary code */
if (memCache) {
opts.cacheProvider = memory_http_cache_provider_1.memCacheProvider;
}
else {
opts.memCache = false;
} /* v8 ignore stop */
return (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/commit/${sha}/statuses`, opts)).body.values;
}
// Returns the combined status for a branch.
async function getBranchStatus(branchName, internalChecksAsSuccess) {
logger_1.logger.debug(`getBranchStatus(${branchName})`);
const statuses = await getStatus(branchName);
logger_1.logger.debug({ branch: branchName, statuses }, 'branch status check result');
if (!statuses.length) {
logger_1.logger.debug('empty branch status check result = returning "pending"');
return 'yellow';
}
const noOfFailures = statuses.filter((status) => status.state === 'FAILED' || status.state === 'STOPPED').length;
if (noOfFailures) {
return 'red';
}
const noOfPending = statuses.filter((status) => status.state === 'INPROGRESS').length;
if (noOfPending) {
return 'yellow';
}
if (!internalChecksAsSuccess &&
statuses.every((status) => status.state === 'SUCCESSFUL' && status.key?.startsWith('renovate/'))) {
logger_1.logger.debug('Successful checks are all internal renovate/ checks, so returning "pending" branch status');
return 'yellow';
}
return 'green';
}
const bbToRenovateStatusMapping = {
SUCCESSFUL: 'green',
INPROGRESS: 'yellow',
FAILED: 'red',
};
async function getBranchStatusCheck(branchName, context) {
const statuses = await getStatus(branchName);
const bbState = statuses.find((status) => status.key === context)?.state;
// TODO #22198
return bbToRenovateStatusMapping[bbState] || null;
}
async function setBranchStatus({ branchName, context, description, state, url: targetUrl, }) {
const sha = await getBranchCommit(branchName);
// TargetUrl can not be empty so default to bitbucket
/* v8 ignore next */
const url = targetUrl ?? 'https://bitbucket.org';
const body = {
name: context,
state: utils.buildStates[state],
key: context,
description,
url,
};
await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/commit/${sha}/statuses/build`, { body });
// update status cache
await getStatus(branchName, false);
}
async function findOpenIssues(title) {
try {
const filters = [
`title=${JSON.stringify(title)}`,
'(state = "new" OR state = "open")',
];
if (renovateUserUuid) {
filters.push(`reporter.uuid="${renovateUserUuid}"`);
}
const filter = encodeURIComponent(filters.join(' AND '));
return ((await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/issues?q=${filter}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body.values /* v8 ignore start */ || [] /* v8 ignore stop */);
}
catch (err) /* v8 ignore start */ {
logger_1.logger.warn({ err }, 'Error finding issues');
return [];
} /* v8 ignore stop */
}
async function findIssue(title) {
logger_1.logger.debug(`findIssue(${title})`);
/* v8 ignore start */
if (!config.has_issues) {
logger_1.logger.debug('Issues are disabled - cannot findIssue');
return null;
} /* v8 ignore stop */
const issues = await findOpenIssues(title);
if (!issues.length) {
return null;
}
const [issue] = issues;
return {
number: issue.id,
body: issue.content?.raw,
};
}
async function closeIssue(issueNumber) {
await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issueNumber}`, {
body: { state: 'closed' },
});
}
function massageMarkdown(input) {
// Remove any HTML we use
return (0, pr_body_1.smartTruncate)(input, maxBodyLength())
.replace('you tick the rebase/retry checkbox', 'by renaming this PR to start with "rebase!"')
.replace('checking the rebase/retry box above', 'renaming the PR to start with "rebase!"')
.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)(/\]\(\.\.\/pull\//g), '](../../pull-requests/')
.replace((0, regex_1.regEx)(/<!--renovate-(?:debug|config-hash):.*?-->/g), '');
}
function maxBodyLength() {
return 50000;
}
async function ensureIssue({ title, reuseTitle, body, }) {
logger_1.logger.debug(`ensureIssue()`);
/* v8 ignore start */
if (!config.has_issues) {
logger_1.logger.debug('Issues are disabled - cannot ensureIssue');
logger_1.logger.debug(`Failed to ensure Issue with title:${title}`);
return null;
} /* v8 ignore stop */
try {
let issues = await findOpenIssues(title);
const description = massageMarkdown((0, sanitize_1.sanitize)(body));
if (!issues.length && reuseTitle) {
issues = await findOpenIssues(reuseTitle);
}
if (issues.length) {
// Close any duplicates
for (const issue of issues.slice(1)) {
await closeIssue(issue.id);
}
const [issue] = issues;
if (issue.title !== title ||
String(issue.content?.raw).trim() !== description.trim()) {
logger_1.logger.debug('Issue updated');
await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/issues/${issue.id}`, {
body: {
content: {
raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description),
markup: 'markdown',
},
},
});
return 'updated';
}
}
else {
logger_1.logger.info('Issue created');
await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/issues`, {
body: {
title,
content: {
raw: (0, read_only_issue_body_1.readOnlyIssueBody)(description),
markup: 'markdown',
},
},
});
return 'created';
}
}
catch (err) /* v8 ignore start */ {
if (err.message.startsWith('Repository has no issue tracker.')) {
logger_1.logger.debug(`Issues are disabled, so could not create issue: ${title}`);
}
else {
logger_1.logger.warn({ err }, 'Could not ensure issue');
}
} /* v8 ignore stop */
return null;
}
/* v8 ignore start */
async function getIssueList() {
logger_1.logger.debug(`getIssueList()`);
if (!config.has_issues) {
logger_1.logger.debug('Issues are disabled - cannot getIssueList');
return [];
}
try {
const filters = ['(state = "new" OR state = "open")'];
if (renovateUserUuid) {
filters.push(`reporter.uuid="${renovateUserUuid}"`);
}
const filter = encodeURIComponent(filters.join(' AND '));
const url = `/2.0/repositories/${config.repository}/issues?q=${filter}`;
const res = await bitbucketHttp.getJsonUnchecked(url, {
cacheProvider: repository_http_cache_provider_1.repoCacheProvider,
});
return res.body.values || [];
}
catch (err) {
logger_1.logger.warn({ err }, 'Error finding issues');
return [];
}
} /* v8 ignore stop */
async function ensureIssueClosing(title) {
/* v8 ignore start */
if (!config.has_issues) {
logger_1.logger.debug('Issues are disabled - cannot ensureIssueClosing');
return;
} /* v8 ignore stop */
const issues = await findOpenIssues(title);
for (const issue of issues) {
await closeIssue(issue.id);
}
}
function addAssignees(_prNr, _assignees) {
// Bitbucket supports "participants" and "reviewers" so does not seem to have the concept of "assignee"
logger_1.logger.warn('Cannot add assignees');
return Promise.resolve();
}
async function addReviewers(prId, reviewers) {
logger_1.logger.debug(`Adding reviewers '${reviewers.join(', ')}' to #${prId}`);
// TODO #22198
const { title } = (await getPr(prId));
const body = {
title,
reviewers: reviewers.map((username) => {
const isUUID = username.startsWith('{') &&
username.endsWith('}') &&
string_match_1.UUIDRegex.test(username.slice(1, -1));
const key = isUUID ? 'uuid' : 'username';
return {
[key]: username,
};
}),
};
await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prId}`, {
body,
});
}
/* v8 ignore start */
function deleteLabel() {
throw new Error('deleteLabel not implemented');
} /* v8 ignore stop */
function ensureComment({ number, topic, content, }) {
// https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment
return comments.ensureComment({
config,
number,
topic,
content: (0, sanitize_1.sanitize)(content),
});
}
function ensureCommentRemoval(deleteConfig) {
return comments.ensureCommentRemoval(config, deleteConfig);
}
async function sanitizeReviewers(reviewers, err) {
if (err.statusCode === 400 && err.body?.error?.fields?.reviewers) {
const sanitizedReviewers = [];
const MSG_AUTHOR_AND_REVIEWER = 'is the author and cannot be included as a reviewer.';
const MSG_MALFORMED_REVIEWERS_LIST = 'Malformed reviewers list';
const MSG_NOT_WORKSPACE_MEMBER = 'is not a member of this workspace and cannot be added to this pull request';
for (const msg of err.body.error.fields.reviewers) {
// Bitbucket returns a 400 if any of the PR reviewer accounts are now inactive (ie: disabled/suspended)
if (msg === MSG_MALFORMED_REVIEWERS_LIST) {
logger_1.logger.debug({ err }, 'PR contains reviewers that may be either inactive or no longer a member of this workspace. Will try setting only active reviewers');
// Validate that each previous PR reviewer account is still active
for (const reviewer of reviewers) {
const reviewerUser = (await bitbucketHttp.getJsonUnchecked(`/2.0/users/${reviewer.uuid}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider })).body;
if (reviewerUser.account_status === 'active') {
// There are cases where an active user may still not be a member of a workspace
if (await isAccountMemberOfWorkspace(reviewer, config.repository)) {
sanitizedReviewers.push(reviewer);
}
}
}
// Bitbucket returns a 400 if any of the PR reviewer accounts are no longer members of this workspace
}
else if (msg.endsWith(MSG_NOT_WORKSPACE_MEMBER)) {
logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are no longer member of this workspace. Will try setting only member reviewers');
// Validate that each previous PR reviewer account is still a member of this workspace
for (const reviewer of reviewers) {
if (await isAccountMemberOfWorkspace(reviewer, config.repository)) {
sanitizedReviewers.push(reviewer);
}
}
}
else if (msg.endsWith(MSG_AUTHOR_AND_REVIEWER)) {
logger_1.logger.debug({ err }, 'PR contains reviewer accounts which are also the author. Will try setting only non-author reviewers');
const author = msg.replace(MSG_AUTHOR_AND_REVIEWER, '').trim();
for (const reviewer of reviewers) {
if (reviewer.display_name !== author) {
sanitizedReviewers.push(reviewer);
}
}
}
else {
return undefined;
}
}
return sanitizedReviewers;
}
return undefined;
}
async function isAccountMemberOfWorkspace(reviewer, repository) {
const workspace = repository.split('/')[0];
try {
await bitbucketHttp.get(`/2.0/workspaces/${workspace}/members/${reviewer.uuid}`, { cacheProvider: memory_http_cache_provider_1.memCacheProvider });
return true;
}
catch (err) {
// HTTP 404: User cannot be found, or the user is not a member of this workspace.
if (err.statusCode === 404) {
logger_1.logger.debug({ err }, `User ${reviewer.display_name} is not a member of the workspace ${workspace}. Will be removed from the PR`);
return false;
}
throw err;
}
}
// Creates PR and returns PR number
async function createPr({ sourceBranch, targetBranch, prTitle: title, prBody: description, platformPrOptions, }) {
// labels is not supported in Bitbucket: https://bitbucket.org/site/master/issues/11976/ability-to-add-labels-to-pull-requests-bb
const base = targetBranch;
logger_1.logger.debug({ repository: config.repository, title, base }, 'Creating PR');
let reviewers = [];
if (platformPrOptions?.bbUseDefaultReviewers) {
const reviewersResponse = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/effective-default-reviewers`, {
paginate: true,
cacheProvider: memory_http_cache_provider_1.memCacheProvider,
})).body;
reviewers = reviewersResponse.values.map((reviewer) => ({
uuid: reviewer.user.uuid,
display_name: reviewer.user.display_name,
}));
}
const body = {
title,
description: (0, sanitize_1.sanitize)(description),
source: {
branch: {
name: sourceBranch,
},
},
destination: {
branch: {
name: base,
},
},
close_source_branch: true,
reviewers,
};
try {
const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, {
body,
})).body;
const pr = utils.prInfo(prRes);
await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, pr);
if (platformPrOptions?.bbAutoResolvePrTasks) {
await autoResolvePrTasks(pr);
}
return pr;
}
catch (err) /* v8 ignore start */ {
// Try sanitizing reviewers
const sanitizedReviewers = await sanitizeReviewers(reviewers, err);
if (sanitizedReviewers === undefined) {
logger_1.logger.warn({ err }, 'Error creating pull request');
throw err;
}
else {
const prRes = (await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests`, {
body: {
...body,
reviewers: sanitizedReviewers,
},
})).body;
const pr = utils.prInfo(prRes);
await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, pr);
if (platformPrOptions?.bbAutoResolvePrTasks) {
await autoResolvePrTasks(pr);
}
return pr;
}
} /* v8 ignore stop */
}
async function autoResolvePrTasks(pr) {
logger_1.logger.debug(`Auto resolve PR tasks in #${pr.number}`);
try {
const unResolvedTasks = (await bitbucketHttp.getJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks`, { paginate: true, pagelen: 100 }, schema_1.UnresolvedPrTasks)).body;
logger_1.logger.trace({
prId: pr.number,
listTaskRes: unResolvedTasks,
}, 'List PR tasks');
for (const task of unResolvedTasks) {
const res = await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${pr.number}/tasks/${task.id}`, {
body: {
state: 'RESOLVED',
content: {
raw: task.content.raw,
},
},
});
logger_1.logger.trace({
prId: pr.number,
updateTaskResponse: res,
}, 'Put PR tasks - mark resolved');
}
}
catch (err) {
logger_1.logger.warn({ prId: pr.number, err }, 'Error resolving PR tasks');
}
}
async function updatePr({ number: prNo, prTitle: title, prBody: description, state, targetBranch, }) {
logger_1.logger.debug(`updatePr(${prNo}, ${title}, body)`);
// Updating a PR in Bitbucket will clear the reviewers if reviewers is not present
const pr = (await bitbucketHttp.getJsonUnchecked(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`)).body;
let updatedPrRes;
try {
const body = {
title,
description: (0, sanitize_1.sanitize)(description),
reviewers: pr.reviewers,
};
if (targetBranch) {
body.destination = {
branch: {
name: targetBranch,
},
};
}
updatedPrRes = (await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, { body })).body;
}
catch (err) {
// Try sanitizing reviewers
const sanitizedReviewers = await sanitizeReviewers(pr.reviewers, err);
if (sanitizedReviewers === undefined) {
throw err;
}
else {
updatedPrRes = (await bitbucketHttp.putJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, {
body: {
title,
description: (0, sanitize_1.sanitize)(description),
reviewers: sanitizedReviewers,
},
})).body;
}
}
if (state === 'closed' && pr) {
await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/decline`);
}
// update pr cache
await pr_cache_1.BitbucketPrCache.setPr(bitbucketHttp, config.repository, renovateUserUuid, utils.prInfo({ ...updatedPrRes, ...(state && { state }) }));
}
async function mergePr({ branchName, id: prNo, strategy: mergeStrategy, }) {
logger_1.logger.debug(`mergePr(${prNo}, ${branchName}, ${mergeStrategy})`);
// Bitbucket Cloud does not support a rebase-alike; https://jira.atlassian.com/browse/BCLOUD-16610
if (mergeStrategy === 'rebase') {
logger_1.logger.warn('Bitbucket Cloud does not support a "rebase" strategy.');
return false;
}
try {
await bitbucketHttp.postJson(`/2.0/repositories/${config.repository}/pullrequests/${prNo}/merge`, {
body: (0, utils_1.mergeBodyTransformer)(mergeStrategy),
});
logger_1.logger.debug('Automerging succeeded');
}
catch (err) /* v8 ignore start */ {
logger_1.logger.debug({ err }, `PR merge error`);
logger_1.logger.info({ pr: prNo }, 'PR automerge failed');
return false;
} /* v8 ignore stop */
return true;
}
//# sourceMappingURL=index.js.map