2018-08-29 05:30:03 +00:00
|
|
|
const parseDiff = require('parse-diff');
|
|
|
|
const api = require('./bb-got-wrapper');
|
|
|
|
const utils = require('./utils');
|
2018-09-12 10:16:17 +00:00
|
|
|
const hostRules = require('../../util/host-rules');
|
2019-02-04 15:03:02 +00:00
|
|
|
const GitStorage = require('../git/storage');
|
2019-05-21 06:21:44 +00:00
|
|
|
const { readOnlyIssueBody } = require('../utils/read-only-issue-body');
|
2019-01-10 11:40:56 +00:00
|
|
|
const { appSlug } = require('../../config/app-strings');
|
2018-08-29 05:30:03 +00:00
|
|
|
|
|
|
|
let config = {};
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
// Initialization
|
2019-05-20 08:59:30 +00:00
|
|
|
initPlatform,
|
2018-08-29 05:30:03 +00:00
|
|
|
getRepos,
|
|
|
|
cleanRepo,
|
|
|
|
initRepo,
|
2019-02-04 15:03:02 +00:00
|
|
|
getRepoStatus,
|
2018-08-29 05:30:03 +00:00
|
|
|
getRepoForceRebase,
|
|
|
|
setBaseBranch,
|
2019-05-17 06:10:40 +00:00
|
|
|
setBranchPrefix,
|
2018-08-29 05:30:03 +00:00
|
|
|
// Search
|
|
|
|
getFileList,
|
|
|
|
// Branch
|
|
|
|
branchExists,
|
|
|
|
getAllRenovateBranches,
|
|
|
|
isBranchStale,
|
|
|
|
getBranchPr,
|
|
|
|
getBranchStatus,
|
|
|
|
getBranchStatusCheck,
|
|
|
|
setBranchStatus,
|
|
|
|
deleteBranch,
|
|
|
|
mergeBranch,
|
|
|
|
getBranchLastCommitTime,
|
2018-09-14 10:51:33 +00:00
|
|
|
// Issue
|
2018-10-03 13:47:03 +00:00
|
|
|
findIssue,
|
2018-08-29 05:30:03 +00:00
|
|
|
ensureIssue,
|
|
|
|
ensureIssueClosing,
|
|
|
|
addAssignees,
|
|
|
|
addReviewers,
|
2018-09-14 10:51:33 +00:00
|
|
|
deleteLabel,
|
2019-05-01 07:28:30 +00:00
|
|
|
getIssueList,
|
2018-08-29 05:30:03 +00:00
|
|
|
// Comments
|
|
|
|
ensureComment,
|
|
|
|
ensureCommentRemoval,
|
|
|
|
// PR
|
|
|
|
getPrList,
|
|
|
|
findPr,
|
|
|
|
createPr,
|
|
|
|
getPr,
|
|
|
|
getPrFiles,
|
|
|
|
updatePr,
|
|
|
|
mergePr,
|
|
|
|
getPrBody,
|
|
|
|
// file
|
|
|
|
commitFilesToBranch,
|
|
|
|
getFile,
|
|
|
|
// Commits
|
|
|
|
getCommitMessages,
|
|
|
|
// vulnerability alerts
|
|
|
|
getVulnerabilityAlerts,
|
|
|
|
};
|
|
|
|
|
2019-05-20 08:59:30 +00:00
|
|
|
function initPlatform({ endpoint, username, password }) {
|
|
|
|
if (!(username && password)) {
|
|
|
|
throw new Error(
|
|
|
|
'Init: You must configure a Bitbucket username and password'
|
|
|
|
);
|
|
|
|
}
|
|
|
|
if (endpoint && endpoint !== 'https://api.bitbucket.org/') {
|
|
|
|
throw new Error(
|
|
|
|
'Init: Bitbucket Cloud endpoint can only be https://api.bitbucket.org/'
|
|
|
|
);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
2019-05-20 08:59:30 +00:00
|
|
|
// TODO: Add a connection check that endpoint/username/password combination are valid
|
|
|
|
const res = {
|
|
|
|
endpoint: 'https://api.bitbucket.org/',
|
|
|
|
};
|
|
|
|
logger.info('Using default Bitbucket Cloud endpoint: ' + res.endpoint);
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Get all repositories that the user has access to
|
|
|
|
async function getRepos() {
|
|
|
|
logger.info('Autodiscovering Bitbucket Cloud repositories');
|
2018-08-29 05:30:03 +00:00
|
|
|
try {
|
|
|
|
const repos = await utils.accumulateValues(
|
|
|
|
`/2.0/repositories/?role=contributor`
|
|
|
|
);
|
|
|
|
return repos.map(repo => repo.full_name);
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
logger.error({ err }, `bitbucket getRepos error`);
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Initialize bitbucket by getting base branch and SHA
|
2019-05-20 08:59:30 +00:00
|
|
|
async function initRepo({ repository, localDir }) {
|
2018-08-29 05:30:03 +00:00
|
|
|
logger.debug(`initRepo("${repository}")`);
|
2019-05-20 08:59:30 +00:00
|
|
|
const opts = hostRules.find({ platform: 'bitbucket' });
|
2018-08-29 05:30:03 +00:00
|
|
|
api.reset();
|
|
|
|
config = {};
|
|
|
|
// TODO: get in touch with @rarkins about lifting up the caching into the app layer
|
|
|
|
config.repository = repository;
|
|
|
|
const platformConfig = {};
|
2019-02-04 15:03:02 +00:00
|
|
|
|
|
|
|
// Always gitFs
|
|
|
|
const url = GitStorage.getUrl({
|
|
|
|
gitFs: 'https',
|
|
|
|
auth: `${opts.username}:${opts.password}`,
|
|
|
|
hostname: 'bitbucket.org',
|
|
|
|
repository,
|
|
|
|
});
|
|
|
|
|
|
|
|
config.storage = new GitStorage();
|
|
|
|
await config.storage.initRepo({
|
|
|
|
...config,
|
|
|
|
localDir,
|
|
|
|
url,
|
|
|
|
});
|
|
|
|
|
2018-08-29 05:30:03 +00:00
|
|
|
try {
|
|
|
|
const info = utils.repoInfoTransformer(
|
|
|
|
(await api.get(`/2.0/repositories/${repository}`)).body
|
|
|
|
);
|
|
|
|
platformConfig.privateRepo = info.privateRepo;
|
|
|
|
platformConfig.isFork = info.isFork;
|
|
|
|
platformConfig.repoFullName = info.repoFullName;
|
|
|
|
config.owner = info.owner;
|
|
|
|
logger.debug(`${repository} owner = ${config.owner}`);
|
|
|
|
config.defaultBranch = info.mainbranch;
|
|
|
|
config.baseBranch = config.defaultBranch;
|
|
|
|
config.mergeMethod = info.mergeMethod;
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
if (err.statusCode === 404) {
|
|
|
|
throw new Error('not-found');
|
|
|
|
}
|
|
|
|
logger.info({ err }, 'Unknown Bitbucket initRepo error');
|
|
|
|
throw err;
|
|
|
|
}
|
|
|
|
delete config.prList;
|
|
|
|
delete config.fileList;
|
|
|
|
await Promise.all([getPrList(), getFileList()]);
|
|
|
|
return platformConfig;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns true if repository has rule enforcing PRs are up-to-date with base branch before merging
|
|
|
|
function getRepoForceRebase() {
|
|
|
|
// BB doesnt have an option to flag staled branches
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2019-05-15 11:04:16 +00:00
|
|
|
async function setBaseBranch(branchName = config.baseBranch) {
|
|
|
|
logger.debug(`Setting baseBranch to ${branchName}`);
|
|
|
|
config.baseBranch = branchName;
|
|
|
|
delete config.baseCommitSHA;
|
|
|
|
delete config.fileList;
|
|
|
|
await config.storage.setBaseBranch(branchName);
|
|
|
|
await getFileList(branchName);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
2019-05-17 06:10:40 +00:00
|
|
|
// istanbul ignore next
|
|
|
|
function setBranchPrefix(branchPrefix) {
|
|
|
|
return config.storage.setBranchPrefix(branchPrefix);
|
|
|
|
}
|
|
|
|
|
2018-08-29 05:30:03 +00:00
|
|
|
// Search
|
|
|
|
|
|
|
|
// Get full file list
|
2019-02-04 15:03:02 +00:00
|
|
|
function getFileList(branchName) {
|
|
|
|
return config.storage.getFileList(branchName);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Branch
|
|
|
|
|
|
|
|
// Returns true if branch exists, otherwise false
|
2019-02-04 15:03:02 +00:00
|
|
|
function branchExists(branchName) {
|
|
|
|
return config.storage.branchExists(branchName);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
2019-02-04 15:03:02 +00:00
|
|
|
function getAllRenovateBranches(branchPrefix) {
|
|
|
|
return config.storage.getAllRenovateBranches(branchPrefix);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
2019-02-04 15:03:02 +00:00
|
|
|
function isBranchStale(branchName) {
|
|
|
|
return config.storage.isBranchStale(branchName);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getFile(filePath, branchName) {
|
|
|
|
return config.storage.getFile(filePath, branchName);
|
|
|
|
}
|
|
|
|
|
2019-05-09 04:43:54 +00:00
|
|
|
async function deleteBranch(branchName, closePr) {
|
|
|
|
if (closePr) {
|
|
|
|
const pr = await findPr(branchName, null, 'open');
|
|
|
|
if (pr) {
|
|
|
|
await api.post(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests/${
|
|
|
|
pr.number
|
|
|
|
}/decline`
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
2019-02-04 15:03:02 +00:00
|
|
|
return config.storage.deleteBranch(branchName);
|
|
|
|
}
|
|
|
|
|
|
|
|
function getBranchLastCommitTime(branchName) {
|
|
|
|
return config.storage.getBranchLastCommitTime(branchName);
|
|
|
|
}
|
|
|
|
|
|
|
|
// istanbul ignore next
|
|
|
|
function getRepoStatus() {
|
|
|
|
return config.storage.getRepoStatus();
|
|
|
|
}
|
|
|
|
|
|
|
|
function mergeBranch(branchName) {
|
|
|
|
return config.storage.mergeBranch(branchName);
|
|
|
|
}
|
2018-08-29 05:30:03 +00:00
|
|
|
|
2019-02-04 15:03:02 +00:00
|
|
|
function commitFilesToBranch(
|
|
|
|
branchName,
|
|
|
|
files,
|
|
|
|
message,
|
|
|
|
parentBranch = config.baseBranch
|
|
|
|
) {
|
|
|
|
return config.storage.commitFilesToBranch(
|
|
|
|
branchName,
|
|
|
|
files,
|
|
|
|
message,
|
|
|
|
parentBranch
|
|
|
|
);
|
|
|
|
}
|
2018-08-29 05:30:03 +00:00
|
|
|
|
2019-02-04 15:03:02 +00:00
|
|
|
function getCommitMessages() {
|
|
|
|
return config.storage.getCommitMessages();
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the Pull Request for a branch. Null if not exists.
|
|
|
|
async function getBranchPr(branchName) {
|
|
|
|
logger.debug(`getBranchPr(${branchName})`);
|
|
|
|
const existingPr = await findPr(branchName, null, 'open');
|
|
|
|
return existingPr ? getPr(existingPr.number) : null;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Returns the combined status for a branch.
|
|
|
|
async function getBranchStatus(branchName, requiredStatusChecks) {
|
|
|
|
logger.debug(`getBranchStatus(${branchName})`);
|
|
|
|
if (!requiredStatusChecks) {
|
|
|
|
// null means disable status checks, so it always succeeds
|
|
|
|
logger.debug('Status checks disabled = returning "success"');
|
|
|
|
return 'success';
|
|
|
|
}
|
|
|
|
if (requiredStatusChecks.length) {
|
|
|
|
// This is Unsupported
|
|
|
|
logger.warn({ requiredStatusChecks }, `Unsupported requiredStatusChecks`);
|
|
|
|
return 'failed';
|
|
|
|
}
|
|
|
|
const sha = await getBranchCommit(branchName);
|
|
|
|
const statuses = await utils.accumulateValues(
|
|
|
|
`/2.0/repositories/${config.repository}/commit/${sha}/statuses`
|
|
|
|
);
|
|
|
|
const noOfFailures = statuses.filter(status => status.state === 'FAILED')
|
|
|
|
.length;
|
2018-09-24 09:47:49 +00:00
|
|
|
logger.debug(
|
|
|
|
{ branch: branchName, sha, statuses },
|
|
|
|
'branch status check result'
|
|
|
|
);
|
2018-08-29 05:30:03 +00:00
|
|
|
if (noOfFailures) {
|
|
|
|
return 'failed';
|
|
|
|
}
|
|
|
|
return 'success';
|
|
|
|
}
|
|
|
|
|
|
|
|
async function getBranchStatusCheck(branchName, context) {
|
|
|
|
const sha = await getBranchCommit(branchName);
|
|
|
|
const statuses = await utils.accumulateValues(
|
|
|
|
`/2.0/repositories/${config.repository}/commit/${sha}/statuses`
|
|
|
|
);
|
|
|
|
const bbState = (statuses.find(status => status.key === context) || {}).state;
|
|
|
|
|
|
|
|
return (
|
|
|
|
Object.keys(utils.buildStates).find(
|
|
|
|
stateKey => utils.buildStates[stateKey] === bbState
|
|
|
|
) || null
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function setBranchStatus(
|
|
|
|
branchName,
|
|
|
|
context,
|
|
|
|
description,
|
|
|
|
state,
|
|
|
|
targetUrl
|
|
|
|
) {
|
|
|
|
const sha = await getBranchCommit(branchName);
|
|
|
|
|
|
|
|
// TargetUrl can not be empty so default to bitbucket
|
|
|
|
const url = targetUrl || 'http://bitbucket.org';
|
|
|
|
|
|
|
|
const body = {
|
|
|
|
name: context,
|
|
|
|
state: utils.buildStates[state],
|
|
|
|
key: context,
|
|
|
|
description,
|
|
|
|
url,
|
|
|
|
};
|
|
|
|
|
|
|
|
await api.post(
|
|
|
|
`/2.0/repositories/${config.repository}/commit/${sha}/statuses/build`,
|
|
|
|
{ body }
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2018-10-29 16:07:50 +00:00
|
|
|
async function findOpenIssues(title) {
|
|
|
|
try {
|
|
|
|
const currentUser = (await api.get('/2.0/user')).body.username;
|
|
|
|
const filter = encodeURIComponent(
|
|
|
|
[
|
|
|
|
`title=${JSON.stringify(title)}`,
|
|
|
|
'(state = "new" OR state = "open")',
|
|
|
|
`reporter.username="${currentUser}"`,
|
|
|
|
].join(' AND ')
|
|
|
|
);
|
|
|
|
return (
|
|
|
|
(await api.get(
|
|
|
|
`/2.0/repositories/${config.repository}/issues?q=${filter}`
|
|
|
|
)).body.values || []
|
|
|
|
);
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
logger.warn('Error finding issues');
|
|
|
|
return [];
|
|
|
|
}
|
2018-10-03 13:47:03 +00:00
|
|
|
}
|
2018-10-29 16:07:50 +00:00
|
|
|
|
|
|
|
async function findIssue(title) {
|
|
|
|
logger.debug(`findIssue(${title})`);
|
|
|
|
const issues = await findOpenIssues(title);
|
|
|
|
if (!issues.length) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
const [issue] = issues;
|
|
|
|
return {
|
|
|
|
number: issue.id,
|
|
|
|
body: issue.content && issue.content.raw,
|
|
|
|
};
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
2018-10-29 16:07:50 +00:00
|
|
|
|
|
|
|
async function closeIssue(issueNumber) {
|
|
|
|
await api.put(
|
|
|
|
`/2.0/repositories/${config.repository}/issues/${issueNumber}`,
|
|
|
|
{
|
|
|
|
body: { state: 'closed' },
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
async function ensureIssue(title, body) {
|
|
|
|
logger.debug(`ensureIssue()`);
|
|
|
|
try {
|
|
|
|
const issues = await findOpenIssues(title);
|
|
|
|
if (issues.length) {
|
|
|
|
// Close any duplicates
|
|
|
|
for (const issue of issues.slice(1)) {
|
|
|
|
await closeIssue(issue.id);
|
|
|
|
}
|
|
|
|
const [issue] = issues;
|
|
|
|
if (String(issue.content.raw).trim() !== body.trim()) {
|
|
|
|
logger.info('Issue updated');
|
|
|
|
await api.put(
|
|
|
|
`/2.0/repositories/${config.repository}/issues/${issue.id}`,
|
|
|
|
{
|
2019-05-21 06:21:44 +00:00
|
|
|
body: {
|
|
|
|
content: { raw: readOnlyIssueBody(body), markup: 'markdown' },
|
|
|
|
},
|
2018-10-29 16:07:50 +00:00
|
|
|
}
|
|
|
|
);
|
|
|
|
return 'updated';
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
logger.info('Issue created');
|
|
|
|
await api.post(`/2.0/repositories/${config.repository}/issues`, {
|
|
|
|
body: {
|
|
|
|
title,
|
2019-05-21 06:21:44 +00:00
|
|
|
content: { raw: readOnlyIssueBody(body), markup: 'markdown' },
|
2018-10-29 16:07:50 +00:00
|
|
|
},
|
|
|
|
});
|
|
|
|
return 'created';
|
|
|
|
}
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
if (err.message.startsWith('Repository has no issue tracker.')) {
|
|
|
|
logger.info(
|
|
|
|
`Issues are disabled, so could not create issue: ${err.message}`
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
logger.warn({ err }, 'Could not ensure issue');
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
2019-05-01 07:28:30 +00:00
|
|
|
// istanbul ignore next
|
|
|
|
function getIssueList() {
|
|
|
|
logger.debug(`getIssueList()`);
|
|
|
|
// TODO: Needs implementation
|
|
|
|
return [];
|
|
|
|
}
|
|
|
|
|
2018-10-29 16:07:50 +00:00
|
|
|
async function ensureIssueClosing(title) {
|
|
|
|
const issues = await findOpenIssues(title);
|
|
|
|
for (const issue of issues) {
|
|
|
|
await closeIssue(issue.id);
|
|
|
|
}
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
function addAssignees() {
|
|
|
|
// Bitbucket supports "participants" and "reviewers" so does not seem to have the concept of "assignee"
|
|
|
|
logger.warn('Cannot add assignees');
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2019-04-09 13:46:40 +00:00
|
|
|
async function addReviewers(prId, reviewers) {
|
|
|
|
logger.debug(`Adding reviewers ${reviewers} to #${prId}`);
|
|
|
|
|
|
|
|
const { title } = await getPr(prId);
|
|
|
|
|
|
|
|
const body = {
|
|
|
|
title,
|
|
|
|
reviewers: reviewers.map(username => ({ username })),
|
|
|
|
};
|
|
|
|
|
|
|
|
await api.put(`/2.0/repositories/${config.repository}/pullrequests/${prId}`, {
|
|
|
|
body,
|
|
|
|
});
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
2018-09-14 10:51:33 +00:00
|
|
|
// istanbul ignore next
|
|
|
|
function deleteLabel() {
|
|
|
|
throw new Error('deleteLabel not implemented');
|
|
|
|
}
|
|
|
|
|
2018-08-29 05:30:03 +00:00
|
|
|
function ensureComment() {
|
|
|
|
// https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment
|
|
|
|
logger.warn('Comment functionality not implemented yet');
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
|
|
|
function ensureCommentRemoval() {
|
|
|
|
// The api does not support removing comments
|
|
|
|
return Promise.resolve();
|
|
|
|
}
|
|
|
|
|
2018-12-08 05:48:05 +00:00
|
|
|
// istanbul ignore next
|
|
|
|
function matchesState(state, desiredState) {
|
|
|
|
if (desiredState === 'all') {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
if (desiredState[0] === '!') {
|
|
|
|
return state !== desiredState.substring(1);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
2018-12-08 05:48:05 +00:00
|
|
|
return state === desiredState;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function findPr(branchName, prTitle, state = 'all') {
|
|
|
|
logger.debug(`findPr(${branchName}, ${prTitle}, ${state})`);
|
2018-08-29 05:30:03 +00:00
|
|
|
const prList = await getPrList();
|
2018-12-08 05:48:05 +00:00
|
|
|
const pr = prList.find(
|
|
|
|
p =>
|
|
|
|
p.branchName === branchName &&
|
|
|
|
(!prTitle || p.title === prTitle) &&
|
|
|
|
matchesState(p.state, state)
|
|
|
|
);
|
2018-08-29 05:30:03 +00:00
|
|
|
if (pr) {
|
|
|
|
logger.debug(`Found PR #${pr.number}`);
|
|
|
|
}
|
|
|
|
return pr;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Creates PR and returns PR number
|
|
|
|
async function createPr(
|
|
|
|
branchName,
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
labels,
|
|
|
|
useDefaultBranch = true
|
|
|
|
) {
|
|
|
|
// labels is not supported in Bitbucket: https://bitbucket.org/site/master/issues/11976/ability-to-add-labels-to-pull-requests-bb
|
|
|
|
|
|
|
|
const base = useDefaultBranch ? config.defaultBranch : config.baseBranch;
|
|
|
|
|
|
|
|
logger.debug({ repository: config.repository, title, base }, 'Creating PR');
|
|
|
|
|
|
|
|
const body = {
|
|
|
|
title,
|
|
|
|
description,
|
|
|
|
source: {
|
|
|
|
branch: {
|
|
|
|
name: branchName,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
destination: {
|
|
|
|
branch: {
|
|
|
|
name: base,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
close_source_branch: true,
|
|
|
|
};
|
|
|
|
|
|
|
|
const prInfo = (await api.post(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests`,
|
|
|
|
{ body }
|
|
|
|
)).body;
|
2019-04-09 12:51:56 +00:00
|
|
|
const pr = { number: prInfo.id, displayNumber: `Pull Request #${prInfo.id}` };
|
2018-10-03 13:08:34 +00:00
|
|
|
// istanbul ignore if
|
|
|
|
if (config.prList) {
|
|
|
|
config.prList.push(pr);
|
|
|
|
}
|
|
|
|
return pr;
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
async function isPrConflicted(prNo) {
|
|
|
|
const diff = (await api.get(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests/${prNo}/diff`,
|
|
|
|
{ json: false }
|
|
|
|
)).body;
|
|
|
|
|
|
|
|
return utils.isConflicted(parseDiff(diff));
|
|
|
|
}
|
|
|
|
|
|
|
|
// Gets details for a PR
|
|
|
|
async function getPr(prNo) {
|
|
|
|
const pr = (await api.get(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests/${prNo}`
|
|
|
|
)).body;
|
|
|
|
|
|
|
|
// istanbul ignore if
|
|
|
|
if (!pr) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
const res = {
|
|
|
|
displayNumber: `Pull Request #${pr.id}`,
|
|
|
|
...utils.prInfo(pr),
|
|
|
|
};
|
|
|
|
|
|
|
|
if (utils.prStates.open.includes(pr.state)) {
|
2018-09-14 18:02:51 +00:00
|
|
|
res.isConflicted = await isPrConflicted(prNo);
|
2018-08-29 05:30:03 +00:00
|
|
|
const commits = await utils.accumulateValues(pr.links.commits.href);
|
|
|
|
if (commits.length === 1) {
|
|
|
|
res.canRebase = true;
|
|
|
|
}
|
|
|
|
}
|
2018-12-08 07:44:55 +00:00
|
|
|
if (await branchExists(pr.source.branch.name)) {
|
|
|
|
res.isStale = await isBranchStale(pr.source.branch.name);
|
|
|
|
}
|
2018-08-29 05:30:03 +00:00
|
|
|
|
|
|
|
return res;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Return a list of all modified files in a PR
|
|
|
|
async function getPrFiles(prNo) {
|
|
|
|
logger.debug({ prNo }, 'getPrFiles');
|
|
|
|
const diff = (await api.get(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests/${prNo}/diff`,
|
|
|
|
{ json: false }
|
|
|
|
)).body;
|
|
|
|
const files = parseDiff(diff).map(file => file.to);
|
|
|
|
return files;
|
|
|
|
}
|
|
|
|
|
|
|
|
async function updatePr(prNo, title, description) {
|
|
|
|
logger.debug(`updatePr(${prNo}, ${title}, body)`);
|
|
|
|
await api.put(`/2.0/repositories/${config.repository}/pullrequests/${prNo}`, {
|
|
|
|
body: { title, description },
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
async function mergePr(prNo, branchName) {
|
|
|
|
logger.debug(`mergePr(${prNo}, ${branchName})`);
|
|
|
|
|
|
|
|
try {
|
|
|
|
await api.post(
|
|
|
|
`/2.0/repositories/${config.repository}/pullrequests/${prNo}/merge`,
|
|
|
|
{
|
|
|
|
body: {
|
|
|
|
close_source_branch: true,
|
|
|
|
merge_strategy: 'merge_commit',
|
|
|
|
message: 'auto merged',
|
|
|
|
},
|
|
|
|
}
|
|
|
|
);
|
|
|
|
delete config.baseCommitSHA;
|
|
|
|
logger.info('Automerging succeeded');
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
function getPrBody(input) {
|
|
|
|
// Remove any HTML we use
|
2018-09-21 09:48:13 +00:00
|
|
|
return input
|
|
|
|
.replace(/<\/?summary>/g, '**')
|
|
|
|
.replace(/<\/?details>/g, '')
|
2019-01-10 11:40:56 +00:00
|
|
|
.replace(new RegExp(`\n---\n\n.*?<!-- ${appSlug}-rebase -->.*?\n`), '')
|
2018-09-21 09:48:13 +00:00
|
|
|
.substring(0, 50000);
|
2018-08-29 05:30:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Return the commit SHA for a branch
|
|
|
|
async function getBranchCommit(branchName) {
|
|
|
|
try {
|
|
|
|
const branch = (await api.get(
|
|
|
|
`/2.0/repositories/${config.repository}/refs/branches/${branchName}`
|
|
|
|
)).body;
|
|
|
|
return branch.target.hash;
|
|
|
|
} catch (err) /* istanbul ignore next */ {
|
|
|
|
logger.debug({ err }, `getBranchCommit('${branchName}') failed'`);
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Pull Request
|
|
|
|
|
2018-12-08 05:38:22 +00:00
|
|
|
async function getPrList() {
|
2018-08-29 05:30:03 +00:00
|
|
|
logger.debug('getPrList()');
|
|
|
|
if (!config.prList) {
|
|
|
|
logger.debug('Retrieving PR list');
|
2018-12-08 05:38:22 +00:00
|
|
|
let url = `/2.0/repositories/${config.repository}/pullrequests?`;
|
|
|
|
url += utils.prStates.all.map(state => 'state=' + state).join('&');
|
|
|
|
const prs = await utils.accumulateValues(url, undefined, undefined, 50);
|
2018-08-29 05:30:03 +00:00
|
|
|
config.prList = prs.map(utils.prInfo);
|
|
|
|
logger.info({ length: config.prList.length }, 'Retrieved Pull Requests');
|
|
|
|
}
|
|
|
|
return config.prList;
|
|
|
|
}
|
|
|
|
|
|
|
|
function cleanRepo() {
|
2019-02-04 15:03:02 +00:00
|
|
|
// istanbul ignore if
|
|
|
|
if (config.storage && config.storage.cleanRepo) {
|
|
|
|
config.storage.cleanRepo();
|
|
|
|
}
|
2018-08-29 05:30:03 +00:00
|
|
|
api.reset();
|
|
|
|
config = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
function getVulnerabilityAlerts() {
|
|
|
|
return [];
|
|
|
|
}
|