Skip to content

Generate, render and apply code diffs. Myers algorithm, versatile rendering (HTML, JSON, ANSI), and full patching support.

License

Notifications You must be signed in to change notification settings

PhpAlto/code-diff

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

3 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Alto Code Diff

A modern PHP library to generate, render and apply diffs, featuring advanced algorithms, versatile rendering, and full patching support.


  PHP Version   CI   Packagist Version   GitHub Sponsors   License

Features

Advanced Diff Algorithms

  • Myers Diff Algorithm (default): Fast and accurate line-by-line and word-by-word diffing (O(ND)).
  • LCS Diff Algorithm: Opt-in Longest Common Subsequence engine (O(MN) time and memory) for deterministic academic use cases.
  • Binary Detection: Automatic detection and rejection of binary content.

Versatile Rendering

Visualize and format diffs for any output medium:

HTML Output

HTML Preview

ANSI Side-by-Side

ANSI Preview

Full Patching Support

  • Unified Diff Parsing: Parse standard unified diff patches into objects.
  • Patch Application: Apply patches to files with "fuzz" factor support.
  • Multi-file Bundles: Handle complex patches affecting multiple files.

Requirements

  • PHP 8.3 or higher

Installation

composer require alto/code-diff

Quick Start

Basic Diff

use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Renderer\UnifiedRenderer;

$old = "line1\nline2\nline3\n";
$new = "line1\nline2 modified\nline3\n";

$result = Diff::build()->compare($old, $new);

$renderer = new UnifiedRenderer('old.txt', 'new.txt');
echo $renderer->render($result);

Output:

--- old.txt
+++ new.txt
@@ -1,3 +1,3 @@
 line1
-line2
+line2 modified
 line3

Word-Level Diff

$result = Diff::build()
    ->withWordDiff()
    ->compare($old, $new);

HTML Output

use Alto\Code\Diff\Renderer\HtmlRenderer;

$renderer = new HtmlRenderer(
    showLineNumbers: true,
    wrapLines: false,
    classPrefix: 'diff-'
);

echo $renderer->render($result);

JSON Output

use Alto\Code\Diff\Renderer\JsonRenderer;

$renderer = new JsonRenderer(prettyPrint: true);
echo $renderer->render($result);

ANSI Side-by-Side Output

use Alto\Code\Diff\Renderer\AnsiSideBySideRenderer;

$renderer = new AnsiSideBySideRenderer(
    showLineNumbers: true,
    width: 120
);

echo $renderer->render($result);

Configuration Options

Context Lines

Control how many unchanged lines to show around changes:

$result = Diff::build()
    ->contextLines(5)  // Default is 3
    ->compare($old, $new);

Ignore Whitespace

Ignore whitespace differences:

$result = Diff::build()
    ->ignoreWhitespace()
    ->compare($old, $new);

Size Limits

Set maximum input size (default 5MB):

$result = Diff::build()
    ->maxBytes(10_000_000)  // 10MB
    ->compare($old, $new);

Parsing and Applying Patches

Parse a Unified Diff

use Alto\Code\Diff\Patch\UnifiedParser;

$patch = <<<'PATCH'
--- old.txt
+++ new.txt
@@ -1,3 +1,3 @@
 line1
-line2
+line2 modified
 line3
PATCH;

$parser = new UnifiedParser();
$bundle = $parser->parse($patch);

foreach ($bundle->files() as $file) {
    echo "File: {$file->oldPath} -> {$file->newPath}\n";
    echo "Hunks: " . count($file->result->hunks()) . "\n";
}

Apply a Patch

use Alto\Code\Diff\Patch\PatchApplier;

$original = "line1\nline2\nline3\n";

$applier = new PatchApplier();
$patched = $applier->apply($original, $patch);

echo $patched;
// Output: line1\nline2 modified\nline3\n

Note: PatchApplier::apply() accepts a single-file patch. For multi-file diffs, parse the patch and call applyBundle() instead.

