<?php require_once __DIR__ . '/BasePage.php'; require_once __DIR__ . '/../git/GitDiff.php'; class DiffPage extends BasePage { private $currentRepo; private $git; private $hash; public function __construct( array $repositories, array $currentRepo, Git $git, string $hash ) { parent::__construct( $repositories ); $this->currentRepo = $currentRepo; $this->git = $git; $this->hash = $hash; $this->title = substr( $hash, 0, 7 ); } public function render() { $this->renderLayout( function() { $commitData = $this->git->read( $this->hash ); $diffEngine = new GitDiff( $this->git ); $lines = explode( "\n", $commitData ); $msg = ''; $isMsg = false; $headers = []; foreach( $lines as $line ) { if( $line === '' ) { $isMsg = true; continue; } if( $isMsg ) { $msg .= $line . "\n"; } else { if( preg_match( '/^(\w+) (.*)$/', $line, $m ) ) { $headers[$m[1]] = $m[2]; } } } $changes = $diffEngine->compare( $this->hash ); $this->renderBreadcrumbs(); $author = $headers['author'] ?? 'Unknown'; $author = preg_replace( '/<[^>]+>/', '<email>', $author ); echo '<div class="commit-details">'; echo '<div class="commit-header">'; echo '<h1 class="commit-title">' . htmlspecialchars( trim( $msg ) ) . '</h1>'; echo '<div class="commit-info">'; echo '<div class="commit-info-row"><span class="commit-info-label">Author</span>' . '<span class="commit-author">' . htmlspecialchars( $author ) . '</span></div>'; echo '<div class="commit-info-row"><span class="commit-info-label">Commit</span>' . '<span class="commit-info-value">' . $this->hash . '</span></div>'; if( isset( $headers['parent'] ) ) { $repoUrl = '&repo=' . urlencode( $this->currentRepo['safe_name'] ); echo '<div class="commit-info-row"><span class="commit-info-label">Parent</span>' . '<span class="commit-info-value">'; echo '<a href="?action=commit&hash=' . $headers['parent'] . $repoUrl . '" class="parent-link">' . substr( $headers['parent'], 0, 7 ) . '</a>'; echo '</span></div>'; } echo '</div></div></div>'; 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>'; }, $this->currentRepo ); } private function renderFileDiff( $change ) { $statusIcon = 'fa-file'; $statusClass = ''; if( $change['type'] === 'A' ) { $statusIcon = 'fa-plus-circle'; $statusClass = 'status-add'; } if( $change['type'] === 'D' ) { $statusIcon = 'fa-minus-circle'; $statusClass = 'status-del'; } if( $change['type'] === 'M' ) { $statusIcon = 'fa-pencil-alt'; $statusClass = 'status-mod'; } echo '<div class="diff-file">'; echo '<div class="diff-header">'; echo '<span class="diff-status ' . $statusClass . '">' . '<i class="fa ' . $statusIcon . '"></i></span>'; echo '<span class="diff-path">' . htmlspecialchars( $change['path'] ) . '</span>'; echo '</div>'; if( $change['is_binary'] ) { echo '<div class="diff-binary">Binary files differ</div>'; } else { echo '<div class="diff-content">'; echo '<table><tbody>'; 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 = '-'; } echo '<tr class="' . $class . '">'; 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>'; } echo '</tbody></table>'; echo '</div>'; } echo '</div>'; } private function renderBreadcrumbs() { $safeName = urlencode( $this->currentRepo['safe_name'] ); $crumbs = [ '<a href="?">Repositories</a>', '<a href="?repo=' . $safeName . '">' . htmlspecialchars( $this->currentRepo['name'] ) . '</a>', '<a href="?action=commits&repo=' . $safeName . '">Commits</a>', substr( $this->hash, 0, 7 ) ]; echo '<div class="breadcrumb">' . implode( ' / ', $crumbs ) . '</div>'; } }