webui_tag.go

  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}