Diff implementation

Related tags

Miscellaneous diff
Overview

sebastian/diff

CI Status Type Coverage

Diff implementation for PHP, factored out of PHPUnit into a stand-alone component.

Installation

You can add this library as a local, per-project dependency to your project using Composer:

composer require sebastian/diff

If you only need this library during development, for instance to run your project's test suite, then you should add it as a development-time dependency:

composer require --dev sebastian/diff

Usage

Generating diff

The Differ class can be used to generate a textual representation of the difference between two strings:

<?php
use SebastianBergmann\Diff\Differ;

$differ = new Differ;
print $differ->diff('foo', 'bar');

The code above yields the output below:

--- Original
+++ New
@@ @@
-foo
+bar

There are three output builders available in this package:

UnifiedDiffOutputBuilder

This is default builder, which generates the output close to udiff and is used by PHPUnit.

<?php

use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder;

$builder = new UnifiedDiffOutputBuilder(
    "--- Original\n+++ New\n", // custom header
    false                      // do not add line numbers to the diff 
);

$differ = new Differ($builder);
print $differ->diff('foo', 'bar');

StrictUnifiedDiffOutputBuilder

Generates (strict) Unified diff's (unidiffs) with hunks, similar to diff -u and compatible with patch and git apply.

<?php

use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\StrictUnifiedDiffOutputBuilder;

$builder = new StrictUnifiedDiffOutputBuilder([
    'collapseRanges'      => true, // ranges of length one are rendered with the trailing `,1`
    'commonLineThreshold' => 6,    // number of same lines before ending a new hunk and creating a new one (if needed)
    'contextLines'        => 3,    // like `diff:  -u, -U NUM, --unified[=NUM]`, for patch/git apply compatibility best to keep at least @ 3
    'fromFile'            => null,
    'fromFileDate'        => null,
    'toFile'              => null,
    'toFileDate'          => null,
]);

$differ = new Differ($builder);
print $differ->diff('foo', 'bar');

DiffOnlyOutputBuilder

Output only the lines that differ.

<?php

use SebastianBergmann\Diff\Differ;
use SebastianBergmann\Diff\Output\DiffOnlyOutputBuilder;

$builder = new DiffOnlyOutputBuilder(
    "--- Original\n+++ New\n"
);

$differ = new Differ($builder);
print $differ->diff('foo', 'bar');

DiffOutputBuilderInterface

You can pass any output builder to the Differ class as longs as it implements the DiffOutputBuilderInterface.

Parsing diff

The Parser class can be used to parse a unified diff into an object graph:

use SebastianBergmann\Diff\Parser;
use SebastianBergmann\Git;

$git = new Git('/usr/local/src/money');

$diff = $git->getDiff(
  '948a1a07768d8edd10dcefa8315c1cbeffb31833',
  'c07a373d2399f3e686234c4f7f088d635eb9641b'
);

$parser = new Parser;

print_r($parser->parse($diff));

The code above yields the output below:

