From 3a8ca018832ee2861dbe8903c8eeba46ad98b3c8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Th=C3=A9o=20FIDRY?= <5175937+theofidry@users.noreply.github.com> Date: Wed, 11 Oct 2023 16:26:43 +0200 Subject: [PATCH] feat: Rework the diff command output (#1054) - Always display a summary of both archives upon equality failure. - Provide a human-readable diff of the archives when possible. - Make the message more explicit that if no diff was found for the archives content, this is true for the selected diff mode, i.e. a difference may be found with another diff mode. --- composer.json | 1 + composer.lock | 134 +++++++++++++------------- src/Console/Command/Diff.php | 89 ++++++++++++------ src/Phar/PharDiff.php | 5 + tests/Console/Command/DiffTest.php | 146 +++++++++++++++++++++++------ 5 files changed, 249 insertions(+), 126 deletions(-) diff --git a/composer.json b/composer.json index 1d5a6c3bd..1ec553207 100644 --- a/composer.json +++ b/composer.json @@ -35,6 +35,7 @@ "phpdocumentor/reflection-docblock": "^5.3", "phpdocumentor/type-resolver": "^1.7", "psr/log": "^3.0", + "sebastian/diff": "^4.0", "seld/jsonlint": "^1.9", "symfony/console": "^6.1.7", "symfony/filesystem": "^6.1.5", diff --git a/composer.lock b/composer.lock index 8fb99834a..cab6be326 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ff1e129b64f2e02d9c4a3963dbc7d10c", + "content-hash": "49fe6f5891e7fa2ae2c999456606c96d", "packages": [ { "name": "amphp/amp", @@ -1778,6 +1778,72 @@ }, "time": "2021-07-14T16:46:02+00:00" }, + { + "name": "sebastian/diff", + "version": "4.0.5", + "source": { + "type": "git", + "url": "https://github.com/sebastianbergmann/diff.git", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", + "shasum": "" + }, + "require": { + "php": ">=7.3" + }, + "require-dev": { + "phpunit/phpunit": "^9.3", + "symfony/process": "^4.2 || ^5" + }, + "type": "library", + "extra": { + "branch-alias": { + "dev-master": "4.0-dev" + } + }, + "autoload": { + "classmap": [ + "src/" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Sebastian Bergmann", + "email": "sebastian@phpunit.de" + }, + { + "name": "Kore Nordmann", + "email": "mail@kore-nordmann.de" + } + ], + "description": "Diff implementation", + "homepage": "https://github.com/sebastianbergmann/diff", + "keywords": [ + "diff", + "udiff", + "unidiff", + "unified diff" + ], + "support": { + "issues": "https://github.com/sebastianbergmann/diff/issues", + "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" + }, + "funding": [ + { + "url": "https://github.com/sebastianbergmann", + "type": "github" + } + ], + "time": "2023-05-07T05:35:17+00:00" + }, { "name": "seld/jsonlint", "version": "1.10.0", @@ -4769,72 +4835,6 @@ ], "time": "2020-10-26T15:52:27+00:00" }, - { - "name": "sebastian/diff", - "version": "4.0.5", - "source": { - "type": "git", - "url": "https://github.com/sebastianbergmann/diff.git", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/diff/zipball/74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "reference": "74be17022044ebaaecfdf0c5cd504fc9cd5a7131", - "shasum": "" - }, - "require": { - "php": ">=7.3" - }, - "require-dev": { - "phpunit/phpunit": "^9.3", - "symfony/process": "^4.2 || ^5" - }, - "type": "library", - "extra": { - "branch-alias": { - "dev-master": "4.0-dev" - } - }, - "autoload": { - "classmap": [ - "src/" - ] - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "BSD-3-Clause" - ], - "authors": [ - { - "name": "Sebastian Bergmann", - "email": "sebastian@phpunit.de" - }, - { - "name": "Kore Nordmann", - "email": "mail@kore-nordmann.de" - } - ], - "description": "Diff implementation", - "homepage": "https://github.com/sebastianbergmann/diff", - "keywords": [ - "diff", - "udiff", - "unidiff", - "unified diff" - ], - "support": { - "issues": "https://github.com/sebastianbergmann/diff/issues", - "source": "https://github.com/sebastianbergmann/diff/tree/4.0.5" - }, - "funding": [ - { - "url": "https://github.com/sebastianbergmann", - "type": "github" - } - ], - "time": "2023-05-07T05:35:17+00:00" - }, { "name": "sebastian/environment", "version": "5.1.5", diff --git a/src/Console/Command/Diff.php b/src/Console/Command/Diff.php index 6bec5a0bf..8be1e90cd 100644 --- a/src/Console/Command/Diff.php +++ b/src/Console/Command/Diff.php @@ -22,6 +22,8 @@ use KevinGH\Box\Phar\DiffMode; use KevinGH\Box\Phar\PharDiff; use KevinGH\Box\Phar\PharInfo; +use SebastianBergmann\Diff\Differ; +use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder; use Symfony\Component\Console\Input\InputArgument; use Symfony\Component\Console\Input\InputOption; use Symfony\Component\Console\Output\BufferedOutput; @@ -119,17 +121,18 @@ public function execute(IO $io): int $io->comment('Comparing the two archives...'); - $pharInfoA = $diff->getPharInfoA(); - $pharInfoB = $diff->getPharInfoB(); - - if ($pharInfoA->equals($pharInfoB)) { + if ($diff->equals()) { $io->success('The two archives are identical.'); return ExitCode::SUCCESS; } - $this->compareArchives($diff, $io); - $this->compareContents($diff, $diffMode, $io); + self::renderSummary($diff->getPharInfoA(), $io); + $io->newLine(); + self::renderSummary($diff->getPharInfoB(), $io); + + $this->renderArchivesDiff($diff, $io); + $this->renderContentsDiff($diff, $diffMode, $io); return ExitCode::FAILURE; } @@ -152,24 +155,25 @@ private static function getPaths(IO $io): array ); } - private function compareArchives(PharDiff $diff, IO $io): void + private function renderArchivesDiff(PharDiff $diff, IO $io): void { - $pharInfoA = $diff->getPharInfoA(); - $pharInfoB = $diff->getPharInfoB(); - - self::renderArchive( - $diff->getPharInfoA()->getFileName(), - $pharInfoA, - $io, + $differ = new Differ( + new UnifiedDiffOutputBuilder("\n--- PHAR A\n+++ PHAR B\n"), ); - $io->newLine(); + $pharA = self::getShortSummary($diff->getPharInfoA(), $io); + $pharB = self::getShortSummary($diff->getPharInfoB(), $io); - self::renderArchive( - $diff->getPharInfoB()->getFileName(), - $pharInfoB, - $io, + if ($pharA === $pharB) { + return; + } + + $result = $differ->diff( + $pharA, + $pharB, ); + + $io->writeln($result); } private static function getDiffMode(IO $io): DiffMode @@ -216,9 +220,14 @@ private static function getDiffMode(IO $io): DiffMode return DiffMode::from($io->getOption(self::DIFF_OPTION)->asNonEmptyString()); } - private function compareContents(PharDiff $diff, DiffMode $diffMode, IO $io): void + private function renderContentsDiff(PharDiff $diff, DiffMode $diffMode, IO $io): void { - $io->comment('Comparing the two archives contents...'); + $io->comment( + sprintf( + 'Comparing the two archives contents (%s diff)...', + $diffMode->value, + ), + ); $checkSumAlgorithm = $io->getOption(self::CHECK_OPTION)->asNullableNonEmptyString() ?? self::DEFAULT_CHECKSUM_ALGO; @@ -231,7 +240,7 @@ private function compareContents(PharDiff $diff, DiffMode $diffMode, IO $io): vo $diffResult = $diff->diff($diffMode); if (null === $diffResult || [[], []] === $diffResult) { - $io->success('The contents are identical'); + $io->writeln('No difference could be observed with this mode.'); return; } @@ -259,10 +268,12 @@ private function compareContents(PharDiff $diff, DiffMode $diffMode, IO $io): vo self::renderPaths('-', $diff->getPharInfoA(), $diffResult[0], $io); self::renderPaths('+', $diff->getPharInfoB(), $diffResult[1], $io); - $io->error(sprintf( - '%d file(s) difference', - count($diffResult[0]) + count($diffResult[1]), - )); + $io->error( + sprintf( + '%d file(s) difference', + count($diffResult[0]) + count($diffResult[1]), + ), + ); } /** @@ -294,19 +305,39 @@ private static function renderPaths(string $symbol, PharInfo $pharInfo, array $p $io->writeln($lines); } - private static function renderArchive(string $fileName, PharInfo $pharInfo, IO $io): void + private static function renderSummary(PharInfo $pharInfo, IO $io): void { $io->writeln( sprintf( 'Archive: %s', - $fileName, + $pharInfo->getFileName(), ), ); + self::renderShortSummary($pharInfo, $io); + } + + private static function getShortSummary(PharInfo $pharInfo, IO $io): string + { + $output = new BufferedOutput( + $io->getVerbosity(), + $io->isDecorated(), + $io->getOutput()->getFormatter(), + ); + + self::renderShortSummary( + $pharInfo, + $io->withOutput($output), + ); + + return $output->fetch(); + } + + private static function renderShortSummary(PharInfo $pharInfo, IO $io): void + { PharInfoRenderer::renderCompression($pharInfo, $io); PharInfoRenderer::renderSignature($pharInfo, $io); PharInfoRenderer::renderMetadata($pharInfo, $io); PharInfoRenderer::renderContentsSummary($pharInfo, $io); - // TODO: checksum } } diff --git a/src/Phar/PharDiff.php b/src/Phar/PharDiff.php index 6fbbbea12..4ccc5b90d 100644 --- a/src/Phar/PharDiff.php +++ b/src/Phar/PharDiff.php @@ -61,6 +61,11 @@ public function getPharInfoB(): PharInfo return $this->pharInfoB; } + public function equals(): bool + { + return $this->pharInfoA->equals($this->pharInfoB); + } + /** * @return null|string|array{string[], string[]} */ diff --git a/tests/Console/Command/DiffTest.php b/tests/Console/Command/DiffTest.php index e79d37ac2..794aba55b 100644 --- a/tests/Console/Command/DiffTest.php +++ b/tests/Console/Command/DiffTest.php @@ -334,7 +334,7 @@ public static function diffPharsProvider(): iterable } } - private static function commonDiffPharsProvider(): iterable + private static function commonDiffPharsProvider(DiffMode $diffMode): iterable { yield 'same PHAR' => [ self::FIXTURES_DIR.'/simple-phar-foo.phar', @@ -353,39 +353,54 @@ private static function commonDiffPharsProvider(): iterable yield 'different data; same content' => [ self::FIXTURES_DIR.'/simple-phar-bar.phar', self::FIXTURES_DIR.'/simple-phar-bar-compressed.phar', - <<<'OUTPUT' + sprintf( + <<<'OUTPUT' - // Comparing the two archives... + // Comparing the two archives... - Archive: simple-phar-bar.phar - Archive Compression: None - Files Compression: None - Signature: SHA-1 - Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 - Metadata: None - Contents: 1 file (6.64KB) + Archive: simple-phar-bar.phar + Archive Compression: None + Files Compression: None + Signature: SHA-1 + Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + Metadata: None + Contents: 1 file (6.64KB) - Archive: simple-phar-bar-compressed.phar - Archive Compression: None - Files Compression: GZ - Signature: SHA-1 - Signature Hash: 3A388D86C91C36659A043D52C2DEB64E8848DD1A - Metadata: None - Contents: 1 file (6.65KB) + Archive: simple-phar-bar-compressed.phar + Archive Compression: None + Files Compression: GZ + Signature: SHA-1 + Signature Hash: 3A388D86C91C36659A043D52C2DEB64E8848DD1A + Metadata: None + Contents: 1 file (6.65KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + -Files Compression: None + +Files Compression: GZ + Signature: SHA-1 + -Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + +Signature Hash: 3A388D86C91C36659A043D52C2DEB64E8848DD1A + Metadata: None + -Contents: 1 file (6.64KB) + +Contents: 1 file (6.65KB) - [OK] The contents are identical + // Comparing the two archives contents (%s diff)... + No difference could be observed with this mode. - OUTPUT, + OUTPUT, + $diffMode->value, + ), ExitCode::FAILURE, ]; } private static function listDiffPharsProvider(): iterable { - yield from self::commonDiffPharsProvider(); + yield from self::commonDiffPharsProvider(DiffMode::LIST); yield 'different files' => [ self::FIXTURES_DIR.'/simple-phar-foo.phar', @@ -410,7 +425,18 @@ private static function listDiffPharsProvider(): iterable Metadata: None Contents: 1 file (6.64KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + Files Compression: None + Signature: SHA-1 + -Signature Hash: 311080EF8E479CE18D866B744B7D467880BFBF57 + +Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + Metadata: None + Contents: 1 file (6.64KB) + + // Comparing the two archives contents (list diff)... --- Files present in "simple-phar-foo.phar" but not in "simple-phar-bar.phar" +++ Files present in "simple-phar-bar.phar" but not in "simple-phar-foo.phar" @@ -449,10 +475,21 @@ private static function listDiffPharsProvider(): iterable Metadata: None Contents: 1 file (6.61KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + Files Compression: None + Signature: SHA-1 + -Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + +Signature Hash: 122A636B8BB0348C9514833D70281EF6306A5BF5 + Metadata: None + -Contents: 1 file (6.64KB) + +Contents: 1 file (6.61KB) - [OK] The contents are identical + // Comparing the two archives contents (list diff)... + No difference could be observed with this mode. OUTPUT, ExitCode::FAILURE, @@ -461,7 +498,7 @@ private static function listDiffPharsProvider(): iterable public static function gitDiffPharsProvider(): iterable { - yield from self::commonDiffPharsProvider(); + yield from self::commonDiffPharsProvider(DiffMode::GIT); yield 'different files' => [ self::FIXTURES_DIR.'/simple-phar-foo.phar', @@ -539,7 +576,7 @@ public static function gitDiffPharsProvider(): iterable public static function GNUDiffPharsProvider(): iterable { - yield from self::commonDiffPharsProvider(); + yield from self::commonDiffPharsProvider(DiffMode::GNU); yield 'different files' => [ self::FIXTURES_DIR.'/simple-phar-foo.phar', @@ -564,7 +601,18 @@ public static function GNUDiffPharsProvider(): iterable Metadata: None Contents: 1 file (6.64KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + Files Compression: None + Signature: SHA-1 + -Signature Hash: 311080EF8E479CE18D866B744B7D467880BFBF57 + +Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + Metadata: None + Contents: 1 file (6.64KB) + + // Comparing the two archives contents (gnu diff)... Only in simple-phar-bar.phar: bar.php Only in simple-phar-foo.phar: foo.php @@ -597,7 +645,19 @@ public static function GNUDiffPharsProvider(): iterable Metadata: None Contents: 1 file (6.61KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + Files Compression: None + Signature: SHA-1 + -Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + +Signature Hash: 122A636B8BB0348C9514833D70281EF6306A5BF5 + Metadata: None + -Contents: 1 file (6.64KB) + +Contents: 1 file (6.61KB) + + // Comparing the two archives contents (gnu diff)... diff --exclude=.phar_meta.json simple-phar-bar.phar/bar.php simple-phar-baz.phar/bar.php 3c3 @@ -610,9 +670,35 @@ public static function GNUDiffPharsProvider(): iterable // Comparing the two archives... - [OK] The two archives are identical + Archive: simple-phar-bar.phar + Archive Compression: None + Files Compression: None + Signature: SHA-1 + Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + Metadata: None + Contents: 1 file (6.64KB) + + Archive: simple-phar-baz.phar + Archive Compression: None + Files Compression: None + Signature: SHA-1 + Signature Hash: 122A636B8BB0348C9514833D70281EF6306A5BF5 + Metadata: None + Contents: 1 file (6.61KB) - // Comparing the two archives contents... + --- PHAR A + +++ PHAR B + @@ @@ + Archive Compression: None + Files Compression: None + Signature: SHA-1 + -Signature Hash: 9ADC09F73909EDF14F8A4ABF9758B6FFAD1BBC51 + +Signature Hash: 122A636B8BB0348C9514833D70281EF6306A5BF5 + Metadata: None + -Contents: 1 file (6.64KB) + +Contents: 1 file (6.61KB) + + // Comparing the two archives contents (gnu diff)... diff '--exclude=.phar_meta.json' simple-phar-bar.phar/bar.php simple-phar-baz.phar/bar.php 3c3