Dave Jarvis' Repositories

git clone https://repo.autonoma.ca/repo/treetrek.git

Makes diff more diff-like

AuthorDave Jarvis <email>
Date2026-02-09 23:23:33 GMT-0800
Commit5c67d51458401daf67a26f7ce4ccc64c7f285080
Parent16107ce
DiffPage.php
$isMsg = false;
$headers = [];
-
foreach ($lines as $line) {
if ($line === '') { $isMsg = true; continue; }
echo '<div class="diff-container">';
-
foreach ($changes as $change) {
$this->renderFileDiff($change);
}
-
if (empty($changes)) {
echo '<div class="empty-state"><p>No changes detected.</p></div>';
}
-
echo '</div>';
echo '<div class="diff-content">';
echo '<table><tbody>';
-
- $lineOld = 1;
- $lineNew = 1;
foreach ($change['hunks'] as $line) {
+ if (isset($line['t']) && $line['t'] === 'gap') {
+ echo '<tr class="diff-gap"><td colspan="3">...</td></tr>';
+ continue;
+ }
+
$class = 'diff-ctx';
$char = ' ';
if ($line['t'] === '+') { $class = 'diff-add'; $char = '+'; }
if ($line['t'] === '-') { $class = 'diff-del'; $char = '-'; }
-
- // Calculate line numbers
- $numOld = ($line['t'] !== '+') ? $lineOld++ : '';
- $numNew = ($line['t'] !== '-') ? $lineNew++ : '';
echo '<tr class="' . $class . '">';
- echo '<td class="diff-num" data-num="' . $numOld . '"></td>';
- echo '<td class="diff-num" data-num="' . $numNew . '"></td>';
+ echo '<td class="diff-num" data-num="' . $line['no'] . '"></td>';
+ echo '<td class="diff-num" data-num="' . $line['nn'] . '"></td>';
echo '<td class="diff-code"><span class="diff-marker">' . $char . '</span>' . htmlspecialchars($line['l']) . '</td>';
echo '</tr>';
GitDiff.php
$parentHash = '';
- // Only diff against the first parent for now
if (preg_match('/^parent ([0-9a-f]{40})/m', $commitData, $matches)) {
$parentHash = $matches[1];
$isBinary = false;
- // Check New Content
if ($newSha) {
$f = new VirtualDiffFile($path, $newContent);
if ($f->isBinary()) $isBinary = true;
}
- // Check Old Content
if (!$isBinary && $oldSha) {
$f = new VirtualDiffFile($path, $oldContent);
private function calculateDiff($old, $new) {
+ // Normalize line endings to avoid "entire file changed" on CRLF vs LF
+ $old = str_replace("\r\n", "\n", $old);
+ $new = str_replace("\r\n", "\n", $new);
+
$oldLines = explode("\n", $old);
$newLines = explode("\n", $new);
$m = count($oldLines);
$n = count($newLines);
+ // LCS Algorithm
$start = 0;
while ($start < $m && $start < $n && $oldLines[$start] === $newLines[$start]) {
$ops = $this->computeLCS($oldSlice, $newSlice);
- $finalDiff = [];
+ // Convert Ops to Hunks with Context
+ $hunks = [];
+ $contextLines = 3;
+
+ // Add full context if file is small, otherwise use hunks
+ if (count($ops) === 0) {
+ // Identical? Return empty or single hunk of text?
+ // If createChange was called, SHAs differed. If content same, maybe just whitespace/normalization?
+ // Return empty implies no visual diff.
+ return [];
+ }
+
+ // Flatten ops into a stream of changes with line numbers
+ $stream = [];
+ // Prefix context
for ($i = 0; $i < $start; $i++) {
- $finalDiff[] = ['t' => ' ', 'l' => $oldLines[$i]];
+ $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $i + 1, 'nn' => $i + 1];
}
+
+ $currO = $start + 1;
+ $currN = $start + 1;
foreach ($ops as $op) {
- $finalDiff[] = $op;
+ if ($op['t'] === ' ') {
+ $stream[] = ['t' => ' ', 'l' => $op['l'], 'no' => $currO++, 'nn' => $currN++];
+ } elseif ($op['t'] === '-') {
+ $stream[] = ['t' => '-', 'l' => $op['l'], 'no' => $currO++, 'nn' => null];
+ } elseif ($op['t'] === '+') {
+ $stream[] = ['t' => '+', 'l' => $op['l'], 'no' => null, 'nn' => $currN++];
+ }
}
+ // Suffix context
for ($i = $m - $end; $i < $m; $i++) {
- $finalDiff[] = ['t' => ' ', 'l' => $oldLines[$i]];
+ $stream[] = ['t' => ' ', 'l' => $oldLines[$i], 'no' => $currO++, 'nn' => $currN++];
}
- return $finalDiff;
+ // Filter stream to create hunks
+ $finalLines = [];
+ $lastVisibleIndex = -1;
+ $streamLen = count($stream);
+
+ for ($i = 0; $i < $streamLen; $i++) {
+ $show = false;
+
+ // Is this line a change?
+ if ($stream[$i]['t'] !== ' ') {
+ $show = true;
+ } else {
+ // Check proximity to a change
+ // Look ahead
+ for ($j = 1; $j <= $contextLines; $j++) {
+ if (($i + $j) < $streamLen && $stream[$i + $j]['t'] !== ' ') {
+ $show = true;
+ break;
+ }
+ }
+ // Look behind
+ if (!$show) {
+ for ($j = 1; $j <= $contextLines; $j++) {
+ if (($i - $j) >= 0 && $stream[$i - $j]['t'] !== ' ') {
+ $show = true;
+ break;
+ }
+ }
+ }
+ }
+
+ if ($show) {
+ if ($lastVisibleIndex !== -1 && $i > $lastVisibleIndex + 1) {
+ // Gap detected
+ $finalLines[] = ['t' => 'gap'];
+ }
+ $finalLines[] = $stream[$i];
+ $lastVisibleIndex = $i;
+ }
+ }
+
+ return $finalLines;
}
}
-/**
- * Helper Class to check binary types on in-memory content.
- * Fixes recursion crash by storing name locally.
- */
class VirtualDiffFile extends File {
- private $content;
- private $vName; // Local storage for name since parent::$name is private
+ private $content;
+ private $vName;
- public function __construct($name, $content) {
- parent::__construct($name, '', '100644', 0, strlen($content));
- $this->vName = $name;
- $this->content = $content;
- }
+ public function __construct($name, $content) {
+ parent::__construct($name, '', '100644', 0, strlen($content));
+ $this->vName = $name;
+ $this->content = $content;
+ }
- public function isBinary(): bool {
- // Use local $this->vName to avoid accessing private parent::$name
- $buffer = substr($this->content, 0, 12);
- return MediaTypeSniffer::isBinary($buffer, $this->vName);
- }
+ public function isBinary(): bool {
+ $buffer = substr($this->content, 0, 12);
+ return MediaTypeSniffer::isBinary($buffer, $this->vName);
+ }
}
repo.css
}
+.diff-gap {
+ background: #0d1117;
+ color: #484f58;
+ text-align: center;
+ font-size: 0.8em;
+ height: 20px;
+}
+.diff-gap td {
+ padding: 0;
+ line-height: 20px;
+ background: rgba(110, 118, 129, 0.1);
+}
+
.status-add { color: #58a6ff; }
.status-del { color: #d29922; }
Delta106 lines added, 37 lines removed, 69-line increase