diff --git a/appview/repo/tags.go b/appview/repo/tags.go --- a/appview/repo/tags.go +++ b/appview/repo/tags.go @@ -4,11 +4,13 @@ import ( "encoding/json" "fmt" "net/http" + "net/url" "tangled.org/core/api/tangled" "tangled.org/core/appview/db" "tangled.org/core/appview/models" "tangled.org/core/appview/pages" + "tangled.org/core/appview/reporesolver" xrpcclient "tangled.org/core/appview/xrpcclient" "tangled.org/core/orm" "tangled.org/core/types" @@ -101,7 +103,31 @@ tag := chi.URLParam(r, "tag") xrpcBytes, err := tangled.RepoTag(r.Context(), xrpcc, repo, tag) if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { - l.Error("failed to call XRPC repo.tags", "err", xrpcerr) + // if we don't match an existing tag, and the tag we're trying + // to match is "latest", resolve to the most recent tag + if tag == "latest" { + tagsBytes, err := tangled.RepoTags(r.Context(), xrpcc, "", 1, repo) + if xrpcerr := xrpcclient.HandleXrpcErr(err); xrpcerr != nil { + l.Error("failed to call XRPC repo.tags for latest", "err", xrpcerr) + rp.pages.Error503(w) + return + } + var tagsResult types.RepoTagsResponse + if err := json.Unmarshal(tagsBytes, &tagsResult); err != nil { + l.Error("failed to decode XRPC response", "err", err) + rp.pages.Error503(w) + return + } + if len(tagsResult.Tags) == 0 { + rp.pages.Error503(w) + return + } + latestTag := tagsResult.Tags[0].Name + ownerSlashRepo := reporesolver.GetBaseRepoPath(r, f) + http.Redirect(w, r, fmt.Sprintf("/%s/tags/%s", ownerSlashRepo, url.PathEscape(latestTag)), http.StatusTemporaryRedirect) + return + } + l.Error("failed to call XRPC repo.tag", "err", xrpcerr) rp.pages.Error503(w) return } diff --git a/knotserver/git/tag_test.go b/knotserver/git/tag_test.go --- a/knotserver/git/tag_test.go +++ b/knotserver/git/tag_test.go @@ -353,6 +353,27 @@ require.NoError(s.T(), err) assert.Len(s.T(), tags, 5, "zero limit should return all tags") } +func (s *TagSuite) TestTags_OrderedNewestFirst() { + s.setupRepoWithTags() + + tags, err := s.repo.Tags(nil) + require.NoError(s.T(), err) + require.Len(s.T(), tags, 5) + + // v3.0.0 has the latest tagger date (baseTime+3h), should be first + assert.Equal(s.T(), "v3.0.0", tags[0].Name, "newest tag should be first") +} + +func (s *TagSuite) TestTags_LatestWithLimit1() { + s.setupRepoWithTags() + + tags, err := s.repo.Tags(&TagsOptions{Limit: 1}) + require.NoError(s.T(), err) + require.Len(s.T(), tags, 1) + + assert.Equal(s.T(), "v3.0.0", tags[0].Name, "limit=1 should return the newest tag") +} + func (s *TagSuite) TestTags_Pattern() { s.setupRepoWithTags()