2020-03-19 05:38:52 +00:00
import URL from 'url' ;
2019-07-17 14:52:09 +00:00
import addrs from 'email-addresses' ;
2020-05-01 16:03:48 +00:00
import parseDiff from 'parse-diff' ;
import { RenovateConfig } from '../../config/common' ;
import {
REPOSITORY_DISABLED ,
REPOSITORY_NOT_FOUND ,
} from '../../constants/error-messages' ;
import { PLATFORM_TYPE_BITBUCKET } from '../../constants/platforms' ;
import { PR_STATE_ALL , PR_STATE_OPEN } from '../../constants/pull-requests' ;
2019-07-15 09:04:05 +00:00
import { logger } from '../../logger' ;
2020-05-01 16:03:48 +00:00
import { BranchStatus } from '../../types' ;
import * as hostRules from '../../util/host-rules' ;
2020-06-01 14:02:25 +00:00
import { BitbucketHttp , setBaseUrl } from '../../util/http/bitbucket' ;
2020-05-01 16:03:48 +00:00
import { sanitize } from '../../util/sanitize' ;
2019-11-26 15:13:07 +00:00
import {
2020-01-07 09:59:14 +00:00
BranchStatusConfig ,
2020-05-01 16:03:48 +00:00
CommitFilesConfig ,
CreatePRConfig ,
2020-01-14 11:13:35 +00:00
EnsureCommentConfig ,
2020-05-03 19:03:55 +00:00
EnsureCommentRemovalConfig ,
2020-05-01 16:03:48 +00:00
EnsureIssueConfig ,
2020-02-25 08:42:24 +00:00
EnsureIssueResult ,
2020-05-01 16:03:48 +00:00
FindPRConfig ,
Issue ,
PlatformConfig ,
Pr ,
RepoConfig ,
RepoParams ,
VulnerabilityAlert ,
2019-11-26 15:13:07 +00:00
} from '../common' ;
2020-05-01 16:03:48 +00:00
import GitStorage , { StatusResult } from '../git/storage' ;
2019-09-25 10:42:11 +00:00
import { smartTruncate } from '../utils/pr-body' ;
2020-05-01 16:03:48 +00:00
import { readOnlyIssueBody } from '../utils/read-only-issue-body' ;
import * as comments from './comments' ;
import * as utils from './utils' ;
2019-05-21 08:34:28 +00:00
2020-06-01 14:02:25 +00:00
const bitbucketHttp = new BitbucketHttp ( ) ;
2020-03-19 05:38:52 +00:00
const BITBUCKET_PROD_ENDPOINT = 'https://api.bitbucket.org/' ;
2019-07-17 14:13:11 +00:00
let config : utils.Config = { } as any ;
2019-05-21 08:34:28 +00:00
export function initPlatform ( {
endpoint ,
username ,
password ,
2020-02-25 08:42:24 +00:00
} : RenovateConfig ) : Promise < PlatformConfig > {
2019-05-20 08:59:30 +00:00
if ( ! ( username && password ) ) {
throw new Error (
'Init: You must configure a Bitbucket username and password'
) ;
}
2020-03-19 05:38:52 +00:00
if ( endpoint && endpoint !== BITBUCKET_PROD_ENDPOINT ) {
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? `
2019-05-20 08:59:30 +00:00
) ;
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
2019-08-14 04:04:09 +00:00
const platformConfig : PlatformConfig = {
2020-03-19 05:38:52 +00:00
endpoint : endpoint || BITBUCKET_PROD_ENDPOINT ,
2019-05-20 08:59:30 +00:00
} ;
2020-02-25 08:42:24 +00:00
return Promise . resolve ( platformConfig ) ;
2019-05-20 08:59:30 +00:00
}
// Get all repositories that the user has access to
2019-11-26 15:13:07 +00:00
export async function getRepos ( ) : Promise < string [ ] > {
2020-02-24 07:43:01 +00:00
logger . debug ( 'Autodiscovering Bitbucket Cloud repositories' ) ;
2018-08-29 05:30:03 +00:00
try {
2019-11-26 15:13:07 +00:00
const repos = await utils . accumulateValues < { full_name : string } > (
2018-08-29 05:30:03 +00:00
` /2.0/repositories/?role=contributor `
) ;
2020-04-12 16:09:36 +00:00
return repos . map ( ( repo ) = > repo . full_name ) ;
2018-08-29 05:30:03 +00:00
} catch ( err ) /* istanbul ignore next */ {
logger . error ( { err } , ` bitbucket getRepos error ` ) ;
throw err ;
}
}
// Initialize bitbucket by getting base branch and SHA
2019-05-21 08:34:28 +00:00
export async function initRepo ( {
repository ,
localDir ,
2019-07-22 05:16:16 +00:00
optimizeForDisabled ,
2019-09-17 07:48:16 +00:00
bbUseDefaultReviewers ,
2020-03-19 05:38:52 +00:00
endpoint = BITBUCKET_PROD_ENDPOINT ,
2019-11-26 15:13:07 +00:00
} : RepoParams ) : Promise < RepoConfig > {
2018-08-29 05:30:03 +00:00
logger . debug ( ` initRepo(" ${ repository } ") ` ) ;
2019-05-24 15:40:39 +00:00
const opts = hostRules . find ( {
2020-02-06 12:15:54 +00:00
hostType : PLATFORM_TYPE_BITBUCKET ,
2020-03-19 05:38:52 +00:00
url : endpoint ,
2019-05-24 15:40:39 +00:00
} ) ;
2020-06-01 14:02:25 +00:00
setBaseUrl ( endpoint ) ;
2019-07-17 14:13:11 +00:00
config = {
repository ,
2020-02-05 18:17:20 +00:00
username : opts.username ,
2019-09-17 07:48:16 +00:00
bbUseDefaultReviewers : bbUseDefaultReviewers !== false ,
2019-07-17 14:13:11 +00:00
} as any ;
2019-11-26 15:13:07 +00:00
let info : utils.RepoInfo ;
2018-08-29 05:30:03 +00:00
try {
2019-08-14 08:51:12 +00:00
info = utils . repoInfoTransformer (
2020-06-01 14:02:25 +00:00
( await bitbucketHttp . getJson ( ` /2.0/repositories/ ${ repository } ` ) ) . body
2018-08-29 05:30:03 +00:00
) ;
2019-07-22 05:16:16 +00:00
if ( optimizeForDisabled ) {
interface RenovateConfig {
enabled : boolean ;
}
let renovateConfig : RenovateConfig ;
try {
2020-01-16 15:21:07 +00:00
renovateConfig = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < RenovateConfig > (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ repository } /src/ ${ info . mainbranch } /renovate.json `
)
) . body ;
2019-07-22 05:16:16 +00:00
} catch {
// Do nothing
}
if ( renovateConfig && renovateConfig . enabled === false ) {
2020-01-12 07:50:11 +00:00
throw new Error ( REPOSITORY_DISABLED ) ;
2019-07-22 05:16:16 +00:00
}
}
2019-07-17 14:13:11 +00:00
Object . assign ( config , {
owner : info.owner ,
defaultBranch : info.mainbranch ,
baseBranch : info.mainbranch ,
mergeMethod : info.mergeMethod ,
has_issues : info.has_issues ,
} ) ;
2018-08-29 05:30:03 +00:00
logger . debug ( ` ${ repository } owner = ${ config . owner } ` ) ;
} catch ( err ) /* istanbul ignore next */ {
if ( err . statusCode === 404 ) {
2020-01-12 07:50:11 +00:00
throw new Error ( REPOSITORY_NOT_FOUND ) ;
2018-08-29 05:30:03 +00:00
}
2020-02-24 07:43:01 +00:00
logger . debug ( { err } , 'Unknown Bitbucket initRepo error' ) ;
2018-08-29 05:30:03 +00:00
throw err ;
}
2019-07-22 05:16:16 +00:00
2020-03-19 05:38:52 +00:00
const { hostname } = URL . parse ( endpoint ) ;
// Converts API hostnames to their respective HTTP git hosts:
// `api.bitbucket.org` to `bitbucket.org`
// `api-staging.<host>` to `staging.<host>`
const hostnameWithoutApiPrefix = /api[.|-](.+)/ . exec ( hostname ) [ 1 ] ;
2019-07-22 05:16:16 +00:00
const url = GitStorage . getUrl ( {
protocol : 'https' ,
2020-02-05 18:17:20 +00:00
auth : ` ${ opts . username } : ${ opts . password } ` ,
2020-03-19 05:38:52 +00:00
hostname : hostnameWithoutApiPrefix ,
2019-07-22 05:16:16 +00:00
repository ,
} ) ;
config . storage = new GitStorage ( ) ;
await config . storage . initRepo ( {
. . . config ,
localDir ,
url ,
} ) ;
2019-08-14 08:51:12 +00:00
const repoConfig : RepoConfig = {
2019-08-14 09:38:13 +00:00
baseBranch : config.baseBranch ,
2019-08-14 08:51:12 +00:00
isFork : info.isFork ,
} ;
2019-08-14 04:04:09 +00:00
return repoConfig ;
2018-08-29 05:30:03 +00:00
}
// Returns true if repository has rule enforcing PRs are up-to-date with base branch before merging
2020-02-25 08:42:24 +00:00
export function getRepoForceRebase ( ) : Promise < boolean > {
2018-08-29 05:30:03 +00:00
// BB doesnt have an option to flag staled branches
2020-02-25 08:42:24 +00:00
return Promise . resolve ( false ) ;
2018-08-29 05:30:03 +00:00
}
2019-11-24 04:09:13 +00:00
// Search
// Get full file list
2020-05-05 18:42:14 +00:00
export function getFileList ( ) : Promise < string [ ] > {
return config . storage . getFileList ( ) ;
2019-11-24 04:09:13 +00:00
}
2019-11-26 15:13:07 +00:00
export async function setBaseBranch (
branchName = config . baseBranch
2020-05-03 18:38:09 +00:00
) : Promise < string > {
2019-05-15 11:04:16 +00:00
logger . debug ( ` Setting baseBranch to ${ branchName } ` ) ;
config . baseBranch = branchName ;
2020-05-05 14:02:29 +00:00
delete config . baseCommitSHA ;
2020-05-03 18:38:09 +00:00
const baseBranchSha = await config . storage . setBaseBranch ( branchName ) ;
return baseBranchSha ;
2018-08-29 05:30:03 +00:00
}
2019-05-28 09:21:17 +00:00
export /* istanbul ignore next */ function setBranchPrefix (
branchPrefix : string
2019-11-26 15:13:07 +00:00
) : Promise < void > {
2019-05-17 06:10:40 +00:00
return config . storage . setBranchPrefix ( branchPrefix ) ;
}
2018-08-29 05:30:03 +00:00
// Branch
// Returns true if branch exists, otherwise false
2019-11-26 15:13:07 +00:00
export function branchExists ( branchName : string ) : Promise < boolean > {
2019-02-04 15:03:02 +00:00
return config . storage . branchExists ( branchName ) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
export function getAllRenovateBranches (
branchPrefix : string
) : Promise < string [ ] > {
2019-02-04 15:03:02 +00:00
return config . storage . getAllRenovateBranches ( branchPrefix ) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
export function isBranchStale ( branchName : string ) : Promise < boolean > {
2019-02-04 15:03:02 +00:00
return config . storage . isBranchStale ( branchName ) ;
}
2019-11-26 15:13:07 +00:00
export function getFile (
filePath : string ,
branchName? : string
) : Promise < string > {
2019-02-04 15:03:02 +00:00
return config . storage . getFile ( filePath , branchName ) ;
}
2019-11-24 04:09:13 +00:00
// istanbul ignore next
2019-11-26 15:13:07 +00:00
function matchesState ( state : string , desiredState : string ) : boolean {
2020-03-05 06:03:47 +00:00
if ( desiredState === PR_STATE_ALL ) {
2019-11-24 04:09:13 +00:00
return true ;
}
2020-02-05 18:17:20 +00:00
if ( desiredState . startsWith ( '!' ) ) {
2019-11-24 04:09:13 +00:00
return state !== desiredState . substring ( 1 ) ;
}
return state === desiredState ;
}
2019-11-26 15:13:07 +00:00
export async function getPrList ( ) : Promise < Pr [ ] > {
2019-11-24 04:09:13 +00:00
logger . debug ( 'getPrList()' ) ;
if ( ! config . prList ) {
logger . debug ( 'Retrieving PR list' ) ;
let url = ` /2.0/repositories/ ${ config . repository } /pullrequests? ` ;
2020-04-12 16:09:36 +00:00
url += utils . prStates . all . map ( ( state ) = > 'state=' + state ) . join ( '&' ) ;
2019-11-24 04:09:13 +00:00
const prs = await utils . accumulateValues ( url , undefined , undefined , 50 ) ;
config . prList = prs . map ( utils . prInfo ) ;
2020-02-24 07:43:01 +00:00
logger . debug ( { length : config.prList.length } , 'Retrieved Pull Requests' ) ;
2019-11-24 04:09:13 +00:00
}
return config . prList ;
}
2020-05-30 05:15:08 +00:00
/* istanbul ignore next */
export async function getPrFiles ( pr : Pr ) : Promise < string [ ] > {
return config . storage . getBranchFiles ( pr . branchName , pr . targetBranch ) ;
}
2020-01-14 15:12:14 +00:00
export async function findPr ( {
branchName ,
prTitle ,
2020-03-05 06:03:47 +00:00
state = PR_STATE_ALL ,
2020-01-14 15:12:14 +00:00
} : FindPRConfig ) : Promise < Pr | null > {
2019-11-24 04:09:13 +00:00
logger . debug ( ` findPr( ${ branchName } , ${ prTitle } , ${ state } ) ` ) ;
const prList = await getPrList ( ) ;
const pr = prList . find (
2020-04-12 16:09:36 +00:00
( p ) = >
2019-11-24 04:09:13 +00:00
p . branchName === branchName &&
( ! prTitle || p . title === prTitle ) &&
matchesState ( p . state , state )
) ;
if ( pr ) {
logger . debug ( ` Found PR # ${ pr . number } ` ) ;
}
return pr ;
}
2019-11-26 15:13:07 +00:00
export async function deleteBranch (
branchName : string ,
closePr? : boolean
) : Promise < void > {
2019-05-09 04:43:54 +00:00
if ( closePr ) {
2020-03-05 06:03:47 +00:00
const pr = await findPr ( { branchName , state : PR_STATE_OPEN } ) ;
2019-05-09 04:43:54 +00:00
if ( pr ) {
2020-06-01 14:02:25 +00:00
await bitbucketHttp . postJson (
2019-06-07 04:34:57 +00:00
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ pr . number } /decline `
2019-05-09 04:43:54 +00:00
) ;
}
}
2019-02-04 15:03:02 +00:00
return config . storage . deleteBranch ( branchName ) ;
}
2019-11-26 15:13:07 +00:00
export function getBranchLastCommitTime ( branchName : string ) : Promise < Date > {
2019-02-04 15:03:02 +00:00
return config . storage . getBranchLastCommitTime ( branchName ) ;
}
// istanbul ignore next
2019-11-26 15:13:07 +00:00
export function getRepoStatus ( ) : Promise < StatusResult > {
2019-02-04 15:03:02 +00:00
return config . storage . getRepoStatus ( ) ;
}
2019-11-26 15:13:07 +00:00
export function mergeBranch ( branchName : string ) : Promise < void > {
2019-02-04 15:03:02 +00:00
return config . storage . mergeBranch ( branchName ) ;
}
2018-08-29 05:30:03 +00:00
2020-05-14 12:13:08 +00:00
export function commitFiles ( {
2020-01-02 09:03:46 +00:00
branchName ,
files ,
message ,
2020-02-24 04:33:51 +00:00
} : CommitFilesConfig ) : Promise < string | null > {
2020-05-14 12:13:08 +00:00
return config . storage . commitFiles ( {
2019-02-04 15:03:02 +00:00
branchName ,
files ,
message ,
2020-01-02 09:03:46 +00:00
} ) ;
2019-02-04 15:03:02 +00:00
}
2018-08-29 05:30:03 +00:00
2019-11-26 15:13:07 +00:00
export function getCommitMessages ( ) : Promise < string [ ] > {
2019-02-04 15:03:02 +00:00
return config . storage . getCommitMessages ( ) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
async function isPrConflicted ( prNo : number ) : Promise < boolean > {
2020-01-16 15:21:07 +00:00
const diff = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . get (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ prNo } /diff ` ,
{ json : false } as any
)
) . body ;
2019-11-24 04:09:13 +00:00
return utils . isConflicted ( parseDiff ( diff ) ) ;
}
2020-06-01 14:02:25 +00:00
interface PrResponse {
id : string ;
state : string ;
links : {
commits : {
href : string ;
} ;
} ;
source : {
branch : {
name : string ;
} ;
} ;
}
2019-11-24 04:09:13 +00:00
// Gets details for a PR
2019-11-26 15:13:07 +00:00
export async function getPr ( prNo : number ) : Promise < Pr | null > {
2020-01-16 15:21:07 +00:00
const pr = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < PrResponse > (
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ prNo } `
)
2020-01-16 15:21:07 +00:00
) . body ;
2019-11-24 04:09:13 +00:00
// istanbul ignore if
if ( ! pr ) {
return null ;
}
const res : any = {
displayNumber : ` Pull Request # ${ pr . id } ` ,
. . . utils . prInfo ( pr ) ,
isModified : false ,
} ;
if ( utils . prStates . open . includes ( pr . state ) ) {
res . isConflicted = await isPrConflicted ( prNo ) ;
// TODO: Is that correct? Should we check getBranchStatus like gitlab?
res . canMerge = ! res . isConflicted ;
// we only want the first two commits, because size tells us the overall number
const url = pr . links . commits . href + '?pagelen=2' ;
2020-06-01 14:02:25 +00:00
const { body } = await bitbucketHttp . getJson < utils.PagedResult < Commit > > (
url
) ;
2019-11-24 04:09:13 +00:00
const size = body . size || body . values . length ;
// istanbul ignore if
if ( size === undefined ) {
logger . warn ( { prNo , url , body } , 'invalid response so can rebase' ) ;
} else if ( size === 1 ) {
if ( global . gitAuthor ) {
const author = addrs . parseOneAddress (
body . values [ 0 ] . author . raw
) as addrs . ParsedMailbox ;
if ( author . address !== global . gitAuthor . email ) {
logger . debug (
{ prNo } ,
'PR is modified: 1 commit but not by configured gitAuthor'
) ;
res . isModified = true ;
}
}
} else {
logger . debug ( { prNo } , ` PR is modified: Found ${ size } commits ` ) ;
res . isModified = true ;
}
}
if ( await branchExists ( pr . source . branch . name ) ) {
res . isStale = await isBranchStale ( pr . source . branch . name ) ;
}
return res ;
}
2019-11-26 15:13:07 +00:00
const escapeHash = ( input : string ) : string = >
input ? input . replace ( /#/g , '%23' ) : input ;
2019-11-24 04:09:13 +00:00
2020-06-01 14:02:25 +00:00
interface BranchResponse {
target : {
hash : string ;
} ;
}
2019-11-24 04:09:13 +00:00
// Return the commit SHA for a branch
2019-11-26 15:13:07 +00:00
async function getBranchCommit ( branchName : string ) : Promise < string | null > {
2019-11-24 04:09:13 +00:00
try {
2020-01-16 15:21:07 +00:00
const branch = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < BranchResponse > (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ config . repository } /refs/branches/ ${ escapeHash (
branchName
) } `
)
) . body ;
2019-11-24 04:09:13 +00:00
return branch . target . hash ;
} catch ( err ) /* istanbul ignore next */ {
logger . debug ( { err } , ` getBranchCommit(' ${ branchName } ') failed' ` ) ;
return null ;
}
}
2018-08-29 05:30:03 +00:00
// Returns the Pull Request for a branch. Null if not exists.
2019-11-26 15:13:07 +00:00
export async function getBranchPr ( branchName : string ) : Promise < Pr | null > {
2018-08-29 05:30:03 +00:00
logger . debug ( ` getBranchPr( ${ branchName } ) ` ) ;
2020-03-05 06:03:47 +00:00
const existingPr = await findPr ( {
branchName ,
state : PR_STATE_OPEN ,
} ) ;
2018-08-29 05:30:03 +00:00
return existingPr ? getPr ( existingPr . number ) : null ;
}
2019-12-03 17:21:40 +00:00
async function getStatus (
branchName : string ,
useCache = true
) : Promise < utils.BitbucketStatus [ ] > {
const sha = await getBranchCommit ( branchName ) ;
return utils . accumulateValues < utils.BitbucketStatus > (
` /2.0/repositories/ ${ config . repository } /commit/ ${ sha } /statuses ` ,
'get' ,
{ useCache }
) ;
}
2018-08-29 05:30:03 +00:00
// Returns the combined status for a branch.
2019-05-21 08:34:28 +00:00
export async function getBranchStatus (
branchName : string ,
requiredStatusChecks? : string [ ]
2020-02-25 08:42:24 +00:00
) : Promise < BranchStatus > {
2018-08-29 05:30:03 +00:00
logger . debug ( ` getBranchStatus( ${ branchName } ) ` ) ;
if ( ! requiredStatusChecks ) {
// null means disable status checks, so it always succeeds
logger . debug ( 'Status checks disabled = returning "success"' ) ;
2020-03-08 14:03:19 +00:00
return BranchStatus . green ;
2018-08-29 05:30:03 +00:00
}
if ( requiredStatusChecks . length ) {
// This is Unsupported
logger . warn ( { requiredStatusChecks } , ` Unsupported requiredStatusChecks ` ) ;
2020-03-08 14:03:19 +00:00
return BranchStatus . red ;
2018-08-29 05:30:03 +00:00
}
2019-12-03 17:21:40 +00:00
const statuses = await getStatus ( branchName ) ;
logger . debug ( { branch : branchName , statuses } , 'branch status check result' ) ;
2019-10-13 04:37:44 +00:00
if ( ! statuses . length ) {
logger . debug ( 'empty branch status check result = returning "pending"' ) ;
2020-03-08 14:03:19 +00:00
return BranchStatus . yellow ;
2019-10-13 04:37:44 +00:00
}
const noOfFailures = statuses . filter (
2020-03-09 04:56:12 +00:00
( status : { state : string } ) = >
status . state === 'FAILED' || status . state === 'STOPPED'
2019-10-13 04:37:44 +00:00
) . length ;
2018-08-29 05:30:03 +00:00
if ( noOfFailures ) {
2020-03-08 14:03:19 +00:00
return BranchStatus . red ;
2018-08-29 05:30:03 +00:00
}
2019-10-13 04:37:44 +00:00
const noOfPending = statuses . filter (
( status : { state : string } ) = > status . state === 'INPROGRESS'
) . length ;
if ( noOfPending ) {
2020-03-08 14:03:19 +00:00
return BranchStatus . yellow ;
2019-10-13 04:37:44 +00:00
}
2020-03-08 14:03:19 +00:00
return BranchStatus . green ;
2018-08-29 05:30:03 +00:00
}
2020-03-08 14:03:19 +00:00
const bbToRenovateStatusMapping : Record < string , BranchStatus > = {
SUCCESSFUL : BranchStatus.green ,
INPROGRESS : BranchStatus.yellow ,
FAILED : BranchStatus.red ,
} ;
2019-05-21 08:34:28 +00:00
export async function getBranchStatusCheck (
branchName : string ,
context : string
2020-03-08 14:03:19 +00:00
) : Promise < BranchStatus | null > {
2019-12-03 17:21:40 +00:00
const statuses = await getStatus ( branchName ) ;
2020-04-12 16:09:36 +00:00
const bbState = ( statuses . find ( ( status ) = > status . key === context ) || { } )
. state ;
2020-03-08 14:03:19 +00:00
return bbToRenovateStatusMapping [ bbState ] || null ;
2018-08-29 05:30:03 +00:00
}
2020-01-07 09:59:14 +00:00
export async function setBranchStatus ( {
branchName ,
context ,
description ,
state ,
url : targetUrl ,
} : BranchStatusConfig ) : Promise < void > {
2018-08-29 05:30:03 +00:00
const sha = await getBranchCommit ( branchName ) ;
// TargetUrl can not be empty so default to bitbucket
2019-05-21 08:34:28 +00:00
const url = targetUrl || /* istanbul ignore next */ 'http://bitbucket.org' ;
2018-08-29 05:30:03 +00:00
const body = {
name : context ,
state : utils.buildStates [ state ] ,
key : context ,
description ,
url ,
} ;
2020-06-01 14:02:25 +00:00
await bitbucketHttp . postJson (
2018-08-29 05:30:03 +00:00
` /2.0/repositories/ ${ config . repository } /commit/ ${ sha } /statuses/build ` ,
{ body }
) ;
2019-12-03 17:21:40 +00:00
// update status cache
await getStatus ( branchName , false ) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
type BbIssue = { id : number ; content ? : { raw : string } } ;
async function findOpenIssues ( title : string ) : Promise < BbIssue [ ] > {
2018-10-29 16:07:50 +00:00
try {
const filter = encodeURIComponent (
[
` title= ${ JSON . stringify ( title ) } ` ,
'(state = "new" OR state = "open")' ,
2019-07-17 14:13:11 +00:00
` reporter.username=" ${ config . username } " ` ,
2018-10-29 16:07:50 +00:00
] . join ( ' AND ' )
) ;
return (
2020-01-16 15:21:07 +00:00
(
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < { values : BbIssue [ ] } > (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ config . repository } /issues?q= ${ filter } `
)
) . body . values || /* istanbul ignore next */ [ ]
2018-10-29 16:07:50 +00:00
) ;
} catch ( err ) /* istanbul ignore next */ {
2019-07-17 14:13:11 +00:00
logger . warn ( { err } , 'Error finding issues' ) ;
2018-10-29 16:07:50 +00:00
return [ ] ;
}
2018-10-03 13:47:03 +00:00
}
2018-10-29 16:07:50 +00:00
2019-11-26 15:13:07 +00:00
export async function findIssue ( title : string ) : Promise < Issue > {
2018-10-29 16:07:50 +00:00
logger . debug ( ` findIssue( ${ title } ) ` ) ;
2019-07-17 14:13:11 +00:00
/* istanbul ignore if */
if ( ! config . has_issues ) {
2019-08-27 04:01:39 +00:00
logger . debug ( 'Issues are disabled - cannot findIssue' ) ;
2019-07-17 14:13:11 +00:00
return null ;
}
2018-10-29 16:07:50 +00:00
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
2019-11-26 15:13:07 +00:00
async function closeIssue ( issueNumber : number ) : Promise < void > {
2020-06-01 14:02:25 +00:00
await bitbucketHttp . putJson (
2018-10-29 16:07:50 +00:00
` /2.0/repositories/ ${ config . repository } /issues/ ${ issueNumber } ` ,
{
body : { state : 'closed' } ,
}
) ;
}
2019-11-26 15:13:07 +00:00
export function getPrBody ( input : string ) : string {
2019-11-24 04:09:13 +00:00
// Remove any HTML we use
return smartTruncate ( input , 50000 )
2020-01-21 08:44:38 +00:00
. replace (
2020-02-24 05:35:41 +00:00
'you tick the rebase/retry checkbox' ,
'rename PR to start with "rebase!"'
2020-01-21 08:44:38 +00:00
)
2019-11-24 04:09:13 +00:00
. replace ( /<\/?summary>/g , '**' )
. replace ( /<\/?details>/g , '' )
2019-12-05 09:45:28 +00:00
. replace ( new RegExp ( ` \ n--- \ n \ n.*?<!-- rebase-check -->.*? \ n ` ) , '' )
2019-11-24 04:09:13 +00:00
. replace ( /\]\(\.\.\/pull\//g , '](../../pull-requests/' ) ;
}
2020-01-07 10:40:53 +00:00
export async function ensureIssue ( {
title ,
body ,
2020-02-25 08:42:24 +00:00
} : EnsureIssueConfig ) : Promise < EnsureIssueResult | null > {
2018-10-29 16:07:50 +00:00
logger . debug ( ` ensureIssue() ` ) ;
2019-09-12 10:48:31 +00:00
const description = getPrBody ( sanitize ( body ) ) ;
2019-07-17 14:13:11 +00:00
/* istanbul ignore if */
if ( ! config . has_issues ) {
2019-08-27 04:01:39 +00:00
logger . warn ( 'Issues are disabled - cannot ensureIssue' ) ;
2020-02-24 07:43:01 +00:00
logger . debug ( { title } , 'Failed to ensure Issue' ) ;
2019-07-17 14:13:11 +00:00
return null ;
}
2018-10-29 16:07:50 +00:00
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 ;
2019-07-27 06:27:51 +00:00
if ( String ( issue . content . raw ) . trim ( ) !== description . trim ( ) ) {
2020-02-24 07:43:01 +00:00
logger . debug ( 'Issue updated' ) ;
2020-06-01 14:02:25 +00:00
await bitbucketHttp . putJson (
2018-10-29 16:07:50 +00:00
` /2.0/repositories/ ${ config . repository } /issues/ ${ issue . id } ` ,
{
2019-05-21 06:21:44 +00:00
body : {
2019-07-27 06:27:51 +00:00
content : {
raw : readOnlyIssueBody ( description ) ,
markup : 'markdown' ,
} ,
2019-05-21 06:21:44 +00:00
} ,
2018-10-29 16:07:50 +00:00
}
) ;
return 'updated' ;
}
} else {
2020-02-24 12:56:04 +00:00
logger . info ( 'Issue created' ) ;
2020-06-01 14:02:25 +00:00
await bitbucketHttp . postJson (
` /2.0/repositories/ ${ config . repository } /issues ` ,
{
body : {
title ,
content : {
raw : readOnlyIssueBody ( description ) ,
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.' ) ) {
2020-02-24 07:43:01 +00:00
logger . debug (
2018-10-29 16:07:50 +00:00
` Issues are disabled, so could not create issue: ${ err . message } `
) ;
} else {
logger . warn ( { err } , 'Could not ensure issue' ) ;
}
}
return null ;
}
2019-11-26 15:13:07 +00:00
export /* istanbul ignore next */ async function getIssueList ( ) : Promise <
Issue [ ]
> {
2019-05-01 07:28:30 +00:00
logger . debug ( ` getIssueList() ` ) ;
2019-07-17 14:13:11 +00:00
/* istanbul ignore if */
if ( ! config . has_issues ) {
2019-08-27 04:01:39 +00:00
logger . debug ( 'Issues are disabled - cannot getIssueList' ) ;
2019-07-17 14:13:11 +00:00
return [ ] ;
}
try {
const filter = encodeURIComponent (
[
'(state = "new" OR state = "open")' ,
` reporter.username=" ${ config . username } " ` ,
] . join ( ' AND ' )
) ;
return (
2020-01-16 15:21:07 +00:00
(
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < { values : Issue [ ] } > (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ config . repository } /issues?q= ${ filter } `
)
) . body . values || /* istanbul ignore next */ [ ]
2019-07-17 14:13:11 +00:00
) ;
} catch ( err ) /* istanbul ignore next */ {
logger . warn ( { err } , 'Error finding issues' ) ;
return [ ] ;
}
2019-05-01 07:28:30 +00:00
}
2019-11-26 15:13:07 +00:00
export async function ensureIssueClosing ( title : string ) : Promise < void > {
2019-07-17 14:13:11 +00:00
/* istanbul ignore if */
if ( ! config . has_issues ) {
2019-08-27 04:01:39 +00:00
logger . debug ( 'Issues are disabled - cannot ensureIssueClosing' ) ;
2019-07-17 14:13:11 +00:00
return ;
}
2018-10-29 16:07:50 +00:00
const issues = await findOpenIssues ( title ) ;
for ( const issue of issues ) {
await closeIssue ( issue . id ) ;
}
2018-08-29 05:30:03 +00:00
}
2019-06-04 02:25:15 +00:00
// eslint-disable-next-line @typescript-eslint/no-unused-vars
2019-11-26 15:13:07 +00:00
export function addAssignees (
_prNr : number ,
_assignees : string [ ]
) : Promise < void > {
2018-08-29 05:30:03 +00:00
// Bitbucket supports "participants" and "reviewers" so does not seem to have the concept of "assignee"
logger . warn ( 'Cannot add assignees' ) ;
return Promise . resolve ( ) ;
}
2019-11-26 15:13:07 +00:00
export async function addReviewers (
prId : number ,
reviewers : string [ ]
) : Promise < void > {
2019-04-09 13:46:40 +00:00
logger . debug ( ` Adding reviewers ${ reviewers } to # ${ prId } ` ) ;
const { title } = await getPr ( prId ) ;
const body = {
title ,
2019-05-21 08:34:28 +00:00
reviewers : reviewers.map ( ( username : string ) = > ( { username } ) ) ,
2019-04-09 13:46:40 +00:00
} ;
2020-06-01 14:02:25 +00:00
await bitbucketHttp . putJson (
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ prId } ` ,
{
body ,
}
) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
export /* istanbul ignore next */ function deleteLabel ( ) : never {
2018-09-14 10:51:33 +00:00
throw new Error ( 'deleteLabel not implemented' ) ;
}
2020-01-14 11:13:35 +00:00
export function ensureComment ( {
number ,
topic ,
content ,
} : EnsureCommentConfig ) : Promise < boolean > {
2018-08-29 05:30:03 +00:00
// https://developer.atlassian.com/bitbucket/api/2/reference/search?q=pullrequest+comment
2020-01-14 11:13:35 +00:00
return comments . ensureComment ( {
config ,
number ,
topic ,
content : sanitize ( content ) ,
} ) ;
2018-08-29 05:30:03 +00:00
}
2020-05-03 19:03:55 +00:00
export function ensureCommentRemoval ( {
number : prNo ,
topic ,
2020-05-12 21:21:58 +00:00
content ,
2020-05-03 19:03:55 +00:00
} : EnsureCommentRemovalConfig ) : Promise < void > {
2020-05-12 21:21:58 +00:00
return comments . ensureCommentRemoval ( config , prNo , topic , content ) ;
2018-08-29 05:30:03 +00:00
}
// Creates PR and returns PR number
2020-01-07 15:33:19 +00:00
export async function createPr ( {
branchName ,
prTitle : title ,
prBody : description ,
useDefaultBranch = true ,
} : CreatePRConfig ) : Promise < Pr > {
2018-08-29 05:30:03 +00:00
// labels is not supported in Bitbucket: https://bitbucket.org/site/master/issues/11976/ability-to-add-labels-to-pull-requests-bb
2019-05-21 08:34:28 +00:00
const base = useDefaultBranch
? config . defaultBranch
: /* istanbul ignore next */ config . baseBranch ;
2018-08-29 05:30:03 +00:00
logger . debug ( { repository : config.repository , title , base } , 'Creating PR' ) ;
2020-05-07 08:23:45 +00:00
let reviewers : { uuid : { raw : string } } [ ] = [ ] ;
2019-09-17 07:48:16 +00:00
if ( config . bbUseDefaultReviewers ) {
2020-01-16 15:21:07 +00:00
const reviewersResponse = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . getJson < utils.PagedResult < Reviewer > > (
2020-01-16 15:21:07 +00:00
` /2.0/repositories/ ${ config . repository } /default-reviewers `
)
) . body ;
2019-09-17 07:48:16 +00:00
reviewers = reviewersResponse . values . map ( ( reviewer : Reviewer ) = > ( {
uuid : reviewer.uuid ,
} ) ) ;
}
2018-08-29 05:30:03 +00:00
const body = {
title ,
2019-09-12 10:48:31 +00:00
description : sanitize ( description ) ,
2018-08-29 05:30:03 +00:00
source : {
branch : {
name : branchName ,
} ,
} ,
destination : {
branch : {
name : base ,
} ,
} ,
close_source_branch : true ,
2019-09-17 07:48:16 +00:00
reviewers ,
2018-08-29 05:30:03 +00:00
} ;
2020-02-13 10:51:48 +00:00
try {
const prInfo = (
2020-06-01 14:02:25 +00:00
await bitbucketHttp . postJson < PrResponse > (
` /2.0/repositories/ ${ config . repository } /pullrequests ` ,
{
body ,
}
)
2020-02-13 10:51:48 +00:00
) . body ;
// TODO: fix types
const pr : Pr = {
number : prInfo . id ,
displayNumber : ` Pull Request # ${ prInfo . id } ` ,
isModified : false ,
} as any ;
// istanbul ignore if
if ( config . prList ) {
config . prList . push ( pr ) ;
}
return pr ;
} catch ( err ) /* istanbul ignore next */ {
logger . warn ( { err } , 'Error creating pull request' ) ;
throw err ;
2018-10-03 13:08:34 +00:00
}
2018-08-29 05:30:03 +00:00
}
2019-09-17 07:48:16 +00:00
interface Reviewer {
uuid : { raw : string } ;
}
2019-07-17 14:52:09 +00:00
interface Commit {
author : { raw : string } ;
}
2018-08-29 05:30:03 +00:00
2019-05-21 08:34:28 +00:00
export async function updatePr (
prNo : number ,
title : string ,
description : string
2019-11-26 15:13:07 +00:00
) : Promise < void > {
2018-08-29 05:30:03 +00:00
logger . debug ( ` updatePr( ${ prNo } , ${ title } , body) ` ) ;
2020-06-01 14:02:25 +00:00
await bitbucketHttp . putJson (
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ prNo } ` ,
{
body : { title , description : sanitize ( description ) } ,
}
) ;
2018-08-29 05:30:03 +00:00
}
2019-11-26 15:13:07 +00:00
export async function mergePr (
prNo : number ,
branchName : string
) : Promise < boolean > {
2018-08-29 05:30:03 +00:00
logger . debug ( ` mergePr( ${ prNo } , ${ branchName } ) ` ) ;
try {
2020-06-01 14:02:25 +00:00
await bitbucketHttp . postJson (
2018-08-29 05:30:03 +00:00
` /2.0/repositories/ ${ config . repository } /pullrequests/ ${ prNo } /merge ` ,
{
body : {
close_source_branch : true ,
merge_strategy : 'merge_commit' ,
message : 'auto merged' ,
} ,
}
) ;
2020-05-05 14:02:29 +00:00
delete config . baseCommitSHA ;
2020-02-24 07:43:01 +00:00
logger . debug ( 'Automerging succeeded' ) ;
2018-08-29 05:30:03 +00:00
} catch ( err ) /* istanbul ignore next */ {
return false ;
}
return true ;
}
// Pull Request
2020-02-25 08:42:24 +00:00
export function cleanRepo ( ) : Promise < void > {
2019-02-04 15:03:02 +00:00
// istanbul ignore if
if ( config . storage && config . storage . cleanRepo ) {
config . storage . cleanRepo ( ) ;
}
2019-05-21 08:34:28 +00:00
config = { } as any ;
2020-02-25 08:42:24 +00:00
return Promise . resolve ( ) ;
2018-08-29 05:30:03 +00:00
}
2020-02-05 18:17:20 +00:00
export function getVulnerabilityAlerts ( ) : Promise < VulnerabilityAlert [ ] > {
return Promise . resolve ( [ ] ) ;
2018-08-29 05:30:03 +00:00
}