| | echo '<div class="refs-list">'; |
| | |
| | - $hasTags = false; |
| | - $repoParam = '&repo=' . urlencode($this->currentRepo['safe_name']); |
| | - |
| | - $this->git->eachTag(function($name, $sha) use ($repoParam, &$hasTags) { |
| | - $hasTags = true; |
| | + $tags = []; |
| | |
| | - // Read the object to peel tags and get dates |
| | + // 1. Collect tags and parse dates |
| | + $this->git->eachTag(function($name, $sha) use (&$tags) { |
| | $data = $this->git->read($sha); |
| | $targetSha = $sha; |
| | $timestamp = 0; |
| | |
| | - // Check if Annotated Tag (starts with 'object <sha>') |
| | + // Check if Annotated Tag |
| | 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]; |
| | - } |
| | + if (preg_match('/^object ([0-9a-f]{40})$/m', $data, $matches)) { |
| | + $targetSha = $matches[1]; |
| | + } |
| | + if (preg_match('/^tagger .* (\d+) [+\-]\d{4}$/m', $data, $matches)) { |
| | + $timestamp = (int)$matches[1]; |
| | + } |
| | } |
| | - // Lightweight Tag (Commit object, starts with 'tree <sha>') |
| | + // Lightweight Tag |
| | else { |
| | - // Extract Author Date |
| | - if (preg_match('/^author .* (\d+) [+\-]\d{4}$/m', $data, $matches)) { |
| | - $timestamp = (int)$matches[1]; |
| | - } |
| | + if (preg_match('/^author .* (\d+) [+\-]\d{4}$/m', $data, $matches)) { |
| | + $timestamp = (int)$matches[1]; |
| | + } |
| | } |
| | |
| | - $dateStr = $timestamp ? date('Y-M-d', $timestamp) : ''; |
| | + $tags[] = [ |
| | + 'name' => $name, |
| | + 'sha' => $sha, |
| | + 'targetSha' => $targetSha, |
| | + 'timestamp' => $timestamp |
| | + ]; |
| | + }); |
| | |
| | - // Links use the Target SHA (Commit) to avoid "Binary file" errors |
| | - $filesUrl = '?hash=' . $targetSha . $repoParam; |
| | - $commitUrl = '?action=commit&hash=' . $targetSha . $repoParam; |
| | + // 2. Sort by date descending (newest first) |
| | + usort($tags, function($a, $b) { |
| | + return $b['timestamp'] <=> $a['timestamp']; |
| | + }); |
| | |
| | - echo '<div class="ref-item">'; |
| | - echo '<span class="ref-type tag">Tag</span>'; |
| | - echo '<a href="' . $filesUrl . '" class="ref-name">' . htmlspecialchars($name) . '</a>'; |
| | + // 3. Render |
| | + if (empty($tags)) { |
| | + echo '<div class="empty-state"><p>No tags found.</p></div>'; |
| | + } else { |
| | + $repoParam = '&repo=' . urlencode($this->currentRepo['safe_name']); |
| | |
| | - if ($dateStr) { |
| | - echo '<span class="commit-date" style="margin-left: auto; margin-right: 15px;">' . $dateStr . '</span>'; |
| | - } |
| | + 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; |
| | |
| | - // We display the Tag SHA, but link to the Commit SHA |
| | - echo '<a href="' . $commitUrl . '" class="commit-hash" ' . (!$dateStr ? 'style="margin-left: auto;"' : '') . '>' . substr($sha, 0, 7) . '</a>'; |
| | - echo '</div>'; |
| | - }); |
| | + echo '<div class="ref-item">'; |
| | + echo '<span class="ref-type tag">Tag</span>'; |
| | + echo '<a href="' . $filesUrl . '" class="ref-name">' . htmlspecialchars($tag['name']) . '</a>'; |
| | |
| | - if (!$hasTags) { |
| | - echo '<div class="empty-state"><p>No tags found.</p></div>'; |
| | + if ($dateStr) { |
| | + echo '<span class="commit-date" style="margin-left: auto; margin-right: 15px;">' . $dateStr . '</span>'; |
| | + } |
| | + |
| | + echo '<a href="' . $commitUrl . '" class="commit-hash" ' . (!$dateStr ? 'style="margin-left: auto;"' : '') . '>' . substr($tag['sha'], 0, 7) . '</a>'; |
| | + echo '</div>'; |
| | + } |
| | } |
| | |