1package web
2
3import (
4 "net/http"
5 "strings"
6 "time"
7
8 "github.com/charmbracelet/log/v2"
9 "github.com/charmbracelet/soft-serve/git"
10 "github.com/charmbracelet/soft-serve/pkg/config"
11 "github.com/charmbracelet/soft-serve/pkg/proto"
12 "github.com/gorilla/mux"
13)
14
15// TagData contains data for rendering individual tag view.
16type TagData struct {
17 RepoBaseData
18 Ref *git.Reference
19 Tag *git.Tag
20 Commit *git.Commit
21 TagDate time.Time
22}
23
24func repoTag(w http.ResponseWriter, r *http.Request) {
25 ctx := r.Context()
26 logger := log.FromContext(ctx)
27 cfg := config.FromContext(ctx)
28 repo := proto.RepositoryFromContext(ctx)
29 vars := mux.Vars(r)
30 tagName := vars["tag"]
31
32 gr, err := openRepository(repo)
33 if err != nil {
34 logger.Debug("failed to open repository", "repo", repo.Name(), "err", err)
35 renderInternalServerError(w, r)
36 return
37 }
38
39 // Check if tag exists
40 if !gr.HasTag(tagName) {
41 logger.Debug("tag not found", "repo", repo.Name(), "tag", tagName)
42 renderNotFound(w, r)
43 return
44 }
45
46 // Get all references and find our tag
47 refs, err := gr.References()
48 if err != nil {
49 logger.Debug("failed to get references", "repo", repo.Name(), "err", err)
50 renderInternalServerError(w, r)
51 return
52 }
53
54 var ref *git.Reference
55 for _, r := range refs {
56 if r.IsTag() && r.Name().Short() == tagName {
57 ref = r
58 break
59 }
60 }
61
62 if ref == nil {
63 logger.Debug("tag reference not found", "repo", repo.Name(), "tag", tagName)
64 renderNotFound(w, r)
65 return
66 }
67
68 var tag *git.Tag
69 var commit *git.Commit
70 var tagDate time.Time
71
72 // Try to get annotated tag
73 tag, err = gr.Tag(tagName)
74 if err == nil && tag != nil {
75 // Annotated tag - get tagger date
76 if tagger := tag.Tagger(); tagger != nil {
77 tagDate = tagger.When
78 }
79 // Get the commit the tag points to via the hash
80 commitHash := ref.ID
81 commit, err = gr.CatFileCommit(commitHash)
82 if err != nil {
83 logger.Debug("failed to get commit from tag", "repo", repo.Name(), "tag", tagName, "err", err)
84 renderInternalServerError(w, r)
85 return
86 }
87 } else {
88 // Lightweight tag - points directly to commit
89 commit, err = gr.CatFileCommit(ref.ID)
90 if err != nil {
91 logger.Debug("failed to get commit", "repo", repo.Name(), "tag", tagName, "err", err)
92 renderNotFound(w, r)
93 return
94 }
95 // Use commit author date for lightweight tags
96 tagDate = commit.Author.When
97 }
98
99 // Fallback to commit date if tag date not available
100 if tagDate.IsZero() && commit != nil {
101 tagDate = commit.Author.When
102 }
103
104 defaultBranch := getDefaultBranch(gr)
105
106 repoDisplayName := repo.ProjectName()
107 if repoDisplayName == "" {
108 repoDisplayName = repo.Name()
109 }
110
111 // Build description from tag message or commit subject
112 description := ""
113 if tag != nil && tag.Message() != "" {
114 description = tag.Message()
115 } else if commit != nil {
116 lines := strings.Split(commit.Message, "\n")
117 if len(lines) > 0 {
118 description = lines[0]
119 }
120 }
121 if len(description) > 200 {
122 description = description[:200] + "..."
123 }
124
125 data := TagData{
126 RepoBaseData: RepoBaseData{
127 BaseData: BaseData{
128 ServerName: cfg.Name,
129 ActiveTab: "tags",
130 Title: tagName + " | " + repoDisplayName,
131 Description: description,
132 },
133 Repo: repo,
134 DefaultBranch: defaultBranch,
135 },
136 Ref: ref,
137 Tag: tag,
138 Commit: commit,
139 TagDate: tagDate,
140 }
141
142 renderHTML(w, "tag.html", data)
143}