#!/usr/bin/env php /dev/null')) { $against = 'HEAD'; } // Only run when we're on a branch (to avoid rebase hell) // http://git-blame.blogspot.nl/2013/06/checking-current-branch-programatically.html $branch = run('git symbolic-ref --short -q HEAD'); if (!$branch) { writeln('Not on any branch'); exit(0); } /* * collect all files which have been added, copied or * modified and store them in an array called output */ $diffLines = array(); exec('git diff-index --cached --full-index --diff-filter=ACM '.$against, $diffLines); writeln(); // Filter files that don't need a check. foreach ($diffLines as $line) { $partList = preg_split('#\s+#', $line, 6); $hash = $partList[3]; $status = $partList[4]; $fileName = $partList[5]; if ('D' === $status) { // deleted file; do nothing continue; } $type = strtolower(pathinfo($fileName, PATHINFO_EXTENSION)); $validator = 'validator_'.$type; if (!$type || !function_exists($validator)) { $type = run("git cat-file -p ".$hash." | head -n1 | awk -F/ '/^#\!/ {print \$NF}' | sed 's/^env //g'"); $validator = 'validator_'.$type; if (!function_exists($validator)) { // No validator writeln(' Skipping "'.format($fileName, 'green').'" no validator available.'); continue; } } write(' Checking "'.format($fileName, 'green').'" with validator '.format($type, 'green').'.'); $output = ''; if (!$validator($hash, $fileName, $output)) { writeln(PHP_EOL.'X ERROR '.implode(PHP_EOL.' ', explode(PHP_EOL, $output)).PHP_EOL, 'red'); $exit = 1; continue; } writeln(' OK', 'green'); } if ($exit > 0) { writeln(PHP_EOL."Please fix the above errors and run 'git add'.", 'gray'); } exit($exit); function validator_php($hash, $fileName, &$output) { if (validator_php_syntax($hash, $fileName, $output)) { return validator_php_cs($hash, $fileName, $output); } return false; } function validator_php_syntax($hash, $fileName, &$output) { $output = ''; $exitCode = 0; $result = run('git cat-file -p '.escapeshellarg($hash).' | php -l', $output, $exitCode, "purge", "default"); if ($result) { return true; } $output = 'Syntax Error'.PHP_EOL.$output; return false; } function validator_php_cs($hash, $fileName, &$output) { // Use .php_cs config if project has one. $configFile = ''; if (file_exists('.php_cs')) { $configFile = ' --config-file='.escapeshellarg(realpath('.php_cs')); } $tmpDir = '/tmp/cs-check/'.$hash; $tmp = $tmpDir.'/'.$fileName; run('mkdir -p '.dirname($tmp)); run('git cat-file -p '.escapeshellarg($hash).' > '.$tmp); $return = null; run('php-cs-fixer fix --dry-run --diff --verbose --rules=@Symfony'.$configFile.' '.escapeshellarg($tmp), $currentOutput, $return, 'default', 'default'); run('rm -rf '.escapeshellarg($tmpDir)); // Check output if ($return !== 0) { $out = explode(PHP_EOL, $currentOutput); $rule = null; foreach ($out as $line) { if (preg_match('#\s+[0-9]+\)\s#', $line)) { $rule = $line; break; } } if ($rule !== null && preg_match('#\((.*)\)#', $rule, $matches)) { $output = 'Code Style errors'.PHP_EOL.$matches[1]; } else { $output = 'Code Style errors'.PHP_EOL.implode(PHP_EOL, $out).PHP_EOL; } return false; } return true; } /** * Runs like exec with a few changes: * - Output is returned as a string. * - Output is NOT appended. * - STDERR is also added to the output. * - STDERR and/or STDOUT can be disabled by passing purge. * - Returns the first output line if successful and false when failed. * - If no output is generated and the exit status equals 0 then true is returned. * * @param string $command * @param string &$output * @param int &$exitCode * @param string $stdout * @param string $stderr * * @return boolean */ function run($command, &$output = '', &$exitCode = 0, $stdout = 'default', $stderr = 'purge') { $descriptors = array( 0 => array("pipe", "r"), // stdin 1 => array("pipe", "w"), // stdout 2 => array("pipe", "w"), // stderr ); $pipes = array(); $out = array(); $process = proc_open($command, $descriptors, $pipes); fclose($pipes[0]); unset($pipes[0]); do { $read = $pipes; $write = $except = array(); if (!stream_select($read, $write, $except, 5)) { writeln('Timeout on process: '.$command, 'red'); break; } foreach ($read as $pipe) { $pipeId = array_search($pipe, $pipes); if ($pipeId === false) { writeln('Unable to determine where the output came from.', 'red'); } if (feof($pipe)) { fclose($pipe); if ($pipeId !== false) { unset($pipes[$pipeId]); } continue; } $line = fgets($pipe); if ($line === false) { continue; } $color = $stderr; if ($pipeId == 1) { $color = $stdout; } if ($color != 'purge') { $out[] = format(rtrim($line), $color); } } } while (count($pipes) > 0); $exitCode = proc_close($process); $output = implode(PHP_EOL, $out); if ($exitCode == 0) { if (!isset($out[0]) || $out[0] == '') { return true; } return $out[0]; } return false; } function format($string, $color = 'default') { if ($color == 'default') { return $string; } if ($color == 'purge') { return ''; } $colors = array( 'gray' => 37, 'green' => 32, 'red' => 31, ); if (!isset($colors[$color])) { writeln($color.' is not a valid color.'); exit(1); } return chr(0x1B).'['.$colors[$color].'m'.$string.chr(0x1B).'[m'; } function writeln($write = '', $color = 'default') { write($write.PHP_EOL, $color); } function write($write = '', $color = 'default') { echo format($write, $color); flush(); }