From f2db45277c2120f7bedff9beb35834cffd8e94da Mon Sep 17 00:00:00 2001 From: Dan Cryer Date: Fri, 20 Feb 2015 13:37:36 +0000 Subject: [PATCH] Adding support for commenting on Github diffs. --- PHPCI/Command/RebuildCommand.php | 92 +++++++++++++++++++++++++++++ PHPCI/Helper/Diff.php | 58 ++++++++++++++++++ PHPCI/Helper/Github.php | 70 ++++++++++++++++++++++ PHPCI/Model/Build.php | 13 ++++ PHPCI/Model/Build/GithubBuild.php | 54 +++++++++++++++++ PHPCI/Plugin/Behat.php | 4 +- PHPCI/Plugin/PhpCodeSniffer.php | 2 + PHPCI/Plugin/PhpCpd.php | 11 ++++ PHPCI/Plugin/PhpDocblockChecker.php | 20 ++++++- PHPCI/Plugin/PhpMessDetector.php | 1 + PHPCI/Plugin/TechnicalDebt.php | 7 ++- PHPCI/Plugin/Util/Factory.php | 4 +- console | 2 + 13 files changed, 333 insertions(+), 5 deletions(-) create mode 100644 PHPCI/Command/RebuildCommand.php create mode 100644 PHPCI/Helper/Diff.php diff --git a/PHPCI/Command/RebuildCommand.php b/PHPCI/Command/RebuildCommand.php new file mode 100644 index 00000000..165d94c7 --- /dev/null +++ b/PHPCI/Command/RebuildCommand.php @@ -0,0 +1,92 @@ + +* @package PHPCI +* @subpackage Console +*/ +class RebuildCommand extends Command +{ + /** + * @var Logger + */ + protected $logger; + + /** + * @var OutputInterface + */ + protected $output; + + /** + * @var boolean + */ + protected $run; + + /** + * @var int + */ + protected $sleep; + + /** + * @param \Monolog\Logger $logger + * @param string $name + */ + public function __construct(Logger $logger, $name = null) + { + parent::__construct($name); + $this->logger = $logger; + } + + protected function configure() + { + $this + ->setName('phpci:rebuild') + ->setDescription('Re-runs the last run build.'); + } + + /** + * Loops through running. + */ + protected function execute(InputInterface $input, OutputInterface $output) + { + $runner = new RunCommand($this->logger); + $runner->setMaxBuilds(1); + $runner->setDaemon(false); + + /** @var \PHPCI\Store\BuildStore $store */ + $store = Factory::getStore('Build'); + $service = new BuildService($store); + + $lastBuild = array_shift($store->getLatestBuilds(null, 1)); + $service->createDuplicateBuild($lastBuild); + + $runner->run(new ArgvInput(array()), $output); + } + + /** + * Called when log entries are made in Builder / the plugins. + * @see \PHPCI\Builder::log() + */ + public function logCallback($log) + { + $this->output->writeln($log); + } +} diff --git a/PHPCI/Helper/Diff.php b/PHPCI/Helper/Diff.php new file mode 100644 index 00000000..e50c7eaf --- /dev/null +++ b/PHPCI/Helper/Diff.php @@ -0,0 +1,58 @@ +get('phpci.github.token'); + + if (!$token) { + return null; + } + + $url = '/repos/' . strtolower($repo) . '/pulls/' . $pullId . '/comments'; + + $params = array( + 'body' => $comment, + 'commit_id' => $commitId, + 'path' => $file, + 'position' => $line, + ); + + $http = new HttpClient('https://api.github.com'); + $http->setHeaders(array( + 'Content-Type: application/x-www-form-urlencoded', + 'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'), + )); + + $http->post($url, json_encode($params)); + } + + /** + * Create a comment on a Github commit. + * @param $repo + * @param $commitId + * @param $file + * @param $line + * @param $comment + * @return null + */ + public function createCommitComment($repo, $commitId, $file, $line, $comment) + { + $token = Config::getInstance()->get('phpci.github.token'); + + if (!$token) { + return null; + } + + $url = '/repos/' . strtolower($repo) . '/commits/' . $commitId . '/comments'; + + $params = array( + 'body' => $comment, + 'path' => $file, + 'position' => $line, + ); + + $http = new HttpClient('https://api.github.com'); + $http->setHeaders(array( + 'Content-Type: application/x-www-form-urlencoded', + 'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'), + )); + + $http->post($url, json_encode($params)); + } } diff --git a/PHPCI/Model/Build.php b/PHPCI/Model/Build.php index 5561d51e..3d07778a 100644 --- a/PHPCI/Model/Build.php +++ b/PHPCI/Model/Build.php @@ -204,4 +204,17 @@ class Build extends BuildBase return $rtn; } + + /** + * Allows specific build types (e.g. Github) to report violations back to their respective services. + * @param Builder $builder + * @param $file + * @param $line + * @param $message + * @return mixed + */ + public function reportError(Builder $builder, $file, $line, $message) + { + return array($builder, $file, $line, $message); + } } diff --git a/PHPCI/Model/Build/GithubBuild.php b/PHPCI/Model/Build/GithubBuild.php index caa21979..7c8f3f6f 100644 --- a/PHPCI/Model/Build/GithubBuild.php +++ b/PHPCI/Model/Build/GithubBuild.php @@ -10,6 +10,8 @@ namespace PHPCI\Model\Build; use PHPCI\Builder; +use PHPCI\Helper\Diff; +use PHPCI\Helper\Github; use PHPCI\Model\Build\RemoteGitBuild; /** @@ -167,4 +169,56 @@ class GithubBuild extends RemoteGitBuild return $success; } + + /** + * @inheritDoc + */ + public function reportError(Builder $builder, $file, $line, $message) + { + $diffLineNumber = $this->getDiffLineNumber($builder, $file, $line); + + if (!is_null($diffLineNumber)) { + $helper = new Github(); + + $repo = $this->getProject()->getReference(); + $prNumber = $this->getExtra('pull_request_number'); + $commit = $this->getCommitId(); + + if (!empty($prNumber)) { + $helper->createPullRequestComment($repo, $prNumber, $commit, $file, $diffLineNumber, $message); + } else { + $helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message); + } + } + } + + /** + * Uses git diff to figure out what the diff line position is, based on the error line number. + * @param Builder $builder + * @param $file + * @param $line + * @return int|null + */ + protected function getDiffLineNumber(Builder $builder, $file, $line) + { + $builder->logExecOutput(false); + + $prNumber = $this->getExtra('pull_request_number'); + $path = $builder->buildPath; + + if (!empty($prNumber)) { + $builder->executeCommand('cd %s && git diff origin/%s "%s"', $path, $this->getBranch(), $file); + } else { + $builder->executeCommand('cd %s && git diff %s^! "%s"', $path, $this->getCommitId(), $file); + } + + $builder->logExecOutput(true); + + $diff = $builder->getLastOutput(); + + $helper = new Diff(); + $lines = $helper->getLinePositions($diff); + + return $lines[$line]; + } } diff --git a/PHPCI/Plugin/Behat.php b/PHPCI/Plugin/Behat.php index ad2956b4..23686270 100644 --- a/PHPCI/Plugin/Behat.php +++ b/PHPCI/Plugin/Behat.php @@ -100,7 +100,7 @@ class Behat implements \PHPCI\Plugin $errorCount = 0; $storeFailures = false; - $data = []; + $data = array(); foreach ($lines as $line) { $line = trim($line); @@ -119,6 +119,8 @@ class Behat implements \PHPCI\Plugin 'file' => $lineParts[0], 'line' => $lineParts[1] ); + + $this->build->reportError($this->phpci, $lineParts[0], $lineParts[1], 'Behat scenario failed.'); } } diff --git a/PHPCI/Plugin/PhpCodeSniffer.php b/PHPCI/Plugin/PhpCodeSniffer.php index 3ee232b7..df276eac 100644 --- a/PHPCI/Plugin/PhpCodeSniffer.php +++ b/PHPCI/Plugin/PhpCodeSniffer.php @@ -237,6 +237,8 @@ class PhpCodeSniffer implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin $fileName = str_replace($this->phpci->buildPath, '', $fileName); foreach ($file['messages'] as $message) { + $this->build->reportError($this->phpci, $fileName, $message['line'], 'PHPCS: ' . $message['message']); + $rtn[] = array( 'file' => $fileName, 'line' => $message['line'], diff --git a/PHPCI/Plugin/PhpCpd.php b/PHPCI/Plugin/PhpCpd.php index 9bbcfc09..c663db5a 100644 --- a/PHPCI/Plugin/PhpCpd.php +++ b/PHPCI/Plugin/PhpCpd.php @@ -140,6 +140,17 @@ class PhpCpd implements \PHPCI\Plugin 'line_end' => (int) $file['line'] + (int) $duplication['lines'], 'code' => (string) $duplication->codefragment ); + + $message = <<codefragment} +``` +CPD; + + $this->build->reportError($this->phpci, $fileName, $file['line'], $message); + } $warnings++; diff --git a/PHPCI/Plugin/PhpDocblockChecker.php b/PHPCI/Plugin/PhpDocblockChecker.php index 9650127f..9f71afef 100644 --- a/PHPCI/Plugin/PhpDocblockChecker.php +++ b/PHPCI/Plugin/PhpDocblockChecker.php @@ -143,12 +143,13 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin // Re-enable exec output logging: $this->phpci->logExecOutput(true); - $output = json_decode($this->phpci->getLastOutput()); + $output = json_decode($this->phpci->getLastOutput(), true); $errors = count($output); $success = true; $this->build->storeMeta('phpdoccheck-warnings', $errors); $this->build->storeMeta('phpdoccheck-data', $output); + $this->reportErrors($output); if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) { $success = false; @@ -156,4 +157,21 @@ class PhpDocblockChecker implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin return $success; } + + /** + * Report all of the errors we've encountered line-by-line. + * @param $output + */ + protected function reportErrors($output) + { + foreach ($output as $error) { + $message = 'Class ' . $error['class'] . ' does not have a Docblock comment.'; + + if ($error['type'] == 'method') { + $message = 'Method ' . $error['class'] . '::' . $error['method'] . ' does not have a Docblock comment.'; + } + + $this->build->reportError($this->phpci, $error['file'], $error['line'], $message); + } + } } diff --git a/PHPCI/Plugin/PhpMessDetector.php b/PHPCI/Plugin/PhpMessDetector.php index 4603d534..21c171cb 100644 --- a/PHPCI/Plugin/PhpMessDetector.php +++ b/PHPCI/Plugin/PhpMessDetector.php @@ -181,6 +181,7 @@ class PhpMessDetector implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin 'message' => (string)$violation, ); + $this->build->reportError($this->phpci, $fileName, (int)$violation['beginline'], (string)$violation); $data[] = $warning; } } diff --git a/PHPCI/Plugin/TechnicalDebt.php b/PHPCI/Plugin/TechnicalDebt.php index c10e7d29..afe6c920 100755 --- a/PHPCI/Plugin/TechnicalDebt.php +++ b/PHPCI/Plugin/TechnicalDebt.php @@ -194,11 +194,16 @@ class TechnicalDebt implements PHPCI\Plugin, PHPCI\ZeroConfigPlugin $errorCount++; $this->phpci->log("Found $search on line $lineNumber of $file:\n$content"); + + $fileName = str_replace($this->directory, '', $file); $data[] = array( - 'file' => str_replace($this->directory, '', $file), + 'file' => $fileName, 'line' => $lineNumber, 'message' => $content ); + + $this->build->reportError($this->phpci, $fileName, $lineNumber, $content); + } } } diff --git a/PHPCI/Plugin/Util/Factory.php b/PHPCI/Plugin/Util/Factory.php index 5430b56f..8127bcd2 100644 --- a/PHPCI/Plugin/Util/Factory.php +++ b/PHPCI/Plugin/Util/Factory.php @@ -76,11 +76,11 @@ class Factory * be passed along with any resources registered with the factory. * * @param $className - * @param array $options + * @param array|null $options * @throws \InvalidArgumentException if $className doesn't represent a valid plugin * @return \PHPCI\Plugin */ - public function buildPlugin($className, array $options = array()) + public function buildPlugin($className, $options = array()) { $this->currentPluginOptions = $options; diff --git a/console b/console index 782cc2d0..a812fc7f 100755 --- a/console +++ b/console @@ -13,6 +13,7 @@ define('PHPCI_IS_CONSOLE', true); require('bootstrap.php'); use PHPCI\Command\RunCommand; +use PHPCI\Command\RebuildCommand; use PHPCI\Command\GenerateCommand; use PHPCI\Command\UpdateCommand; use PHPCI\Command\InstallCommand; @@ -25,6 +26,7 @@ use b8\Store\Factory; $application = new Application(); $application->add(new RunCommand($loggerConfig->getFor('RunCommand'))); +$application->add(new RebuildCommand($loggerConfig->getFor('RunCommand'))); $application->add(new InstallCommand); $application->add(new UpdateCommand($loggerConfig->getFor('UpdateCommand'))); $application->add(new GenerateCommand);