Skip to content

Commit 273c9ab

Browse files
committed
[sync] Refine file diff handling and tests (#62)
1 parent 699de1f commit 273c9ab

23 files changed

Lines changed: 798 additions & 407 deletions

composer.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
"rector/jack": "^0.5",
5757
"rector/rector": "^2.4",
5858
"saggre/phpdocumentor-markdown": "^1.0",
59+
"sebastian/diff": "^7.0",
5960
"shipmonk/composer-dependency-analyser": "^1.8.4",
6061
"symfony/config": "^7.4 || ^8.0",
6162
"symfony/console": "^7.4 || ^8.0",

src/Console/Command/CopyResourceCommand.php

Lines changed: 47 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
use Composer\Command\BaseCommand;
2323
use FastForward\DevTools\Filesystem\FinderFactoryInterface;
2424
use FastForward\DevTools\Filesystem\FilesystemInterface;
25-
use FastForward\DevTools\Resource\OverwriteDiffRenderer;
25+
use FastForward\DevTools\Resource\FileDiffer;
2626
use Symfony\Component\Config\FileLocatorInterface;
2727
use Symfony\Component\Console\Attribute\AsCommand;
2828
use Symfony\Component\Console\Input\InputInterface;
@@ -47,13 +47,13 @@ final class CopyResourceCommand extends BaseCommand
4747
* @param FilesystemInterface $filesystem the filesystem used for copy operations
4848
* @param FileLocatorInterface $fileLocator the locator used to resolve source resources
4949
* @param FinderFactoryInterface $finderFactory the factory used to create finders for directory resources
50-
* @param OverwriteDiffRenderer $overwriteDiffRenderer the renderer used to summarize overwrite changes
50+
* @param FileDiffer $fileDiffer the service used to summarize overwrite changes
5151
*/
5252
public function __construct(
5353
private readonly FilesystemInterface $filesystem,
5454
private readonly FileLocatorInterface $fileLocator,
5555
private readonly FinderFactoryInterface $finderFactory,
56-
private readonly OverwriteDiffRenderer $overwriteDiffRenderer,
56+
private readonly FileDiffer $fileDiffer,
5757
) {
5858
parent::__construct();
5959
}
@@ -126,7 +126,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
126126
$targetPath = (string) $this->filesystem->getAbsolutePath($target);
127127

128128
if (is_dir($sourcePath)) {
129-
return $this->copyDirectory($sourcePath, $targetPath, $overwrite, $dryRun, $check, $interactive, $input, $output);
129+
return $this->copyDirectory(
130+
$sourcePath,
131+
$targetPath,
132+
$overwrite,
133+
$dryRun,
134+
$check,
135+
$interactive,
136+
$input,
137+
$output
138+
);
130139
}
131140

132141
return $this->copyFile($sourcePath, $targetPath, $overwrite, $dryRun, $check, $interactive, $input, $output);
@@ -139,6 +148,10 @@ protected function execute(InputInterface $input, OutputInterface $output): int
139148
* @param string $targetPath the resolved target directory
140149
* @param bool $overwrite whether existing files MAY be overwritten
141150
* @param OutputInterface $output the output used to report copy results
151+
* @param bool $dryRun
152+
* @param bool $check
153+
* @param bool $interactive
154+
* @param InputInterface $input
142155
*
143156
* @return int the command status code
144157
*/
@@ -163,7 +176,16 @@ private function copyDirectory(
163176
$destination = Path::join($targetPath, $file->getRelativePathname());
164177
$status = max(
165178
$status,
166-
$this->copyFile($file->getRealPath(), $destination, $overwrite, $dryRun, $check, $interactive, $input, $output),
179+
$this->copyFile(
180+
$file->getRealPath(),
181+
$destination,
182+
$overwrite,
183+
$dryRun,
184+
$check,
185+
$interactive,
186+
$input,
187+
$output
188+
),
167189
);
168190
}
169191

@@ -177,6 +199,10 @@ private function copyDirectory(
177199
* @param string $targetPath the resolved target file
178200
* @param bool $overwrite whether an existing target file MAY be overwritten
179201
* @param OutputInterface $output the output used to report copy results
202+
* @param bool $dryRun
203+
* @param bool $check
204+
* @param bool $interactive
205+
* @param InputInterface $input
180206
*
181207
* @return int the command status code
182208
*/
@@ -189,21 +215,24 @@ private function copyFile(
189215
bool $interactive,
190216
InputInterface $input,
191217
OutputInterface $output,
192-
): int
193-
{
218+
): int {
194219
if (! $overwrite && ! $dryRun && ! $check && ! $interactive && $this->filesystem->exists($targetPath)) {
195220
$output->writeln(\sprintf('<comment>Skipped existing resource %s.</comment>', $targetPath));
196221

197222
return self::SUCCESS;
198223
}
199224

200225
if (($overwrite || $dryRun || $check || $interactive) && $this->filesystem->exists($targetPath)) {
201-
$comparison = $this->overwriteDiffRenderer->render($sourcePath, $targetPath);
226+
$comparison = $this->fileDiffer->diff($sourcePath, $targetPath);
227+
228+
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->getSummary()));
202229

203-
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->summary()));
230+
if ($comparison->isChanged()) {
231+
$consoleDiff = $this->fileDiffer->formatForConsole($comparison->getDiff(), $output->isDecorated());
204232

205-
if ($comparison->isChanged() && null !== $comparison->diff()) {
206-
$output->writeln($comparison->diff());
233+
if (null !== $consoleDiff) {
234+
$output->writeln($consoleDiff);
235+
}
207236
}
208237

209238
if ($comparison->isUnchanged()) {
@@ -218,7 +247,11 @@ private function copyFile(
218247
return self::SUCCESS;
219248
}
220249

221-
if ($interactive && $input->isInteractive() && ! $this->shouldReplaceResource($input, $output, $targetPath)) {
250+
if ($interactive && $input->isInteractive() && ! $this->shouldReplaceResource(
251+
$input,
252+
$output,
253+
$targetPath
254+
)) {
222255
$output->writeln(\sprintf('<comment>Skipped replacing %s.</comment>', $targetPath));
223256

224257
return self::SUCCESS;
@@ -244,6 +277,7 @@ private function shouldReplaceResource(InputInterface $input, OutputInterface $o
244277
{
245278
$question = new ConfirmationQuestion(\sprintf('Replace drifted resource %s? [y/N] ', $targetPath), false);
246279

247-
return (bool) $this->getHelper('question')->ask($input, $output, $question);
280+
return (bool) $this->getHelper('question')
281+
->ask($input, $output, $question);
248282
}
249283
}

src/Console/Command/GitAttributesCommand.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
use FastForward\DevTools\GitAttributes\MergerInterface;
2929
use FastForward\DevTools\GitAttributes\ReaderInterface;
3030
use FastForward\DevTools\GitAttributes\WriterInterface;
31-
use FastForward\DevTools\Resource\OverwriteDiffRenderer;
31+
use FastForward\DevTools\Resource\FileDiffer;
3232
use Symfony\Component\Console\Attribute\AsCommand;
3333
use Symfony\Component\Console\Input\InputInterface;
3434
use Symfony\Component\Console\Input\InputOption;
@@ -70,6 +70,7 @@ final class GitAttributesCommand extends BaseCommand
7070
* @param WriterInterface $writer the writer component
7171
* @param FilesystemInterface $filesystem the filesystem component
7272
* @param ComposerJsonInterface $composer the composer.json accessor
73+
* @param FileDiffer $fileDiffer
7374
*/
7475
public function __construct(
7576
private readonly CandidateProviderInterface $candidateProvider,
@@ -80,7 +81,7 @@ public function __construct(
8081
private readonly WriterInterface $writer,
8182
private readonly ComposerJsonInterface $composer,
8283
private readonly FilesystemInterface $filesystem,
83-
private readonly OverwriteDiffRenderer $overwriteDiffRenderer,
84+
private readonly FileDiffer $fileDiffer,
8485
) {
8586
parent::__construct();
8687
}
@@ -146,18 +147,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int
146147
$existingContent = $this->reader->read($gitattributesPath);
147148
$content = $this->merger->merge($existingContent, $entries, $keepInExportPaths);
148149
$renderedContent = $this->writer->render($content);
149-
$comparison = $this->overwriteDiffRenderer->renderContents(
150+
$comparison = $this->fileDiffer->diffContents(
150151
'generated .gitattributes synchronization',
151152
$gitattributesPath,
152153
$renderedContent,
153154
'' === $existingContent ? null : $this->writer->render($existingContent),
154155
\sprintf('Updating managed file %s from generated .gitattributes synchronization.', $gitattributesPath),
155156
);
156157

157-
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->summary()));
158+
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->getSummary()));
158159

159-
if ($comparison->isChanged() && null !== $comparison->diff()) {
160-
$output->writeln($comparison->diff());
160+
if ($comparison->isChanged()) {
161+
$consoleDiff = $this->fileDiffer->formatForConsole($comparison->getDiff(), $output->isDecorated());
162+
163+
if (null !== $consoleDiff) {
164+
$output->writeln($consoleDiff);
165+
}
161166
}
162167

163168
if ($comparison->isUnchanged()) {
@@ -172,7 +177,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
172177
return self::SUCCESS;
173178
}
174179

175-
if ($interactive && $input->isInteractive() && ! $this->shouldWriteGitAttributes($input, $output, $gitattributesPath)) {
180+
if ($interactive && $input->isInteractive() && ! $this->shouldWriteGitAttributes(
181+
$input,
182+
$output,
183+
$gitattributesPath
184+
)) {
176185
$output->writeln(\sprintf('<comment>Skipped updating %s.</comment>', $gitattributesPath));
177186

178187
return self::SUCCESS;
@@ -201,7 +210,8 @@ private function shouldWriteGitAttributes(InputInterface $input, OutputInterface
201210
{
202211
$question = new ConfirmationQuestion(\sprintf('Update managed file %s? [y/N] ', $targetPath), false);
203212

204-
return (bool) $this->getHelper('question')->ask($input, $output, $question);
213+
return (bool) $this->getHelper('question')
214+
->ask($input, $output, $question);
205215
}
206216

207217
/**

src/Console/Command/GitHooksCommand.php

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
use Composer\Command\BaseCommand;
2323
use FastForward\DevTools\Filesystem\FinderFactoryInterface;
2424
use FastForward\DevTools\Filesystem\FilesystemInterface;
25-
use FastForward\DevTools\Resource\OverwriteDiffRenderer;
25+
use FastForward\DevTools\Resource\FileDiffer;
2626
use Symfony\Component\Config\FileLocatorInterface;
2727
use Symfony\Component\Console\Attribute\AsCommand;
2828
use Symfony\Component\Console\Input\InputInterface;
@@ -47,12 +47,13 @@ final class GitHooksCommand extends BaseCommand
4747
* @param FilesystemInterface $filesystem the filesystem used to copy hooks
4848
* @param FileLocatorInterface $fileLocator the locator used to find packaged hooks
4949
* @param FinderFactoryInterface $finderFactory the factory used to create finders for hook files
50+
* @param FileDiffer $fileDiffer
5051
*/
5152
public function __construct(
5253
private readonly FilesystemInterface $filesystem,
5354
private readonly FileLocatorInterface $fileLocator,
5455
private readonly FinderFactoryInterface $finderFactory,
55-
private readonly OverwriteDiffRenderer $overwriteDiffRenderer,
56+
private readonly FileDiffer $fileDiffer,
5657
) {
5758
parent::__construct();
5859
}
@@ -133,12 +134,16 @@ protected function execute(InputInterface $input, OutputInterface $output): int
133134
}
134135

135136
if (($overwrite || $dryRun || $check || $interactive) && $this->filesystem->exists($hookPath)) {
136-
$comparison = $this->overwriteDiffRenderer->render($file->getRealPath(), $hookPath);
137+
$comparison = $this->fileDiffer->diff($file->getRealPath(), $hookPath);
137138

138-
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->summary()));
139+
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->getSummary()));
139140

140-
if ($comparison->isChanged() && null !== $comparison->diff()) {
141-
$output->writeln($comparison->diff());
141+
if ($comparison->isChanged()) {
142+
$consoleDiff = $this->fileDiffer->formatForConsole($comparison->getDiff(), $output->isDecorated());
143+
144+
if (null !== $consoleDiff) {
145+
$output->writeln($consoleDiff);
146+
}
142147
}
143148

144149
if ($comparison->isUnchanged()) {
@@ -184,6 +189,7 @@ private function shouldReplaceHook(InputInterface $input, OutputInterface $outpu
184189
{
185190
$question = new ConfirmationQuestion(\sprintf('Replace drifted Git hook %s? [y/N] ', $hookPath), false);
186191

187-
return (bool) $this->getHelper('question')->ask($input, $output, $question);
192+
return (bool) $this->getHelper('question')
193+
->ask($input, $output, $question);
188194
}
189195
}

src/Console/Command/GitIgnoreCommand.php

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
use FastForward\DevTools\GitIgnore\MergerInterface;
2424
use FastForward\DevTools\GitIgnore\ReaderInterface;
2525
use FastForward\DevTools\GitIgnore\WriterInterface;
26-
use FastForward\DevTools\Resource\OverwriteDiffRenderer;
26+
use FastForward\DevTools\Resource\FileDiffer;
2727
use Symfony\Component\Config\FileLocatorInterface;
2828
use Symfony\Component\Console\Attribute\AsCommand;
2929
use Symfony\Component\Console\Input\InputInterface;
@@ -59,13 +59,14 @@ final class GitIgnoreCommand extends BaseCommand
5959
* @param ReaderInterface $reader the reader component
6060
* @param WriterInterface|null $writer the writer component
6161
* @param FileLocatorInterface $fileLocator the file locator
62+
* @param FileDiffer $fileDiffer
6263
*/
6364
public function __construct(
6465
private readonly MergerInterface $merger,
6566
private readonly ReaderInterface $reader,
6667
private readonly WriterInterface $writer,
6768
private readonly FileLocatorInterface $fileLocator,
68-
private readonly OverwriteDiffRenderer $overwriteDiffRenderer,
69+
private readonly FileDiffer $fileDiffer,
6970
) {
7071
parent::__construct();
7172
}
@@ -132,18 +133,22 @@ protected function execute(InputInterface $input, OutputInterface $output): int
132133
$project = $this->reader->read($targetPath);
133134

134135
$merged = $this->merger->merge($canonical, $project);
135-
$comparison = $this->overwriteDiffRenderer->renderContents(
136+
$comparison = $this->fileDiffer->diffContents(
136137
'generated .gitignore synchronization',
137138
$merged->path(),
138139
$this->writer->render($merged),
139140
$this->writer->render($project),
140141
\sprintf('Updating managed file %s from generated .gitignore synchronization.', $merged->path()),
141142
);
142143

143-
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->summary()));
144+
$output->writeln(\sprintf('<comment>%s</comment>', $comparison->getSummary()));
144145

145-
if ($comparison->isChanged() && null !== $comparison->diff()) {
146-
$output->writeln($comparison->diff());
146+
if ($comparison->isChanged()) {
147+
$consoleDiff = $this->fileDiffer->formatForConsole($comparison->getDiff(), $output->isDecorated());
148+
149+
if (null !== $consoleDiff) {
150+
$output->writeln($consoleDiff);
151+
}
147152
}
148153

149154
if ($comparison->isUnchanged()) {
@@ -158,7 +163,11 @@ protected function execute(InputInterface $input, OutputInterface $output): int
158163
return self::SUCCESS;
159164
}
160165

161-
if ($interactive && $input->isInteractive() && ! $this->shouldWriteGitIgnore($input, $output, $merged->path())) {
166+
if ($interactive && $input->isInteractive() && ! $this->shouldWriteGitIgnore(
167+
$input,
168+
$output,
169+
$merged->path()
170+
)) {
162171
$output->writeln(\sprintf('<comment>Skipped updating %s.</comment>', $merged->path()));
163172

164173
return self::SUCCESS;
@@ -184,6 +193,7 @@ private function shouldWriteGitIgnore(InputInterface $input, OutputInterface $ou
184193
{
185194
$question = new ConfirmationQuestion(\sprintf('Update managed file %s? [y/N] ', $targetPath), false);
186195

187-
return (bool) $this->getHelper('question')->ask($input, $output, $question);
196+
return (bool) $this->getHelper('question')
197+
->ask($input, $output, $question);
188198
}
189199
}

0 commit comments

Comments
 (0)