From 77dce932a1ea8defd42d810d5a2726620ffa2bb8 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 13:08:55 -0400 Subject: [PATCH 01/13] Move hash, phpcs, git root, and path comamnds to ShellOperator --- PhpcsChanged/Cli.php | 16 ++--- PhpcsChanged/GitWorkflow.php | 105 ++--------------------------- PhpcsChanged/ShellOperator.php | 15 ++++- PhpcsChanged/UnixShell.php | 118 +++++++++++++++++++++++++++++++++ 4 files changed, 141 insertions(+), 113 deletions(-) diff --git a/PhpcsChanged/Cli.php b/PhpcsChanged/Cli.php index f8feb89..4126e5a 100644 --- a/PhpcsChanged/Cli.php +++ b/PhpcsChanged/Cli.php @@ -16,7 +16,7 @@ use PhpcsChanged\CacheManager; use function PhpcsChanged\{getNewPhpcsMessages, getNewPhpcsMessagesFromFiles, getVersion}; use function PhpcsChanged\SvnWorkflow\{getSvnUnifiedDiff, getSvnFileInfo, isNewSvnFile, getSvnUnmodifiedPhpcsOutput, getSvnModifiedPhpcsOutput, getSvnRevisionId}; -use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff, isNewGitFile, getGitUnmodifiedPhpcsOutput, getGitModifiedPhpcsOutput, validateGitFileExists, getModifiedGitFileHash, getUnmodifiedGitFileHash}; +use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff, isNewGitFile, validateGitFileExists}; function getDebug(bool $debugEnabled): callable { return @@ -345,16 +345,10 @@ function runGitWorkflow(CliOptions $options, ShellOperator $shell, CacheManager function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperator $shell, CacheManager $cache, callable $debug): PhpcsMessages { $git = getenv('GIT') ?: 'git'; - $phpcs = getenv('PHPCS') ?: 'phpcs'; - $cat = getenv('CAT') ?: 'cat'; $phpcsStandard = $options->phpcsStandard; - $phpcsStandardOption = $phpcsStandard ? ' --standard=' . escapeshellarg($phpcsStandard) : ''; - $warningSeverity = $options->warningSeverity; - $phpcsStandardOption .= isset($warningSeverity) ? ' --warning-severity=' . escapeshellarg($warningSeverity) : ''; $errorSeverity = $options->errorSeverity; - $phpcsStandardOption .= isset($errorSeverity) ? ' --error-severity=' . escapeshellarg($errorSeverity) : ''; $fileName = $shell->getFileNameFromPath($gitFile); try { @@ -363,12 +357,12 @@ function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperat $modifiedFilePhpcsOutput = null; $modifiedFileHash = ''; if (isCachingEnabled($options->toArray())) { - $modifiedFileHash = getModifiedGitFileHash($gitFile, $git, $cat, [$shell, 'executeCommand'], $options->toArray(), $debug); + $modifiedFileHash = $shell->getGitHashOfModifiedFile($gitFile); $modifiedFilePhpcsOutput = $cache->getCacheForFile($gitFile, 'new', $modifiedFileHash, $phpcsStandard ?? '', $warningSeverity ?? '', $errorSeverity ?? ''); $debug(($modifiedFilePhpcsOutput ? 'Using' : 'Not using') . " cache for modified file '{$gitFile}' at hash '{$modifiedFileHash}', and standard '{$phpcsStandard}'"); } if (! $modifiedFilePhpcsOutput) { - $modifiedFilePhpcsOutput = getGitModifiedPhpcsOutput($gitFile, $git, $phpcs, $cat, $phpcsStandardOption, [$shell, 'executeCommand'], $options->toArray(), $debug); + $modifiedFilePhpcsOutput = $shell->getPhpcsOutputOfModifiedGitFile($gitFile); if (isCachingEnabled($options->toArray())) { $cache->setCacheForFile($gitFile, 'new', $modifiedFileHash, $phpcsStandard ?? '', $warningSeverity ?? '', $errorSeverity ?? '', $modifiedFilePhpcsOutput); } @@ -393,12 +387,12 @@ function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperat $unmodifiedFilePhpcsOutput = null; $unmodifiedFileHash = ''; if (isCachingEnabled($options->toArray())) { - $unmodifiedFileHash = getUnmodifiedGitFileHash($gitFile, $git, $cat, [$shell, 'executeCommand'], $options->toArray(), $debug); + $unmodifiedFileHash = $shell->getGitHashOfUnmodifiedFile($gitFile); $unmodifiedFilePhpcsOutput = $cache->getCacheForFile($gitFile, 'old', $unmodifiedFileHash, $phpcsStandard ?? '', $warningSeverity ?? '', $errorSeverity ?? ''); $debug(($unmodifiedFilePhpcsOutput ? 'Using' : 'Not using') . " cache for unmodified file '{$gitFile}' at hash '{$unmodifiedFileHash}', and standard '{$phpcsStandard}'"); } if (! $unmodifiedFilePhpcsOutput) { - $unmodifiedFilePhpcsOutput = getGitUnmodifiedPhpcsOutput($gitFile, $git, $phpcs, $phpcsStandardOption, [$shell, 'executeCommand'], $options->toArray(), $debug); + $unmodifiedFilePhpcsOutput = $shell->getPhpcsOutputOfUnmodifiedGitFile($gitFile); if (isCachingEnabled($options->toArray())) { $cache->setCacheForFile($gitFile, 'old', $unmodifiedFileHash, $phpcsStandard ?? '', $warningSeverity ?? '', $errorSeverity ?? '', $unmodifiedFilePhpcsOutput); } diff --git a/PhpcsChanged/GitWorkflow.php b/PhpcsChanged/GitWorkflow.php index 503cdd0..2b53970 100644 --- a/PhpcsChanged/GitWorkflow.php +++ b/PhpcsChanged/GitWorkflow.php @@ -23,8 +23,10 @@ function validateGitFileExists(string $gitFile, ShellOperator $shell, CliOptions } } -function isRunFromGitRoot(string $git, callable $executeCommand, array $options, callable $debug): bool { +function isRunFromGitRoot(ShellOperator $shell, CliOptions $options): bool { + // This should never change while the script runs so we cache it. static $isRunFromGitRoot; + if (isset($options['no-cache-git-root'])) { $isRunFromGitRoot = null; } @@ -32,13 +34,11 @@ function isRunFromGitRoot(string $git, callable $executeCommand, array $options, return $isRunFromGitRoot; } - $gitRootCommand = "{$git} rev-parse --show-toplevel"; - $gitRoot = $executeCommand($gitRootCommand); - $gitRoot = trim($gitRoot); + $debug = getDebug($options->debug); + $gitRoot = $shell->getGitRootDirectory(); $isRunFromGitRoot = (getcwd() === $gitRoot); $debug('is run from git root: ' . var_export($isRunFromGitRoot, true)); - return $isRunFromGitRoot; } @@ -103,98 +103,3 @@ function isNewGitFileLocal(string $gitFile, string $git, callable $executeComman } return isset($gitStatusOutput[0]) && $gitStatusOutput[0] === 'A'; } - -function getGitUnmodifiedPhpcsOutput(string $gitFile, string $git, string $phpcs, string $phpcsStandardOption, callable $executeCommand, array $options, callable $debug): string { - $unmodifiedFileContents = getUnmodifiedGitRevisionContentsCommand($gitFile, $git, $options, $executeCommand, $debug); - - $unmodifiedFilePhpcsOutputCommand = "{$unmodifiedFileContents} | {$phpcs} --report=json -q" . $phpcsStandardOption . ' --stdin-path=' . escapeshellarg($gitFile) . ' -'; - $debug('running unmodified file phpcs command:', $unmodifiedFilePhpcsOutputCommand); - $unmodifiedFilePhpcsOutput = $executeCommand($unmodifiedFilePhpcsOutputCommand); - if (! $unmodifiedFilePhpcsOutput) { - throw new ShellException("Cannot get unmodified file phpcs output for file '{$gitFile}'"); - } - $debug('unmodified file phpcs command output:', $unmodifiedFilePhpcsOutput); - return $unmodifiedFilePhpcsOutput; -} - -function getGitModifiedPhpcsOutput(string $gitFile, string $git, string $phpcs, string $cat, string $phpcsStandardOption, callable $executeCommand, array $options, callable $debug): string { - $modifiedFileContents = getModifiedGitRevisionContentsCommand($gitFile, $git, $cat, $options, $executeCommand, $debug); - - $modifiedFilePhpcsOutputCommand = "{$modifiedFileContents} | {$phpcs} --report=json -q" . $phpcsStandardOption . ' --stdin-path=' . escapeshellarg($gitFile) .' -'; - $debug('running modified file phpcs command:', $modifiedFilePhpcsOutputCommand); - $modifiedFilePhpcsOutput = $executeCommand($modifiedFilePhpcsOutputCommand); - if (! $modifiedFilePhpcsOutput) { - throw new ShellException("Cannot get modified file phpcs output for file '{$gitFile}'"); - } - $debug('modified file phpcs command output:', $modifiedFilePhpcsOutput); - if (false !== strpos($modifiedFilePhpcsOutput, 'You must supply at least one file or directory to process')) { - $debug('phpcs output implies modified file is empty'); - return ''; - } - return $modifiedFilePhpcsOutput; -} - -function getFullPathToFileCommand(string $gitFile, string $git): string { - return "{$git} ls-files --full-name " . escapeshellarg($gitFile); -} - -function getModifiedGitRevisionContentsCommand(string $gitFile, string $git, string $cat, array $options, callable $executeCommand, callable $debug): string { - $fullPathCommand = getFullPathToFileCommand($gitFile, $git); - if (isset($options['git-base']) && ! empty($options['git-base'])) { - // for git-base mode, we get the contents of the file from the HEAD version of the file in the current branch - if (isRunFromGitRoot($git, $executeCommand, $options, $debug)) { - return "{$git} show HEAD:" . escapeshellarg($gitFile); - } - return "{$git} show HEAD:$({$fullPathCommand})"; - } else if (isset($options['git-unstaged'])) { - // for git-unstaged mode, we get the contents of the file from the current working copy - return "{$cat} " . escapeshellarg($gitFile); - } - // default mode is git-staged, so we get the contents from the staged version of the file - if (isRunFromGitRoot($git, $executeCommand, $options, $debug)) { - return "{$git} show :0:" . escapeshellarg($gitFile); - } - return "{$git} show :0:$({$fullPathCommand})"; -} - -function getUnmodifiedGitRevisionContentsCommand(string $gitFile, string $git, array $options, callable $executeCommand, callable $debug): string { - if (isset($options['git-base']) && ! empty($options['git-base'])) { - // git-base - $rev = escapeshellarg($options['git-base']); - } else if (isset($options['git-unstaged'])) { - // git-unstaged - $rev = ':0'; // :0 in this case means "staged version or HEAD if there is no staged version" - } else { - // git-staged - $rev = 'HEAD'; - } - if (isRunFromGitRoot($git, $executeCommand, $options, $debug)) { - return "{$git} show {$rev}:" . escapeshellarg($gitFile); - } - $fullPathCommand = getFullPathToFileCommand($gitFile, $git); - return "{$git} show {$rev}:$({$fullPathCommand})"; -} - -function getModifiedGitFileHash(string $gitFile, string $git, string $cat, callable $executeCommand, array $options, callable $debug): string { - $fileContents = getModifiedGitRevisionContentsCommand($gitFile, $git, $cat, $options, $executeCommand, $debug); - $command = "{$fileContents} | {$git} hash-object --stdin"; - $debug('running modified file git hash command:', $command); - $hash = $executeCommand($command); - if (! $hash) { - throw new ShellException("Cannot get modified file hash for file '{$gitFile}'"); - } - $debug('modified file git hash command output:', $hash); - return $hash; -} - -function getUnmodifiedGitFileHash(string $gitFile, string $git, string $cat, callable $executeCommand, array $options, callable $debug): string { - $fileContents = getUnmodifiedGitRevisionContentsCommand($gitFile, $git, $options, $executeCommand, $debug); - $command = "{$fileContents} | {$git} hash-object --stdin"; - $debug('running unmodified file git hash command:', $command); - $hash = $executeCommand($command); - if (! $hash) { - throw new ShellException("Cannot get unmodified file hash for file '{$gitFile}'"); - } - $debug('unmodified file git hash command output:', $hash); - return $hash; -} diff --git a/PhpcsChanged/ShellOperator.php b/PhpcsChanged/ShellOperator.php index 9b627fe..806c8c1 100644 --- a/PhpcsChanged/ShellOperator.php +++ b/PhpcsChanged/ShellOperator.php @@ -11,10 +11,9 @@ interface ShellOperator { public function validateExecutableExists(string $name, string $command): void; + // TODO: remove executeCommand from the interface and rely on the more specific methods. public function executeCommand(string $command, array &$output = null, int &$return_val = null): string; - public function doesFileExistInGit(string $fileName): bool; - public function isReadable(string $fileName): bool; public function getFileHash(string $fileName): string; @@ -24,4 +23,16 @@ public function exitWithCode(int $code): void; public function printError(string $message): void; public function getFileNameFromPath(string $path): string; + + public function doesFileExistInGit(string $fileName): bool; + + public function getGitRootDirectory(): string; + + public function getGitHashOfModifiedFile(string $fileName): string; + + public function getGitHashOfUnmodifiedFile(string $fileName): string; + + public function getPhpcsOutputOfModifiedGitFile(string $fileName): string; + + public function getPhpcsOutputOfUnmodifiedGitFile(string $fileName): string; } diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 20dce7f..41bef94 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -46,6 +46,124 @@ public function doesFileExistInGit(string $fileName): bool { return true; } + public function getGitRootDirectory(): string { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $gitRootCommand = "{$git} rev-parse --show-toplevel"; + $debug('getting git root directory with command:', $gitRootCommand); + $gitRoot = $this->executeCommand($gitRootCommand); + return trim($gitRoot); + } + + private function getFullGitPathToFile(string $fileName): string { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); + $debug('getting full path to file with command:', $command); + $fullPath = $this->executeCommand($command); + return trim($fullPath); + } + + private function getModifiedFileContentsCommand(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + $cat = getenv('CAT') ?: 'cat'; + $fullPath = $this->getFullGitPathToFile($fileName); + if ($this->options->mode === Mode::GIT_BASE) { + // for git-base mode, we get the contents of the file from the HEAD version of the file in the current branch + return "{$git} show HEAD:" . escapeshellarg($fullPath); + } + if ($this->options->mode === Modes::GIT_UNSTAGED) { + // for git-unstaged mode, we get the contents of the file from the current working copy + return "{$cat} " . escapeshellarg($fileName); + } + // default mode is git-staged, so we get the contents from the staged version of the file + return "{$git} show :0:" . escapeshellarg($fullPath); + } + + private function getUnmodifiedFileContentsCommand(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + if ($this->options->mode === Mode::GIT_BASE) { + $rev = escapeshellarg($this->options->gitBase); + } else if ($this->options->mode === Mode::GIT_UNSTAGED) { + $rev = ':0'; // :0 in this case means "staged version or HEAD if there is no staged version" + } else { + // git-staged is the default + $rev = 'HEAD'; + } + $fullPath = $this->getFullGitPathToFile($fileName); + return "{$git} show {$rev}:" . escapeshellarg($fullPath); + } + + public function getGitHashOfModifiedFile(string $fileName): string { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $fileContentsCommand = $this->getModifiedFileContentsCommand($fileName); + $command = "{$fileContentsCommand} | {$git} hash-object --stdin"; + $debug('running modified file git hash command:', $command); + $hash = $this->executeCommand($command); + if (! $hash) { + throw new ShellException("Cannot get modified file hash for file '{$fileName}'"); + } + $debug('modified file git hash command output:', $hash); + return $hash; + } + + public function getGitHashOfUnmodifiedFile(string $fileName): string { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $fileContentsCommand = $this->getUnmodifiedFileContentsCommand($fileName); + $command = "{$fileContentsCommand} | {$git} hash-object --stdin"; + $debug('running unmodified file git hash command:', $command); + $hash = $this->executeCommand($command); + if (! $hash) { + throw new ShellException("Cannot get unmodified file hash for file '{$fileName}'"); + } + $debug('unmodified file git hash command output:', $hash); + return $hash; + } + + private function getPhpcsStandardOption(): string { + $phpcsStandard = $this->options->phpcsStandard; + $phpcsStandardOption = $phpcsStandard ? ' --standard=' . escapeshellarg($phpcsStandard) : ''; + $warningSeverity = $this->options->warningSeverity; + $phpcsStandardOption .= isset($warningSeverity) ? ' --warning-severity=' . escapeshellarg($warningSeverity) : ''; + $errorSeverity = $this->options->errorSeverity; + $phpcsStandardOption .= isset($errorSeverity) ? ' --error-severity=' . escapeshellarg($errorSeverity) : ''; + return $phpcsStandardOption + } + + public function getPhpcsOutputOfModifiedGitFile(string $fileName): string { + $debug = getDebug($this->options->debug); + $phpcs = getenv('PHPCS') ?: 'phpcs'; + $fileContentsCommand = $this->getModifiedFileContentsCommand($fileName); + $modifiedFilePhpcsOutputCommand = "{$fileContentsCommand} | {$phpcs} --report=json -q" . $this->getPhpcsStandardOption() . ' --stdin-path=' . escapeshellarg($fileName) .' -'; + $debug('running modified file phpcs command:', $modifiedFilePhpcsOutputCommand); + $modifiedFilePhpcsOutput = $this->executeCommand($modifiedFilePhpcsOutputCommand); + if (! $modifiedFilePhpcsOutput) { + throw new ShellException("Cannot get modified file phpcs output for file '{$fileName}'"); + } + $debug('modified file phpcs command output:', $modifiedFilePhpcsOutput); + if (false !== strpos($modifiedFilePhpcsOutput, 'You must supply at least one file or directory to process')) { + $debug('phpcs output implies modified file is empty'); + return ''; + } + return $modifiedFilePhpcsOutput; + } + + public function getPhpcsOutputOfUnmodifiedGitFile(string $fileName): string { + $debug = getDebug($this->options->debug); + $phpcs = getenv('PHPCS') ?: 'phpcs'; + $unmodifiedFileContentsCommand = $this->getUnmodifiedFileContentsCommand($fileName); + $unmodifiedFilePhpcsOutputCommand = "{$unmodifiedFileContentsCommand} | {$phpcs} --report=json -q" . $this->getPhpcsStandardOption() . ' --stdin-path=' . escapeshellarg($fileName) . ' -'; + $debug('running unmodified file phpcs command:', $unmodifiedFilePhpcsOutputCommand); + $unmodifiedFilePhpcsOutput = $this->executeCommand($unmodifiedFilePhpcsOutputCommand); + if (! $unmodifiedFilePhpcsOutput) { + throw new ShellException("Cannot get unmodified file phpcs output for file '{$fileName}'"); + } + $debug('unmodified file phpcs command output:', $unmodifiedFilePhpcsOutput); + return $unmodifiedFilePhpcsOutput; + } + public function isReadable(string $fileName): bool { return is_readable($fileName); } From 864813ae660fabd6fc57e4d6bc89e5496b2fe41b Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 13:10:50 -0400 Subject: [PATCH 02/13] Use Modes constants for assigning mode option --- PhpcsChanged/CliOptions.php | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/PhpcsChanged/CliOptions.php b/PhpcsChanged/CliOptions.php index 65cb8ea..66ade4f 100644 --- a/PhpcsChanged/CliOptions.php +++ b/PhpcsChanged/CliOptions.php @@ -113,19 +113,19 @@ public static function fromArray(array $options): self { $cliOptions->files = $options['files']; } if (isset($options['svn'])) { - $cliOptions->mode = 'svn'; + $cliOptions->mode = Modes::SVN; } if (isset($options['git'])) { - $cliOptions->mode = 'git-staged'; + $cliOptions->mode = Modes::GIT_STAGED; } if (isset($options['git-unstaged'])) { - $cliOptions->mode = 'git-unstaged'; + $cliOptions->mode = Modes::GIT_UNSTAGED; } if (isset($options['git-staged'])) { - $cliOptions->mode = 'git-staged'; + $cliOptions->mode = Modes::GIT_STAGED; } if (isset($options['git-base'])) { - $cliOptions->mode = 'git-base'; + $cliOptions->mode = Modes::GIT_BASE; $cliOptions->gitBase = $options['git-base']; } if (isset($options['report'])) { @@ -144,15 +144,15 @@ public static function fromArray(array $options): self { $cliOptions->useCache = false; } if (isset($options['diff'])) { - $cliOptions->mode = 'manual'; + $cliOptions->mode = Modes::MANUAL; $cliOptions->diffFile = $options['diff']; } if (isset($options['phpcs-unmodified'])) { - $cliOptions->mode = 'manual'; + $cliOptions->mode = Modes::MANUAL; $cliOptions->phpcsUnmodified = $options['phpcs-unmodified']; } if (isset($options['phpcs-modified'])) { - $cliOptions->mode = 'manual'; + $cliOptions->mode = Modes::MANUAL; $cliOptions->phpcsModified = $options['phpcs-modified']; } if (isset($options['s'])) { From fff3bc2fc5470994bbfaf78747518d0573dae5a6 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 13:19:58 -0400 Subject: [PATCH 03/13] Fix syntax error in UnixShell --- PhpcsChanged/UnixShell.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 41bef94..55eb82a 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -129,7 +129,7 @@ private function getPhpcsStandardOption(): string { $phpcsStandardOption .= isset($warningSeverity) ? ' --warning-severity=' . escapeshellarg($warningSeverity) : ''; $errorSeverity = $this->options->errorSeverity; $phpcsStandardOption .= isset($errorSeverity) ? ' --error-severity=' . escapeshellarg($errorSeverity) : ''; - return $phpcsStandardOption + return $phpcsStandardOption; } public function getPhpcsOutputOfModifiedGitFile(string $fileName): string { From 98ab835e575d043ae96a1b5ee654073a5e0a6728 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 13:23:56 -0400 Subject: [PATCH 04/13] Fix Modes import --- PhpcsChanged/UnixShell.php | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 55eb82a..d38c1e2 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -5,6 +5,7 @@ use PhpcsChanged\ShellOperator; use PhpcsChanged\CliOptions; +use PhpcsChanged\Modes; use function PhpcsChanged\Cli\printError; use function PhpcsChanged\Cli\getDebug; @@ -68,7 +69,7 @@ private function getModifiedFileContentsCommand(string $fileName): string { $git = getenv('GIT') ?: 'git'; $cat = getenv('CAT') ?: 'cat'; $fullPath = $this->getFullGitPathToFile($fileName); - if ($this->options->mode === Mode::GIT_BASE) { + if ($this->options->mode === Modes::GIT_BASE) { // for git-base mode, we get the contents of the file from the HEAD version of the file in the current branch return "{$git} show HEAD:" . escapeshellarg($fullPath); } @@ -82,9 +83,9 @@ private function getModifiedFileContentsCommand(string $fileName): string { private function getUnmodifiedFileContentsCommand(string $fileName): string { $git = getenv('GIT') ?: 'git'; - if ($this->options->mode === Mode::GIT_BASE) { + if ($this->options->mode === Modes::GIT_BASE) { $rev = escapeshellarg($this->options->gitBase); - } else if ($this->options->mode === Mode::GIT_UNSTAGED) { + } else if ($this->options->mode === Modes::GIT_UNSTAGED) { $rev = ':0'; // :0 in this case means "staged version or HEAD if there is no staged version" } else { // git-staged is the default From c16c89a369ec7d2f49779e6c6cb70115ac0bf5e0 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 13:24:13 -0400 Subject: [PATCH 05/13] Copy git commands to TestShell --- tests/helpers/TestShell.php | 101 ++++++++++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/tests/helpers/TestShell.php b/tests/helpers/TestShell.php index fcb8faa..de3be0e 100644 --- a/tests/helpers/TestShell.php +++ b/tests/helpers/TestShell.php @@ -104,4 +104,105 @@ public function doesFileExistInGit(string $fileName): bool { } return true; } + + public function getGitRootDirectory(): string { + $git = getenv('GIT') ?: 'git'; + $gitRootCommand = "{$git} rev-parse --show-toplevel"; + $gitRoot = $this->executeCommand($gitRootCommand); + return trim($gitRoot); + } + + private function getFullGitPathToFile(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); + $fullPath = $this->executeCommand($command); + return trim($fullPath); + } + + private function getModifiedFileContentsCommand(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + $cat = getenv('CAT') ?: 'cat'; + $fullPath = $this->getFullGitPathToFile($fileName); + if ($this->options->mode === Modes::GIT_BASE) { + // for git-base mode, we get the contents of the file from the HEAD version of the file in the current branch + return "{$git} show HEAD:" . escapeshellarg($fullPath); + } + if ($this->options->mode === Modes::GIT_UNSTAGED) { + // for git-unstaged mode, we get the contents of the file from the current working copy + return "{$cat} " . escapeshellarg($fileName); + } + // default mode is git-staged, so we get the contents from the staged version of the file + return "{$git} show :0:" . escapeshellarg($fullPath); + } + + private function getUnmodifiedFileContentsCommand(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + if ($this->options->mode === Modes::GIT_BASE) { + $rev = escapeshellarg($this->options->gitBase); + } else if ($this->options->mode === Modes::GIT_UNSTAGED) { + $rev = ':0'; // :0 in this case means "staged version or HEAD if there is no staged version" + } else { + // git-staged is the default + $rev = 'HEAD'; + } + $fullPath = $this->getFullGitPathToFile($fileName); + return "{$git} show {$rev}:" . escapeshellarg($fullPath); + } + + public function getGitHashOfModifiedFile(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + $fileContentsCommand = $this->getModifiedFileContentsCommand($fileName); + $command = "{$fileContentsCommand} | {$git} hash-object --stdin"; + $hash = $this->executeCommand($command); + if (! $hash) { + throw new ShellException("Cannot get modified file hash for file '{$fileName}'"); + } + return $hash; + } + + public function getGitHashOfUnmodifiedFile(string $fileName): string { + $git = getenv('GIT') ?: 'git'; + $fileContentsCommand = $this->getUnmodifiedFileContentsCommand($fileName); + $command = "{$fileContentsCommand} | {$git} hash-object --stdin"; + $hash = $this->executeCommand($command); + if (! $hash) { + throw new ShellException("Cannot get unmodified file hash for file '{$fileName}'"); + } + return $hash; + } + + private function getPhpcsStandardOption(): string { + $phpcsStandard = $this->options->phpcsStandard; + $phpcsStandardOption = $phpcsStandard ? ' --standard=' . escapeshellarg($phpcsStandard) : ''; + $warningSeverity = $this->options->warningSeverity; + $phpcsStandardOption .= isset($warningSeverity) ? ' --warning-severity=' . escapeshellarg($warningSeverity) : ''; + $errorSeverity = $this->options->errorSeverity; + $phpcsStandardOption .= isset($errorSeverity) ? ' --error-severity=' . escapeshellarg($errorSeverity) : ''; + return $phpcsStandardOption; + } + + public function getPhpcsOutputOfModifiedGitFile(string $fileName): string { + $phpcs = getenv('PHPCS') ?: 'phpcs'; + $fileContentsCommand = $this->getModifiedFileContentsCommand($fileName); + $modifiedFilePhpcsOutputCommand = "{$fileContentsCommand} | {$phpcs} --report=json -q" . $this->getPhpcsStandardOption() . ' --stdin-path=' . escapeshellarg($fileName) .' -'; + $modifiedFilePhpcsOutput = $this->executeCommand($modifiedFilePhpcsOutputCommand); + if (! $modifiedFilePhpcsOutput) { + throw new ShellException("Cannot get modified file phpcs output for file '{$fileName}'"); + } + if (false !== strpos($modifiedFilePhpcsOutput, 'You must supply at least one file or directory to process')) { + return ''; + } + return $modifiedFilePhpcsOutput; + } + + public function getPhpcsOutputOfUnmodifiedGitFile(string $fileName): string { + $phpcs = getenv('PHPCS') ?: 'phpcs'; + $unmodifiedFileContentsCommand = $this->getUnmodifiedFileContentsCommand($fileName); + $unmodifiedFilePhpcsOutputCommand = "{$unmodifiedFileContentsCommand} | {$phpcs} --report=json -q" . $this->getPhpcsStandardOption() . ' --stdin-path=' . escapeshellarg($fileName) . ' -'; + $unmodifiedFilePhpcsOutput = $this->executeCommand($unmodifiedFilePhpcsOutputCommand); + if (! $unmodifiedFilePhpcsOutput) { + throw new ShellException("Cannot get unmodified file phpcs output for file '{$fileName}'"); + } + return $unmodifiedFilePhpcsOutput; + } } From e09b9a85b87cbefcb40bc5f631ac327e25afcc04 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 19:27:47 -0400 Subject: [PATCH 06/13] Add doesUnmodifiedFileExistInGit to ShellOperator --- PhpcsChanged/Cli.php | 4 ++-- PhpcsChanged/GitWorkflow.php | 34 --------------------------- PhpcsChanged/ShellOperator.php | 4 +++- PhpcsChanged/UnixShell.php | 42 ++++++++++++++++++++++++++++++++-- tests/helpers/TestShell.php | 42 ++++++++++++++++++++++++++++++++-- 5 files changed, 85 insertions(+), 41 deletions(-) diff --git a/PhpcsChanged/Cli.php b/PhpcsChanged/Cli.php index 4126e5a..3b1c0f5 100644 --- a/PhpcsChanged/Cli.php +++ b/PhpcsChanged/Cli.php @@ -16,7 +16,7 @@ use PhpcsChanged\CacheManager; use function PhpcsChanged\{getNewPhpcsMessages, getNewPhpcsMessagesFromFiles, getVersion}; use function PhpcsChanged\SvnWorkflow\{getSvnUnifiedDiff, getSvnFileInfo, isNewSvnFile, getSvnUnmodifiedPhpcsOutput, getSvnModifiedPhpcsOutput, getSvnRevisionId}; -use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff, isNewGitFile, validateGitFileExists}; +use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff, validateGitFileExists}; function getDebug(bool $debugEnabled): callable { return @@ -377,7 +377,7 @@ function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperat throw new NoChangesException("Modified file '{$gitFile}' has no PHPCS messages; skipping"); } - $isNewFile = isNewGitFile($gitFile, $git, [$shell, 'executeCommand'], $options->toArray(), $debug); + $isNewFile = $shell->doesUnmodifiedFileExistInGit($gitFile); if ($isNewFile) { $debug('Skipping the linting of the unmodified file as it is a new file.'); } diff --git a/PhpcsChanged/GitWorkflow.php b/PhpcsChanged/GitWorkflow.php index 2b53970..8271f9c 100644 --- a/PhpcsChanged/GitWorkflow.php +++ b/PhpcsChanged/GitWorkflow.php @@ -69,37 +69,3 @@ function getGitUnifiedDiff(string $gitFile, string $git, callable $executeComman $debug('diff command output:', $unifiedDiff); return $unifiedDiff; } - -function isNewGitFile(string $gitFile, string $git, callable $executeCommand, array $options, callable $debug): bool { - if ( isset($options['git-base']) && ! empty($options['git-base']) ) { - return isNewGitFileRemote( $gitFile, $git, $executeCommand, $options, $debug ); - } else { - return isNewGitFileLocal( $gitFile, $git, $executeCommand, $options, $debug ); - } -} - -function isNewGitFileRemote(string $gitFile, string $git, callable $executeCommand, array $options, callable $debug): bool { - $gitStatusCommand = "{$git} cat-file -e " . escapeshellarg($options['git-base']) . ':$(' . getFullPathToFileCommand($gitFile, $git) . ') 2>/dev/null'; - $debug('checking status of file with command:', $gitStatusCommand); - /** @var int */ - $return_val = 1; - $gitStatusOutput = []; - $gitStatusOutput = $executeCommand($gitStatusCommand, $gitStatusOutput, $return_val); - $debug('status command output:', $gitStatusOutput); - $debug('status command return val:', $return_val); - return 0 !== $return_val; -} - -function isNewGitFileLocal(string $gitFile, string $git, callable $executeCommand, array $options, callable $debug): bool { - $gitStatusCommand = "{$git} status --porcelain " . 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)) { - return false; - } - 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'; -} diff --git a/PhpcsChanged/ShellOperator.php b/PhpcsChanged/ShellOperator.php index 806c8c1..56f464c 100644 --- a/PhpcsChanged/ShellOperator.php +++ b/PhpcsChanged/ShellOperator.php @@ -12,7 +12,7 @@ interface ShellOperator { public function validateExecutableExists(string $name, string $command): void; // TODO: remove executeCommand from the interface and rely on the more specific methods. - public function executeCommand(string $command, array &$output = null, int &$return_val = null): string; + public function executeCommand(string $command, int &$return_val = null): string; public function isReadable(string $fileName): bool; @@ -26,6 +26,8 @@ public function getFileNameFromPath(string $path): string; public function doesFileExistInGit(string $fileName): bool; + public function doesUnmodifiedFileExistInGit(string $fileName): bool; + public function getGitRootDirectory(): string; public function getGitHashOfModifiedFile(string $fileName): string; diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index d38c1e2..02b25be 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -29,9 +29,10 @@ public function validateExecutableExists(string $name, string $command): void { } } - public function executeCommand(string $command, array &$output = null, int &$return_val = null): string { + public function executeCommand(string $command, int &$return_val = null): string { + $output = []; exec($command, $output, $return_val); - return join(PHP_EOL, $output) . PHP_EOL; + return implode(PHP_EOL, $output) . PHP_EOL; } public function doesFileExistInGit(string $fileName): bool { @@ -47,6 +48,43 @@ public function doesFileExistInGit(string $fileName): bool { return true; } + private function doesFileExistInGitBase(string $fileName): bool { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $gitStatusCommand = "{$git} cat-file -e " . escapeshellarg($this->options->gitBase) . ':' . escapeshellarg($this->getFullGitPathToFile($fileName)) . ' 2>/dev/null'; + $debug('checking status of file with command:', $gitStatusCommand); + /** @var int */ + $return_val = 1; + $gitStatusOutput = $this->executeCommand($gitStatusCommand, $return_val); + $debug('status command output:', $gitStatusOutput); + $debug('status command return val:', $return_val); + return 0 !== $return_val; + } + + // TODO: this is very similar to doesFileExistInGit; can we combine them? + private function isFileStagedForAdding(string $fileName): bool { + $debug = getDebug($this->options->debug); + $git = getenv('GIT') ?: 'git'; + $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); + $debug('checking git status of file with command:', $gitStatusCommand); + $gitStatusOutput = $this->executeCommand($gitStatusCommand); + $debug('git status output:', $gitStatusOutput); + if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { + return false; + } + if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } + return isset($gitStatusOutput[0]) && $gitStatusOutput[0] === 'A'; + } + + public function doesUnmodifiedFileExistInGit(string $fileName): bool { + if ($this->options->mode === Modes::GIT_BASE) { + return $this->doesFileExistInGitBase($fileName); + } + return $this->isFileStagedForAdding($fileName); + } + public function getGitRootDirectory(): string { $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; diff --git a/tests/helpers/TestShell.php b/tests/helpers/TestShell.php index de3be0e..1f0cb13 100644 --- a/tests/helpers/TestShell.php +++ b/tests/helpers/TestShell.php @@ -3,7 +3,10 @@ namespace PhpcsChangedTests; +use PhpcsChanged\CliOptions; +use PhpcsChanged\Modes; use PhpcsChanged\ShellOperator; +use PhpcsChanged\ShellException; class TestShell implements ShellOperator { @@ -15,12 +18,18 @@ class TestShell implements ShellOperator { private $fileHashes = []; + private CliOptions $options; + public function __construct(array $readableFileNames) { foreach ($readableFileNames as $fileName) { $this->registerReadableFileName($fileName); } } + public function setOptions(CliOptions $options): void { + $this->options = $options; + } + public function registerReadableFileName(string $fileName, bool $override = false): bool { if (!isset($this->readableFileNames[$fileName]) || $override ) { $this->readableFileNames[$fileName] = true; @@ -66,11 +75,10 @@ public function getFileHash(string $fileName): string { return $this->fileHashes[$fileName] ?? $fileName; } - public function executeCommand(string $command, array &$output = null, int &$return_val = null): string { + public function executeCommand(string $command, int &$return_val = null): string { foreach ($this->commands as $registeredCommand => $return) { if ($registeredCommand === substr($command, 0, strlen($registeredCommand)) ) { $return_val = $return['return_val']; - $output = $return['output']; $this->commandsCalled[$registeredCommand] = $command; return $return['output']; } @@ -205,4 +213,34 @@ public function getPhpcsOutputOfUnmodifiedGitFile(string $fileName): string { } return $unmodifiedFilePhpcsOutput; } + + private function doesFileExistInGitBase(string $fileName): bool { + $git = getenv('GIT') ?: 'git'; + $gitStatusCommand = "{$git} cat-file -e " . escapeshellarg($this->options->gitBase) . ':' . escapeshellarg($this->getFullGitPathToFile($fileName)) . ' 2>/dev/null'; + /** @var int */ + $return_val = 1; + $this->executeCommand($gitStatusCommand, $return_val); + return 0 !== $return_val; + } + + // TODO: this is very similar to doesFileExistInGit; can we combine them? + private function isFileStagedForAdding(string $fileName): bool { + $git = getenv('GIT') ?: 'git'; + $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); + $gitStatusOutput = $this->executeCommand($gitStatusCommand); + if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { + return false; + } + if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } + return isset($gitStatusOutput[0]) && $gitStatusOutput[0] === 'A'; + } + + public function doesUnmodifiedFileExistInGit(string $fileName): bool { + if ($this->options->mode === Modes::GIT_BASE) { + return $this->doesFileExistInGitBase($fileName); + } + return $this->isFileStagedForAdding($fileName); + } } From 1c23c13089d53af8f4443397a45446fd3e3afe27 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 19:39:59 -0400 Subject: [PATCH 07/13] Update GitWorkflowTest to rely on separate git ls-files command --- tests/GitWorkflowTest.php | 149 +++++++++++++++++++++----------------- 1 file changed, 81 insertions(+), 68 deletions(-) diff --git a/tests/GitWorkflowTest.php b/tests/GitWorkflowTest.php index 9d9f440..50aadb1 100644 --- a/tests/GitWorkflowTest.php +++ b/tests/GitWorkflowTest.php @@ -14,7 +14,7 @@ use PhpcsChangedTests\PhpcsFixture; use PhpcsChangedTests\TestCache; use function PhpcsChanged\Cli\runGitWorkflow; -use function PhpcsChanged\GitWorkflow\{isNewGitFile, getGitUnifiedDiff}; +use function PhpcsChanged\GitWorkflow\getGitUnifiedDiff; final class GitWorkflowTest extends TestCase { public $fixture; @@ -26,28 +26,6 @@ public function setUp(): void { $this->phpcs = new PhpcsFixture(); } - public function testIsNewGitFileReturnsTrueForNewFile() { - $gitFile = 'foobar.php'; - $git = 'git'; - $executeCommand = function($command) { - if (false !== strpos($command, "git status --porcelain 'foobar.php'")) { - return $this->fixture->getNewFileInfo('foobar.php'); - } - }; - $this->assertTrue(isNewGitFile($gitFile, $git, $executeCommand, array(), '\PhpcsChangedTests\Debug')); - } - - public function testIsNewGitFileReturnsFalseForOldFile() { - $gitFile = 'foobar.php'; - $git = 'git'; - $executeCommand = function($command) { - if (false !== strpos($command, "git status --porcelain 'foobar.php'")) { - return $this->fixture->getModifiedFileInfo('foobar.php'); - } - }; - $this->assertFalse(isNewGitFile($gitFile, $git, $executeCommand, array(), '\PhpcsChangedTests\Debug')); - } - public function testGetGitUnifiedDiff() { $gitFile = 'foobar.php'; $git = 'git'; @@ -67,10 +45,12 @@ public function testFullGitWorkflowForOneFileStaged() { $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --staged --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show HEAD:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -83,10 +63,12 @@ public function testFullGitWorkflowForOneFileUnstaged() { $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php'", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-unstaged' => '1', 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -97,6 +79,7 @@ public function testFullGitWorkflowForOneChangedFileWithoutPhpcsMessagesLintsOnl $gitFile = 'foobar.php'; $shell = new TestShell([$gitFile]); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getEmptyResults()->toPhpcsJson()); $options = CliOptions::fromArray([ @@ -104,6 +87,7 @@ public function testFullGitWorkflowForOneChangedFileWithoutPhpcsMessagesLintsOnl 'git-unstaged' => '1', 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getEmptyResults(); @@ -111,7 +95,7 @@ public function testFullGitWorkflowForOneChangedFileWithoutPhpcsMessagesLintsOnl $this->assertEquals($expected->getMessages(), $messages->getMessages()); $this->assertFalse($shell->wasCommandCalled("git diff --no-prefix 'foobar.php'")); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); } public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCache() { @@ -120,9 +104,10 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCache() { $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -131,6 +116,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCache() { 'cache' => false, // getopt is weird and sets options to false 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); @@ -139,7 +125,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCache() { $shell->resetCommandsCalled(); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertFalse($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); } @@ -149,9 +135,10 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -163,6 +150,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith 'error-severity' => '2', 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); @@ -172,7 +160,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertFalse($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); foreach( $cache->getEntries() as $entry ) { @@ -186,9 +174,10 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs --report=json -q --standard='standard' --warning-severity='0' --error-severity='0'", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs --report=json -q --standard='standard' --warning-severity='0' --error-severity='0'", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -200,6 +189,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith 'error-severity' => '0', 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); @@ -209,7 +199,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertFalse($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); $cacheEntries = $cache->getEntries(); @@ -225,9 +215,10 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -237,6 +228,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith 'standard' => 'standard', 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); @@ -246,7 +238,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenUsesCacheWith $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertFalse($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); $cacheEntries = $cache->getEntries(); @@ -260,11 +252,12 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenClearsOldCach $gitFile = 'foobar.php'; $shell = new TestShell([$gitFile]); $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -273,17 +266,18 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenClearsOldCach 'cache' => false, // getopt is weird and sets options to false 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); - $shell->deregisterCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin"); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'old-file-hash-2'); + $shell->deregisterCommand("git show :0:'files/foobar.php' | git hash-object --stdin"); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'old-file-hash-2'); $shell->resetCommandsCalled(); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertTrue($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertTrue($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertFalse($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); } @@ -293,9 +287,10 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenClearsNewCach $fixture = $this->fixture->getAddedLineDiff('foobar.php', 'use Foobar;'); $shell->registerCommand("git diff --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("cat 'foobar.php' | phpcs", $this->phpcs->getResults('STDIN', [21, 20], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php') | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show :0:'files/foobar.php' | git hash-object --stdin", 'previous-file-hash'); $shell->registerCommand("cat 'foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray([ @@ -304,6 +299,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenClearsNewCach 'cache' => false, // getopt is weird and sets options to false 'files' => [$gitFile], ]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache(), '\PhpcsChangedTests\Debug' ); $expected = $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'); @@ -314,7 +310,7 @@ public function testFullGitWorkflowForOneFileUnstagedCachesDataThenClearsNewCach $shell->resetCommandsCalled(); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); $this->assertEquals($expected->getMessages(), $messages->getMessages()); - $this->assertFalse($shell->wasCommandCalled("git show :0:$(git ls-files --full-name 'foobar.php') | phpcs")); + $this->assertFalse($shell->wasCommandCalled("git show :0:'files/foobar.php' | phpcs")); $this->assertTrue($shell->wasCommandCalled("cat 'foobar.php' | phpcs")); } @@ -327,12 +323,15 @@ public function testFullGitWorkflowForMultipleFilesStaged() { $shell->registerCommand("git diff --staged --no-prefix 'baz.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getModifiedFileInfo('foobar.php')); $shell->registerCommand("git status --porcelain 'baz.php'", $this->fixture->getModifiedFileInfo('baz.php')); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'baz.php')", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'baz.php')", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Baz.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git ls-files --full-name 'baz.php'", "files/baz.php"); + $shell->registerCommand("git show HEAD:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); + $shell->registerCommand("git show HEAD:'files/baz.php'", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/baz.php'", $this->phpcs->getResults('STDIN', [20, 21], 'Found unused symbol Baz.')->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => $gitFiles]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = PhpcsMessages::merge([ $this->phpcs->getResults('bin/foobar.php', [20], 'Found unused symbol Foobar.'), @@ -348,10 +347,12 @@ public function testFullGitWorkflowForUnchangedFileWithPhpcsMessages() { $fixture = $this->fixture->getEmptyFileDiff(); $shell->registerCommand("git diff --staged --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", ''); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show HEAD:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20])->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = PhpcsMessages::fromArrays([], '/dev/null'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -362,10 +363,12 @@ public function testFullGitWorkflowForUnchangedFileWithoutPhpcsMessages() { $gitFile = 'foobar.php'; $shell = new TestShell([$gitFile]); $shell->registerCommand("git status --porcelain 'foobar.php'", ''); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [])->toPhpcsJson()); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [])->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show HEAD:'files/foobar.php'", $this->phpcs->getResults('STDIN', [])->toPhpcsJson()); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [])->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = PhpcsMessages::fromArrays([], '/dev/null'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -379,10 +382,12 @@ public function testFullGitWorkflowForNonGitFile() { $fixture = $this->fixture->getEmptyFileDiff(); $shell->registerCommand("git diff --staged --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", "?? foobar.php" ); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'foobar.php')", $this->fixture->getNonGitFileShow('foobar.php'), 128); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show HEAD:'files/foobar.php'", $this->fixture->getNonGitFileShow('foobar.php'), 128); + $shell->registerCommand("git show :0:'files/foobar.php'", $this->phpcs->getResults('STDIN', [20], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); } @@ -393,9 +398,11 @@ public function testFullGitWorkflowForNewFile() { $fixture = $this->fixture->getNewFileDiff('foobar.php'); $shell->registerCommand("git diff --staged --no-prefix 'foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'foobar.php'", $this->fixture->getNewFileInfo('foobar.php')); - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $this->phpcs->getResults('STDIN', [5, 6], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php", $this->phpcs->getResults('STDIN', [5, 6], 'Found unused symbol Foobar.')->toPhpcsJson()); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = $this->phpcs->getResults('foobar.php', [5, 6], 'Found unused symbol Foobar.'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -413,9 +420,11 @@ public function testFullGitWorkflowForEmptyNewFile() { Run "phpcs --help" for usage information '; - $shell->registerCommand("git show :0:$(git ls-files --full-name 'foobar.php')", $fixture, 1); + $shell->registerCommand("git ls-files --full-name 'foobar.php'", "files/foobar.php"); + $shell->registerCommand("git show :0:'files/foobar.php", $fixture, 1); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-staged' => 1, 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = PhpcsMessages::fromArrays([], '/dev/null'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -426,16 +435,18 @@ function testFullGitWorkflowForInterBranchDiff() { $gitFile = 'bin/foobar.php'; $shell = new TestShell([$gitFile]); $fixture = $this->fixture->getAltAddedLineDiff('foobar.php', 'use Foobar;'); + $shell->registerCommand("git ls-files --full-name 'bin/foobar.php'", "files/bin/foobar.php"); $shell->registerCommand("git merge-base 'master' HEAD", "0123456789abcdef0123456789abcdef01234567\n"); $shell->registerCommand("git diff '0123456789abcdef0123456789abcdef01234567'... --no-prefix 'bin/foobar.php'", $fixture); $shell->registerCommand("git status --porcelain 'bin/foobar.php'", ''); - $shell->registerCommand("git cat-file -e '0123456789abcdef0123456789abcdef01234567':$(git ls-files --full-name 'bin/foobar.php')", ''); - $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':$(git ls-files --full-name 'bin/foobar.php') | phpcs --report=json -q --stdin-path='bin/foobar.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/bin\/foobar.php', [6], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':$(git ls-files --full-name 'bin/foobar.php') | git hash-object --stdin", 'previous-file-hash'); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'bin/foobar.php') | phpcs --report=json -q --stdin-path='bin/foobar.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/bin\/foobar.php', [6, 7], 'Found unused symbol Foobar.')->toPhpcsJson()); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'bin/foobar.php') | git hash-object --stdin", 'new-file-hash'); + $shell->registerCommand("git cat-file -e '0123456789abcdef0123456789abcdef01234567':'files/bin/foobar.php'", ''); + $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':'files/bin/foobar.php' | phpcs --report=json -q --stdin-path='bin/foobar.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/bin\/foobar.php', [6], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':'files/bin/foobar.php' | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show HEAD:'files/bin/foobar.php' | phpcs --report=json -q --stdin-path='bin/foobar.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/bin\/foobar.php', [6, 7], 'Found unused symbol Foobar.')->toPhpcsJson()); + $shell->registerCommand("git show HEAD:'files/bin/foobar.php' | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-base' => 'master', 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = $this->phpcs->getResults('bin/foobar.php', [6], 'Found unused symbol Foobar.'); $messages = runGitWorkflow($options, $shell, $cache, '\PhpcsChangedTests\Debug'); @@ -448,14 +459,16 @@ function testNameDetectionInFullGitWorkflowForInterBranchDiff() { $shell->registerCommand("git status --porcelain 'test.php'", $this->fixture->getModifiedFileInfo('test.php')); $fixture = $this->fixture->getAltNewFileDiff('test.php'); + $shell->registerCommand("git ls-files --full-name 'test.php'", "files/test.php"); $shell->registerCommand("git merge-base 'master' HEAD", "0123456789abcdef0123456789abcdef01234567\n"); $shell->registerCommand("git diff '0123456789abcdef0123456789abcdef01234567'... --no-prefix 'test.php'", $fixture); - $shell->registerCommand("git cat-file -e '0123456789abcdef0123456789abcdef01234567':$(git ls-files --full-name 'test.php')", '', 128); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'test.php') | phpcs --report=json -q --stdin-path='test.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/test.php', [6, 7, 8], "Found unused symbol 'Foobar'.")->toPhpcsJson()); - $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':$(git ls-files --full-name 'test.php') | git hash-object --stdin", 'previous-file-hash'); - $shell->registerCommand("git show HEAD:$(git ls-files --full-name 'test.php') | git hash-object --stdin", 'new-file-hash'); + $shell->registerCommand("git cat-file -e '0123456789abcdef0123456789abcdef01234567':'files/test.php'", '', 128); + $shell->registerCommand("git show HEAD:'files/test.php' | phpcs --report=json -q --stdin-path='test.php' -", $this->phpcs->getResults('\/srv\/www\/wordpress-default\/public_html\/test\/test.php', [6, 7, 8], "Found unused symbol 'Foobar'.")->toPhpcsJson()); + $shell->registerCommand("git show '0123456789abcdef0123456789abcdef01234567':'files/test.php' | git hash-object --stdin", 'previous-file-hash'); + $shell->registerCommand("git show HEAD:'files/test.php | git hash-object --stdin", 'new-file-hash'); $shell->registerCommand("git rev-parse --show-toplevel", 'run-from-git-root'); $options = CliOptions::fromArray(['no-cache-git-root' => 1, 'git-base' => 'master', 'files' => [$gitFile]]); + $shell->setOptions($options); $cache = new CacheManager( new TestCache() ); $expected = PhpcsMessages::merge([ $this->phpcs->getResults('test.php', [6], "Found unused symbol 'Foobar'."), From 0779e575dba68d9760ed4a59d7930839115212b8 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 19:42:27 -0400 Subject: [PATCH 08/13] Remove property type in TestShell --- tests/helpers/TestShell.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/helpers/TestShell.php b/tests/helpers/TestShell.php index 1f0cb13..b5f0192 100644 --- a/tests/helpers/TestShell.php +++ b/tests/helpers/TestShell.php @@ -18,7 +18,7 @@ class TestShell implements ShellOperator { private $fileHashes = []; - private CliOptions $options; + private $options; public function __construct(array $readableFileNames) { foreach ($readableFileNames as $fileName) { From 9ad6443aa1a65e04b9f41b3617728c9f0f23cd94 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 19:45:33 -0400 Subject: [PATCH 09/13] Remove isRunFromGitRoot/getGitRootDirectory --- PhpcsChanged/GitWorkflow.php | 19 ------------------- PhpcsChanged/ShellOperator.php | 2 -- PhpcsChanged/UnixShell.php | 9 --------- tests/helpers/TestShell.php | 7 ------- 4 files changed, 37 deletions(-) diff --git a/PhpcsChanged/GitWorkflow.php b/PhpcsChanged/GitWorkflow.php index 8271f9c..251bcc5 100644 --- a/PhpcsChanged/GitWorkflow.php +++ b/PhpcsChanged/GitWorkflow.php @@ -23,25 +23,6 @@ function validateGitFileExists(string $gitFile, ShellOperator $shell, CliOptions } } -function isRunFromGitRoot(ShellOperator $shell, CliOptions $options): bool { - // This should never change while the script runs so we cache it. - static $isRunFromGitRoot; - - if (isset($options['no-cache-git-root'])) { - $isRunFromGitRoot = null; - } - if (null !== $isRunFromGitRoot) { - return $isRunFromGitRoot; - } - - $debug = getDebug($options->debug); - $gitRoot = $shell->getGitRootDirectory(); - $isRunFromGitRoot = (getcwd() === $gitRoot); - - $debug('is run from git root: ' . var_export($isRunFromGitRoot, true)); - return $isRunFromGitRoot; -} - function getGitMergeBase(string $git, callable $executeCommand, array $options, callable $debug): string { if ( empty($options['git-base']) ) { return ''; diff --git a/PhpcsChanged/ShellOperator.php b/PhpcsChanged/ShellOperator.php index 56f464c..f7fe468 100644 --- a/PhpcsChanged/ShellOperator.php +++ b/PhpcsChanged/ShellOperator.php @@ -28,8 +28,6 @@ public function doesFileExistInGit(string $fileName): bool; public function doesUnmodifiedFileExistInGit(string $fileName): bool; - public function getGitRootDirectory(): string; - public function getGitHashOfModifiedFile(string $fileName): string; public function getGitHashOfUnmodifiedFile(string $fileName): string; diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 02b25be..72c3355 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -85,15 +85,6 @@ public function doesUnmodifiedFileExistInGit(string $fileName): bool { return $this->isFileStagedForAdding($fileName); } - public function getGitRootDirectory(): string { - $debug = getDebug($this->options->debug); - $git = getenv('GIT') ?: 'git'; - $gitRootCommand = "{$git} rev-parse --show-toplevel"; - $debug('getting git root directory with command:', $gitRootCommand); - $gitRoot = $this->executeCommand($gitRootCommand); - return trim($gitRoot); - } - private function getFullGitPathToFile(string $fileName): string { $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; diff --git a/tests/helpers/TestShell.php b/tests/helpers/TestShell.php index b5f0192..beeddab 100644 --- a/tests/helpers/TestShell.php +++ b/tests/helpers/TestShell.php @@ -113,13 +113,6 @@ public function doesFileExistInGit(string $fileName): bool { return true; } - public function getGitRootDirectory(): string { - $git = getenv('GIT') ?: 'git'; - $gitRootCommand = "{$git} rev-parse --show-toplevel"; - $gitRoot = $this->executeCommand($gitRootCommand); - return trim($gitRoot); - } - private function getFullGitPathToFile(string $fileName): string { $git = getenv('GIT') ?: 'git'; $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); From 2a66546f6ff0c4b6378804606e2a2c2c91639f65 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 20:03:40 -0400 Subject: [PATCH 10/13] Remove validateGitFileExists --- PhpcsChanged/Cli.php | 4 +--- PhpcsChanged/GitWorkflow.php | 14 ------------ PhpcsChanged/ShellOperator.php | 2 -- PhpcsChanged/UnixShell.php | 42 ++++++++++++++++++++-------------- tests/helpers/TestShell.php | 14 ------------ 5 files changed, 26 insertions(+), 50 deletions(-) diff --git a/PhpcsChanged/Cli.php b/PhpcsChanged/Cli.php index 3b1c0f5..c6f5fc2 100644 --- a/PhpcsChanged/Cli.php +++ b/PhpcsChanged/Cli.php @@ -16,7 +16,7 @@ use PhpcsChanged\CacheManager; use function PhpcsChanged\{getNewPhpcsMessages, getNewPhpcsMessagesFromFiles, getVersion}; use function PhpcsChanged\SvnWorkflow\{getSvnUnifiedDiff, getSvnFileInfo, isNewSvnFile, getSvnUnmodifiedPhpcsOutput, getSvnModifiedPhpcsOutput, getSvnRevisionId}; -use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff, validateGitFileExists}; +use function PhpcsChanged\GitWorkflow\{getGitMergeBase, getGitUnifiedDiff}; function getDebug(bool $debugEnabled): callable { return @@ -352,8 +352,6 @@ function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperat $fileName = $shell->getFileNameFromPath($gitFile); try { - validateGitFileExists($gitFile, $shell, $options); - $modifiedFilePhpcsOutput = null; $modifiedFileHash = ''; if (isCachingEnabled($options->toArray())) { diff --git a/PhpcsChanged/GitWorkflow.php b/PhpcsChanged/GitWorkflow.php index 251bcc5..b19b440 100644 --- a/PhpcsChanged/GitWorkflow.php +++ b/PhpcsChanged/GitWorkflow.php @@ -9,20 +9,6 @@ use PhpcsChanged\ShellOperator; use function PhpcsChanged\Cli\getDebug; -function validateGitFileExists(string $gitFile, ShellOperator $shell, CliOptions $options): void { - $debug = getDebug($options->debug); - if (isset($options->noVerifyGitFile)) { - $debug('skipping Git file exists check.'); - return; - } - if (! $shell->isReadable($gitFile)) { - throw new ShellException("Cannot read file '{$gitFile}'"); - } - if (! $shell->doesFileExistInGit($gitFile)) { - throw new ShellException("File does not appear to be tracked by git: '{$gitFile}'"); - } -} - function getGitMergeBase(string $git, callable $executeCommand, array $options, callable $debug): string { if ( empty($options['git-base']) ) { return ''; diff --git a/PhpcsChanged/ShellOperator.php b/PhpcsChanged/ShellOperator.php index f7fe468..09275ea 100644 --- a/PhpcsChanged/ShellOperator.php +++ b/PhpcsChanged/ShellOperator.php @@ -24,8 +24,6 @@ public function printError(string $message): void; public function getFileNameFromPath(string $path): string; - public function doesFileExistInGit(string $fileName): bool; - public function doesUnmodifiedFileExistInGit(string $fileName): bool; public function getGitHashOfModifiedFile(string $fileName): string; diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 72c3355..5ae86a0 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -18,6 +18,11 @@ class UnixShell implements ShellOperator { */ private $options; + /** + * @var Array + */ + private $fullPaths = []; + public function __construct(CliOptions $options) { $this->options = $options; } @@ -35,19 +40,6 @@ public function executeCommand(string $command, int &$return_val = null): string return implode(PHP_EOL, $output) . PHP_EOL; } - public function doesFileExistInGit(string $fileName): bool { - $debug = getDebug($this->options->debug); - $git = getenv('GIT') ?: 'git'; - $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); - $debug('checking git existence of file with command:', $gitStatusCommand); - $gitStatusOutput = $this->executeCommand($gitStatusCommand); - $debug('git status output:', $gitStatusOutput); - if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { - return false; - } - return true; - } - private function doesFileExistInGitBase(string $fileName): bool { $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; @@ -61,14 +53,18 @@ private function doesFileExistInGitBase(string $fileName): bool { return 0 !== $return_val; } - // TODO: this is very similar to doesFileExistInGit; can we combine them? - private function isFileStagedForAdding(string $fileName): bool { + private function getGitStatusForFile(string $fileName): string { $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); $debug('checking git status of file with command:', $gitStatusCommand); $gitStatusOutput = $this->executeCommand($gitStatusCommand); $debug('git status output:', $gitStatusOutput); + return $gitStatusOutput; + } + + private function isFileStagedForAdding(string $fileName): bool { + $gitStatusOutput = $this->getGitStatusForFile($fileName); if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { return false; } @@ -86,12 +82,24 @@ public function doesUnmodifiedFileExistInGit(string $fileName): bool { } private function getFullGitPathToFile(string $fileName): string { + if ($this->fullPaths[$fileName]) { + return $this->fullPaths[$fileName]; + } $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; + $gitStatusOutput = $this->getGitStatusForFile($fileName); + if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } + if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); $debug('getting full path to file with command:', $command); - $fullPath = $this->executeCommand($command); - return trim($fullPath); + $fullPath = trim($this->executeCommand($command)); + // This will not change so we can cache it. + $this->fullPaths[$fileName] = $fullPath; + return $fullPath; } private function getModifiedFileContentsCommand(string $fileName): string { diff --git a/tests/helpers/TestShell.php b/tests/helpers/TestShell.php index beeddab..7db0204 100644 --- a/tests/helpers/TestShell.php +++ b/tests/helpers/TestShell.php @@ -100,19 +100,6 @@ public function getFileNameFromPath(string $path): string { return end($parts); } - public function doesFileExistInGit(string $fileName): bool { - if (! $this->isReadable($fileName)) { - return false; - } - $git = getenv('GIT') ?: 'git'; - $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); - $gitStatusOutput = $this->executeCommand($gitStatusCommand); - if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { - return false; - } - return true; - } - private function getFullGitPathToFile(string $fileName): string { $git = getenv('GIT') ?: 'git'; $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); @@ -216,7 +203,6 @@ private function doesFileExistInGitBase(string $fileName): bool { return 0 !== $return_val; } - // TODO: this is very similar to doesFileExistInGit; can we combine them? private function isFileStagedForAdding(string $fileName): bool { $git = getenv('GIT') ?: 'git'; $gitStatusCommand = "{$git} status --porcelain " . escapeshellarg($fileName); From 0447054078e80aa89e04d2cc389ba25a89c3f034 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 20:05:41 -0400 Subject: [PATCH 11/13] Restore functionality of noVerifyGitFile --- PhpcsChanged/UnixShell.php | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index 5ae86a0..c3d9fc7 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -87,12 +87,14 @@ private function getFullGitPathToFile(string $fileName): string { } $debug = getDebug($this->options->debug); $git = getenv('GIT') ?: 'git'; - $gitStatusOutput = $this->getGitStatusForFile($fileName); - if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { - throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); - } - if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { - throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + if (! $this->options->noVerifyGitFile) { + $gitStatusOutput = $this->getGitStatusForFile($fileName); + if (! $gitStatusOutput || false === strpos($gitStatusOutput, $fileName)) { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } + if (isset($gitStatusOutput[0]) && $gitStatusOutput[0] === '?') { + throw new ShellException("File does not appear to be tracked by git: '{$fileName}'"); + } } $command = "{$git} ls-files --full-name " . escapeshellarg($fileName); $debug('getting full path to file with command:', $command); From 4958a6fa1be710093414531348357bcfa64c81f8 Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 20:07:29 -0400 Subject: [PATCH 12/13] Restore isReadable check in git flow --- PhpcsChanged/Cli.php | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/PhpcsChanged/Cli.php b/PhpcsChanged/Cli.php index c6f5fc2..e7043d7 100644 --- a/PhpcsChanged/Cli.php +++ b/PhpcsChanged/Cli.php @@ -352,6 +352,10 @@ function runGitWorkflowForFile(string $gitFile, CliOptions $options, ShellOperat $fileName = $shell->getFileNameFromPath($gitFile); try { + if (! $shell->isReadable($gitFile)) { + throw new ShellException("Cannot read file '{$gitFile}'"); + } + $modifiedFilePhpcsOutput = null; $modifiedFileHash = ''; if (isCachingEnabled($options->toArray())) { From 0f69ed34d53cbd88f6c314e28e5c71c0c97289fc Mon Sep 17 00:00:00 2001 From: Payton Swick Date: Sat, 6 May 2023 20:14:19 -0400 Subject: [PATCH 13/13] Use array_key_exists to test for cached full path --- PhpcsChanged/UnixShell.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/PhpcsChanged/UnixShell.php b/PhpcsChanged/UnixShell.php index c3d9fc7..80ecbe9 100644 --- a/PhpcsChanged/UnixShell.php +++ b/PhpcsChanged/UnixShell.php @@ -82,7 +82,7 @@ public function doesUnmodifiedFileExistInGit(string $fileName): bool { } private function getFullGitPathToFile(string $fileName): string { - if ($this->fullPaths[$fileName]) { + if (array_key_exists($fileName, $this->fullPaths)) { return $this->fullPaths[$fileName]; } $debug = getDebug($this->options->debug);