Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions src/Configuration/ConfigurationFactory.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

use Rector\ChangesReporting\Output\ConsoleOutputFormatter;
use Rector\Configuration\Parameter\SimpleParameterProvider;
use Rector\FileSystem\GitDirtyFileFetcher;
use Rector\ValueObject\Configuration;
use RectorPrefix202511\Symfony\Component\Console\Input\InputInterface;
use RectorPrefix202511\Symfony\Component\Console\Style\SymfonyStyle;
Expand Down Expand Up @@ -97,6 +98,12 @@ private function resolvePaths(InputInterface $input): array
$this->setFilesWithoutExtensionParameter($commandLinePaths);
return $commandLinePaths;
}

$optionDirty = (bool) $input->getOption(\Rector\Configuration\Option::DIRTY);
if ($optionDirty) {
return (new GitDirtyFileFetcher())->fetchDirtyFiles();
}

// fallback to parameter
$configPaths = SimpleParameterProvider::provideArrayParameter(\Rector\Configuration\Option::PATHS);
$this->setFilesWithoutExtensionParameter($configPaths);
Expand Down
4 changes: 4 additions & 0 deletions src/Configuration/Option.php
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,10 @@ final class Option
* @var string
*/
public const NO_DIFFS = 'no-diffs';
/**
* @var string
*/
public const DIRTY = 'dirty';
/**
* @var string
*/
Expand Down
1 change: 1 addition & 0 deletions src/Console/ProcessConfigureDecorator.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,6 @@ public static function decorate(Command $command): void
$command->addOption(Option::PARALLEL_PORT, null, InputOption::VALUE_REQUIRED);
$command->addOption(Option::PARALLEL_IDENTIFIER, null, InputOption::VALUE_REQUIRED);
$command->addOption(Option::XDEBUG, null, InputOption::VALUE_NONE, 'Display xdebug output.');
$command->addOption(Option::DIRTY, null, InputOption::VALUE_NONE, 'Only process files with uncommitted changes according to Git (git status --porcelain)');
}
}
96 changes: 96 additions & 0 deletions src/FileSystem/GitDirtyFileFetcher.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?php

declare(strict_types=1);

namespace Rector\FileSystem;

use function array_filter;
use function explode;
use function is_file;
use function strlen;
use function substr;
use function trim;

final class GitDirtyFileFetcher
{
/**
* A callable that accepts a command string and returns an array of output lines,
* or a string. By default null -> uses exec().
*
* @var callable|null
*/
private $commandRunner;

/**
* @param callable|null $commandRunner signature: fn(string $cmd): array|string
*/
public function __construct(?callable $commandRunner = null)
{
$this->commandRunner = $commandRunner;
}

/**
* Return relative file paths reported by `git status --porcelain`.
*
* - includes modified (M), added (A), untracked (??) files
* - strips the two-character status prefix and whitespace
*
* @return string[] relative paths
*/
public function fetchDirtyFiles(): array
{
$lines = $this->runCommand('git status --porcelain');

$files = [];

foreach ($lines as $line) {
$line = (string)$line;
if (trim($line) === '') {
continue;
}

// porcelain format: two-char status space path (path can include spaces)
// e.g. " M src/Service/Foo.php", "?? newfile.php"
if (strlen($line) <= 3) {
continue;
}

$path = trim(substr($line, 3));

if ($path === '') {
continue;
}

// prefer files on disk to avoid passing directories
if (is_file($path)) {
$files[] = $path;
}
}

// remove duplicates and empty entries
$files = array_filter($files);

return array_values($files);
}

/**
* @return string[] lines
*/
private function runCommand(string $command): array
{
if ($this->commandRunner !== null) {
$result = ($this->commandRunner)($command);
if (is_array($result)) {
return $result;
}
if (is_string($result)) {
return explode("\n", $result);
}
return [];
}

$output = [];
@exec($command, $output);
return $output;
}
}