Skip to content

Commit

Permalink
Merge pull request #23 from danielbachhuber/20-create-new-branch
Browse files Browse the repository at this point in the history
Close existing PR and open a new one when dependencies are outdated
  • Loading branch information
danielbachhuber authored Jul 31, 2019
2 parents 7decde7 + 68dea24 commit 87190a8
Show file tree
Hide file tree
Showing 2 changed files with 95 additions and 122 deletions.
7 changes: 3 additions & 4 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
210 changes: 92 additions & 118 deletions src/Runner.php
Original file line number Diff line number Diff line change
Expand Up @@ -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 );
Expand Down Expand Up @@ -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 = <<<EOT
Expand All @@ -155,13 +172,6 @@ public function start( $target_dir, $opts ) {
Logger::error( 'Failed to push changes to origin.' );
}

if ( $existing_PR_branch && $this->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() ) {
Expand All @@ -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() ) {
Expand All @@ -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.
*
Expand Down Expand Up @@ -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] );
}
}

Expand Down

0 comments on commit 87190a8

Please sign in to comment.