Skip to content

Commit dd58dd6

Browse files
author
Dan Cryer
committed
Adding support for commenting on Github diffs.
1 parent 071e36a commit dd58dd6

File tree

13 files changed

+333
-5
lines changed

13 files changed

+333
-5
lines changed

PHPCI/Command/RebuildCommand.php

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
<?php
2+
/**
3+
* PHPCI - Continuous Integration for PHP
4+
*
5+
* @copyright Copyright 2014, Block 8 Limited.
6+
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
7+
* @link https://www.phptesting.org/
8+
*/
9+
10+
namespace PHPCI\Command;
11+
12+
use b8\Store\Factory;
13+
use Monolog\Logger;
14+
use PHPCI\Service\BuildService;
15+
use Symfony\Component\Console\Command\Command;
16+
use Symfony\Component\Console\Input\ArgvInput;
17+
use Symfony\Component\Console\Input\InputInterface;
18+
use Symfony\Component\Console\Output\OutputInterface;
19+
20+
/**
21+
* Re-runs the last run build.
22+
* @author Dan Cryer <[email protected]>
23+
* @package PHPCI
24+
* @subpackage Console
25+
*/
26+
class RebuildCommand extends Command
27+
{
28+
/**
29+
* @var Logger
30+
*/
31+
protected $logger;
32+
33+
/**
34+
* @var OutputInterface
35+
*/
36+
protected $output;
37+
38+
/**
39+
* @var boolean
40+
*/
41+
protected $run;
42+
43+
/**
44+
* @var int
45+
*/
46+
protected $sleep;
47+
48+
/**
49+
* @param \Monolog\Logger $logger
50+
* @param string $name
51+
*/
52+
public function __construct(Logger $logger, $name = null)
53+
{
54+
parent::__construct($name);
55+
$this->logger = $logger;
56+
}
57+
58+
protected function configure()
59+
{
60+
$this
61+
->setName('phpci:rebuild')
62+
->setDescription('Re-runs the last run build.');
63+
}
64+
65+
/**
66+
* Loops through running.
67+
*/
68+
protected function execute(InputInterface $input, OutputInterface $output)
69+
{
70+
$runner = new RunCommand($this->logger);
71+
$runner->setMaxBuilds(1);
72+
$runner->setDaemon(false);
73+
74+
/** @var \PHPCI\Store\BuildStore $store */
75+
$store = Factory::getStore('Build');
76+
$service = new BuildService($store);
77+
78+
$lastBuild = array_shift($store->getLatestBuilds(null, 1));
79+
$service->createDuplicateBuild($lastBuild);
80+
81+
$runner->run(new ArgvInput(array()), $output);
82+
}
83+
84+
/**
85+
* Called when log entries are made in Builder / the plugins.
86+
* @see \PHPCI\Builder::log()
87+
*/
88+
public function logCallback($log)
89+
{
90+
$this->output->writeln($log);
91+
}
92+
}

PHPCI/Helper/Diff.php

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
/**
3+
* PHPCI - Continuous Integration for PHP
4+
*
5+
* @copyright Copyright 2015, Block 8 Limited.
6+
* @license https://github.com/Block8/PHPCI/blob/master/LICENSE.md
7+
* @link https://www.phptesting.org/
8+
*/
9+
10+
namespace PHPCI\Helper;
11+
12+
use b8\Cache;
13+
use b8\Config;
14+
use b8\HttpClient;
15+
16+
/**
17+
* Provides some basic diff processing functionality.
18+
* @package PHPCI\Helper
19+
*/
20+
class Diff
21+
{
22+
/**
23+
* Take a diff
24+
* @param string $diff
25+
* @return array
26+
*/
27+
public function getLinePositions($diff)
28+
{
29+
$rtn = array();
30+
31+
$diffLines = explode(PHP_EOL, $diff);
32+
33+
while (1) {
34+
$line = array_shift($diffLines);
35+
36+
if (substr($line, 0, 2) == '@@') {
37+
array_unshift($diffLines, $line);
38+
break;
39+
}
40+
}
41+
42+
$lineNumber = 0;
43+
$position = 0;
44+
45+
foreach ($diffLines as $diffLine) {
46+
if (preg_match('/@@\s+\-[0-9]+\,[0-9]+\s+\+([0-9]+)\,([0-9]+)/', $diffLine, $matches)) {
47+
$lineNumber = (int)$matches[1] - 1;
48+
}
49+
50+
$rtn[$lineNumber] = $position;
51+
52+
$lineNumber++;
53+
$position++;
54+
}
55+
56+
return $rtn;
57+
}
58+
}

