1#!/usr/bin/env php 2<?php 3/** 4 * A commit hook for SVN. 5 * 6 * PHP version 5 7 * 8 * @category PHP 9 * @package PHP_CodeSniffer 10 * @author Jack Bates <ms419@freezone.co.uk> 11 * @author Greg Sherwood <gsherwood@squiz.net> 12 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 13 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 14 * @link http://pear.php.net/package/PHP_CodeSniffer 15 */ 16 17if (is_file(dirname(__FILE__).'/../CodeSniffer/CLI.php') === true) { 18 include_once dirname(__FILE__).'/../CodeSniffer/CLI.php'; 19} else { 20 include_once 'PHP/CodeSniffer/CLI.php'; 21} 22 23define('PHP_CODESNIFFER_SVNLOOK', '/usr/bin/svnlook'); 24 25 26/** 27 * A class to process command line options. 28 * 29 * @category PHP 30 * @package PHP_CodeSniffer 31 * @author Jack Bates <ms419@freezone.co.uk> 32 * @author Greg Sherwood <gsherwood@squiz.net> 33 * @copyright 2006-2014 Squiz Pty Ltd (ABN 77 084 670 600) 34 * @license https://github.com/squizlabs/PHP_CodeSniffer/blob/master/licence.txt BSD Licence 35 * @version Release: @package_version@ 36 * @link http://pear.php.net/package/PHP_CodeSniffer 37 */ 38class PHP_CodeSniffer_SVN_Hook extends PHP_CodeSniffer_CLI 39{ 40 41 42 /** 43 * Get a list of default values for all possible command line arguments. 44 * 45 * @return array 46 */ 47 public function getDefaults() 48 { 49 $defaults = parent::getDefaults(); 50 51 $defaults['svnArgs'] = array(); 52 return $defaults; 53 54 }//end getDefaults() 55 56 57 /** 58 * Processes an unknown command line argument. 59 * 60 * Assumes all unknown arguments are files and folders to check. 61 * 62 * @param string $arg The command line argument. 63 * @param int $pos The position of the argument on the command line. 64 * 65 * @return void 66 */ 67 public function processUnknownArgument($arg, $pos) 68 { 69 $this->values['svnArgs'][] = escapeshellarg($arg); 70 71 }//end processUnknownArgument() 72 73 74 /** 75 * Runs PHP_CodeSniffer over files are directories. 76 * 77 * @param array $values An array of values determined from CLI args. 78 * 79 * @return int The number of error and warning messages shown. 80 * @see getCommandLineValues() 81 */ 82 public function process($values=array()) 83 { 84 if (empty($values) === true) { 85 $values = $this->getCommandLineValues(); 86 } else { 87 $values = array_merge($this->getDefaults(), $values); 88 $this->values = $values; 89 } 90 91 // Get list of files in this transaction. 92 $command = PHP_CODESNIFFER_SVNLOOK.' changed '.implode(' ', $values['svnArgs']); 93 $handle = popen($command, 'r'); 94 if ($handle === false) { 95 echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL; 96 exit(2); 97 } 98 99 $contents = stream_get_contents($handle); 100 fclose($handle); 101 102 // Do not check deleted paths. 103 $contents = preg_replace('/^D.*/m', null, $contents); 104 105 // Drop the four characters representing the action which precede the path on 106 // each line. 107 $contents = preg_replace('/^.{4}/m', null, $contents); 108 109 $values['standard'] = $this->validateStandard($values['standard']); 110 foreach ($values['standard'] as $standard) { 111 if (PHP_CodeSniffer::isInstalledStandard($standard) === false) { 112 // They didn't select a valid coding standard, so help them 113 // out by letting them know which standards are installed. 114 echo 'ERROR: the "'.$standard.'" coding standard is not installed. '; 115 $this->printInstalledStandards(); 116 exit(2); 117 } 118 } 119 120 $phpcs = new PHP_CodeSniffer( 121 $values['verbosity'], 122 $values['tabWidth'], 123 $values['encoding'] 124 ); 125 126 // Set file extensions if they were specified. Otherwise, 127 // let PHP_CodeSniffer decide on the defaults. 128 if (empty($values['extensions']) === false) { 129 $phpcs->setAllowedFileExtensions($values['extensions']); 130 } else { 131 $phpcs->setAllowedFileExtensions(array_keys($phpcs->defaultFileExtensions)); 132 } 133 134 // Set ignore patterns if they were specified. 135 if (empty($values['ignored']) === false) { 136 $phpcs->setIgnorePatterns($values['ignored']); 137 } 138 139 // Set some convenience member vars. 140 if ($values['errorSeverity'] === null) { 141 $this->errorSeverity = PHPCS_DEFAULT_ERROR_SEV; 142 } else { 143 $this->errorSeverity = $values['errorSeverity']; 144 } 145 146 if ($values['warningSeverity'] === null) { 147 $this->warningSeverity = PHPCS_DEFAULT_WARN_SEV; 148 } else { 149 $this->warningSeverity = $values['warningSeverity']; 150 } 151 152 if (empty($values['reports']) === true) { 153 $this->values['reports']['full'] = $values['reportFile']; 154 } 155 156 // Initialize PHP_CodeSniffer listeners but don't process any files. 157 $phpcs->setCli($this); 158 $phpcs->initStandard($values['standard'], $values['sniffs']); 159 160 // Need double quotes around the following regex beause the vertical whitespace 161 // char is not always treated correctly for whatever reason. 162 foreach (preg_split("/\v|\n/", $contents, -1, PREG_SPLIT_NO_EMPTY) as $path) { 163 // No need to process folders as each changed file is checked. 164 if (substr($path, -1) === '/') { 165 continue; 166 } 167 168 // We need to check ignore rules ourself because they are 169 // not checked when processing a single file. 170 if ($phpcs->shouldProcessFile($path, dirname($path)) === false) { 171 continue; 172 } 173 174 // Get the contents of each file, as it would be after this transaction. 175 $command = PHP_CODESNIFFER_SVNLOOK.' cat '.implode(' ', $values['svnArgs']).' '.escapeshellarg($path); 176 $handle = popen($command, 'r'); 177 if ($handle === false) { 178 echo 'ERROR: Could not execute "'.$command.'"'.PHP_EOL.PHP_EOL; 179 exit(2); 180 } 181 182 $contents = stream_get_contents($handle); 183 fclose($handle); 184 185 $phpcs->processFile($path, $contents); 186 }//end foreach 187 188 return $this->printErrorReport( 189 $phpcs, 190 $values['reports'], 191 $values['showSources'], 192 $values['reportFile'], 193 $values['reportWidth'] 194 ); 195 196 }//end process() 197 198 199 /** 200 * Prints out the usage information for this script. 201 * 202 * @return void 203 */ 204 public function printUsage() 205 { 206 parent::printUsage(); 207 208 echo PHP_EOL; 209 echo ' Each additional argument is passed to the `svnlook changed ...`'.PHP_EOL; 210 echo ' and `svnlook cat ...` commands. The report is printed on standard output,'.PHP_EOL; 211 echo ' however Subversion displays only standard error to the user, so in a'.PHP_EOL; 212 echo ' pre-commit hook, this script should be invoked as follows:'.PHP_EOL; 213 echo PHP_EOL; 214 echo ' '.basename($_SERVER['argv'][0]).' ... "$REPOS" -t "$TXN" >&2 || exit 1'.PHP_EOL; 215 216 }//end printUsage() 217 218 219}//end class 220 221$phpcs = new PHP_CodeSniffer_SVN_Hook(); 222 223PHP_CodeSniffer_Reporting::startTiming(); 224$phpcs->checkRequirements(); 225 226$numErrors = $phpcs->process(); 227if ($numErrors !== 0) { 228 exit(1); 229} 230