diff --git a/README.md b/README.md index e162c1a8..99c4ef59 100644 --- a/README.md +++ b/README.md @@ -21,22 +21,23 @@ For colored output install the suggested package `jakub-onderka/php-console-high ## Options for run -- `-p ` Specify PHP-CGI executable to run (default: 'php'). -- `-s, --short` Set short_open_tag to On (default: Off). -- `-a, --asp` Set asp_tags to On (default: Off). -- `-e ` Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml) -- `--exclude` Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters. -- `-j ` Run jobs in parallel (default: 10). -- `--colors` Force enable colors in console output. -- `--no-colors` Disable colors in console output. -- `--no-progress` Disable progress in console output. -- `--json` Output results as JSON string (require PHP 5.4). -- `--blame` Try to show git blame for row with error. -- `--git ` Path to Git executable to show blame message (default: 'git'). -- `--stdin` Load files and folder to test from standard input. -- `--ignore-fails` Ignore failed tests. -- `-h, --help` Print this help. -- `-V, --version` Display this application version. +- `-p ` Specify PHP-CGI executable to run (default: 'php'). +- `-s, --short` Set short_open_tag to On (default: Off). +- `-a, --asp` Set asp_tags to On (default: Off). +- `-e ` Check only files with selected extensions separated by comma. (default: php,php3,php4,php5,phtml) +- `--exclude` Exclude a file or directory. If you want exclude multiple items, use multiple exclude parameters. +- `-j ` Run jobs in parallel (default: 10). +- `--colors` Force enable colors in console output. +- `--no-colors` Disable colors in console output. +- `--no-progress` Disable progress in console output. +- `--json` Output results as JSON string (require PHP 5.4). +- `--blame` Try to show git blame for row with error. +- `--git ` Path to Git executable to show blame message (default: 'git'). +- `--stdin` Load files and folder to test from standard input. +- `--ignore-fails` Ignore failed tests. +- `--only-git-changed` Check only files which is changed compared to git HEAD (required git) +- `-h, --help` Print this help. +- `-V, --version` Display this application version. ## Recommended setting for usage with Symfony framework diff --git a/parallel-lint.php b/parallel-lint.php index 372d7252..b313db65 100644 --- a/parallel-lint.php +++ b/parallel-lint.php @@ -20,24 +20,25 @@ function showOptions() { ?> Options: - -p Specify PHP-CGI executable to run (default: 'php'). - -s, --short Set short_open_tag to On (default: Off). - -a, -asp Set asp_tags to On (default: Off). - -e Check only files with selected extensions separated by comma. - (default: php,php3,php4,php5,phtml) - --exclude Exclude a file or directory. If you want exclude multiple items, - use multiple exclude parameters. - -j Run jobs in parallel (default: 10). - --colors Enable colors in console output. (disables auto detection of color support) - --no-colors Disable colors in console output. - --no-progress Disable progress in console output. - --json Output results as JSON string (require PHP 5.4). - --blame Try to show git blame for row with error. - --git Path to Git executable to show blame message (default: 'git'). - --stdin Load files and folder to test from standard input. - --ignore-fails Ignore failed tests. - -h, --help Print this help. - -V, --version Display this application version + -p Specify PHP-CGI executable to run (default: 'php'). + -s, --short Set short_open_tag to On (default: Off). + -a, -asp Set asp_tags to On (default: Off). + -e Check only files with selected extensions separated by comma. + (default: php,php3,php4,php5,phtml) + --exclude Exclude a file or directory. If you want exclude multiple items, + use multiple exclude parameters. + -j Run jobs in parallel (default: 10). + --colors Enable colors in console output. (disables auto detection of color support) + --no-colors Disable colors in console output. + --no-progress Disable progress in console output. + --json Output results as JSON string (require PHP 5.4). + --blame Try to show git blame for row with error. + --git Path to Git executable to show blame message (default: 'git'). + --stdin Load files and folder to test from standard input. + --ignore-fails Ignore failed tests. + --only-git-changed Check only files which is changed compared to git HEAD (required git) + -h, --help Print this help. + -V, --version Display this application version writeHeader($phpExecutable->getVersionId(), $settings->parallelJobs, $phpExecutable->getHhvmVersion()); - $files = $this->getFilesFromPaths($settings->paths, $settings->extensions, $settings->excluded); + $files = $this->getFilesFromPaths( + $settings->paths, + $settings->extensions, + $settings->excluded, + $settings->gitChangedFiles + ); if (empty($files)) { throw new Exception('No file found to check.'); @@ -153,10 +158,11 @@ protected function gitBlame(Result $result, Settings $settings) * @param array $paths * @param array $extensions * @param array $excluded + * @param array $changed * @return array * @throws NotExistsPathException */ - protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = array()) + protected function getFilesFromPaths(array $paths, array $extensions, array $excluded = [], array $changed = []) { $extensions = array_flip($extensions); $files = array(); @@ -167,7 +173,10 @@ protected function getFilesFromPaths(array $paths, array $extensions, array $exc } else if (is_dir($path)) { $iterator = new \RecursiveDirectoryIterator($path, \FilesystemIterator::SKIP_DOTS); if (!empty($excluded)) { - $iterator = new RecursiveDirectoryFilterIterator($iterator, $excluded); + $iterator = new RecursiveDirectoryExcludedFilterIterator($iterator, $excluded); + } + if (!empty($changed)) { + $iterator = new RecursiveDirectoryChangedFilesFilterIterator($iterator, $changed); } $iterator = new \RecursiveIteratorIterator( $iterator, @@ -192,11 +201,51 @@ protected function getFilesFromPaths(array $paths, array $extensions, array $exc } } -class RecursiveDirectoryFilterIterator extends \RecursiveFilterIterator +abstract class AbstractRecursiveDirectoryFilterIterator extends \RecursiveFilterIterator { /** @var \RecursiveDirectoryIterator */ - private $iterator; + protected $iterator; + /** + * (PHP 5 >= 5.1.0)
+ * Check whether the inner iterator's current element has children + * + * @link http://php.net/manual/en/recursivefilteriterator.haschildren.php + * @return bool true if the inner iterator has children, otherwise false + */ + public function hasChildren() + { + return $this->iterator->hasChildren(); + } + + /** + * @param string $file + * @return string + */ + protected function getPathname($file) + { + $file = $this->normalizeDirectorySeparator($file); + + if ('.' . DIRECTORY_SEPARATOR !== $file[0] . $file[1]) { + $file = '.' . DIRECTORY_SEPARATOR . $file; + } + + $directoryFile = new \SplFileInfo($file); + return $directoryFile->getPathname(); + } + + /** + * @param string $file + * @return string + */ + protected function normalizeDirectorySeparator($file) + { + return str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $file); + } +} + +class RecursiveDirectoryExcludedFilterIterator extends AbstractRecursiveDirectoryFilterIterator +{ /** @var array */ private $excluded = array(); @@ -230,18 +279,6 @@ public function accept() return !in_array($current, $this->excluded); } - /** - * (PHP 5 >= 5.1.0)
- * Check whether the inner iterator's current element has children - * - * @link http://php.net/manual/en/recursivefilteriterator.haschildren.php - * @return bool true if the inner iterator has children, otherwise false - */ - public function hasChildren() - { - return $this->iterator->hasChildren(); - } - /** * (PHP 5 >= 5.1.0)
* Return the inner iterator's children contained in a RecursiveFilterIterator @@ -253,29 +290,52 @@ public function getChildren() { return new self($this->iterator->getChildren(), $this->excluded); } +} + +class RecursiveDirectoryChangedFilesFilterIterator extends AbstractRecursiveDirectoryFilterIterator +{ + /** @var array */ + private $changedFiles = array(); /** - * @param string $file - * @return string + * @param \RecursiveDirectoryIterator $iterator + * @param array $changedFiles */ - private function getPathname($file) + public function __construct(\RecursiveDirectoryIterator $iterator, array $changedFiles) { - $file = $this->normalizeDirectorySeparator($file); + parent::__construct($iterator); + $this->iterator = $iterator; + $this->changedFiles = array_map(array($this, 'getPathname'), $changedFiles); + } - if ('.' . DIRECTORY_SEPARATOR !== $file[0] . $file[1]) { - $file = '.' . DIRECTORY_SEPARATOR . $file; + /** + * (PHP 5 >= 5.1.0)
+ * Check whether the current element of the iterator is acceptable + * + * @link http://php.net/manual/en/filteriterator.accept.php + * @return bool true if the current element is acceptable, otherwise false. + */ + public function accept() + { + $current = $this->current()->getPathname(); + $current = $this->normalizeDirectorySeparator($current); + + if ('.' . DIRECTORY_SEPARATOR !== $current[0] . $current[1]) { + $current = '.' . DIRECTORY_SEPARATOR . $current; } - $directoryFile = new \SplFileInfo($file); - return $directoryFile->getPathname(); + return in_array($current, $this->changedFiles); } /** - * @param string $file - * @return string + * (PHP 5 >= 5.1.0)
+ * Return the inner iterator's children contained in a RecursiveFilterIterator + * + * @link http://php.net/manual/en/recursivefilteriterator.getchildren.php + * @return \RecursiveFilterIterator containing the inner iterator's children. */ - private function normalizeDirectorySeparator($file) + public function getChildren() { - return str_replace(array('\\', '/'), DIRECTORY_SEPARATOR, $file); + return new self($this->iterator->getChildren(), $this->changedFiles); } -} +} \ No newline at end of file diff --git a/src/Settings.php b/src/Settings.php index e4c3dc39..2bca88fb 100644 --- a/src/Settings.php +++ b/src/Settings.php @@ -123,6 +123,12 @@ class Settings */ public $ignoreFails = false; + /** + * Array of file or directories to check because they have been changed + * @var bool + */ + public $gitChangedFiles = array(); + /** * @param array $paths */ @@ -141,6 +147,8 @@ public static function parseArguments(array $arguments) $arguments = new ArrayIterator(array_slice($arguments, 1)); $settings = new self; + $gitChangedFilesCommand = "git --no-pager diff --name-only --diff-filter=MARC| grep -E '.php'"; + foreach ($arguments as $argument) { if ($argument{0} !== '-') { $settings->paths[] = $argument; @@ -204,6 +212,12 @@ public static function parseArguments(array $arguments) $settings->ignoreFails = true; break; + case '--only-git-changed': + $gitChangedFiles = []; + exec($gitChangedFilesCommand, $gitChangedFiles); + $settings->gitChangedFiles = $gitChangedFiles; + break; + default: throw new InvalidArgumentException($argument); } diff --git a/tests/Manager.run.phpt b/tests/Manager.run.phpt index 169608c7..fa3c2e75 100644 --- a/tests/Manager.run.phpt +++ b/tests/Manager.run.phpt @@ -54,6 +54,38 @@ class ManagerRunTest extends Tester\TestCase Assert::true($result->hasError()); } + public function testOnlyChangedFiles() + { + $settings = $this->prepareSettings(); + $settings->paths = array('examples/example-04/'); + + $manager = $this->getManager($settings); + $result = $manager->run($settings); + Assert::true($result->hasError()); + + $settings->gitChangedFiles = array('examples/example-04/index.php', 'examples/example-04/dir1/index.php'); + + $manager = $this->getManager($settings); + $result = $manager->run($settings); + Assert::false($result->hasError()); + } + + public function testOnlyChangedFilesWithEmptyArray() + { + $settings = $this->prepareSettings(); + $settings->paths = array('examples/example-04/'); + + $manager = $this->getManager($settings); + $result = $manager->run($settings); + Assert::true($result->hasError()); + + $settings->gitChangedFiles = array(); + + $manager = $this->getManager($settings); + $result = $manager->run($settings); + Assert::true($result->hasError()); + } + public function testExcludeRelativeSubdirectory() { $settings = $this->prepareSettings();