diff --git a/.travis.yml b/.travis.yml index 71d2b3c..4a64b2f 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,11 +8,10 @@ branches: matrix: include: - - php: 7.1 + - php: 7.3 env: dependencies=highest - - php: 7.0.11 - - php: 5.6 - - php: 5.5 + - php: 7.2 + - php: 7.1 env: dependencies=lowest sudo: false diff --git a/src/Runner.php b/src/Runner.php index 26d3225..ef011ab 100644 --- a/src/Runner.php +++ b/src/Runner.php @@ -43,22 +43,42 @@ public function start( $target_dir, $opts ) { } // Determine whether there is an existing open PR with Composer updates - $existing_PR_branch = $this->checkExisting(); + $existing_PR_branch = $this->checkExistingPRBranch(); if ( $existing_PR_branch ) { - Logger::info( "Using existing branch: $existing_PR_branch" ); + exec( 'git rev-parse --abbrev-ref HEAD', $initial_branch, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Failed to fetch initial branch.' ); + } + $initial_branch = $initial_branch[0]; + Logger::info( "Inspecting existing branch: $existing_PR_branch" ); passthru( 'git fetch' ); passthru( 'git checkout ' . $existing_PR_branch ); + // Check to see if there are any outdated dependencies. + $this->runComposerInstall(); + exec( 'composer outdated', $output, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Failed to run composer outdated.' ); + } + if ( empty( $output ) ) { + Logger::info('Exiting since no updates were detected on existing PR branch.'); + exit(0); + } + // Close the existing PR and delete its branch. + $this->closeExistingPRBranch( $existing_PR_branch ); + // Check out the initial branch locally and delete the local PR branch. + passthru( 'git checkout ' . $initial_branch ); + $cmd = 'git branch -D ' . escapeshellarg( $existing_PR_branch ); + Logger::info( $cmd ); + passthru( $cmd, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Failed to delete local branch.' ); + } + // Fall through to create the new branch. } // Perform an initial install to sanity check the package. - $args = getenv( 'CLU_COMPOSER_INSTALL_ARGS' ) ? : '--no-dev --no-interaction'; - $cmd = 'composer install ' . $args; - Logger::info( $cmd ); - passthru( $cmd, $return_code ); - if ( 0 !== $return_code ) { - Logger::error( 'Composer failed to install dependencies.' ); - } + $this->runComposerInstall(); $cmd = 'composer outdated'; Logger::info( $cmd ); @@ -121,16 +141,13 @@ public function start( $target_dir, $opts ) { Logger::info( 'Set git config name and email.' ); $date = date( 'Y-m-d-H-i' ); - $branch_name = $existing_PR_branch; - if ( !$existing_PR_branch ) { - // Checkout a dated branch to make the commit - $branch_name = 'clu-' . $date; - $cmd = 'git checkout -b ' . escapeshellarg( $branch_name ); - Logger::info( $cmd ); - passthru( $cmd, $return_code ); - if ( 0 !== $return_code ) { - Logger::error( 'Failed to check out branch.' ); - } + // Checkout a dated branch to make the commit + $branch_name = 'clu-' . $date; + $cmd = 'git checkout -b ' . escapeshellarg( $branch_name ); + Logger::info( $cmd ); + passthru( $cmd, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Failed to check out branch.' ); } $message = <<isGitLab() ) { - // Add comment to existing PR with $message - $commitSha = exec( 'git rev-parse HEAD', $output_lines, $return_code); - $this->addCommitComment( $message, $this->project(), $commitSha ); - Logger::success( sprintf( 'Updated %s with composer.lock changes.', $this->getRequestType() ) ); - return; - } if ( $this->isGitHub() ) { $cmd = 'hub pull-request -m ' . escapeshellarg( $message ); } elseif ( $this->isGitLab() ) { @@ -176,7 +186,7 @@ public function start( $target_dir, $opts ) { Logger::success( sprintf( 'Created %s with composer.lock changes.', $this->getRequestType() ) ); } - private function checkExisting() { + private function checkExistingPRBranch() { if ( $this->isGitHub() ) { $cmd = 'hub pr list --format="%t%n" --state=open'; } elseif ( $this->isGitLab() ) { @@ -197,6 +207,50 @@ private function checkExisting() { return false; } + /** + * Closes an existing PR branch. + * + * @param string $branch_name Name of the branch. + * @return boolean + */ + private function closeExistingPRBranch( $branch_name ) { + if ( $this->isGitHub() ) { + $cmd = 'hub pr list --format="%i" --state=open'; + } elseif ( $this->isGitLab() ) { + $cmd = 'lab mr list'; + } else { + return false; + } + exec($cmd, $output_lines, $return_code); + if ( 0 !== $return_code ) { + Logger::error( sprintf( 'Unable to check for existing %ss', $this->getRequestType() ) ); + } + $number = false; + foreach( $output_lines as $line ) { + if ( preg_match( '/^\#([\d]+)/', $line, $matches ) ) { + $number = $matches[1]; + } + } + if ( ! $number ) { + Logger::error( sprintf( 'Unable to find existing %s number', $this->getRequestType() ) ); + } + if ( $this->isGitHub() ) { + $cmd = sprintf( 'hub api -XPATCH repos/%s/issues/%d -f state=closed', $this->project(), $number ); + } elseif ( $this->isGitLab() ) { + $cmd = sprintf( 'lab mr close %d', $number ); + } + exec($cmd, $output_lines, $return_code); + if ( 0 !== $return_code ) { + Logger::error( sprintf( 'Unable to close existing %s', $this->getRequestType() ) ); + } + $cmd = 'git push origin --delete ' . escapeshellarg( $branch_name ); + Logger::info( $cmd ); + passthru( $cmd, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Failed to delete origin branch.' ); + } + } + /** * Check the Sensiolabs security component if availble. * @@ -243,102 +297,22 @@ private function getRequestType() { return $this->isGitLab() ? 'merge request' : 'pull request'; } - private function addCommitComment( $message, $project, $commitSha ) { - // We expect that GITHUB token should always be defined; however, we - // will silently omit the comment if it is not, since the new commit - // is already visible on the PR, and the separate comment is therefore - // not necessary for correct operation. - $auth = getenv( 'GITHUB_TOKEN' ); - if ( !$auth ) { - return; - } - - $uri = "repos/$project/commits/$commitSha/comments"; - $data = [ - 'body' => $message, - ]; - - $this->curlGitHub($uri, $data, $auth); - } - - public function curlGitHub( $uri, $postData = [], $auth = '' ) { - Logger::info( 'Call GitHub API: ' . $uri ); - $ch = $this->createGitHubPostChannel( $uri, $postData, $auth ); - return $this->execCurlRequest( $ch, 'GitHub' ); - } - - protected function createGitHubPostChannel( $uri, $postData = [], $auth = '' ) { - $url = "https://api.github.com/$uri"; - $ch = $this->createAuthorizationHeaderCurlChannel( $url, $auth ); - $this->setCurlChannelPostData( $ch, $postData ); - - return $ch; - } - - protected function createAuthorizationHeaderCurlChannel( $url, $auth = '' ) { - $headers = [ - 'Content-Type: application/json', - 'User-Agent: pantheon/terminus-build-tools-plugin' - ]; - - if (!empty($auth)) { - $headers[] = "Authorization: token $auth"; - } - - $ch = curl_init(); - curl_setopt( $ch, CURLOPT_URL, $url ); - curl_setopt( $ch, CURLOPT_RETURNTRANSFER, 1 ); - curl_setopt( $ch, CURLOPT_TIMEOUT, 5 ); - curl_setopt( $ch, CURLOPT_HTTPHEADER, $headers ); - - return $ch; - } - - protected function setCurlChannelPostData( $ch, $postData, $force = false ) { - if ( !empty($postData) || $force ) { - $payload = json_encode( $postData ); - curl_setopt( $ch, CURLOPT_POST, 1 ); - curl_setopt( $ch, CURLOPT_POSTFIELDS, $payload ); - } - } - - public function execCurlRequest( $ch, $service = 'API request' ) { - $result = curl_exec($ch); - if( curl_errno($ch) ) - { - Logger::error( curl_error($ch) ); - } - $data = json_decode( $result, true ); - $httpCode = curl_getinfo( $ch, CURLINFO_HTTP_CODE ); - curl_close( $ch ); - - $errors = []; - if ( isset($data['errors']) ) { - foreach ( $data['errors'] as $error ) { - $errors[] = $error['message']; - } - } - if ( $httpCode && ($httpCode >= 300) ) { - $errors[] = "Http status code: $httpCode"; - } - - $message = isset( $data['message'] ) ? "{$data['message']}." : ''; - - if ( !empty($message) || !empty($errors) ) { - $errors = implode( "\n", $errors ); - Logger::error( "$service error: $message $errors" ); + /** + * Runs `composer install`. + */ + private function runComposerInstall() { + $args = getenv( 'CLU_COMPOSER_INSTALL_ARGS' ) ? : '--no-dev --no-interaction'; + $cmd = 'composer install ' . $args; + Logger::info( $cmd ); + passthru( $cmd, $return_code ); + if ( 0 !== $return_code ) { + Logger::error( 'Composer failed to install dependencies.' ); } - - return $data; - } - - private function repo() { - return $this->repo_url; } private function project() { if ( preg_match( '#([^/:]*/.*)$#', $this->repo_url, $matches ) ) { - return rtrim( $matches[1], '.git' ); + return preg_replace( '#\.git$#', '', $matches[1] ); } }