Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions docs/en/configuring.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ php-censor:
host: localhost
name: php-censor-queue
lifetime: 600
bitbucket:
username: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
app_password: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
comments:
commit: false # This option allow/deny to post comments to Bitbucket commit
pull_request: false # This option allow/deny to post comments to Bitbucket Pull Request
github:
token: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'
comments:
Expand Down
8 changes: 8 additions & 0 deletions src/PHPCensor/Command/InstallCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -261,6 +261,14 @@ protected function getConfigInformation(InputInterface $input, OutputInterface $
'smtp_password' => null,
'smtp_encryption' => false,
],
'bitbucket' => [
'username' => null,
'app_password' => null,
'comments' => [
'commit' => false,
'pull_request' => false,
],
],
'github' => [
'token' => null,
'comments' => [
Expand Down
115 changes: 106 additions & 9 deletions src/PHPCensor/Controller/WebhookController.php
Original file line number Diff line number Diff line change
Expand Up @@ -90,21 +90,32 @@ public function bitbucket($projectId)

$payload = json_decode(file_get_contents("php://input"), true);

if (empty($payload['push']['changes'])) {
// Invalid event from bitbucket
return [
'status' => 'failed',
'commits' => []
];
// Handle Pull Request webhooks:
if (!empty($payload['pullrequest'])) {
return $this->bitbucketPullRequest($project, $payload);
}

return $this->bitbucketWebhook($payload, $project);
// Handle Push (and Tag) webhooks:
if (!empty($payload['push']['changes'])) {
return $this->bitbucketCommitRequest($project, $payload);
}

// Invalid event from bitbucket
return [
'status' => 'failed',
'commits' => []
];
}

/**
* Bitbucket webhooks.
* Handle the payload when Bitbucket sends a commit webhook.
*
* @param Project $project
* @param array $payload
*
* @return array
*/
protected function bitbucketWebhook($payload, $project)
protected function bitbucketCommitRequest(Project $project, array $payload)
{
$results = [];
$status = 'failed';
Expand Down Expand Up @@ -134,6 +145,92 @@ protected function bitbucketWebhook($payload, $project)
return ['status' => $status, 'commits' => $results];
}

/**
* Handle the payload when Bitbucket sends a Pull Request webhook.
*
* @param Project $project
* @param array $payload
*
* @return array
*
* @throws Exception
*/
protected function bitbucketPullRequest(Project $project, array $payload)
{
// We only want to know about open pull requests:
if (!in_array($_SERVER['HTTP_X_EVENT_KEY'], ['pullrequest:created', 'pullrequest:updated'])) {
return ['status' => 'ok'];
}

$headers = [];
$username = Config::getInstance()->get('php-censor.bitbucket.username');
$appPassword = Config::getInstance()->get('php-censor.bitbucket.app_password');

if (empty($username) || empty($appPassword)) {
throw new Exception('Please provide Username and App Password of your Bitbucket account.');
}

$commitsUrl = $payload['pullrequest']['links']['commits']['href'];

$client = new Client();
$commitsResponse = $client->get($commitsUrl, [
'auth' => [$username, $appPassword],
]);
$httpStatus = (integer)$commitsResponse->getStatusCode();

// Check we got a success response:
if ($httpStatus < 200 || $httpStatus >= 300) {
throw new Exception('Could not get commits, failed API request.');
}

$results = [];
$status = 'failed';
$commits = json_decode($commitsResponse->getBody(), true)['values'];
foreach ($commits as $commit) {
// Skip all but the current HEAD commit ID:
$id = $commit['hash'];
if (strpos($id, $payload['pullrequest']['source']['commit']['hash']) !== 0) {
$results[$id] = ['status' => 'ignored', 'message' => 'not branch head'];
continue;
}

try {
$branch = $payload['pullrequest']['destination']['branch']['name'];
$committer = $commit['author']['raw'];
if (strpos($committer, '>') !== false) {
// In order not to loose email if it is RAW, w/o "<>" symbols
$committer = substr($committer, 0, strpos($committer, '>'));
$committer = substr($committer, strpos($committer, '<') + 1);
}
$message = $commit['message'];

$extra = [
'build_type' => 'pull_request',
'pull_request_number' => $payload['pullrequest']['id'],
'remote_branch' => $payload['pullrequest']['source']['branch']['name'],
'remote_reference' => $payload['pullrequest']['source']['repository']['full_name'],
];

$results[$id] = $this->createBuild($project, $id, $branch, null, $committer, $message, $extra);
$status = 'ok';
} catch (Exception $ex) {
$results[$id] = ['status' => 'failed', 'error' => $ex->getMessage()];
}
}

return ['status' => $status, 'commits' => $results];
}

/**
* Bitbucket webhooks.
*
* @deprecated, for BC purpose
*/
protected function bitbucketWebhook($payload, $project)
{
return $this->bitbucketCommitRequest($project, $payload);
}

/**
* Bitbucket POST service.
*/
Expand Down
108 changes: 108 additions & 0 deletions src/PHPCensor/Helper/Bitbucket.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
<?php

namespace PHPCensor\Helper;

use b8\Config;
use GuzzleHttp\Client;

/**
* The Bitbucket Helper class provides some Bitbucket API call functionality.
*/
class Bitbucket
{
/**
* Create a comment on a specific file (and commit) in a Bitbucket Pull Request.
*
* @param string $repo
* @param int $pullId
* @param string $commitId
* @param string $file
* @param int $line
* @param string $comment
*
* @return null
*/
public function createPullRequestComment($repo, $pullId, $commitId, $file, $line, $comment)
{
$username = Config::getInstance()->get('php-censor.bitbucket.username');
$appPassword = Config::getInstance()->get('php-censor.bitbucket.app_password');

if (empty($username) || empty($appPassword)) {
return;
}

$url = '/1.0/repositories/' . $repo . '/pullrequests/' . $pullId . '/comments/';
$client = new Client(['base_uri' => 'https://api.bitbucket.org']);
$response = $client->post($url, [
'auth' => [$username, $appPassword],
'headers' => [
'Content-Type' => 'application/json',
],
'json' => [
'content' => $comment,
'anchor' => substr($commitId, 0, 12),
'filename' => $file,
'line_to' => $line,
],
]);
}

/**
* Create a comment on a Bitbucket commit.
*
* @param $repo
* @param $commitId
* @param $file
* @param $line
* @param $comment
* @return null
*/
public function createCommitComment($repo, $commitId, $file, $line, $comment)
{
$username = Config::getInstance()->get('php-censor.bitbucket.username');
$appPassword = Config::getInstance()->get('php-censor.bitbucket.app_password');

if (empty($username) || empty($appPassword)) {
return;
}

$url = '/1.0/repositories/' . $repo . '/changesets/' . $commitId . '/comments';

$client = new Client(['base_uri' => 'https://api.bitbucket.org']);
$response = $client->post($url, [
'auth' => [$username, $appPassword],
'headers' => [
'Content-Type' => 'application/json',
],
'json' => [
'content' => $comment,
'filename' => $file,
'line_to' => $line,
],
]);
}

/**
* @param string $repo
* @param int $pullRequestId
*
* @return string
*/
public function getPullRequestDiff($repo, $pullRequestId)
{
$username = Config::getInstance()->get('php-censor.bitbucket.username');
$appPassword = Config::getInstance()->get('php-censor.bitbucket.app_password');

if (empty($username) || empty($appPassword)) {
return;
}

$url = '/2.0/repositories/' . $repo . '/pullrequests/' . $pullRequestId . '/diff';

$client = new Client(['base_uri' => 'https://api.bitbucket.org']);

$response = $client->get($url, ['auth' => [$username, $appPassword]]);

return (string)$response->getBody();
}
}
Loading