diff --git a/src/Console/Command/PurgeCommand.php b/src/Console/Command/PurgeCommand.php index cdc3f02..bbef3d2 100644 --- a/src/Console/Command/PurgeCommand.php +++ b/src/Console/Command/PurgeCommand.php @@ -16,7 +16,6 @@ namespace Composer\Satis\Console\Command; use Composer\Command\BaseCommand; use Composer\Json\JsonFile; use Composer\Satis\PackageSelection\PackageSelection; -use Composer\Util\Filesystem; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Output\OutputInterface; @@ -128,24 +127,9 @@ class PurgeCommand extends BaseCommand return 0; } - $filesystem = new Filesystem(); foreach ($unreferenced as $currentFile) { if (!$dryRun) { - try { - $filesystem->unlink($currentFile->getPathname()); - } catch (\RuntimeException $exception) { - // Another process, e.g. a concurrent build or purge, may have removed the archive already. - if (file_exists($currentFile->getPathname())) { - throw $exception; - } - - $output->writeln(sprintf( - 'Skipped missing archive: %s', - $currentFile->getRelativePathname() - )); - - continue; - } + unlink($currentFile->getPathname()); } $output->writeln(sprintf( @@ -182,7 +166,7 @@ class PurgeCommand extends BaseCommand if (is_dir($path) && $depth > 0 && $this->removeEmptyDirectories($output, $path, $depth - 1) - && @rmdir($path) + && rmdir($path) ) { $output->writeln(sprintf('Removed empty directory: %s', $path)); } else { diff --git a/tests/Console/Command/PurgeCommandTest.php b/tests/Console/Command/PurgeCommandTest.php index 2a57c22..7894b6f 100644 --- a/tests/Console/Command/PurgeCommandTest.php +++ b/tests/Console/Command/PurgeCommandTest.php @@ -19,7 +19,6 @@ use PHPUnit\Framework\Attributes\CoversClass; use PHPUnit\Framework\Attributes\DataProvider; use PHPUnit\Framework\Attributes\TestDox; use PHPUnit\Framework\TestCase; -use Symfony\Component\Console\Input\ArrayInput; use Symfony\Component\Console\Output\BufferedOutput; use Symfony\Component\Console\Output\NullOutput; use Symfony\Component\Console\Tester\CommandTester; @@ -535,121 +534,6 @@ class PurgeCommandTest extends TestCase self::assertStringContainsString('Removed empty directory', $tester->getDisplay()); } - #[TestDox('execute() skips unreferenced archives that are already gone when they are about to be removed')] - public function testExecuteSkipsArchivesThatDisappearDuringPurge(): void - { - $outputDir = vfsStream::url('satis/output-vanished'); - mkdir($outputDir); - $archiveDir = $outputDir . '/dist'; - mkdir($archiveDir . '/vendor', 0755, true); - - $archives = [ - $archiveDir . '/vendor/first.tar.gz', - $archiveDir . '/vendor/second.tar.gz', - ]; - foreach ($archives as $archive) { - file_put_contents($archive, 'x'); - } - - $this->writeOutputRepository($outputDir, $this->packageEntry( - 'vendor/other', - '1.0.0', - 'https://example.com/dist/vendor/other.tar.gz' - )); - - $this->writeSatisConfig([ - 'name' => 'test/satis-repo', - 'homepage' => 'https://example.com', - 'output-dir' => $outputDir, - 'archive' => [ - 'directory' => 'dist', - 'prefix-url' => 'https://example.com', - ], - ]); - - // Simulates a concurrent build or purge: once the first archive is removed, the others vanish too. - $output = new class($archives) extends BufferedOutput { - /** - * @param list $archives - */ - public function __construct(private readonly array $archives) - { - parent::__construct(); - } - - protected function doWrite(string $message, bool $newline): void - { - parent::doWrite($message, $newline); - - if (str_contains($message, 'Removed archive')) { - foreach ($this->archives as $archive) { - if (file_exists($archive)) { - unlink($archive); - } - } - } - } - }; - - $input = new ArrayInput([ - 'file' => $this->configPath, - 'output-dir' => $outputDir, - ]); - $input->setInteractive(false); - - $app = new Application(); - $app->addCommand(new PurgeCommand()); - - $statusCode = $app->find('purge')->run($input, $output); - - $display = $output->fetch(); - self::assertSame(0, $statusCode); - self::assertSame(1, substr_count($display, 'Removed archive')); - self::assertStringContainsString('Skipped missing archive', $display); - self::assertStringContainsString('Done.', $display); - foreach ($archives as $archive) { - self::assertFileDoesNotExist($archive); - } - } - - #[TestDox('execute() throws when an unreferenced archive exists but cannot be removed')] - public function testExecuteThrowsWhenArchiveCannotBeRemoved(): void - { - $outputDir = vfsStream::url('satis/output-locked'); - mkdir($outputDir); - $archiveDir = $outputDir . '/dist'; - mkdir($archiveDir . '/vendor', 0755, true); - - $unreferencedFile = $archiveDir . '/vendor/unreferenced.tar.gz'; - file_put_contents($unreferencedFile, 'x'); - chmod($archiveDir . '/vendor', 0555); - - $this->writeOutputRepository($outputDir, $this->packageEntry( - 'vendor/other', - '1.0.0', - 'https://example.com/dist/vendor/other.tar.gz' - )); - - $this->writeSatisConfig([ - 'name' => 'test/satis-repo', - 'homepage' => 'https://example.com', - 'output-dir' => $outputDir, - 'archive' => [ - 'directory' => 'dist', - 'prefix-url' => 'https://example.com', - ], - ]); - - $tester = $this->createTester(); - - $this->expectException(\RuntimeException::class); - $this->expectExceptionMessage('Could not delete ' . $unreferencedFile); - $tester->execute([ - 'file' => $this->configPath, - 'output-dir' => $outputDir, - ]); - } - // --- Dry-run --- #[TestDox('execute() does not remove archives or empty dirs when dry-run is enabled')]