Apply Patch with Fuzz Factor

$applier = new PatchApplier(fuzz: 2);
$patched = $applier->apply($original, $patch);

Apply Patch to Multiple Files

use Alto\Code\Diff\Model\DiffBundle;

$files = [
    'file1.txt' => "content1\n",
    'file2.txt' => "content2\n",
];

$applier = new PatchApplier();
$patchedFiles = $applier->applyBundle($files, $bundle);

Emitting Unified Diffs

From DiffResult

use Alto\Code\Diff\Patch\UnifiedEmitter;

$result = Diff::build()->compare($old, $new);

$emitter = new UnifiedEmitter();
$patch = $emitter->emit($result);

From DiffBundle

use Alto\Code\Diff\Model\DiffBundle;
use Alto\Code\Diff\Model\DiffFile;

$files = [
    new DiffFile('file1.txt', 'file1.txt', $result1),
    new DiffFile('file2.txt', 'file2.txt', $result2),
];

$bundle = new DiffBundle($files);
$emitter = new UnifiedEmitter();
$patch = $emitter->emit($bundle);

Renderer Options

UnifiedRenderer

new UnifiedRenderer(
    oldLabel: 'a/file.txt',  // Label for old version
    newLabel: 'b/file.txt'   // Label for new version
);

HtmlRenderer

new HtmlRenderer(
    showLineNumbers: true,      // Show line numbers
    wrapLines: false,           // Wrap long lines
    classPrefix: 'diff-'        // CSS class prefix
);

JsonRenderer

new JsonRenderer(
    prettyPrint: true  // Format with indentation
);

AnsiSideBySideRenderer

new AnsiSideBySideRenderer(
    showLineNumbers: true,  // Show line numbers
    width: 120              // Terminal width
);

Advanced Usage

Custom Diff Engine

You can choose between the built-in engines or implement your own.

MyersDiffEngine (Default): Uses the O(ND) Myers algorithm. Best for most use cases, especially when differences are small.

LcsDiffEngine: Uses the standard O(MN) LCS algorithm. Enable it explicitly with ->withEngine(new LcsDiffEngine()) only for small inputs, because its quadratic memory footprint is intended for controlled, academic scenarios.

use Alto\Code\Diff\Engine\LcsDiffEngine;

$result = Diff::build()
    ->withEngine(new LcsDiffEngine())
    ->compare($old, $new);

Implementing a Custom Engine

use Alto\Code\Diff\Engine\DiffEngineInterface;

class MyCustomEngine implements DiffEngineInterface
{
    public function diff(string $old, string $new, Options $opts): DiffResult
    {
        // Custom implementation
    }
}

$result = Diff::build()
    ->withEngine(new MyCustomEngine())
    ->compare($old, $new);

Working with Git Patches

The library supports parsing git-style unified diffs with headers:

$patch = <<<'PATCH'
diff --git a/file.txt b/file.txt
index abcdef..123456 100644
--- a/file.txt
+++ b/file.txt
@@ -1,3 +1,3 @@
 line1
-line2
+line2 modified
 line3
PATCH;

$parser = new UnifiedParser();
$bundle = $parser->parse($patch);

// Access headers
$file = $bundle->files()[0];
$file->headers['diff'];   // 'diff --git a/file.txt b/file.txt'
$file->headers['index'];  // 'index abcdef..123456 100644'

Documentation

For more detailed information, please refer to the documentation in the docs/ directory:

Testing

Run the test suite:

vendor/bin/phpunit

Run tests with coverage:

vendor/bin/phpunit --coverage-text

License

This project is licensed under the MIT License.

About

Generate, render and apply code diffs. Myers algorithm, versatile rendering (HTML, JSON, ANSI), and full patching support.

Topics

Resources

License

Code of conduct

Stars

Watchers

Forks

Sponsor this project

 

Languages