Skip to content

Commit

Permalink
Add git workflow (#4)
Browse files Browse the repository at this point in the history
* Add git workflow to README

* Add runGitWorkflow to executable

* Add ShellException

* Add exitWithCode and printError to ShellOperator

* Add exitWithCode and printError to UnixShell

* Always return a string from executeCommand

* Use ShellOperator printError and exitWithCode

* Add git option to cli docs

* Use ShellException in SvnWorkflow

* Add untracked/unmodified tests to SvnWorkflowTest

* Add GitWorkflow

* Add runGitWorkflow
  • Loading branch information
sirbrillig authored Jun 12, 2019
1 parent f8f124a commit 30cfcaa
Show file tree
Hide file tree
Showing 11 changed files with 557 additions and 21 deletions.
69 changes: 56 additions & 13 deletions PhpcsChanged/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use PhpcsChanged\ShellOperator;
use function PhpcsChanged\{getNewPhpcsMessages, getNewPhpcsMessagesFromFiles, getVersion};
use function PhpcsChanged\SvnWorkflow\{getSvnUnifiedDiff, isNewSvnFile, getSvnBasePhpcsOutput, getSvnNewPhpcsOutput, validateSvnFileExists};
use function PhpcsChanged\GitWorkflow\{getGitUnifiedDiff, isNewGitFile, getGitBasePhpcsOutput, getGitNewPhpcsOutput, validateGitFileExists};

function getDebug($debugEnabled) {
return function(...$outputs) use ($debugEnabled) {
Expand All @@ -24,10 +25,14 @@ function getDebug($debugEnabled) {
};
}

function printErrorAndExit($output) {
function printError($output) {
fwrite(STDERR, 'phpcs-changed: Fatal error!' . PHP_EOL);
fwrite(STDERR, $output . PHP_EOL);
die(1);
}

function printErrorAndExit($output) {
printError($output);
exit(1);
}

function getLongestString(array $strings): int {
Expand All @@ -51,7 +56,7 @@ function printVersion() {
phpcs-changed version {$version}
EOF;
die(0);
exit(0);
}

function printHelp() {
Expand All @@ -76,12 +81,13 @@ function printHelp() {

echo <<<EOF
Automatic mode will try to gather data itself if you specify the version
control system (only svn supported right now):
control system:
EOF;

printTwoColumns([
'--svn <FILE>' => 'This is the file to check.',
'--svn <FILE>' => 'This is the svn-versioned file to check.',
'--git <FILE>' => 'This is the git-versioned file to check.',
]);

echo <<<EOF
Expand All @@ -99,13 +105,13 @@ function printHelp() {
]);
echo <<<EOF
If using automatic mode, this requires three shell commands: 'svn', 'cat', and
'phpcs'. If those commands are not in your PATH or you would like to override
them, you can use the environment variables 'SVN', 'CAT', and 'PHPCS',
respectively, to specify the full path for each one.
If using automatic mode, this requires three shell commands: 'svn' or 'git',
'cat', and 'phpcs'. If those commands are not in your PATH or you would like to
override them, you can use the environment variables 'SVN', 'GIT', 'CAT', and
'PHPCS', respectively, to specify the full path for each one.
EOF;
die(0);
exit(0);
}

function getReporter(string $reportType): Reporter {
Expand Down Expand Up @@ -145,16 +151,53 @@ function runSvnWorkflow($svnFile, $options, ShellOperator $shell, callable $debu

$phpcsStandard = $options['standard'] ?? null;
$phpcsStandardOption = $phpcsStandard ? ' --standard=' . escapeshellarg($phpcsStandard) : '';
validateSvnFileExists($svnFile, [$shell, 'isReadable']);
validateSvnFileExists($svnFile, $svn, [$shell, 'isReadable'], [$shell, 'executeCommand'], $debug);
$unifiedDiff = getSvnUnifiedDiff($svnFile, $svn, [$shell, 'executeCommand'], $debug);
$isNewFile = isNewSvnFile($svnFile, $svn, [$shell, 'executeCommand'], $debug);
$oldFilePhpcsOutput = $isNewFile ? '' : getSvnBasePhpcsOutput($svnFile, $svn, $phpcs, $phpcsStandardOption, [$shell, 'executeCommand'], $debug);
$newFilePhpcsOutput = getSvnNewPhpcsOutput($svnFile, $phpcs, $cat, $phpcsStandardOption, [$shell, 'executeCommand'], $debug);
} catch( NonFatalException $err ) {
$debug($err->getMessage());
exit(0);
$shell->exitWithCode(0);
throw $err; // Just in case we do not actually exit
} catch( \Exception $err ) {
printErrorAndExit($err->getMessage());
$shell->printError($err->getMessage());
$shell->exitWithCode(1);
throw $err; // Just in case we do not actually exit
}

$debug('processing data...');
$fileName = DiffLineMap::getFileNameFromDiff($unifiedDiff);
return getNewPhpcsMessages($unifiedDiff, PhpcsMessages::fromPhpcsJson($oldFilePhpcsOutput, $fileName), PhpcsMessages::fromPhpcsJson($newFilePhpcsOutput, $fileName));
}

function runGitWorkflow($gitFile, $options, ShellOperator $shell, callable $debug): PhpcsMessages {
$git = getenv('GIT') ?: 'git';
$phpcs = getenv('PHPCS') ?: 'phpcs';
$cat = getenv('CAT') ?: 'cat';

try {
$debug('validating executables');
$shell->validateExecutableExists('git', $git);
$shell->validateExecutableExists('phpcs', $phpcs);
$shell->validateExecutableExists('cat', $cat);
$debug('executables are valid');

$phpcsStandard = $options['standard'] ?? null;
$phpcsStandardOption = $phpcsStandard ? ' --standard=' . escapeshellarg($phpcsStandard) : '';
validateGitFileExists($gitFile, $git, [$shell, 'isReadable'], [$shell, 'executeCommand'], $debug);
$unifiedDiff = getGitUnifiedDiff($gitFile, $git, [$shell, 'executeCommand'], $debug);
$isNewFile = isNewGitFile($gitFile, $git, [$shell, 'executeCommand'], $debug);
$oldFilePhpcsOutput = $isNewFile ? '' : getGitBasePhpcsOutput($gitFile, $git, $phpcs, $phpcsStandardOption, [$shell, 'executeCommand'], $debug);
$newFilePhpcsOutput = getGitNewPhpcsOutput($gitFile, $phpcs, $cat, $phpcsStandardOption, [$shell, 'executeCommand'], $debug);
} catch(NonFatalException $err) {
$debug($err->getMessage());
$shell->exitWithCode(0);
throw $err; // Just in case we do not actually exit
} catch(\Exception $err) {
$shell->printError($err->getMessage());
$shell->exitWithCode(1);
throw $err; // Just in case we do not actually exit
}

$debug('processing data...');
Expand Down
67 changes: 67 additions & 0 deletions PhpcsChanged/GitWorkflow.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
<?php
declare(strict_types=1);

namespace PhpcsChanged\GitWorkflow;

use PhpcsChanged\NonFatalException;
use PhpcsChanged\ShellException;

function validateGitFileExists(string $gitFile, string $git, callable $isReadable, callable $executeCommand, callable $debug): void {
if (! $isReadable($gitFile)) {
throw new ShellException("Cannot read file '{$gitFile}'");
}
$gitStatusCommand = "${git} status --short " . escapeshellarg($gitFile);
$debug('checking git existence of file with command:', $gitStatusCommand);
$gitStatusOutput = $executeCommand($gitStatusCommand);
$debug('git status output:', $gitStatusOutput);
if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') {
throw new ShellException("File does not appear to be tracked by git: '{$gitFile}'");
}
}

function getGitUnifiedDiff(string $gitFile, string $git, callable $executeCommand, callable $debug): string {
$unifiedDiffCommand = "{$git} diff --staged --no-prefix " . escapeshellarg($gitFile);
$debug('running diff command:', $unifiedDiffCommand);
$unifiedDiff = $executeCommand($unifiedDiffCommand);
if (! $unifiedDiff) {
throw new NonFatalException("Cannot get git diff for file '{$gitFile}'; skipping");
}
$debug('diff command output:', $unifiedDiff);
return $unifiedDiff;
}

function isNewGitFile(string $gitFile, string $git, callable $executeCommand, callable $debug): bool {
$gitStatusCommand = "${git} status --short " . escapeshellarg($gitFile);
$debug('checking git status of file with command:', $gitStatusCommand);
$gitStatusOutput = $executeCommand($gitStatusCommand);
$debug('git status output:', $gitStatusOutput);
if (! $gitStatusOutput || false === strpos($gitStatusOutput, $gitFile)) {
throw new ShellException("Cannot get git status for file '{$gitFile}'");
}
if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') {
throw new ShellException("File does not appear to be tracked by git: '{$gitFile}'");
}
return isset($gitStatusOutput[0]) && $gitStatusOutput[0] === 'A';
}

function getGitBasePhpcsOutput(string $gitFile, string $git, string $phpcs, string $phpcsStandardOption, callable $executeCommand, callable $debug): string {
$oldFilePhpcsOutputCommand = "${git} show HEAD:" . escapeshellarg($gitFile) . " | {$phpcs} --report=json" . $phpcsStandardOption;
$debug('running orig phpcs command:', $oldFilePhpcsOutputCommand);
$oldFilePhpcsOutput = $executeCommand($oldFilePhpcsOutputCommand);
if (! $oldFilePhpcsOutput) {
throw new ShellException("Cannot get old phpcs output for file '{$gitFile}'");
}
$debug('orig phpcs command output:', $oldFilePhpcsOutput);
return $oldFilePhpcsOutput;
}

function getGitNewPhpcsOutput(string $gitFile, string $phpcs, string $cat, string $phpcsStandardOption, callable $executeCommand, callable $debug): string {
$newFilePhpcsOutputCommand = "{$cat} " . escapeshellarg($gitFile) . " | {$phpcs} --report=json" . $phpcsStandardOption;
$debug('running new phpcs command:', $newFilePhpcsOutputCommand);
$newFilePhpcsOutput = $executeCommand($newFilePhpcsOutputCommand);
if (! $newFilePhpcsOutput) {
throw new ShellException("Cannot get new phpcs output for file '{$gitFile}'");
}
$debug('new phpcs command output:', $newFilePhpcsOutput);
return $newFilePhpcsOutput;
}
7 changes: 7 additions & 0 deletions PhpcsChanged/ShellException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
declare(strict_types=1);

namespace PhpcsChanged;

class ShellException extends \Exception {
}
4 changes: 4 additions & 0 deletions PhpcsChanged/ShellOperator.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,8 @@ public function validateExecutableExists(string $name, string $command): void;
public function executeCommand(string $command): string;

public function isReadable(string $fileName): bool;

public function exitWithCode(int $code): void;

public function printError(string $message): void;
}
18 changes: 13 additions & 5 deletions PhpcsChanged/SvnWorkflow.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,18 @@
namespace PhpcsChanged\SvnWorkflow;

use PhpcsChanged\NonFatalException;
use PhpcsChanged\ShellException;

function validateSvnFileExists(string $svnFile, callable $isReadable): void {
function validateSvnFileExists(string $svnFile, string $svn, callable $isReadable, callable $executeCommand, callable $debug): void {
if (! $isReadable($svnFile)) {
throw new \Exception("Cannot read file '{$svnFile}'");
throw new ShellException("Cannot read file '{$svnFile}'");
}
$svnStatusCommand = "${svn} info " . escapeshellarg($svnFile);
$debug('checking svn existence of file with command:', $svnStatusCommand);
$svnStatusOutput = $executeCommand($svnStatusCommand);
$debug('svn status output:', $svnStatusOutput);
if (! $svnStatusOutput || false === strpos($svnStatusOutput, 'Schedule:')) {
throw new ShellException("Cannot get svn existence info for file '{$svnFile}'");
}
}

Expand All @@ -28,7 +36,7 @@ function isNewSvnFile(string $svnFile, string $svn, callable $executeCommand, ca
$svnStatusOutput = $executeCommand($svnStatusCommand);
$debug('svn status output:', $svnStatusOutput);
if (! $svnStatusOutput || false === strpos($svnStatusOutput, 'Schedule:')) {
throw new \Exception("Cannot get svn info for file '{$svnFile}'");
throw new ShellException("Cannot get svn info for file '{$svnFile}'");
}
return (false !== strpos($svnStatusOutput, 'Schedule: add'));
}
Expand All @@ -38,7 +46,7 @@ function getSvnBasePhpcsOutput(string $svnFile, string $svn, string $phpcs, stri
$debug('running orig phpcs command:', $oldFilePhpcsOutputCommand);
$oldFilePhpcsOutput = $executeCommand($oldFilePhpcsOutputCommand);
if (! $oldFilePhpcsOutput) {
throw new \Exception("Cannot get old phpcs output for file '{$svnFile}'");
throw new ShellException("Cannot get old phpcs output for file '{$svnFile}'");
}
$debug('orig phpcs command output:', $oldFilePhpcsOutput);
return $oldFilePhpcsOutput;
Expand All @@ -49,7 +57,7 @@ function getSvnNewPhpcsOutput(string $svnFile, string $phpcs, string $cat, strin
$debug('running new phpcs command:', $newFilePhpcsOutputCommand);
$newFilePhpcsOutput = $executeCommand($newFilePhpcsOutputCommand);
if (! $newFilePhpcsOutput) {
throw new \Exception("Cannot get new phpcs output for file '{$svnFile}'");
throw new ShellException("Cannot get new phpcs output for file '{$svnFile}'");
}
$debug('new phpcs command output:', $newFilePhpcsOutput);
return $newFilePhpcsOutput;
Expand Down
11 changes: 10 additions & 1 deletion PhpcsChanged/UnixShell.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,19 @@ public function validateExecutableExists(string $name, string $command): void {
}

public function executeCommand(string $command): string {
return shell_exec($command);
return shell_exec($command) ?? '';
}

public function isReadable(string $fileName): bool {
return is_readable($fileName);
}

public function exitWithCode(int $code): void {
exit($code);
}

public function printError(string $output): void {
fwrite(STDERR, 'phpcs-changed: Fatal error!' . PHP_EOL);
fwrite(STDERR, $output . PHP_EOL);
}
}
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ cat file.php | phpcs --report=json > file.php.phpcs
phpcs-changed --report json --diff file.php.diff --phpcs-orig file.php.orig.phpcs --phpcs-new file.php.phpcs
```

Alernatively, we can have the script use svn and phpcs itself:
Alernatively, we can have the script use svn and phpcs itself by using the `--svn` option:

```
phpcs-changed --svn file.php --report json
Expand Down Expand Up @@ -140,6 +140,13 @@ Both will output something like:
}
```

If the file was versioned by git, we can do the same with the `--git` option:

```
phpcs-changed --git file.php --report json
```


### CLI Options

You can use `--report` to customize the output type. `full` (the default) is human-readable and `json` prints a JSON object as shown above. These match the phpcs reporters of the same names.
Expand Down
8 changes: 8 additions & 0 deletions bin/phpcs-changed
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ use function PhpcsChanged\Cli\{
getDebug,
runManualWorkflow,
runSvnWorkflow,
runGitWorkflow,
reportMessagesAndExit
};
use PhpcsChanged\UnixShell;
Expand All @@ -26,6 +27,7 @@ $options = getopt(
'phpcs-orig:',
'phpcs-new:',
'svn:',
'git:',
'standard:',
'report:',
'debug',
Expand Down Expand Up @@ -58,4 +60,10 @@ if ($svnFile) {
reportMessagesAndExit(runSvnWorkflow($svnFile, $options, $shell, $debug), $reportType);
}

$gitFile = $options['git'] ?? null;
if ($gitFile) {
$shell = new UnixShell();
reportMessagesAndExit(runGitWorkflow($gitFile, $options, $shell, $debug), $reportType);
}

printHelp();
5 changes: 4 additions & 1 deletion index.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use PhpcsChanged\DiffLineMap;
use PhpcsChanged\PhpcsMessages;
use PhpcsChanged\ShellException;

require_once __DIR__ . '/PhpcsChanged/Version.php';
require_once __DIR__ . '/PhpcsChanged/DiffLine.php';
Expand All @@ -17,7 +18,9 @@
require_once __DIR__ . '/PhpcsChanged/JsonReporter.php';
require_once __DIR__ . '/PhpcsChanged/FullReporter.php';
require_once __DIR__ . '/PhpcsChanged/NonFatalException.php';
require_once __DIR__ . '/PhpcsChanged/ShellException.php';
require_once __DIR__ . '/PhpcsChanged/SvnWorkflow.php';
require_once __DIR__ . '/PhpcsChanged/GitWorkflow.php';
require_once __DIR__ . '/PhpcsChanged/ShellOperator.php';
require_once __DIR__ . '/PhpcsChanged/UnixShell.php';

Expand All @@ -42,7 +45,7 @@ function getNewPhpcsMessagesFromFiles(string $diffFile, string $phpcsOldFile, st
$oldFilePhpcsOutput = file_get_contents($phpcsOldFile);
$newFilePhpcsOutput = file_get_contents($phpcsNewFile);
if (! $unifiedDiff || ! $oldFilePhpcsOutput || ! $newFilePhpcsOutput) {
throw new \Exception('Cannot read input files.'); // TODO: make custom Exception
throw new ShellException('Cannot read input files.');
}
return getNewPhpcsMessages(
$unifiedDiff,
Expand Down
Loading

0 comments on commit 30cfcaa

Please sign in to comment.