Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move hash, phpcs, and root git commands to ShellOperator #75

Merged
merged 13 commits into from
May 7, 2023
Merged
22 changes: 9 additions & 13 deletions PhpcsChanged/Cli.php
Original file line number Diff line number Diff line change
Expand Up @@ -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};

function getDebug(bool $debugEnabled): callable {
return
Expand Down Expand Up @@ -345,30 +345,26 @@ 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 {
validateGitFileExists($gitFile, $shell, $options);
if (! $shell->isReadable($gitFile)) {
throw new ShellException("Cannot read file '{$gitFile}'");
}

$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);
}
Expand All @@ -383,7 +379,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.');
}
Expand All @@ -393,12 +389,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);
}
Expand Down
16 changes: 8 additions & 8 deletions PhpcsChanged/CliOptions.php
Original file line number Diff line number Diff line change
Expand Up @@ -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'])) {
Expand All @@ -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'])) {
Expand Down
162 changes: 0 additions & 162 deletions PhpcsChanged/GitWorkflow.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +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 isRunFromGitRoot(string $git, callable $executeCommand, array $options, callable $debug): bool {
static $isRunFromGitRoot;
if (isset($options['no-cache-git-root'])) {
$isRunFromGitRoot = null;
}
if (null !== $isRunFromGitRoot) {
return $isRunFromGitRoot;
}

$gitRootCommand = "{$git} rev-parse --show-toplevel";
$gitRoot = $executeCommand($gitRootCommand);
$gitRoot = trim($gitRoot);
$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 '';
Expand Down Expand Up @@ -69,132 +36,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';
}

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;
}
15 changes: 12 additions & 3 deletions PhpcsChanged/ShellOperator.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,8 @@
interface ShellOperator {
public function validateExecutableExists(string $name, string $command): void;

public function executeCommand(string $command, array &$output = null, int &$return_val = null): string;

public function doesFileExistInGit(string $fileName): bool;
// TODO: remove executeCommand from the interface and rely on the more specific methods.
public function executeCommand(string $command, int &$return_val = null): string;

public function isReadable(string $fileName): bool;

Expand All @@ -24,4 +23,14 @@ public function exitWithCode(int $code): void;
public function printError(string $message): void;

public function getFileNameFromPath(string $path): string;

public function doesUnmodifiedFileExistInGit(string $fileName): bool;

public function getGitHashOfModifiedFile(string $fileName): string;

public function getGitHashOfUnmodifiedFile(string $fileName): string;

public function getPhpcsOutputOfModifiedGitFile(string $fileName): string;

public function getPhpcsOutputOfUnmodifiedGitFile(string $fileName): string;
}
Loading