 ebddd9bb7f
			
		
	
	ebddd9bb7f
	
	
	
		
			
			The pre-commit hook will lint-check the added PHP files and also check their CS. Signed-off-by: Jacob Kiers <jacob@alphacomm.nl>
		
			
				
	
	
		
			267 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
			
		
		
	
	
			267 lines
		
	
	
		
			6.8 KiB
		
	
	
	
		
			PHP
		
	
	
		
			Executable File
		
	
	
	
	
| #!/usr/bin/env php
 | |
| <?php
 | |
| /**
 | |
|  * .git/hooks/pre-commit
 | |
|  *
 | |
|  * This pre-commit hooks will check for PHP errors (lint), and make sure the
 | |
|  * code is PSR-2 compliant.
 | |
|  *
 | |
|  * @author  Reen Lokum http://github.com/reenl
 | |
|  *
 | |
|  * The orignal version of this file can be found at:
 | |
|  * https://github.com/zendframework/zf2/blob/master/README-GIT.md
 | |
|  *
 | |
|  * Extended with functionality found at:
 | |
|  * http://kvz.io/blog/2013/12/29/one-git-commit-hook-to-rule-them-all/
 | |
|  */
 | |
| 
 | |
| $exit = 0;
 | |
| 
 | |
| // Initial commit
 | |
| $against = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
 | |
| if (run('git rev-parse --verify HEAD > /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 --verbose --level=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();
 | |
| }
 |