PHPCI/Helper/Github.php

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,4 +108,74 @@ public function getRepositories()
108108

109109
return $rtn;
110110
}
111+
112+
/**
113+
* Create a comment on a specific file (and commit) in a Github Pull Request.
114+
* @param $repo
115+
* @param $pullId
116+
* @param $commitId
117+
* @param $file
118+
* @param $line
119+
* @param $comment
120+
* @return null
121+
*/
122+
public function createPullRequestComment($repo, $pullId, $commitId, $file, $line, $comment)
123+
{
124+
$token = Config::getInstance()->get('phpci.github.token');
125+
126+
if (!$token) {
127+
return null;
128+
}
129+
130+
$url = '/repos/' . strtolower($repo) . '/pulls/' . $pullId . '/comments';
131+
132+
$params = array(
133+
'body' => $comment,
134+
'commit_id' => $commitId,
135+
'path' => $file,
136+
'position' => $line,
137+
);
138+
139+
$http = new HttpClient('https://api.github.com');
140+
$http->setHeaders(array(
141+
'Content-Type: application/x-www-form-urlencoded',
142+
'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'),
143+
));
144+
145+
$http->post($url, json_encode($params));
146+
}
147+
148+
/**
149+
* Create a comment on a Github commit.
150+
* @param $repo
151+
* @param $commitId
152+
* @param $file
153+
* @param $line
154+
* @param $comment
155+
* @return null
156+
*/
157+
public function createCommitComment($repo, $commitId, $file, $line, $comment)
158+
{
159+
$token = Config::getInstance()->get('phpci.github.token');
160+
161+
if (!$token) {
162+
return null;
163+
}
164+
165+
$url = '/repos/' . strtolower($repo) . '/commits/' . $commitId . '/comments';
166+
167+
$params = array(
168+
'body' => $comment,
169+
'path' => $file,
170+
'position' => $line,
171+
);
172+
173+
$http = new HttpClient('https://api.github.com');
174+
$http->setHeaders(array(
175+
'Content-Type: application/x-www-form-urlencoded',
176+
'Authorization: Basic ' . base64_encode($token . ':x-oauth-basic'),
177+
));
178+
179+
$http->post($url, json_encode($params));
180+
}
111181
}

PHPCI/Model/Build.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,4 +204,17 @@ public function getCommitMessage()
204204

205205
return $rtn;
206206
}
207+
208+
/**
209+
* Allows specific build types (e.g. Github) to report violations back to their respective services.
210+
* @param Builder $builder
211+
* @param $file
212+
* @param $line
213+
* @param $message
214+
* @return mixed
215+
*/
216+
public function reportError(Builder $builder, $file, $line, $message)
217+
{
218+
return array($builder, $file, $line, $message);
219+
}
207220
}

PHPCI/Model/Build/GithubBuild.php

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@
1010
namespace PHPCI\Model\Build;
1111

1212
use PHPCI\Builder;
13+
use PHPCI\Helper\Diff;
14+
use PHPCI\Helper\Github;
1315
use PHPCI\Model\Build\RemoteGitBuild;
1416

