login/commits: syntax highlighting of diff lines
authorMischa POSLAWSKY <perl@shiar.org>
Fri, 15 Jan 2021 17:41:47 +0000 (18:41 +0100)
committerMischa POSLAWSKY <perl@shiar.org>
Tue, 27 Apr 2021 00:48:25 +0000 (02:48 +0200)
Parse chunks returned by git's porcelain word diffing interface,
formatting old/new columns similar to split github commit output.
Ignore strict line numbers in favour of minimal text comparison.

login/commits/index.php

index c53bf39ad6b267a9fcc82127ed6893c6b96abc45..dbd8cd9f46055fa6440a42533a76d52d6fd68902 100644 (file)
@@ -9,20 +9,119 @@ if (!$hash) {
        return TRUE;
 }
 
-print "<h2>Wijzigingen in $hash</h2>\n";
+$this->title = "Wijzigingen in ".strtoupper($hash);
+print "<h2>{$this->title}</h2>\n";
 
-$gitcmd = "git show ".$hash;
+$gitcmd = "git show "
+       . "--word-diff=porcelain --no-prefix --pretty='%H%n%at\t%an\t%w(0,0,1)%B' "
+       . escapeshellarg($hash);
 $log = popen($gitcmd, 'r');
-if (!$log or strpos(fgets($log), "commit $hash") !== 0) {
+if (!$log or strpos(fgets($log), $hash) !== 0) {
        $Page->place['warn'] = "Kon inhoud niet ophalen met <code>$gitcmd</code>";
        return;
 }
 
-print '<pre>';
+# read metadata and commit message
+list ($atime, $author, $msg) = explode("\t", fgets($log), 3);
 while ( $line = fgets($log) ) {
-       print htmlspecialchars($line);
+       if ($line == "\n") {
+               fgets($log); // assume another empty line
+               break;
+       }
+       $msg .= substr($line, 1);
 }
-print "</pre>\n";
 
+# commit head
+print '<p>';
+printf('<small class="date">%s • %s</small>',
+       htmlspecialchars($author), strftime('%F %H:%M', $atime)
+);
+print "\n".nl2br(htmlspecialchars($msg));
+print "</p>\n";
+
+print '<style>
+.diff {
+       white-space: pre-wrap;
+       white-space: break-spaces;
+       font-family: monospace;
+}
+td:first-of-type.change,
+del {
+       background: #F002;
+       text-decoration: none;
+}
+td:last-of-type.change,
+ins {
+       background: #0F02;
+       text-decoration: none;
+}
+.diff .head {
+       background: #00F2;
+}
+</style>';
+
+# body
+$row = $ln = NULL;
+$col = ['', ''];
+print '<table class="diff">';
+while ( $line = fgets($log) ) {
+       preg_match(isset($ln) ? '/^(\W|\S+ )(.*)/' : '/^(\S+ )(.*)/', $line, $part);
+       $body = htmlspecialchars($part[2]);
+       switch ($part[1]) {
+       case 'diff ':
+               # file start
+               if (preg_match('/^--git (.+) (.*)/', $part[2], $diffhead)) {
+                       $row = "$diffhead[1]";
+                       if ($diffhead[1] !== $diffhead[2]) {
+                               $row .= " → $diffhead[2]";
+                       }
+               }
+               else {
+                       $row = '?';
+               }
+               $ln = NULL;
+               break;
+       case '@@ ':
+               # chunk start
+               if (preg_match('/^[-](\d+)(?:,\d+)? [+](\d+)(?:,\d+)? @@/', $part[2], $diffstart)) {
+                       array_shift($diffstart);
+                       $ln = $diffstart;
+               }
+               else {
+                       $ln = ['?', '?']; # unrecognised diff header
+               }
+               print '<tr class="head"><td colspan="4">'.$row.'</tr>'."\n";
+               break;
+       case '-':
+               $col[0] .= "<del>$body</del>";
+               break;
+       case '+':
+               $col[1] .= "<ins>$body</ins>";
+               break;
+       case ' ':
+               $col[0] .= $body;
+               $col[1] .= $body;
+               break;
+       case '~':
+               # part end
+               print '<tr>';
+               foreach ($col as $i => $line) {
+                       if (empty($line)) {
+                               print '<th><td>';
+                               continue;
+                       }
+                       printf('<th>%s</th><td%s>%s</td>',
+                               $ln[$i]++,
+                               $col[0] == $col[1] ? '' : ' class="change"',
+                               $line
+                       );
+               }
+               print "</tr>\n";
+               $col = ['', ''];
+               break;
+       }
+}
+print "</table>\n";
 pclose($log);
+
 return;