Array
(
    [0] => SebastianBergmann\Diff\Diff Object
        (
            [from:SebastianBergmann\Diff\Diff:private] => a/tests/MoneyTest.php
            [to:SebastianBergmann\Diff\Diff:private] => b/tests/MoneyTest.php
            [chunks:SebastianBergmann\Diff\Diff:private] => Array
                (
                    [0] => SebastianBergmann\Diff\Chunk Object
                        (
                            [start:SebastianBergmann\Diff\Chunk:private] => 87
                            [startRange:SebastianBergmann\Diff\Chunk:private] => 7
                            [end:SebastianBergmann\Diff\Chunk:private] => 87
                            [endRange:SebastianBergmann\Diff\Chunk:private] => 7
                            [lines:SebastianBergmann\Diff\Chunk:private] => Array
                                (
                                    [0] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>      * @covers SebastianBergmann\Money\Money::add
                                        )

                                    [1] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>      * @covers SebastianBergmann\Money\Money::newMoney
                                        )

                                    [2] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>      */
                                        )

                                    [3] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 2
                                            [content:SebastianBergmann\Diff\Line:private] =>     public function testAnotherMoneyWithSameCurrencyObjectCanBeAdded()
                                        )

                                    [4] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 1
                                            [content:SebastianBergmann\Diff\Line:private] =>     public function testAnotherMoneyObjectWithSameCurrencyCanBeAdded()
                                        )

                                    [5] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>     {
                                        )

                                    [6] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>         $a = new Money(1, new Currency('EUR'));
                                        )

                                    [7] => SebastianBergmann\Diff\Line Object
                                        (
                                            [type:SebastianBergmann\Diff\Line:private] => 3
                                            [content:SebastianBergmann\Diff\Line:private] =>         $b = new Money(2, new Currency('EUR'));
                                        )
                                )
                        )
                )
        )
)
Comments
  • Clarification of the unified output format

    Clarification of the unified output format

    The unified output format looks a bit different from the one implemented in GNU diff. Consider the two files:

    ↪  cat a.txt 
    1
    2
    3
    4
    5
    6
    7
    8
    
    ↪  cat b.txt 
    1
    2
    3
    4
    5
    6'
    7
    8
    

    If compared by GNU diff, the comparison result is the following:

    --- a.txt	2017-10-11 12:40:59.304512847 -0700
    +++ b.txt	2017-10-11 12:41:14.776322246 -0700
    @@ -3,6 +3,6 @@
     3
     4
     5
    -6
    +6'
     7
     8
    

    The changed line is surrounded by unchanged lines (3 at most, by default) to represent the context.

    If compared by the Differ, the result is the following:

    --- Original
    +++ New
    @@ @@
     1
     2
     3
     4
     5
    -6
    +6'
    

    The context is represented by 5 lines before and none after.

    If the number of non-changed lines before the change is greater than 5 (must be controlled by AbstractChunkOutputBuilder::getCommonChunks(int $lineThreshold)), then they are entirely dropped:

    --- Original
    +++ New
    @@ @@
    -7
    +7'
    

    At the same time, the non-changed lines after the change are dropped independently on the size of the chunk.

    Is this behavior by design? If yes, what's the exact meaning of $lineThreshold.

    opened by morozov 19
  • Add context lines

    Add context lines

    Last of the 3 PR's, this one adds context line around the diff. TBH I'm not sure about the diff format here so please let me know if it is still not correct.

    opened by SpacePossum 18
  • Allow spaces in parser

    Allow spaces in parser

    The from and to in diffs did not include the full path name when spaces are being used. This change allows spaces in the file paths as well (no tabs).

    opened by Matth-- 12
  • POC add line numbers

    POC add line numbers

    This PR introduces the line numbers to the UnifiedDiffOutputBuilder (finally ;) )

    However there a few things to consider;

    • the warning inserted about line break differences cause the output of the output builder to be not compat with the patch tool
    • the output builder does not add the \No newline at end of file to the output when a string has not a line break as last character, making the output not compat with the patch tool
    • with above exceptions the output follows the unified diff format, like patch -u and can be consumed like diff -u
    • the output is now minimal around the chunks, we might pick a better default; for example diff picks 3 lines above and below a changed line (-u, -U NUM, --unified[=NUM] output NUM (default 3) lines of unified context)
    • above might be a valuable option to be passed through the constructor to allow more tweaking

    I left the incompats because;

    • removing a feature that people might like (the warning) is not nice
    • the \No newline at end of file is only of value when diff'ing files, and not arbitrary strings as typically done by PHPUnit

    I plan on writing a full compat version of the output builder so we can offer it 3rd party.

    Let me know what you all think :)

    ping @keradus @julienfalque please have a look if you've time

    PS. The last commit contains a test that is prop. best not merged to the repo, but is very handy when debugging.

    opened by SpacePossum 12
  • Clean up the test suit

    Clean up the test suit

    Sadly, after the release of v2 not all are happy with it. I'm sorry and hereby apologize to everyone who is effected by the changes.

    The intent for v2 was to provide an extendable diff library together with some issue resolving which required BC breaks. However some changes were not up to par and need work.

    This PR is the start of resolving the reported issue. First of I want to create a nice and clean test suite for the package such that new changes can be reviewed more easy.

    The changes are:

    • Exception - Move to own namespace. (I'm still puzzled why this worked with PSR4)
    • UnitTest - Code grooming.
      • move test to own namespace
      • break up DifferTest to dedicated test per class

    To be clear, besides the namespace change of the Exceptions, there are no functional changes to the package in this PR (only to its tests).

    Ping @keradus if you've time please do a sanity check :)

    opened by SpacePossum 9
  • Out of memory

    Out of memory

    I tried lastest php-cs-fixer, which use this diff implementation. Everything is cool, until you try to generate a diff on files which size is 50kB+. For example, trying to use it on couple common wp plugins fails:

    ~/...../wp-content/plugins/jetpack $ php-cs-fixer fix --diff --dry-run -v . PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes) in /Users/....../vendor/sebastian/diff/src/Differ.php on line 274

    Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes) in /Users/......l/vendor/sebastian/diff/src/Differ.php on line 274

    Same goes on disquss plugin:

    ~/...../wp-content/plugins/disqus-conditional-load $ php-cs-fixer fix --diff --dry-run -v . PHP Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes) in /Users/....../vendor/sebastian/diff/src/Differ.php on line 274

    Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 72 bytes) in /Users/...../vendor/sebastian/diff/src/Differ.php on line 274

    Last one failed on disqus-conditional-load.php file which has 57443 bytes.

    opened by pikiel 7
  • Roadmap For 1.2?

    Roadmap For 1.2?

    I'm just interested to know when the v1.2.0 release will be stamped. Have you got anything else planned before the release? There are some really nice changes here.

    opened by GrahamCampbell 7
  • Fix tests to work with SF5 process package

    Fix tests to work with SF5 process package

    The PR fixes the tests because of the SF5 process API interface changes and updates the code style of some tests. There are no functional changes to the package itself.

    fixes: https://github.com/sebastianbergmann/diff/issues/90 replaces https://github.com/sebastianbergmann/diff/pull/89

    opened by SpacePossum 6
  • [Feature] Percent change

    [Feature] Percent change

    I would like to be able to indicate (similar to ~~git~~ GitHub) the relative amount a file has changed. I could not find any convenient way to do that currently, other than creating a diff string and calculating the size of the changes. I think it would make a nice addition to this library.

    opened by MGatner 5
  • Fix edge cases / Improve mem. usage

    Fix edge cases / Improve mem. usage

    • Remove reference mismatch on finding same ending of array.
    • Do not crash on undefined var. on PHP7
    • More tests

    Tagged as WIP as some cases still are missing test coverage, but I would like Travis to report on the work so far. After this enough groundwork is done to start adding line numbers.

    opened by SpacePossum 5
  • Demo issue #86

    Demo issue #86

    The purpose of this PR is to demonstrate issue sebastianbergmann/phpunit#4918 (I'm not requesting this to be merged).

    HangingDemoTest executes Differ::diff() on two large strings. Differ::diff() hangs for hours in this case.

    (The test skips itself if the CI environment variable is set, so that it doesn't consume github action quota.)

    opened by arnaud-lb 4
  • Numeric array values not supported

    Numeric array values not supported

    Tested on latest main:

    Psy Shell v0.11.9 (PHP 8.1.6 — cli) by Justin Hileman
    > $ob = new SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder
    = SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder {#2715}
    
    > (new SebastianBergmann\Diff\Differ($ob))->diff([1], [2])
       TypeError  substr(): Argument #1 ($string) must be of type string, int given.
    
    > wtf -a
       TypeError  substr(): Argument #1 ($string) must be of type string, int given.
    --
     0:  () at vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php:98
     1:  substr() at vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php:98
     2:  SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder->writeDiffHunks() at vendor/sebastian/diff/src/Output/UnifiedDiffOutputBuilder.php:62
     3:  SebastianBergmann\Diff\Output\UnifiedDiffOutputBuilder->getDiff() at vendor/sebastian/diff/src/Differ.php:54
     4:  SebastianBergmann\Diff\Differ->diff() at eval()'d code:1
     5:  eval() at vendor/psy/psysh/src/ExecutionLoopClosure.php:53
     6:  Psy\{closure}() at vendor/psy/psysh/src/ExecutionClosure.php:89
     7:  Psy\ExecutionClosure->execute() at vendor/psy/psysh/src/Shell.php:394
     8:  Psy\Shell->doInteractiveRun() at vendor/psy/psysh/src/Shell.php:365
     9:  Psy\Shell->doRun() at vendor/symfony/console/Application.php:168
    10:  Symfony\Component\Console\Application->run() at vendor/psy/psysh/src/Shell.php:340
    11:  Psy\Shell->run() at vendor/psy/psysh/src/functions.php:454
    12:  Psy\{closure}() at vendor/psy/psysh/bin/psysh:148
    13:  include() at vendor/bin/psysh:117
    

    Not sure if this is a bug as the documentation doesn't say anything about diffing arrays, but the function signature suggests it's supported and PHPUnit uses it. The same command works fine with string values:

    > (new SebastianBergmann\Diff\Differ($ob))->diff(['1'], ['2'])
    = """
      --- Original\n
      +++ New\n
      @@ @@\n
      -1\n
      +2\n
      """
    
    opened by tgr 2
Owner
Sebastian Bergmann
Sebastian Bergmann is the creator of PHPUnit. He co-founded thePHP.cc and helps PHP teams build better software.
Sebastian Bergmann
PHP implementation of circuit breaker pattern.

What is php-circuit-breaker A component helping you gracefully handle outages and timeouts of external services (usually remote, 3rd party services).

ArturEjsmont 169 Jul 28, 2022
Implementation of the Token Bucket algorithm in PHP.

Token Bucket This is a threadsafe implementation of the Token Bucket algorithm in PHP. You can use a token bucket to limit an usage rate for a resourc

null 477 Jan 7, 2023
A PHP implementation of the Unleash protocol aka Feature Flags in GitLab.

A PHP implementation of the Unleash protocol aka Feature Flags in GitLab. This implementation conforms to the official Unleash standards and implement

Dominik Chrástecký 2 Aug 18, 2021
An implementation of the Minecraft: Bedrock Edition protocol in PHP

BedrockProtocol An implementation of the Minecraft: Bedrock Edition protocol in PHP This library implements all of the packets in the Minecraft: Bedro

PMMP 94 Jan 6, 2023
PHP Implementation of PASERK

PASERK (PHP) Platform Agnostic SERialized Keys. Requires PHP 7.1 or newer. PASERK Specification The PASERK Specification can be found in this reposito

Paragon Initiative Enterprises 9 Nov 22, 2022
Mutex lock implementation

Yii Mutex This package provides mutex implementation and allows mutual execution of concurrent processes in order to prevent "race conditions". This i

Yii Software 30 Dec 28, 2022
A minimalistic implementation of Promises for PHP

libPromise A minimalistic implementation of Promises for PHP. Installation via DEVirion Install the DEVirion plugin and start your server. This will c

null 8 Sep 27, 2022
PHP's Promse implementation depends on the Swoole module.

php-promise-swoole PHP's Promse implementation depends on the Swoole module. Promise::allsettled([ /** Timer 调用 */ /** Timer call */

拓荒者 3 Mar 15, 2022
Pheature flags toggle CRUD implementation

Pheature Flags Toggle CRUD Pheature flags toggle CRUD implementation Installation Describe package installation composer require pheature/toggle-crud

Pheature Flags 5 Dec 14, 2022
Frequently asked questions crud implementation for laravel projects

Laravel FAQs This is a simple package to help manage frequently asked questions in a project. Installation You can install the package via composer by

Detosphere Ltd 3 Jun 29, 2022
A circular buffer implementation in PHP

Circular Buffer Installation ?? This is a great place for showing how to install the package, see below: Run $ composer require lctrs/circular-buffer

null 1 Jan 11, 2022
A simple laravel package for wallet implementation

wallet A simple laravel package for wallet implementation. This package can basically be plugged into a laravel project and it will handle wallet impl

Ademuyiwa Adetunji 8 Dec 1, 2022
My own implementation of the backend challenge.

Millions backend challenge My own implementation of the backend challenge. Implemented features Show posts paginated, and ordered by creation date wit

Mouad ZIANI 8 Jun 21, 2022
A Magento implementation for validating JSON Structures against a given Schema

Zepgram JsonSchema A Magento implementation for validating JSON Structures against a given Schema with support for Schemas of Draft-3 or Draft-4. Base

Benjamin Calef 1 Nov 5, 2021
This package contains a PHP implementation to solve 3D bin packing problems.

3D Bin Packager This package contains a PHP implementation to solve 3d bin packing problems based on gedex implementation on Go and enzoruiz implement

Farista Latuconsina 7 Nov 21, 2022
Pheature flags toggle model implementation

Pheature Flags Toggle Model implementation Pheature flags toggle model implementation Installation Describe package installation composer require phea

Pheature Flags 5 Dec 12, 2022
PHP implementation for reading and writing Apache Parquet files/streams

php-parquet This is the first parquet file format reader/writer implementation in PHP, based on the Thrift sources provided by the Apache Foundation.

null 17 Oct 25, 2022
A pure PHP implementation of the MessagePack serialization format / msgpack.org[PHP]

msgpack.php A pure PHP implementation of the MessagePack serialization format. Features Fully compliant with the latest MessagePack specification, inc

Eugene Leonovich 368 Dec 19, 2022
PHP implementation of PSON

PSON-PHP Information This library is an php implementation of PSON. This software is licensed under the MIT License. Installation You can install this

Simplito 1 Sep 29, 2019