Dave Jarvis' Repositories

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

Adds tag renderer

AuthorDave Jarvis <email>
Date2026-02-10 19:12:35 GMT-0800
Commitc3267c94011e7e5a2678e4ac60f390376ce2ddb5
Parenteefd18e
Git.php
<?php
require_once 'File.php';
+require_once 'Tag.php';
require_once 'GitRefs.php';
require_once 'GitPacks.php';
public function eachTag( callable $callback ): void {
- $this->refs->scanRefs( 'refs/tags', $callback );
+ $this->refs->scanRefs( 'refs/tags', function($name, $sha) use ($callback) {
+ $data = $this->read($sha);
+
+ $targetSha = $sha;
+ $timestamp = 0;
+ $message = '';
+ $author = '';
+
+ // Determine if Annotated Tag or Lightweight Tag
+ if (strncmp($data, 'object ', 7) === 0) {
+ // Annotated Tag
+ if (preg_match('/^object ([0-9a-f]{40})$/m', $data, $m)) {
+ $targetSha = $m[1];
+ }
+ if (preg_match('/^tagger (.*) <.*> (\d+) [+\-]\d{4}$/m', $data, $m)) {
+ $author = trim($m[1]);
+ $timestamp = (int)$m[2];
+ }
+
+ $pos = strpos($data, "\n\n");
+ if ($pos !== false) {
+ $message = trim(substr($data, $pos + 2));
+ }
+ } else {
+ // Lightweight Tag (points directly to commit)
+ // We parse the commit data to get date/author
+ if (preg_match('/^author (.*) <.*> (\d+) [+\-]\d{4}$/m', $data, $m)) {
+ $author = trim($m[1]);
+ $timestamp = (int)$m[2];
+ }
+
+ $pos = strpos($data, "\n\n");
+ if ($pos !== false) {
+ $message = trim(substr($data, $pos + 2));
+ }
+ }
+
+ $callback(new Tag(
+ $name,
+ $sha,
+ $targetSha,
+ $timestamp,
+ $message,
+ $author
+ ));
+ });
}
Tag.php
+<?php
+require_once 'TagRenderer.php';
+
+class Tag {
+ private string $name;
+ private string $sha;
+ private string $targetSha;
+ private int $timestamp;
+ private string $message;
+ private string $author;
+
+ public function __construct(
+ string $name,
+ string $sha,
+ string $targetSha,
+ int $timestamp,
+ string $message,
+ string $author
+ ) {
+ $this->name = $name;
+ $this->sha = $sha;
+ $this->targetSha = $targetSha;
+ $this->timestamp = $timestamp;
+ $this->message = $message;
+ $this->author = $author;
+ }
+
+ public function compare(Tag $other): int {
+ return $other->timestamp <=> $this->timestamp;
+ }
+
+ public function render(TagRenderer $renderer): void {
+ $renderer->renderTagItem(
+ $this->name,
+ $this->sha,
+ $this->targetSha,
+ $this->timestamp,
+ $this->message,
+ $this->author
+ );
+ }
+}
TagRenderer.php
+<?php
+interface TagRenderer {
+ public function renderTagItem(
+ string $name,
+ string $sha,
+ string $targetSha,
+ int $timestamp,
+ string $message,
+ string $author
+ ): void;
+
+ public function renderTime(int $timestamp): void;
+}
+
+class HtmlTagRenderer implements TagRenderer {
+ private string $repoSafeName;
+
+ public function __construct(string $repoSafeName) {
+ $this->repoSafeName = $repoSafeName;
+ }
+
+ public function renderTagItem(
+ string $name,
+ string $sha,
+ string $targetSha,
+ int $timestamp,
+ string $message,
+ string $author
+ ): void {
+ $repoParam = '&repo=' . urlencode($this->repoSafeName);
+ $filesUrl = '?hash=' . $targetSha . $repoParam;
+ $commitUrl = '?action=commits&hash=' . $targetSha . $repoParam;
+
+ echo '<div class="ref-item tag-item">';
+
+ // Header: Name and Date
+ echo '<div class="ref-header">';
+ echo '<a href="' . $filesUrl . '" class="ref-name">';
+ echo '<i class="fas fa-tag"></i> ' . htmlspecialchars($name);
+ echo '</a>';
+
+ if ($timestamp > 0) {
+ echo '<span class="ref-date">';
+ $this->renderTime($timestamp);
+ echo '</span>';
+ }
+ echo '</div>';
+
+ // Body: Message
+ if ($message !== '') {
+ // Get first line only for brevity in list view
+ $firstLine = strtok($message, "\n");
+ echo '<div class="ref-message">' . htmlspecialchars($firstLine) . '</div>';
+ }
+
+ // Footer: SHA and Author
+ echo '<div class="ref-meta">';
+ echo '<a href="' . $commitUrl . '" class="commit-hash">' . substr($sha, 0, 7) . '</a>';
+
+ if ($author !== '') {
+ echo '<span class="ref-author"> by ' . htmlspecialchars($author) . '</span>';
+ }
+ echo '</div>';
+
+ echo '</div>';
+ }
+
+ public function renderTime(int $timestamp): void {
+ if (!$timestamp) {
+ echo 'never';
+ return;
+ }
+
+ $diff = time() - $timestamp;
+
+ if ($diff < 5) {
+ echo 'just now';
+ return;
+ }
+
+ $tokens = [
+ 31536000 => 'year',
+ 2592000 => 'month',
+ 604800 => 'week',
+ 86400 => 'day',
+ 3600 => 'hour',
+ 60 => 'minute',
+ 1 => 'second'
+ ];
+
+ foreach ($tokens as $unit => $text) {
+ if ($diff < $unit) continue;
+ $num = floor($diff / $unit);
+ echo $num . ' ' . $text . (($num > 1) ? 's' : '') . ' ago';
+ return;
+ }
+
+ echo 'just now';
+ }
+}
TagsPage.php
<?php
+require_once 'TagRenderer.php';
+
class TagsPage extends BasePage {
private $currentRepo;
$tags = [];
- $repoParam = '&repo=' . urlencode($this->currentRepo['safe_name']);
-
- // 1. Collect tags and parse dates
- $this->git->eachTag(function($name, $sha) use (&$tags) {
- // Read the object to peel tags and get dates
- $data = $this->git->read($sha);
- $targetSha = $sha;
- $timestamp = 0;
-
- // Check if Annotated Tag (starts with 'object <sha>')
- if (strncmp($data, 'object ', 7) === 0) {
- // Extract target SHA
- if (preg_match('/^object ([0-9a-f]{40})$/m', $data, $matches)) {
- $targetSha = $matches[1];
- }
- // Extract Tagger Date
- if (preg_match('/^tagger .* (\d+) [+\-]\d{4}$/m', $data, $matches)) {
- $timestamp = (int)$matches[1];
- }
- }
- // Lightweight Tag (Commit object)
- else {
- // Extract Author Date
- if (preg_match('/^author .* (\d+) [+\-]\d{4}$/m', $data, $matches)) {
- $timestamp = (int)$matches[1];
- }
- }
-
- $tags[] = [
- 'name' => $name,
- 'sha' => $sha,
- 'targetSha' => $targetSha,
- 'timestamp' => $timestamp
- ];
+ $this->git->eachTag(function(Tag $tag) use (&$tags) {
+ $tags[] = $tag;
});
- // 2. Sort by date descending (newest first)
- usort($tags, function($a, $b) {
- return $b['timestamp'] <=> $a['timestamp'];
+ usort($tags, function(Tag $a, Tag $b) {
+ return $a->compare($b);
});
- // 3. Render
+ $renderer = new HtmlTagRenderer($this->currentRepo['safe_name']);
+
if (empty($tags)) {
echo '<div class="empty-state"><p>No tags found.</p></div>';
} else {
foreach ($tags as $tag) {
- $dateStr = $tag['timestamp'] ? date('Y-M-d', $tag['timestamp']) : '';
- $filesUrl = '?hash=' . $tag['targetSha'] . $repoParam;
- $commitUrl = '?action=commit&hash=' . $tag['targetSha'] . $repoParam;
-
- echo '<div class="ref-item">';
- // "Tag" label removed here
- echo '<a href="' . $filesUrl . '" class="ref-name">' . htmlspecialchars($tag['name']) . '</a>';
-
- if ($dateStr) {
- echo '<span class="commit-date" style="margin-left: auto; margin-right: 15px;">' . $dateStr . '</span>';
- }
-
- // We display the Tag SHA, but link to the Commit SHA
- echo '<a href="' . $commitUrl . '" class="commit-hash" ' . (!$dateStr ? 'style="margin-left: auto;"' : '') . '>' . substr($tag['sha'], 0, 7) . '</a>';
- echo '</div>';
+ $tag->render($renderer);
}
}
Delta198 lines added, 54 lines removed, 144-line increase