| | require_once 'Git.php'; |
| | require_once 'Config.php'; |
| | +require_once 'Router.php'; |
| | |
| | Config::init(); |
| | |
| | -// Initialize the "Host" Git object to scan for other repos |
| | $repoRoot = new Git(Config::getReposPath()); |
| | $repositories = []; |
| | - |
| | -// Populate local list using the iterator |
| | $repoRoot->eachRepository(function($repo) use (&$repositories) { |
| | $repositories[] = $repo; |
| | }); |
| | - |
| | -function getCurrentRepo($repositories) { |
| | - $requested = $_GET['repo'] ?? ''; |
| | - $decodedRequested = urldecode($requested); |
| | - |
| | - foreach ($repositories as $key => $repo) { |
| | - if ($repo['safe_name'] === $requested || $repo['name'] === $decodedRequested) { |
| | - return $repo; |
| | - } |
| | - } |
| | - return null; |
| | -} |
| | - |
| | -function sanitizePath($path) { |
| | - $path = str_replace(['..', '\\', "\0"], ['', '/', ''], $path); |
| | - return preg_replace('/[^a-zA-Z0-9_\-\.\/]/', '', $path); |
| | -} |
| | - |
| | -function time_elapsed_string($timestamp) { |
| | - if (!$timestamp) return 'never'; |
| | - |
| | - $diff = time() - $timestamp; |
| | - if ($diff < 5) return 'just now'; |
| | - |
| | - $tokens = [ |
| | - 31536000 => 'year', |
| | - 2592000 => 'month', |
| | - 604800 => 'week', |
| | - 86400 => 'day', |
| | - 3600 => 'hour', |
| | - 60 => 'minute', |
| | - 1 => 'second' |
| | - ]; |
| | - |
| | - foreach ($tokens as $unit => $text) { |
| | - if ($diff < $unit) continue; |
| | - $numberOfUnits = floor($diff / $unit); |
| | - return $numberOfUnits . ' ' . $text . (($numberOfUnits > 1) ? 's' : '') . ' ago'; |
| | - } |
| | - return 'just now'; |
| | -} |
| | - |
| | -$action = $_GET['action'] ?? 'home'; |
| | -$hash = sanitizePath($_GET['hash'] ?? ''); |
| | - |
| | -$currentRepo = getCurrentRepo($repositories); |
| | -$repoParam = $currentRepo ? '&repo=' . urlencode($currentRepo['safe_name']) : ''; |
| | -$git = $currentRepo ? new Git($currentRepo['path']) : null; |
| | - |
| | -?> |
| | -<!DOCTYPE html> |
| | -<html lang="en"> |
| | -<head> |
| | - <meta charset="UTF-8"> |
| | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | - <title><?php echo Config::SITE_TITLE; ?><?php echo $currentRepo ? ' - ' . htmlspecialchars($currentRepo['name']) : ''; ?></title> |
| | - <link rel="stylesheet" href="repo.css"> |
| | -</head> |
| | -<body> |
| | -<div class="container"> |
| | - <header> |
| | - <h1><?php echo Config::SITE_TITLE; ?></h1> |
| | - <nav class="nav"> |
| | - <a href="?">Home</a> |
| | - <?php if ($currentRepo): ?> |
| | - <a href="?repo=<?php echo urlencode($currentRepo['safe_name']); ?>">Files</a> |
| | - <a href="?action=commits<?php echo $repoParam; ?>">Commits</a> |
| | - <a href="?action=refs<?php echo $repoParam; ?>">Branches</a> |
| | - <?php endif; ?> |
| | - |
| | - <?php if ($currentRepo): ?> |
| | - <div class="repo-selector"> |
| | - <label>Repository:</label> |
| | - <select onchange="window.location.href='?repo=' + encodeURIComponent(this.value)"> |
| | - <option value="">Select repository...</option> |
| | - <?php foreach ($repositories as $repo): ?> |
| | - <option value="<?php echo htmlspecialchars($repo['safe_name']); ?>" <?php echo $repo['safe_name'] === $currentRepo['safe_name'] ? 'selected' : ''; ?>> |
| | - <?php echo htmlspecialchars($repo['name']); ?> |
| | - </option> |
| | - <?php endforeach; ?> |
| | - </select> |
| | - </div> |
| | - <?php endif; ?> |
| | - </nav> |
| | - |
| | - <?php if ($currentRepo): ?> |
| | - <div style="margin-top: 15px;"> |
| | - <span class="current-repo"> |
| | - Current: <strong><?php echo htmlspecialchars($currentRepo['name']); ?></strong> |
| | - </span> |
| | - </div> |
| | - <?php endif; ?> |
| | - </header> |
| | - |
| | - <?php |
| | - if (!$currentRepo) { |
| | - echo '<h2>Repositories</h2>'; |
| | - |
| | - if (empty($repositories)) { |
| | - echo '<div class="empty-state">No repositories found in ' . htmlspecialchars(Config::getReposPath()) . '</div>'; |
| | - } else { |
| | - echo '<div class="repo-grid">'; |
| | - foreach ($repositories as $repo) { |
| | - $repoGit = new Git($repo['path']); |
| | - // MOVED LOGIC: Uses Git::getMainBranch |
| | - $mainBranch = $repoGit->getMainBranch(); |
| | - |
| | - $branchCount = 0; |
| | - $repoGit->eachBranch(function() use (&$branchCount) { $branchCount++; }); |
| | - |
| | - $tagCount = 0; |
| | - $repoGit->eachTag(function() use (&$tagCount) { $tagCount++; }); |
| | - |
| | - echo '<a href="?repo=' . urlencode($repo['safe_name']) . '" class="repo-card">'; |
| | - echo '<h3>' . htmlspecialchars($repo['name']) . '</h3>'; |
| | - |
| | - if ($mainBranch) { |
| | - echo '<p>Branch: ' . htmlspecialchars($mainBranch['name']) . '</p>'; |
| | - } |
| | - |
| | - echo '<p>' . $branchCount . ' branches, ' . $tagCount . ' tags</p>'; |
| | - |
| | - $repoGit->history('HEAD', 1, function($commit) { |
| | - echo '<p style="margin-top: 8px; color: #58a6ff;">' . time_elapsed_string($commit->date) . '</p>'; |
| | - }); |
| | - |
| | - echo '</a>'; |
| | - } |
| | - echo '</div>'; |
| | - } |
| | - } else { |
| | - // MOVED LOGIC: Uses Git::getMainBranch |
| | - $mainBranch = $git->getMainBranch(); |
| | - |
| | - if (!$mainBranch) { |
| | - echo '<div class="empty-state">'; |
| | - echo '<h3>No branches found</h3>'; |
| | - echo '<p>This repository appears to be empty.</p>'; |
| | - echo '</div>'; |
| | - } elseif ($action === 'commits') { |
| | - echo '<div class="breadcrumb">'; |
| | - echo '<a href="?">Repositories</a>'; |
| | - echo '<span>/</span>'; |
| | - echo '<a href="?repo=' . urlencode($currentRepo['safe_name']) . '">' . htmlspecialchars($currentRepo['name']) . '</a>'; |
| | - echo '<span>/</span>'; |
| | - echo '<span>Commits</span>'; |
| | - echo '</div>'; |
| | - |
| | - echo '<h2>Commit History <span class="branch-badge">' . htmlspecialchars($mainBranch['name']) . '</span></h2>'; |
| | - |
| | - echo '<div class="commit-list">'; |
| | - $startSha = $hash ?: $mainBranch['hash']; |
| | - |
| | - $git->history($startSha, 100, function($commit) use ($repoParam) { |
| | - $msgParts = explode("\n", $commit->message); |
| | - $subject = $msgParts[0]; |
| | - |
| | - echo '<div class="commit-row">'; |
| | - echo '<a href="?action=commit&hash=' . $commit->sha . $repoParam . '" class="sha">' . substr($commit->sha, 0, 7) . '</a>'; |
| | - echo '<span class="message">' . htmlspecialchars($subject) . '</span>'; |
| | - echo '<span class="meta">'; |
| | - echo htmlspecialchars($commit->author) . ' • ' . date('Y-m-d H:i', $commit->date); |
| | - echo '</span>'; |
| | - echo '</div>'; |
| | - }); |
| | - echo '</div>'; |
| | - |
| | - } else { |
| | - $commitInfo = null; |
| | - $git->history($hash ?: $mainBranch['hash'], 1, function($c) use (&$commitInfo) { |
| | - $commitInfo = $c; |
| | - }); |
| | - |
| | - if (!$commitInfo) { |
| | - $git->history($mainBranch['hash'], 1, function($c) use (&$commitInfo) { |
| | - $commitInfo = $c; |
| | - }); |
| | - } |
| | - |
| | - $targetHash = $hash ?: $mainBranch['hash']; |
| | - $viewType = 'tree'; |
| | - $blobContent = ''; |
| | - $treeEntries = []; |
| | - |
| | - $git->walk($targetHash, function($entry) use (&$treeEntries) { |
| | - $e = (array)$entry; |
| | - $e['hash'] = $e['sha']; |
| | - $e['type'] = $e['isDir'] ? 'tree' : 'blob'; |
| | - $treeEntries[] = $e; |
| | - }); |
| | - |
| | - if (!empty($treeEntries)) { |
| | - $viewType = 'tree'; |
| | - usort($treeEntries, function($a, $b) { |
| | - if ($a['isDir'] !== $b['isDir']) return $a['isDir'] ? -1 : 1; |
| | - return strcasecmp($a['name'], $b['name']); |
| | - }); |
| | - } |
| | - elseif ($hash) { |
| | - $viewType = 'blob'; |
| | - $git->stream($targetHash, function($data) use (&$blobContent) { |
| | - $blobContent = $data; |
| | - }); |
| | - |
| | - if ($blobContent === null) { |
| | - echo '<div class="empty-state">Object not found</div>'; |
| | - $viewType = 'error'; |
| | - } |
| | - } |
| | - |
| | - if ($viewType === 'blob') { |
| | - echo '<div class="breadcrumb">'; |
| | - echo '<a href="?">Repositories</a>'; |
| | - echo '<span>/</span>'; |
| | - echo '<a href="?repo=' . urlencode($currentRepo['safe_name']) . '">' . htmlspecialchars($currentRepo['name']) . '</a>'; |
| | - echo '<span>/</span>'; |
| | - echo '<span>File</span>'; |
| | - echo '</div>'; |
| | - |
| | - echo '<h2>' . substr($targetHash, 0, 7) . '</h2>'; |
| | - |
| | - echo '<div class="blob-content">'; |
| | - echo '<div class="blob-header">' . strlen($blobContent) . ' bytes</div>'; |
| | - echo '<div class="blob-code">' . htmlspecialchars($blobContent) . '</div>'; |
| | - echo '</div>'; |
| | - } elseif ($viewType === 'tree') { |
| | - echo '<div class="breadcrumb">'; |
| | - echo '<a href="?">Repositories</a>'; |
| | - echo '<span>/</span>'; |
| | - echo '<a href="?repo=' . urlencode($currentRepo['safe_name']) . '">' . htmlspecialchars($currentRepo['name']) . '</a>'; |
| | - if ($hash) { |
| | - echo '<span>/</span>'; |
| | - echo '<span>Tree ' . substr($targetHash, 0, 7) . '</span>'; |
| | - } |
| | - echo '</div>'; |
| | - |
| | - echo '<h2>' . htmlspecialchars($currentRepo['name']) . ' <span class="branch-badge">' . htmlspecialchars($mainBranch['name']) . '</span></h2>'; |
| | - |
| | - if ($commitInfo) { |
| | - echo '<div class="commit-item" style="margin-bottom: 20px;">'; |
| | - echo '<div><a href="?action=commit&hash=' . $commitInfo->sha . $repoParam . '" class="commit-hash">' . substr($commitInfo->sha, 0, 7) . '</a></div>'; |
| | - echo '<div class="commit-message">' . htmlspecialchars(trim(explode("\n", $commitInfo->message)[0])) . '</div>'; |
| | - echo '<div class="commit-meta">'; |
| | - echo '<span class="commit-author">' . htmlspecialchars($commitInfo->author) . '</span>'; |
| | - echo ' committed on '; |
| | - echo '<span class="commit-date">' . date('Y-m-d H:i:s', $commitInfo->date) . '</span>'; |
| | - echo '</div>'; |
| | - echo '</div>'; |
| | - } |
| | - |
| | - echo '<h3>Files</h3>'; |
| | - |
| | - if (empty($treeEntries)) { |
| | - echo '<div class="empty-state">Empty directory</div>'; |
| | - } else { |
| | - echo '<div class="file-list">'; |
| | - foreach ($treeEntries as $entry) { |
| | - $icon = $entry['type'] === 'tree' ? '[dir]' : '[file]'; |
| | - $url = '?repo=' . urlencode($currentRepo['safe_name']) . '&hash=' . $entry['hash']; |
| | |
| | - echo '<a href="' . $url . '" class="file-item">'; |
| | - echo '<span class="file-mode">' . $entry['mode'] . '</span>'; |
| | - echo '<span class="file-name"><span class="' . ($entry['type'] === 'tree' ? 'dir-icon' : 'file-icon') . '">' . $icon . '</span> ' . htmlspecialchars($entry['name']) . '</span>'; |
| | - echo '</a>'; |
| | - } |
| | - echo '</div>'; |
| | - } |
| | - } |
| | - } |
| | - } |
| | - ?> |
| | -</div> |
| | -</body> |
| | -</html> |
| | +$router = new Router($repositories); |
| | +$page = $router->route(); |
| | +$page->render(); |
| | |
| | |