"use strict" ;
const path = require ( "path" ) ;
const fs = require ( "fs" ) ;
const _ = require ( "underscore" ) ;
const settings = require ( "../settings" ) ;
const featureNamePattern = /^feature-(\d+)(?:-[a-zA-Z0-9]+)+$/ ;
const versionNamePattern = /^v\d+(\.\d+)*$/ ;
const masterNamePattern = /^master$/ ;
const writeComment = ( options , message , callback ) => options . github . issues . createComment ( {
"body" : message ,
"number" : options . number ,
"owner" : options . baseRepoOptions . owner ,
"repo" : options . baseRepoOptions . reponame
} , callback ) ;
const closePullRequest = ( options , message , callback ) => writeComment ( options , message , ( err ) => {
if ( err ) {
return callback ( err ) ;
}
return options . github . issues . edit ( {
"number" : options . number ,
"owner" : options . baseRepoOptions . owner ,
"repo" : options . baseRepoOptions . reponame ,
"state" : "closed"
} , callback ) ;
} ) ;
const checkHasIssue = ( options , issueNumber , callback ) => options . github . issues . get ( {
"number" : issueNumber ,
"owner" : options . baseRepoOptions . owner ,
"repo" : options . baseRepoOptions . reponame
} , ( err , result ) => {
if ( err && err . code !== 404 ) {
return callback ( err ) ;
}
if ( err || result . number . toString ( ) !== issueNumber ) {
return callback ( null , false ) ;
}
if ( result . pull _request && result . pull _request . url ) {
return callback ( null , false ) ;
}
return callback ( null , true , result . title ) ;
} ) ;
const checkHasReleases = ( options , callback ) => options . github . repos . getReleases ( {
"owner" : options . baseRepoOptions . owner ,
"per_page" : 1 ,
"repo" : options . baseRepoOptions . reponame
} , ( err , result ) => {
if ( err ) {
return callback ( err ) ;
}
return callback ( null , result && result . length ) ;
} ) ;
const checkPullRequest = ( options , callback ) => {
const head = options . headRepoOptions ;
const base = options . baseRepoOptions ;
if ( head . reponame !== base . reponame ) {
return closePullRequest ( options , "Base and head repository names should match" , callback ) ;
}
if ( head . owner === base . owner ) {
if ( ! versionNamePattern . test ( head . branchname ) || ! masterNamePattern . test ( base . branchname ) ) {
return closePullRequest ( options , "Only merging from version to master is allowed" , callback ) ;
}
return checkHasReleases ( options , ( err , hasReleases ) => {
if ( err ) {
return writeComment ( options , "Unable to check for releases" , callback ) ;
}
if ( ! hasReleases ) {
return closePullRequest ( options , "Merging from version to master is only allowed for repositories with releases" , callback ) ;
}
if ( options . action === "opened" ) {
return writeComment ( options , ` Switching master branch to ${ head . branchname } release ` , callback ) ;
}
return process . nextTick ( callback ) ;
} ) ;
}
if ( ! featureNamePattern . test ( head . branchname ) ) {
return closePullRequest ( options , ` Only merging from feature branch is allowed (pattern: \` ${ featureNamePattern } \` ) ` , callback ) ;
}
if ( ! versionNamePattern . test ( base . branchname ) && ! masterNamePattern . test ( base . branchname ) ) {
return closePullRequest ( options , ` Only merging to master or version branch is allowed; merging to ' ${ base . branchname } ' is not supported ` , callback ) ;
}
const issueNumber = featureNamePattern . exec ( head . branchname ) [ 1 ] ;
return checkHasIssue ( options , issueNumber , ( err , hasIssue , issueTitle ) => {
if ( err ) {
return writeComment ( options , ` Unable to check for issue: \r \n \r \n ${ err . message } ` , callback ) ;
}
if ( ! hasIssue ) {
return closePullRequest ( options , ` Unable to find issue # ${ issueNumber } ` , callback ) ;
}
const shouldHaveReleases = versionNamePattern . test ( base . branchname ) ;
return checkHasReleases ( options , ( err , hasReleases ) => {
if ( err ) {
return writeComment ( options , "Unable to check for releases" , callback ) ;
}
if ( shouldHaveReleases && ! hasReleases ) {
return closePullRequest ( options , "Merging from feature to version is only allowed for repositories with releases" , callback ) ;
}
if ( ! shouldHaveReleases && hasReleases ) {
return closePullRequest ( options , "Merging from feature to master is only allowed for repositories without releases" , callback ) ;
}
if ( options . action === "opened" ) {
return writeComment ( options , ` Merging feature # ${ issueNumber } ( ${ issueTitle } ) to ${ base . branchname } ${ shouldHaveReleases ? " release" : "" } ` , callback ) ;
}
return process . nextTick ( callback ) ;
} ) ;
} ) ;
} ;
const getStatusMessageFromRelease = ( app , options , callback ) => {
const releaseDir = path . join ( app . get ( "releasepath" ) , options . owner , options . reponame , options . branch , options . rev ) ;
const reportFile = path . join ( releaseDir , "/report.json" ) ;
options . attemptsGetReport = ( options . attemptsGetReport || 0 ) + 1 ;
fs . exists ( reportFile , ( exists ) => {
if ( ! exists ) {
return setTimeout ( ( ) => fs . exists ( releaseDir , ( dirExists ) => {
if ( ! dirExists ) {
return callback ( "Release directory not found. Probably repository hooks are not configured" ) ;
}
if ( options . attemptsGetReport > 100 ) {
return callback ( "Report file not found" ) ;
}
// Maybe it is building right now
return setTimeout ( ( ) => getStatusMessageFromRelease ( app , options , callback ) , 10000 ) ;
} ) , 2000 ) ;
}
return setTimeout ( ( ) => fs . readFile ( reportFile , ( err , dataBuffer ) => {
if ( err ) {
return callback ( err ) ;
}
const data = dataBuffer . toString ( ) ;
if ( ! data ) {
return callback ( "Report file not found" ) ;
}
const report = JSON . parse ( data ) ;
if ( report . result === "MBSNotFound" ) {
return callback ( "mbs.json is not found" ) ;
}
if ( report . result && ( ( report . result . errors || { } ) . $allMessages || [ ] ) . length + ( ( report . result . warns || { } ) . $allMessages || [ ] ) . length > 0 ) {
return callback ( _ . map (
( report . result . errors || { } ) . $allMessages || [ ] , ( message ) => ` ERR: ${ message . message } `
) . concat ( _ . map (
( report . result . warns || { } ) . $allMessages || [ ] , ( message ) => ` WARN: ${ message . message } `
) )
. join ( "\r\n" ) ) ;
}
if ( ! report . result || report . err ) {
return callback ( ` CRITICAL ERROR: ${ report . err } ` ) ;
}
if ( ( report . result . infos . $allMessages || [ ] ) . length > 0 ) {
return callback ( null , report . result . infos . $allMessages [ report . result . infos . $allMessages . length - 1 ] . message ) ;
}
return callback ( null , "OK" ) ;
} ) , 1000 ) ;
} ) ;
} ;
exports . commentOnPullRequest = ( options , callback ) => {
options . github = settings . createGithub ( options . baseRepoOptions . owner ) ;
return checkPullRequest ( options , ( err , successMessage ) => getStatusMessageFromRelease ( options . app , options . headRepoOptions , ( err , successMessage ) => {
const escapedErr = err . substring ( 0 , 64000 ) . replace ( /`/g , "` " ) ;
const message = err
? ` Was not built: \r \n \r \n \` \` \` \r \n ${ escapedErr } \r \n \` \` \` \r \n \r \n DO NOT MERGE! `
: ` Build OK \r \n \r \n ${ successMessage } ` ;
const statusUrlMessage = ` Build status URL: ${ settings . siteRoot } status/ ${ options . headRepoOptions . owner } / ${ options . headRepoOptions . reponame } / ${ options . headRepoOptions . rev } \r \n \r \n ` ;
return writeComment ( options , ` ${ message } \r \n \r \n ${ statusUrlMessage } ` , callback ) ;
} ) ) ;
} ;