main • app/services/DiffEngine.php
<?php
class DiffEngine
{
private BlobStore $blobs;
public function __construct(BlobStore $blobs) { $this->blobs = $blobs; }
public function diffSnapshots(array $a, array $b): array
{
$paths = array_unique(array_merge(array_keys($a), array_keys($b)));
sort($paths);
$changes = [];
foreach ($paths as $p) {
$inA = array_key_exists($p, $a);
$inB = array_key_exists($p, $b);
if (!$inA && $inB) $changes[] = ['path'=>$p,'type'=>'added','a'=>null,'b'=>$b[$p]];
elseif ($inA && !$inB) $changes[] = ['path'=>$p,'type'=>'deleted','a'=>$a[$p],'b'=>null];
elseif ($inA && $inB && $a[$p] !== $b[$p]) $changes[] = ['path'=>$p,'type'=>'modified','a'=>$a[$p],'b'=>$b[$p]];
}
return $changes;
}
public function lineDiff(string $old, string $new): array
{
$a = preg_split("/\r\n|\n|\r/", $old);
$b = preg_split("/\r\n|\n|\r/", $new);
$n = count($a); $m = count($b);
$dp = array_fill(0, $n+1, array_fill(0, $m+1, 0));
for ($i=$n-1; $i>=0; $i--) {
for ($j=$m-1; $j>=0; $j--) {
$dp[$i][$j] = ($a[$i] === $b[$j]) ? $dp[$i+1][$j+1] + 1 : max($dp[$i+1][$j], $dp[$i][$j+1]);
}
}
$i=0; $j=0; $out=[];
while ($i<$n && $j<$m) {
if ($a[$i] === $b[$j]) {
$out[] = ['type'=>'ctx','line'=>$a[$i]];
$i++; $j++;
} elseif ($dp[$i+1][$j] >= $dp[$i][$j+1]) {
$out[] = ['type'=>'del','line'=>$a[$i]];
$i++;
} else {
$out[] = ['type'=>'add','line'=>$b[$j]];
$j++;
}
}
while ($i<$n) { $out[]=['type'=>'del','line'=>$a[$i++]]; }
while ($j<$m) { $out[]=['type'=>'add','line'=>$b[$j++]]; }
return $out;
}
public function renderLineDiffHtml(array $diffLines): string
{
$h = '<pre class="diff mono" style="white-space:pre;overflow:auto;border:1px solid #e5e7eb;border-radius:10px;padding:10px;background:#0d1117;color:#c9d1d9;">';
foreach ($diffLines as $d) {
$line = htmlspecialchars($d['line'] ?? '', ENT_QUOTES, 'UTF-8');
if ($d['type'] === 'add') $h .= "<div style='color:#3fb950;'>+ {$line}</div>";
elseif ($d['type'] === 'del') $h .= "<div style='color:#ff7b72;'>- {$line}</div>";
else $h .= "<div style='color:#c9d1d9;'> {$line}</div>";
}
$h .= '</pre>';
return $h;
}
}