1517
/**
@@ -167,4 +169,56 @@ protected function postCloneSetup(Builder $builder, $cloneTo)
167169

168170
return $success;
169171
}
172+
173+
/**
174+
* @inheritDoc
175+
*/
176+
public function reportError(Builder $builder, $file, $line, $message)
177+
{
178+
$diffLineNumber = $this->getDiffLineNumber($builder, $file, $line);
179+
180+
if (!is_null($diffLineNumber)) {
181+
$helper = new Github();
182+
183+
$repo = $this->getProject()->getReference();
184+
$prNumber = $this->getExtra('pull_request_number');
185+
$commit = $this->getCommitId();
186+
187+
if (!empty($prNumber)) {
188+
$helper->createPullRequestComment($repo, $prNumber, $commit, $file, $diffLineNumber, $message);
189+
} else {
190+
$helper->createCommitComment($repo, $commit, $file, $diffLineNumber, $message);
191+
}
192+
}
193+
}
194+
195+
/**
196+
* Uses git diff to figure out what the diff line position is, based on the error line number.
197+
* @param Builder $builder
198+
* @param $file
199+
* @param $line
200+
* @return int|null
201+
*/
202+
protected function getDiffLineNumber(Builder $builder, $file, $line)
203+
{
204+
$builder->logExecOutput(false);
205+
206+
$prNumber = $this->getExtra('pull_request_number');
207+
$path = $builder->buildPath;
208+
209+
if (!empty($prNumber)) {
210+
$builder->executeCommand('cd %s && git diff origin/%s "%s"', $path, $this->getBranch(), $file);
211+
} else {
212+
$builder->executeCommand('cd %s && git diff %s^! "%s"', $path, $this->getCommitId(), $file);
213+
}
214+
215+
$builder->logExecOutput(true);
216+
217+
$diff = $builder->getLastOutput();
218+
219+
$helper = new Diff();
220+
$lines = $helper->getLinePositions($diff);
221+
222+
return $lines[$line];
223+
}
170224
}

PHPCI/Plugin/Behat.php

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,7 @@ public function parseBehatOutput()
100100

101101
$errorCount = 0;
102102
$storeFailures = false;
103-
$data = [];
103+
$data = array();
104104

105105
foreach ($lines as $line) {
106106
$line = trim($line);
@@ -119,6 +119,8 @@ public function parseBehatOutput()
119119
'file' => $lineParts[0],
120120
'line' => $lineParts[1]
121121
);
122+
123+
$this->build->reportError($this->phpci, $lineParts[0], $lineParts[1], 'Behat scenario failed.');
122124
}
123125
}
124126

PHPCI/Plugin/PhpCodeSniffer.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,8 @@ protected function processReport($output)
237237
$fileName = str_replace($this->phpci->buildPath, '', $fileName);
238238

239239
foreach ($file['messages'] as $message) {
240+
$this->build->reportError($this->phpci, $fileName, $message['line'], 'PHPCS: ' . $message['message']);
241+
240242
$rtn[] = array(
241243
'file' => $fileName,
242244
'line' => $message['line'],

PHPCI/Plugin/PhpCpd.php

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,17 @@ protected function processReport($xmlString)
140140
'line_end' => (int) $file['line'] + (int) $duplication['lines'],
141141
'code' => (string) $duplication->codefragment
142142
);
143+
144+
$message = <<<CPD
145+
Copy and paste detected:
146+
147+
```
148+
{$duplication->codefragment}
149+
```
150+
CPD;
151+
152+
$this->build->reportError($this->phpci, $fileName, $file['line'], $message);
153+
143154
}
144155

145156
$warnings++;

PHPCI/Plugin/PhpDocblockChecker.php

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,17 +143,35 @@ public function execute()
143143
// Re-enable exec output logging:
144144
$this->phpci->logExecOutput(true);
145145

146-
$output = json_decode($this->phpci->getLastOutput());
146+
$output = json_decode($this->phpci->getLastOutput(), true);
147147
$errors = count($output);
148148
$success = true;
149149

150150
$this->build->storeMeta('phpdoccheck-warnings', $errors);
151151
$this->build->storeMeta('phpdoccheck-data', $output);
152+
$this->reportErrors($output);
152153

153154
if ($this->allowed_warnings != -1 && $errors > $this->allowed_warnings) {
154155
$success = false;
155156
}
156157

157158
return $success;
158159
}
160+
161+
/**
162+
* Report all of the errors we've encountered line-by-line.
163+
* @param $output
164+
*/
165+
protected function reportErrors($output)
166+
{
167+
foreach ($output as $error) {
168+
$message = 'Class ' . $error['class'] . ' does not have a Docblock comment.';
169+
170+
if ($error['type'] == 'method') {
171+
$message = 'Method ' . $error['class'] . '::' . $error['method'] . ' does not have a Docblock comment.';
172+
}
173+
174+
$this->build->reportError($this->phpci, $error['file'], $error['line'], $message);
175+
}
176+
}
159177
}

0 commit comments

Comments
 (0)