Formats
Alto Code Diff exchanges changes as standard unified diffs and understands
common Git headers. A DiffResult represents one comparison; a DiffBundle
groups path-labelled results for multi-file patches. The package operates on
strings and associative arrays, while the application owns file-system access.
Emit a patch
UnifiedEmitter::emit() accepts a DiffResult or a multi-file DiffBundle.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Patch\UnifiedEmitter;
$result = Diff::build()->compare(
"Line one\nLine two\n",
"Line one\nLine two changed\n",
);
$patch = (new UnifiedEmitter())->emit($result);
echo $patch;A bare result uses a and b as labels. To control paths or represent multiple files, construct DiffFile objects and place them in a DiffBundle.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Model\DiffBundle;
use Alto\Code\Diff\Model\DiffFile;
use Alto\Code\Diff\Patch\UnifiedEmitter;
$result = Diff::build()->compare("old\n", "new\n");
$file = new DiffFile(
oldPath: 'src/example.txt',
newPath: 'src/example.txt',
result: $result,
headers: [
'diff' => 'diff --git a/src/example.txt b/src/example.txt',
'index' => 'index 3367afd..3e75765 100644',
],
);
echo (new UnifiedEmitter())->emit(new DiffBundle([$file]));Supported metadata keys are diff, index, old_mode, new_mode, new_file_mode, deleted_file_mode, similarity_index, rename_from, rename_to, copy_from, and copy_to.
Parse a patch
UnifiedParser::parse(string $patch): DiffBundle validates hunk lengths and returns files, paths, headers, results, hunks, and edits. Leading a/ and b/ path prefixes are removed.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Patch\UnifiedParser;
$patch = <<<'PATCH'
diff --git a/example.txt b/example.txt
index 3367afd..3e75765 100644
--- a/example.txt
+++ b/example.txt
@@ -1 +1 @@
-old
+new
PATCH;
$bundle = (new UnifiedParser())->parse($patch);
$file = $bundle->files()[0];
printf("%s -> %s\n", $file->oldPath, $file->newPath);The parser recognizes file modes, creation, deletion, rename, copy, similarity, and index headers. It preserves no-trailing-newline markers. See Errors for malformed hunks and binary patch markers.
Apply a single-file patch
PatchApplier::apply(string $original, string $unifiedPatch): string accepts exactly one patched file. An empty patch returns the original string.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Patch\PatchApplier;
$original = "Line one\nLine two\n";
$patch = <<<'PATCH'
--- a/example.txt
+++ b/example.txt
@@ -1,2 +1,2 @@
Line one
-Line two
+Line two changed
PATCH;
$updated = (new PatchApplier())->apply($original, $patch);
echo $updated;The constructor accepts fuzz and maxBytes, both defaulting to 0 and
5_000_000. Fuzz searches that many lines before and after a hunk's expected
position. It does not resolve conflicts or accept different source text.
Apply a bundle
Use applyBundle(array $files, DiffBundle $bundle): array for multiple files. The input and result use path => content maps.
The method handles modifications, renames, creations from /dev/null, and
deletions to /dev/null. The library returns updated content but never writes
it to disk. Missing paths and unmatched hunks are covered in Errors.
Round trip
This example emits, parses, and applies a patch while keeping both files in memory. The path keys are data; the package does not open them.
<?php
require __DIR__.'/vendor/autoload.php';
use Alto\Code\Diff\Diff;
use Alto\Code\Diff\Model\DiffBundle;
use Alto\Code\Diff\Model\DiffFile;
use Alto\Code\Diff\Patch\PatchApplier;
use Alto\Code\Diff\Patch\UnifiedEmitter;
use Alto\Code\Diff\Patch\UnifiedParser;
$files = ['a.txt' => "old\n", 'b.txt' => "keep\n"];
$change = new DiffFile(
'a.txt',
'a.txt',
Diff::build()->compare($files['a.txt'], "new\n"),
);
$patch = (new UnifiedEmitter())->emit(new DiffBundle([$change]));
$bundle = (new UnifiedParser())->parse($patch);
$updated = (new PatchApplier())->applyBundle($files, $bundle);
echo json_encode($updated, JSON_THROW_ON_ERROR), "\n";The output is:
{"a.txt":"new\n","b.txt":"keep\n"}