From 4f44bcfdc1a5a0b0cc69f9b8259dfc8de76bd778 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Fri, 3 Jan 2025 23:25:20 -0800 Subject: [PATCH 01/11] add [diff] option to default command --- app/Commands/DefaultCommand.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Commands/DefaultCommand.php b/app/Commands/DefaultCommand.php index 4d487f12..e4736001 100644 --- a/app/Commands/DefaultCommand.php +++ b/app/Commands/DefaultCommand.php @@ -41,6 +41,7 @@ protected function configure() new InputOption('test', '', InputOption::VALUE_NONE, 'Test for code style errors without fixing them'), new InputOption('bail', '', InputOption::VALUE_NONE, 'Test for code style errors without fixing them and stop on first error'), new InputOption('repair', '', InputOption::VALUE_NONE, 'Fix code style errors but exit with status 1 if there were any changes made'), + new InputOption('diff', '', InputOption::VALUE_REQUIRED, 'Only fix files that have changed since branching off from the given branch', null, ['main', 'master', 'origin/main', 'origin/master']), new InputOption('dirty', '', InputOption::VALUE_NONE, 'Only fix files that have uncommitted changes'), new InputOption('format', '', InputOption::VALUE_REQUIRED, 'The output format that should be used'), new InputOption('cache-file', '', InputArgument::OPTIONAL, 'The path to the cache file'), From 86adda746fdab85ccfd9e7bad802a65a755a21fd Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Fri, 3 Jan 2025 23:28:30 -0800 Subject: [PATCH 02/11] add path resolver for git diff --- app/Project.php | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/app/Project.php b/app/Project.php index 50461c75..f5a5da5c 100644 --- a/app/Project.php +++ b/app/Project.php @@ -18,6 +18,10 @@ public static function paths($input) return static::resolveDirtyPaths(); } + if ($diff = $input->getOption('diff')) { + return static::resolveDiffPaths($diff); + } + return $input->getArgument('path'); } @@ -46,4 +50,21 @@ public static function resolveDirtyPaths() return $files; } + + /** + * Resolves the paths that have changed since branching off from the given branch, if any. + * + * @param string $branch + * @return array + */ + public static function resolveDiffPaths($branch) + { + $files = app(PathsRepository::class)->diff($branch); + + if (empty($files)) { + abort(0, "No files have changed since branching off of {$branch}."); + } + + return $files; + } } From e295403a66c145e9b5530d3e5f9614875fc90162 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Fri, 3 Jan 2025 23:29:37 -0800 Subject: [PATCH 03/11] add [diff] method to PathsRepository contract. (Breaking?) --- app/Contracts/PathsRepository.php | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/app/Contracts/PathsRepository.php b/app/Contracts/PathsRepository.php index b1548694..4f89c481 100644 --- a/app/Contracts/PathsRepository.php +++ b/app/Contracts/PathsRepository.php @@ -10,4 +10,12 @@ interface PathsRepository * @return array */ public function dirty(); + + /** + * Determine the files that have changed since branching off from the given branch. + * + * @param string $branch + * @return array + */ + public function diff($branch); } From bf788ebc59e09dbe82ec817aa5f53e1c7efac65f Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:28:27 -0800 Subject: [PATCH 04/11] extract method for future re-use --- app/Repositories/GitPathsRepository.php | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index 37bdc058..67fc6c0d 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -4,6 +4,7 @@ use App\Contracts\PathsRepository; use App\Factories\ConfigurationFactory; +use Illuminate\Support\Collection; use Illuminate\Support\Str; use Symfony\Component\Process\Process; @@ -40,7 +41,20 @@ public function dirty() $dirtyFiles = collect(preg_split('/\R+/', $process->getOutput(), flags: PREG_SPLIT_NO_EMPTY)) ->mapWithKeys(fn ($file) => [substr($file, 3) => trim(substr($file, 0, 3))]) ->reject(fn ($status) => $status === 'D') - ->map(fn ($status, $file) => $status === 'R' ? Str::after($file, ' -> ') : $file) + ->map(fn ($status, $file) => $status === 'R' ? Str::after($file, ' -> ') : $file); + + return $this->processFileNames($dirtyFiles); + } + + /** + * Process the files. + * + * @param \Illuminate\Support\Collection $files + * @return array + */ + protected function processFileNames(Collection $fileNames) + { + return $fileNames ->map(function ($file) { if (PHP_OS_FAMILY === 'Windows') { $file = str_replace('/', DIRECTORY_SEPARATOR, $file); From d189b1bb87d3c4d138dc661ca35bb1ddfadd7c3b Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:28:56 -0800 Subject: [PATCH 05/11] implement new [diff] method --- app/Repositories/GitPathsRepository.php | 26 +++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index 67fc6c0d..d7d68706 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -46,6 +46,32 @@ public function dirty() return $this->processFileNames($dirtyFiles); } + /** + * {@inheritDoc} + */ + public function diff($branch) + { + $files = [ + 'committed' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', "{$branch}...HEAD", '--', '**.php']))->run(), + 'staged' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', '--cached', '--', '**.php']))->run(), + 'unstaged' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', '--', '**.php']))->run(), + ]; + + $files = collect($files) + ->each(fn ($process) => abort_if( + boolean: ! $process->isSuccessful(), + code: 1, + message: 'The [--diff] option is only available when using Git.', + )) + ->map(fn ($process) => $process->getOutput()) + ->map(fn ($output) => explode(PHP_EOL, $output)) + ->flatten() + ->filter() + ->values(); + + return $this->processFileNames($files); + } + /** * Process the files. * From ec6bedb7a847b688fdb40efb5b0a4254e679e3a4 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:29:50 -0800 Subject: [PATCH 06/11] lint --- app/Contracts/PathsRepository.php | 2 +- app/Repositories/GitPathsRepository.php | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/Contracts/PathsRepository.php b/app/Contracts/PathsRepository.php index 4f89c481..3a291585 100644 --- a/app/Contracts/PathsRepository.php +++ b/app/Contracts/PathsRepository.php @@ -13,7 +13,7 @@ public function dirty(); /** * Determine the files that have changed since branching off from the given branch. - * + * * @param string $branch * @return array */ diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index d7d68706..a4d6df15 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -42,7 +42,7 @@ public function dirty() ->mapWithKeys(fn ($file) => [substr($file, 3) => trim(substr($file, 0, 3))]) ->reject(fn ($status) => $status === 'D') ->map(fn ($status, $file) => $status === 'R' ? Str::after($file, ' -> ') : $file); - + return $this->processFileNames($dirtyFiles); } @@ -68,7 +68,7 @@ public function diff($branch) ->flatten() ->filter() ->values(); - + return $this->processFileNames($files); } From fb19a6fe4b4cff947b121de7dacde6d8b56e2624 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:44:34 -0800 Subject: [PATCH 07/11] skip duplicates --- app/Repositories/GitPathsRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index a4d6df15..d5a36dae 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -67,6 +67,7 @@ public function diff($branch) ->map(fn ($output) => explode(PHP_EOL, $output)) ->flatten() ->filter() + ->unique() ->values(); return $this->processFileNames($files); From 681ab1d493ed8fc82b6eb027a515f12c14cddbcf Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:44:51 -0800 Subject: [PATCH 08/11] include untracked files in diff list --- app/Repositories/GitPathsRepository.php | 1 + 1 file changed, 1 insertion(+) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index d5a36dae..a15aec5e 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -55,6 +55,7 @@ public function diff($branch) 'committed' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', "{$branch}...HEAD", '--', '**.php']))->run(), 'staged' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', '--cached', '--', '**.php']))->run(), 'unstaged' => tap(new Process(['git', 'diff', '--name-only', '--diff-filter=AM', '--', '**.php']))->run(), + 'untracked' => tap(new Process(['git', 'ls-files', '--others', '--exclude-standard', '--', '**.php']))->run(), ]; $files = collect($files) From 803b2eb9795ab01ad339a037c5b6eccf0ca6024c Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 01:45:05 -0800 Subject: [PATCH 09/11] add tests --- tests/Feature/DiffTest.php | 86 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 86 insertions(+) create mode 100644 tests/Feature/DiffTest.php diff --git a/tests/Feature/DiffTest.php b/tests/Feature/DiffTest.php new file mode 100644 index 00000000..76b1fcfc --- /dev/null +++ b/tests/Feature/DiffTest.php @@ -0,0 +1,86 @@ +shouldReceive('diff') + ->with('main') + ->once() + ->andReturn([ + base_path('tests/Fixtures/without-issues-laravel/file.php'), + ]); + + $this->swap(PathsRepository::class, $paths); + + [$statusCode, $output] = run('default', ['--diff' => 'main']); + + expect($statusCode)->toBe(0) + ->and($output) + ->toContain('── Laravel', ' 1 file'); +}); + +it('ignores the path argument', function () { + $paths = Mockery::mock(PathsRepository::class); + + $paths + ->shouldReceive('diff') + ->once() + ->andReturn([ + base_path('tests/Fixtures/without-issues-laravel/file.php'), + ]); + + $this->swap(PathsRepository::class, $paths); + + [$statusCode, $output] = run('default', [ + '--diff' => 'main', + 'path' => base_path(), + ]); + + expect($statusCode)->toBe(0) + ->and($output) + ->toContain('── Laravel', ' 1 file'); +}); + +it('does not abort when there are no diff files', function () { + $paths = Mockery::mock(PathsRepository::class); + + $paths + ->shouldReceive('diff') + ->once() + ->andReturn([]); + + $this->swap(PathsRepository::class, $paths); + + [$statusCode, $output] = run('default', [ + '--diff' => 'main', + ]); + + expect($statusCode)->toBe(0) + ->and($output) + ->toContain('── Laravel', ' 0 files'); +}); + +it('parses nested branch names', function () { + $paths = Mockery::mock(PathsRepository::class); + + $paths + ->shouldReceive('diff') + ->with('origin/main') + ->once() + ->andReturn([ + base_path('tests/Fixtures/without-issues-laravel/file.php'), + ]); + + $this->swap(PathsRepository::class, $paths); + + [$statusCode, $output] = run('default', [ + '--diff' => 'origin/main', + ]); + + expect($statusCode)->toBe(0) + ->and($output) + ->toContain('── Laravel', ' 1 file'); +}); From c1a694cc52a78ed6c29e851590f6a567eba8fe4c Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 02:30:19 -0800 Subject: [PATCH 10/11] fixes --- app/Repositories/GitPathsRepository.php | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index a15aec5e..68bbe3d8 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -77,12 +77,12 @@ public function diff($branch) /** * Process the files. * - * @param \Illuminate\Support\Collection $files - * @return array + * @param \Illuminate\Support\Collection $fileNames + * @return array */ protected function processFileNames(Collection $fileNames) { - return $fileNames + $processedFileNames = $fileNames ->map(function ($file) { if (PHP_OS_FAMILY === 'Windows') { $file = str_replace('/', DIRECTORY_SEPARATOR, $file); @@ -100,6 +100,6 @@ protected function processFileNames(Collection $fileNames) ->files() ))); - return array_values(array_intersect($files, $dirtyFiles)); + return array_values(array_intersect($files, $processedFileNames)); } } From 03423b9d6d49ecdeccc23d0e8c5580e11506a030 Mon Sep 17 00:00:00 2001 From: Len Woodward Date: Sat, 4 Jan 2025 02:42:06 -0800 Subject: [PATCH 11/11] make stan happy --- app/Repositories/GitPathsRepository.php | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/app/Repositories/GitPathsRepository.php b/app/Repositories/GitPathsRepository.php index 68bbe3d8..d1763b41 100644 --- a/app/Repositories/GitPathsRepository.php +++ b/app/Repositories/GitPathsRepository.php @@ -41,7 +41,8 @@ public function dirty() $dirtyFiles = collect(preg_split('/\R+/', $process->getOutput(), flags: PREG_SPLIT_NO_EMPTY)) ->mapWithKeys(fn ($file) => [substr($file, 3) => trim(substr($file, 0, 3))]) ->reject(fn ($status) => $status === 'D') - ->map(fn ($status, $file) => $status === 'R' ? Str::after($file, ' -> ') : $file); + ->map(fn ($status, $file) => $status === 'R' ? Str::after($file, ' -> ') : $file) + ->values(); return $this->processFileNames($dirtyFiles); } @@ -69,7 +70,8 @@ public function diff($branch) ->flatten() ->filter() ->unique() - ->values(); + ->values() + ->map(fn ($s) => (string) $s); return $this->processFileNames($files); } @@ -77,7 +79,7 @@ public function diff($branch) /** * Process the files. * - * @param \Illuminate\Support\Collection $fileNames + * @param \Illuminate\Support\Collection $fileNames * @return array */ protected function processFileNames(Collection $fileNames) @@ -90,7 +92,6 @@ protected function processFileNames(Collection $fileNames) return $this->path.DIRECTORY_SEPARATOR.$file; }) - ->values() ->all(); $files = array_values(array_map(function ($splFile) {