| | <?php |
| | -error_reporting(E_ALL); |
| | -ini_set('display_errors', 1); |
| | - |
| | -define('REPOS_PATH', '/home/autonoma/repos'); |
| | -define('SITE_TITLE', 'Dave Jarvisβ Repositories'); |
| | - |
| | -// --- CACHING SETUP --- |
| | -define('CACHE_DIR', __DIR__ . '/cache'); |
| | -define('CACHE_EXPIRY', 3600); // 1 hour in seconds (you requested at least an hour) |
| | - |
| | -if (!is_dir(CACHE_DIR)) { |
| | - mkdir(CACHE_DIR, 0777, true); |
| | -} |
| | -// --- END CACHING SETUP --- |
| | - |
| | -/** |
| | - * Executes a Git command and caches the output. |
| | - * @param string $repo The repository name. |
| | - * @param string $command The Git command arguments. |
| | - * @return string The command output. |
| | - */ |
| | -function execGitCached($repo, $command) { |
| | - // 1. Generate a unique key for the cache file based on repo and command |
| | - $cache_key = md5($repo . '|' . $command); |
| | - $cache_file = CACHE_DIR . '/' . $cache_key . '.cache'; |
| | - |
| | - // 2. Check if the cache file exists and is still valid |
| | - if (file_exists($cache_file) && (time() - filemtime($cache_file) < CACHE_EXPIRY)) { |
| | - return file_get_contents($cache_file); |
| | - } |
| | - |
| | - // 3. If no valid cache, execute the command (using the original execGit logic) |
| | - $repoPath = REPOS_PATH . '/' . basename($repo); |
| | - $isBare = is_file($repoPath . '/HEAD') && !is_dir($repoPath . '/.git'); |
| | - |
| | - if ($isBare) { |
| | - $cmd = "git --git-dir=" . escapeshellarg($repoPath) . " " . $command . " 2>&1"; |
| | - } else { |
| | - if (!is_dir($repoPath . '/.git')) { |
| | - // Cannot execute command without a .git directory for non-bare repos |
| | - $output = ''; |
| | - } else { |
| | - $cmd = "cd " . escapeshellarg($repoPath) . " && git " . $command . " 2>&1"; |
| | - } |
| | - } |
| | - |
| | - $output = isset($cmd) ? shell_exec($cmd) : ''; |
| | - |
| | - // 4. Save the new output to the cache file |
| | - if (!empty($output)) { |
| | - file_put_contents($cache_file, $output); |
| | - } |
| | - |
| | - return $output; |
| | -} |
| | - |
| | - |
| | -// The original execGit is kept (renamed to _execGitRaw) for functions that shouldn't be cached, |
| | -// like getBlobBinary (for raw downloads) or if needed for debugging, but we'll revert to |
| | -// the original name to be safe if a function wasn't updated. However, the 'blob' and 'commit' |
| | -// actions use unique hashes, so caching them is less effective unless done at the 'getBlob' level. |
| | -// Let's replace 'execGit' with 'execGitRaw' and update the functions. |
| | -function execGitRaw($repo, $command) { |
| | - $repoPath = REPOS_PATH . '/' . basename($repo); |
| | - // Check if it's a bare repo or regular repo |
| | - $isBare = is_file($repoPath . '/HEAD') && !is_dir($repoPath . '/.git'); |
| | - if ($isBare) { |
| | - $cmd = "git --git-dir=" . escapeshellarg($repoPath) . " " . $command . " 2>&1"; |
| | - } else { |
| | - if (!is_dir($repoPath . '/.git')) { |
| | - return ''; |
| | - } |
| | - $cmd = "cd " . escapeshellarg($repoPath) . " && git " . $command . " 2>&1"; |
| | - } |
| | - |
| | - return shell_exec($cmd); |
| | -} |
| | - |
| | -// Map the new function name to the old one in all call sites. |
| | -function execGit($repo, $command) { |
| | - // For simplicity, we'll route all non-binary calls to the cached version. |
| | - // The functions using it below are the high-frequency ones we need to cache. |
| | - return execGitCached($repo, $command); |
| | -} |
| | - |
| | - |
| | -function getRepositories() { |
| | - $repos = []; |
| | - if (!is_dir(REPOS_PATH)) { |
| | - return $repos; |
| | - } |
| | - |
| | - $dirs = scandir(REPOS_PATH); |
| | - foreach ($dirs as $dir) { |
| | - if ($dir === '.' || $dir === '..') continue; |
| | - $path = REPOS_PATH . '/' . $dir; |
| | - // Check for both bare repos and regular repos |
| | - if (is_dir($path) && (is_dir($path . '/.git') || is_file($path . '/HEAD'))) { |
| | - $repos[] = $dir; |
| | - } |
| | - } |
| | - |
| | - // Load custom order from order.txt if it exists |
| | - $orderFile = __DIR__ . '/order.txt'; |
| | - if (file_exists($orderFile)) { |
| | - $order = array_filter(array_map('trim', file($orderFile))); |
| | - // Create an array with order positions |
| | - $orderMap = array_flip($order); |
| | - // Sort repos: ordered first, then alphabetically for the rest |
| | - usort($repos, function($a, $b) use ($orderMap) { |
| | - $aPos = isset($orderMap[$a]) ? $orderMap[$a] : PHP_INT_MAX; |
| | - $bPos = isset($orderMap[$b]) ? $orderMap[$b] : PHP_INT_MAX; |
| | - |
| | - if ($aPos === PHP_INT_MAX && $bPos === PHP_INT_MAX) { |
| | - |
| | - // Both not in order.txt, sort alphabetically |
| | - return strcmp($a, $b); |
| | - } |
| | - |
| | - return $aPos - $bPos; |
| | - }); |
| | - } else { |
| | - // No order file, just sort alphabetically |
| | - sort($repos); |
| | - } |
| | - |
| | - return $repos; |
| | -} |
| | - |
| | -function getRepoInfo($repo) { |
| | - $info = []; |
| | - $repoPath = REPOS_PATH . '/' . basename($repo); |
| | - |
| | - // Get description (This uses file system access, so no execGit needed) |
| | - $descFile = is_dir($repoPath . '/.git') ? $repoPath . '/.git/description' : $repoPath . '/description'; |
| | - $info['description'] = file_exists($descFile) ? trim(file_get_contents($descFile)) : 'No description'; |
| | - if ($info['description'] === 'Unnamed repository; edit this file \'description\' to name the repository.') { |
| | - $info['description'] = 'No description'; |
| | - } |
| | - |
| | - // Get last commit (Uses execGitCached) |
| | - // Note: The Git command for 'log -1' is cheap and frequently changing, but caching it |
| | - // for a short time (e.g., 5 mins) or 1 hour as requested, still helps with high traffic. |
| | - $log = execGitCached($repo, "log -1 --format='%H|%an|%ae|%at|%s'"); |
| | - if (!empty($log) && $log !== null) { |
| | - $parts = explode('|', trim($log)); |
| | - if (count($parts) >= 5) { |
| | - $info['last_commit'] = [ |
| | - 'hash' => $parts[0], |
| | - 'author' => $parts[1], |
| | - 'email' => $parts[2], |
| | - 'date' => $parts[3], |
| | - |
| | - 'message' => $parts[4] |
| | - ]; |
| | - } |
| | - } |
| | - |
| | - // Get branch count (Uses execGitCached) |
| | - $branches = execGitCached($repo, "branch -a"); |
| | - $info['branches'] = empty($branches) ? 0 : count(array_filter(explode("\n", $branches))); |
| | - |
| | - // Get tag count (Uses execGitCached) |
| | - $tags = execGitCached($repo, "tag"); |
| | - $info['tags'] = empty($tags) ? 0 : count(array_filter(explode("\n", $tags))); |
| | - |
| | - return $info; |
| | -} |
| | - |
| | -function getBranches($repo) { |
| | - // Uses execGitCached |
| | - $output = execGitCached($repo, "branch -a --format='%(refname:short)|%(committerdate:unix)|%(subject)'"); |
| | - $branches = []; |
| | - if (empty($output) || $output === null) { |
| | - return $branches; |
| | - } |
| | - foreach (explode("\n", trim($output)) as $line) { |
| | - if (empty($line)) continue; |
| | - $parts = explode('|', $line, 3); |
| | - if (count($parts) >= 3) { |
| | - $branches[] = [ |
| | - 'name' => $parts[0], |
| | - 'date' => $parts[1], |
| | - 'message' => $parts[2] |
| | - ]; |
| | - } |
| | - } |
| | - return $branches; |
| | -} |
| | - |
| | -function getCommits($repo, $branch = 'HEAD', $limit = 30) { |
| | - // Uses execGitCached (key includes branch and limit) |
| | - $output = execGitCached($repo, "log $branch -$limit --format='%H|%an|%ae|%at|%s'"); |
| | - $commits = []; |
| | - if (empty($output) || $output === null) { |
| | - return $commits; |
| | - } |
| | - foreach (explode("\n", trim($output)) as $line) { |
| | - if (empty($line)) continue; |
| | - $parts = explode('|', $line, 5); |
| | - if (count($parts) >= 5) { |
| | - $commits[] = [ |
| | - 'hash' => $parts[0], |
| | - 'author' => $parts[1], |
| | - 'email' => $parts[2], |
| | - 'date' => $parts[3], |
| | - |
| | - 'message' => $parts[4] |
| | - ]; |
| | - } |
| | - } |
| | - return $commits; |
| | -} |
| | - |
| | -function getCommitDetails($repo, $hash) { |
| | - // Commit details are unique by hash, but since we call it once per commit view, |
| | - // caching it for 1 hour is still useful if the same commit page is reloaded often. |
| | - $info = execGitCached($repo, "show --format='%H|%an|%ae|%at|%s|%b' --stat $hash"); |
| | - $diff = execGitCached($repo, "show --format='' $hash"); |
| | - $lines = explode("\n", $info); |
| | - $header = array_shift($lines); |
| | - $parts = explode('|', $header, 6); |
| | - return [ |
| | - 'hash' => $parts[0] ?? '', |
| | - 'author' => $parts[1] ?? '', |
| | - 'email' => $parts[2] ?? '', |
| | - 'date' => $parts[3] ?? '', |
| | - 'message' => $parts[4] ?? '', |
| | - 'body' => $parts[5] ?? '', |
| | - 'stat' => implode("\n", $lines), |
| | - 'diff' => $diff |
| | - ]; |
| | -} |
| | - |
| | -function getTree($repo, $ref = 'HEAD', $path = '') { |
| | - // This is the most complex one to cache because of HEAD resolution. |
| | - // The key generated by execGitCached will use the initial $ref and $path. |
| | - |
| | - // For bare repos, we need to resolve HEAD to an actual branch |
| | - if ($ref === 'HEAD') { |
| | - // Resolve symbolic-ref using a raw (uncached) or short-term cached call if necessary |
| | - // Sticking to execGitCached here for simplicity and to match the prompt's request for |
| | - // general process reduction, assuming the resolution of HEAD won't change often. |
| | - $defaultBranch = execGitCached($repo, "symbolic-ref HEAD"); |
| | - if (!empty($defaultBranch) && $defaultBranch !== null) { |
| | - $ref = trim(str_replace('refs/heads/', '', $defaultBranch)); |
| | - } else { |
| | - // Fallback to master or main |
| | - $branches = execGitCached($repo, "branch"); |
| | - if (!empty($branches)) { |
| | - $branchList = array_filter(array_map('trim', explode("\n", $branches))); |
| | - if (!empty($branchList)) { |
| | - $ref = trim(str_replace('* ', '', $branchList[0])); |
| | - } |
| | - } |
| | - } |
| | - } |
| | - |
| | - $fullPath = $path ? $ref . ':' . $path : $ref; |
| | - $cmd = "ls-tree " . escapeshellarg($fullPath); |
| | - // Uses execGitCached (key includes resolved ref and path) |
| | - $output = execGitCached($repo, $cmd); |
| | - $items = []; |
| | - if (empty($output) || $output === null) { |
| | - return $items; |
| | - } |
| | - |
| | - foreach (explode("\n", trim($output)) as $line) { |
| | - if (empty($line)) continue; |
| | - if (preg_match('/^(\d+)\s+(blob|tree)\s+([a-f0-9]+)\s+(.+)$/', $line, $matches)) { |
| | - $items[] = [ |
| | - 'mode' => $matches[1], |
| | - 'type' => $matches[2], |
| | - 'hash' => $matches[3], |
| | - 'name' => $matches[4] |
| | - |
| | - ]; |
| | - } |
| | - } |
| | - |
| | - usort($items, function($a, $b) { |
| | - if ($a['type'] !== $b['type']) { |
| | - return $a['type'] === 'tree' ? -1 : 1; |
| | - } |
| | - return strcmp($a['name'], $b['name']); |
| | - }); |
| | - return $items; |
| | -} |
| | - |
| | -function getBlob($repo, $hash) { |
| | - // Blob content is immutable by hash, so caching it is highly effective. |
| | - return execGitCached($repo, "cat-file blob $hash"); |
| | -} |
| | - |
| | -function getBlobBinary($repo, $hash) { |
| | - // The proc_open version is needed for binary data. This is a complex call, |
| | - // so we'll implement a cache check *before* calling the raw proc_open logic. |
| | - |
| | - // 1. Check Cache |
| | - $cache_key = md5($repo . '|blob_binary|' . $hash); |
| | - $cache_file = CACHE_DIR . '/' . $cache_key . '.cache'; |
| | - if (file_exists($cache_file) && (time() - filemtime($cache_file) < CACHE_EXPIRY)) { |
| | - return file_get_contents($cache_file); |
| | - } |
| | - |
| | - // 2. Execute raw command if no cache found |
| | - $repoPath = REPOS_PATH . '/' . basename($repo); |
| | - $isBare = is_file($repoPath . '/HEAD') && !is_dir($repoPath . '/.git'); |
| | - |
| | - if ($isBare) { |
| | - $cmd = "git --git-dir=" . escapeshellarg($repoPath) . " cat-file blob " . escapeshellarg($hash); |
| | - } else { |
| | - $cmd = "cd " . escapeshellarg($repoPath) . " && git cat-file blob " . escapeshellarg($hash); |
| | - } |
| | - |
| | - $descriptors = [ |
| | - 0 => ['pipe', 'r'], |
| | - 1 => ['pipe', 'w'], |
| | - 2 => ['pipe', 'w'] |
| | - ]; |
| | - $process = proc_open($cmd, $descriptors, $pipes); |
| | - if (is_resource($process)) { |
| | - fclose($pipes[0]); |
| | - $output = stream_get_contents($pipes[1]); |
| | - fclose($pipes[1]); |
| | - fclose($pipes[2]); |
| | - proc_close($process); |
| | - |
| | - // 3. Save to cache |
| | - if (!empty($output)) { |
| | - file_put_contents($cache_file, $output); |
| | - } |
| | - |
| | - return $output; |
| | - } |
| | - return ''; |
| | -} |
| | - |
| | -function isImageFile($filename) { |
| | - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
| | - return in_array($ext, ['png', 'jpg', 'jpeg', 'gif', 'svg', 'webp', 'bmp', 'ico']); |
| | -} |
| | - |
| | -function getImageMimeType($filename) { |
| | - $ext = strtolower(pathinfo($filename, PATHINFO_EXTENSION)); |
| | - $mimeTypes = [ |
| | - 'png' => 'image/png', |
| | - 'jpg' => 'image/jpeg', |
| | - 'jpeg' => 'image/jpeg', |
| | - 'gif' => 'image/gif', |
| | - 'svg' => 'image/svg+xml', |
| | - 'webp' => 'image/webp', |
| | - 'bmp' => 'image/bmp', |
| | - 'ico' => 'image/x-icon' |
| | - ]; |
| | - return $mimeTypes[$ext] ?? 'application/octet-stream'; |
| | -} |
| | - |
| | -function formatDate($timestamp) { |
| | - return date('M j, Y H:i', intval($timestamp)); |
| | -} |
| | - |
| | -function timeAgo($timestamp) { |
| | - $diff = time() - intval($timestamp); |
| | - |
| | - // If the difference is less than a minute |
| | - if ($diff < 60) { |
| | - return 'just now'; |
| | - } |
| | - |
| | - // Array of time units to iterate through, from largest to smallest |
| | - $units = [ |
| | - 31536000 => 'year', // Approx 365 days |
| | - 2592000 => 'month', // Approx 30 days |
| | - 604800 => 'week', // 7 days |
| | - 86400 => 'day', // 24 hours |
| | - 3600 => 'hour', // 60 minutes |
| | - 60 => 'minute', // 60 seconds |
| | - ]; |
| | +// Load configuration |
| | +require_once __DIR__ . '/config.php'; |
| | |
| | - foreach ($units as $seconds => $unit_name) { |
| | - if ($diff >= $seconds) { |
| | - $value = floor($diff / $seconds); |
| | - |
| | - // Single line of code for pluralization |
| | - $plural = ($value > 1) ? 's' : ''; |
| | - |
| | - return $value . ' ' . $unit_name . $plural . ' ago'; |
| | - } |
| | - } |
| | -} |
| | +// Load function libraries |
| | +require_once __DIR__ . '/includes/git_functions.php'; |
| | +require_once __DIR__ . '/includes/repo_functions.php'; |
| | +require_once __DIR__ . '/includes/helpers.php'; |
| | |
| | -// Routing |
| | +// Get request parameters |
| | $action = $_GET['action'] ?? 'list'; |
| | $repo = $_GET['repo'] ?? ''; |
| | $ref = $_GET['ref'] ?? 'HEAD'; |
| | $path = $_GET['path'] ?? ''; |
| | $hash = $_GET['hash'] ?? ''; |
| | |
| | -// Theme selection logic: sets the CSS file based on the 'theme' URL parameter |
| | +// Theme selection |
| | $current_theme = $_GET['theme'] ?? 'dark'; |
| | $css_file = ($current_theme === 'dark') ? 'dark.css' : 'light.css'; |
| | |
| | -// Handle raw download |
| | +// Handle raw file download |
| | if ($action === 'raw' && !empty($repo) && !empty($hash)) { |
| | $name = $_GET['name'] ?? 'file'; |
| | $content = getBlobBinary($repo, $hash); |
| | - |
| | + |
| | header('Content-Type: application/octet-stream'); |
| | header('Content-Disposition: attachment; filename="' . $name . '"'); |
| | header('Content-Length: ' . strlen($content)); |
| | echo $content; |
| | -exit; |
| | + exit; |
| | } |
| | |
| | -?> |
| | -<!DOCTYPE html> |
| | -<html lang="en"> |
| | -<head> |
| | - <meta charset="UTF-8"> |
| | - <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| | - <title><?php echo $action === 'list' ? SITE_TITLE : htmlspecialchars($repo) . ' - ' . SITE_TITLE; ?></title> |
| | - <link rel="stylesheet" href="<?php echo $css_file; ?>"> |
| | - <style> |
| | - .header { |
| | - display: flex; |
| | - justify-content: space-between; |
| | - align-items: center; |
| | - padding: 10px 20px; /* Assuming some padding */ |
| | - } |
| | - .theme-toggle a { |
| | - padding: 5px 10px; |
| | - border: 1px solid currentColor; /* Use a color visible on both themes */ |
| | - border-radius: 4px; |
| | - text-decoration: none; |
| | - /* Force contrast color: dark text on light, light text on dark */ |
| | - color: <?php echo ($current_theme === 'light') ? '#000000' : '#FFFFFF'; ?>; |
| | - background-color: <?php echo ($current_theme === 'light') ? '#F0F0F0' : '#333333'; ?>; |
| | - } |
| | - .theme-toggle a:hover { |
| | - opacity: 0.8; |
| | - } |
| | - </style> |
| | -</head> |
| | -<body> |
| | - <div class="header"> |
| | - <h1><a href="?<?php echo http_build_query(array_merge($_GET, ['theme' => $current_theme, 'action' => 'list', 'repo' => '', 'ref' => '', 'path' => '', 'hash' => ''])); ?>">π <?php echo SITE_TITLE; ?></a></h1> |
| | - |
| | - <div class="theme-toggle"> |
| | - <?php |
| | - // Build base query, preserving the current state but replacing 'theme' |
| | - $query_params = $_GET; |
| | - unset($query_params['theme']); |
| | - |
| | - // Get the link for the *opposite* theme |
| | - $opposite_theme = ($current_theme === 'light') ? 'dark' : 'light'; |
| | - $toggle_link = '?' . http_build_query(array_merge($query_params, ['theme' => $opposite_theme])); |
| | - ?> |
| | - <a href="<?php echo htmlspecialchars($toggle_link); ?>" class="btn-theme" title="Switch to <?php echo $opposite_theme; ?> Mode"> |
| | - <?php if ($current_theme === 'light'): ?> |
| | - π Dark |
| | - <?php else: ?> |
| | - βοΈLight |
| | - <?php endif; ?> |
| | - </a> |
| | - </div> |
| | - </div> |
| | - |
| | - <div class="container"> |
| | - <?php if ($action === 'list'): ?> |
| | - <?php $repos = getRepositories(); ?> |
| | - <?php if (empty($repos)): ?> |
| | - <div class="card"> |
| | - <div class="empty-state"> |
| | - <div class="empty-state-icon">π</div> |
| | - |
| | - <p>No repositories found in <?php echo htmlspecialchars(REPOS_PATH); ?></p> |
| | - </div> |
| | - </div> |
| | - <?php else: ?> |
| | - <ul class="repo-list"> |
| | - <?php foreach ($repos as $r): ?> |
| | - <?php $info = getRepoInfo($r); ?> |
| | - <li class="repo-item"> |
| | - <div class="repo-name"> |
| | - <a href="?action=repo&repo=<?php echo urlencode($r); ?>&theme=<?php echo $current_theme; ?>"><?php echo htmlspecialchars($r); ?></a> |
| | - </div> |
| | - <div class="repo-desc"><?php echo htmlspecialchars($info['description']); ?></div> |
| | - <div class="repo-meta"> |
| | - <?php if (isset($info['last_commit'])): ?> |
| | - <span>π <?php echo timeAgo($info['last_commit']['date']); ?></span> |
| | - <?php endif; ?> |
| | - </div> |
| | - </li> |
| | - <?php endforeach; ?> |
| | - </ul> |
| | - <?php endif; ?> |
| | - |
| | - <?php elseif ($action === 'repo'): ?> |
| | - <div class="breadcrumb"> |
| | - <a href="?theme=<?php echo $current_theme; ?>">Repositories</a> <span>/</span> <strong><?php echo htmlspecialchars($repo); ?></strong> |
| | - </div> |
| | - |
| | - <?php |
| | - $base_repo_link = "?action=repo&repo=" . urlencode($repo) . "&theme=" . $current_theme; |
| | - $view = $_GET['view'] ?? ''; |
| | - ?> |
| | - <div class="nav-tabs"> |
| | - <a href="<?php echo $base_repo_link; ?>" class="nav-tab <?php echo empty($view) ? 'active' : ''; ?>">π Commits</a> |
| | - <a href="<?php echo $base_repo_link; ?>&view=tree" class="nav-tab <?php echo $view === 'tree' ? 'active' : ''; ?>">π Files</a> |
| | - <a href="<?php echo $base_repo_link; ?>&view=branches" class="nav-tab <?php echo $view === 'branches' ? 'active' : ''; ?>">πΏ Branches</a> |
| | - </div> |
| | - |
| | - <?php if ($view === 'tree'): ?> |
| | - <?php $items = getTree($repo, $ref, $path); ?> |
| | - <div class="card"> |
| | - <div class="card-header"> |
| | - <?php if (empty($path)): ?> |
| | - Root directory |
| | - |
| | - <?php else: ?> |
| | - <?php echo htmlspecialchars($path); ?> |
| | - <?php endif; ?> |
| | - </div> |
| | - <ul class="file-tree"> |
| | - <?php if (!empty($path)): ?> |
| | - <li class="file-item"> |
| | - |
| | - <span class="file-icon">π</span> |
| | - <div class="file-name"> |
| | - <?php |
| | - |
| | - $parentPath = dirname($path); |
| | - if ($parentPath === '.' || $parentPath === '') { |
| | - $parentPath = ''; |
| | - } |
| | - ?> |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&view=tree&ref=<?php echo urlencode($ref); ?>&path=<?php echo urlencode($parentPath); ?>&theme=<?php echo $current_theme; ?>">../</a> |
| | - |
| | - </div> |
| | - </li> |
| | - <?php endif; ?> |
| | - <?php foreach ($items as $item): ?> |
| | - <li class="file-item"> |
| | - <span class="file-icon"><?php echo $item['type'] === 'tree' ? 'π' : 'π'; ?></span> |
| | - <div class="file-name"> |
| | - <?php if ($item['type'] === 'tree'): ?> |
| | - |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&view=tree&ref=<?php echo urlencode($ref); ?>&path=<?php echo urlencode($path . ($path ? '/' : '') . $item['name']); ?>&theme=<?php echo $current_theme; ?>"> |
| | - <?php echo htmlspecialchars($item['name']); ?>/ |
| | - </a> |
| | - <?php else: ?> |
| | - |
| | - <a href="?action=blob&repo=<?php echo urlencode($repo); ?>&hash=<?php echo urlencode($item['hash']); ?>&name=<?php echo urlencode($item['name']); ?>&theme=<?php echo $current_theme; ?>"> |
| | - <?php echo htmlspecialchars($item['name']); ?> |
| | - </a> |
| | - <?php endif; ?> |
| | - </div> |
| | - </li> |
| | - <?php endforeach; ?> |
| | - </ul> |
| | - </div> |
| | - |
| | - <?php elseif ($view === 'branches'): ?> |
| | - <?php $branches = getBranches($repo); ?> |
| | - <div class="card"> |
| | - <ul class="commit-list"> |
| | - <?php foreach ($branches as $branch): ?> |
| | - <li class="commit-item"> |
| | - |
| | - <div class="commit-message"> |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&ref=<?php echo urlencode($branch['name']); ?>&theme=<?php echo $current_theme; ?>"> |
| | - |
| | - πΏ <?php echo htmlspecialchars($branch['name']); ?> |
| | - </a> |
| | - <div class="commit-meta"> |
| | - |
| | - <?php echo htmlspecialchars($branch['message']); ?> β’ |
| | - <?php echo timeAgo($branch['date']); ?> |
| | - </div> |
| | - </div> |
| | - </li> |
| | - |
| | - <?php endforeach; ?> |
| | - </ul> |
| | - </div> |
| | - |
| | - <?php else: ?> |
| | - <?php $commits = getCommits($repo, $ref); ?> |
| | - <div class="card"> |
| | - <ul class="commit-list"> |
| | - <?php foreach ($commits as $commit): ?> |
| | - <li class="commit-item"> |
| | - |
| | - <div class="commit-message"> |
| | - <a href="?action=commit&repo=<?php echo urlencode($repo); ?>&hash=<?php echo urlencode($commit['hash']); ?>&theme=<?php echo $current_theme; ?>"> |
| | - |
| | - <?php echo htmlspecialchars($commit['message']); ?> |
| | - </a> |
| | - <div class="commit-meta"> |
| | - |
| | - <?php echo htmlspecialchars($commit['author']); ?> committed <?php echo timeAgo($commit['date']); ?> |
| | - </div> |
| | - </div> |
| | - |
| | - <div class="commit-hash"> |
| | - <a href="?action=commit&repo=<?php echo urlencode($repo); ?>&hash=<?php echo urlencode($commit['hash']); ?>&theme=<?php echo $current_theme; ?>"> |
| | - <?php echo substr($commit['hash'], 0, 7); ?> |
| | - </a> |
| | - </div> |
| | - </li> |
| | - |
| | - <?php endforeach; ?> |
| | - </ul> |
| | - </div> |
| | - <?php endif; ?> |
| | - |
| | - <?php elseif ($action === 'commit'): ?> |
| | - <?php $commit = getCommitDetails($repo, $hash); ?> |
| | - <div class="breadcrumb"> |
| | - <a href="?theme=<?php echo $current_theme; ?>">Repositories</a> <span>/</span> |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&theme=<?php echo $current_theme; ?>"><?php echo htmlspecialchars($repo); ?></a> <span>/</span> |
| | - <strong>Commit <?php echo substr($hash, 0, 7); ?></strong> |
| | - </div> |
| | - |
| | - <div class="card"> |
| | - <div class="commit-detail"> |
| | - <div class="commit-title"><?php echo htmlspecialchars($commit['message']); ?></div> |
| | - <?php if (!empty($commit['body'])): ?> |
| | - <p style="color: #586069; margin-bottom: 1.5rem;"><?php echo nl2br(htmlspecialchars($commit['body'])); ?></p> |
| | - <?php endif; ?> |
| | - |
| | - <div class="commit-info"> |
| | - <div class="commit-info-row"> |
| | - <div class="commit-info-label">Author:</div> |
| | - |
| | - <div><?php echo htmlspecialchars($commit['author']); ?> <<?php echo htmlspecialchars($commit['email']); ?>></div> |
| | - </div> |
| | - <div class="commit-info-row"> |
| | - <div class="commit-info-label">Date:</div> |
| | - |
| | - <div><?php echo formatDate($commit['date']); ?> (<?php echo timeAgo($commit['date']); ?>)</div> |
| | - </div> |
| | - <div class="commit-info-row"> |
| | - <div class="commit-info-label">Commit:</div> |
| | - |
| | - <div style="font-family: monospace;"><?php echo htmlspecialchars($commit['hash']); ?></div> |
| | - </div> |
| | - </div> |
| | - |
| | - <?php if (!empty($commit['stat'])): ?> |
| | - |
| | - <div class="code-block" style="margin-bottom: 1.5rem;"> |
| | - <pre><?php echo htmlspecialchars($commit['stat']); ?></pre> |
| | - </div> |
| | - <?php endif; ?> |
| | - |
| | - <?php if (!empty($commit['diff'])): ?> |
| | - <div class="code-block"> |
| | - <div class="diff"> |
| | - |
| | - <?php foreach (explode("\n", $commit['diff']) as $line): ?> |
| | - <?php |
| | - |
| | - $class = ''; |
| | - if (substr($line, 0, 1) === '+' && substr($line, 0, 3) !== '+++') { |
| | - $class = 'diff-add'; |
| | - } elseif (substr($line, 0, 1) === '-' && substr($line, 0, 3) !== '---') { |
| | - $class = 'diff-del'; |
| | - } elseif (substr($line, 0, 2) === '@@' || substr($line, 0, 4) === 'diff') { |
| | - $class = 'diff-header'; |
| | - } |
| | - ?> |
| | - <div class="diff-line <?php echo $class; ?>"><?php echo htmlspecialchars($line); ?></div> |
| | - <?php endforeach; ?> |
| | - </div> |
| | - </div> |
| | - <?php endif; ?> |
| | - </div> |
| | - </div> |
| | - |
| | - <?php elseif ($action === 'blob'): |
| | - $name = $_GET['name'] ?? 'file'; |
| | - $isImage = isImageFile($name); |
| | +// Set page title |
| | +$page_title = $action === 'list' ? SITE_TITLE : htmlspecialchars($repo) . ' - ' . SITE_TITLE; |
| | |
| | - // Check file size before loading |
| | - $repoPath = REPOS_PATH . '/' . basename($repo); |
| | - $isBare = is_file($repoPath . '/HEAD') && !is_dir($repoPath . '/.git'); |
| | +// Include header |
| | +include __DIR__ . '/views/header.php'; |
| | |
| | - if ($isBare) { |
| | - $sizeCmd = "git --git-dir=" . escapeshellarg($repoPath) . " cat-file -s " . escapeshellarg($hash); |
| | - } else { |
| | - $sizeCmd = "cd " . escapeshellarg($repoPath) . " && git cat-file -s " . escapeshellarg($hash); |
| | - } |
| | +// Route to appropriate view |
| | +switch ($action) { |
| | + case 'list': |
| | + include __DIR__ . '/views/list.php'; |
| | + break; |
| | |
| | - $fileSize = intval(trim(shell_exec($sizeCmd))); |
| | - $maxDisplaySize = 10 * 1024 * 1024; // 10MB limit for display |
| | - $tooLarge = $fileSize > $maxDisplaySize; |
| | - ?> |
| | - <div class="breadcrumb"> |
| | - <a href="?theme=<?php echo $current_theme; ?>">Repositories</a> <span>/</span> |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&theme=<?php echo $current_theme; ?>"><?php echo htmlspecialchars($repo); ?></a> <span>/</span> |
| | - <a href="?action=repo&repo=<?php echo urlencode($repo); ?>&view=tree&theme=<?php echo $current_theme; ?>">Files</a> <span>/</span> |
| | - <strong><?php echo htmlspecialchars($name); ?></strong> |
| | - </div> |
| | + case 'repo': |
| | + include __DIR__ . '/views/repo.php'; |
| | + break; |
| | |
| | - <div class="card"> |
| | - <div class="card-header"> |
| | - <?php echo htmlspecialchars($name); ?> |
| | - <span style="color: #666; font-size: 0.9em; margin-left: 10px;"> |
| | - (<?php echo number_format($fileSize / 1024, 2); ?> KB) |
| | - </span> |
| | - </div> |
| | - <div class="file-actions"> |
| | - <a href="?action=raw&repo=<?php echo urlencode($repo); ?>&hash=<?php echo urlencode($hash); ?>&name=<?php echo urlencode($name); ?>" class="btn" download> |
| | - π₯ Download |
| | - </a> |
| | - </div> |
| | + case 'commit': |
| | + include __DIR__ . '/views/commit.php'; |
| | + break; |
| | |
| | - <?php if ($tooLarge): ?> |
| | - <div class="empty-state"> |
| | - <div class="empty-state-icon">β οΈ</div> |
| | - <p>File is too large to display (<?php echo number_format($fileSize / 1024 / 1024, 2); ?> MB)</p> |
| | - <p>Please download it to view the contents.</p> |
| | - </div> |
| | - <?php elseif ($isImage): ?> |
| | - <div class="image-preview"> |
| | - <img src="data:<?php echo getImageMimeType($name); ?>;base64,<?php echo base64_encode(getBlobBinary($repo, $hash)); ?>" alt="<?php echo htmlspecialchars($name); ?>"> |
| | - </div> |
| | - <?php else: |
| | - $content = getBlob($repo, $hash); |
| | - // Check if content is empty OR if it is binary/not valid UTF-8 |
| | - if (empty($content) || mb_detect_encoding($content, 'UTF-8', true) === false): |
| | - ?> |
| | - <div class="empty-state"> |
| | - <div class="empty-state-icon">π¦</div> |
| | - <p>This appears to be a binary file.</p> |
| | - <p>Please download it to view the contents.</p> |
| | - </div> |
| | - <?php else: ?> |
| | - <div class="code-block"> |
| | - <pre><?php echo htmlspecialchars($content); ?></pre> |
| | - </div> |
| | - <?php endif; ?> |
| | - <?php endif; ?> |
| | - </div> |
| | - <?php endif; ?> |
| | - </div> |
| | -</body> |
| | -</html> |
| | + case 'blob': |
| | + include __DIR__ . '/views/blob.php'; |
| | + break; |
| | + |
| | + default: |
| | + include __DIR__ . '/views/list.php'; |
| | + break; |
| | +} |
| | + |
| | +// Include footer |
| | +include __DIR__ . '/views/footer.php'; |
| | |
| | |