diff --git a/pkg/web/templates/tag.html b/pkg/web/templates/tag.html new file mode 100644 index 0000000000000000000000000000000000000000..7170b3852478c502ea0b7cfcd7da0af7a19ddfb9 --- /dev/null +++ b/pkg/web/templates/tag.html @@ -0,0 +1,49 @@ +{{define "content"}} + + +
+
+

{{.Ref.Name.Short}}

+ {{if .Commit}} +

+ {{ $names := .Commit | attributionNames }} + {{ $nlen := len $names }} + {{ range $i, $n := $names }} + {{ if gt $i 0 }} + {{ if eq $nlen 2 }} and {{ else if eq $i (sub $nlen 1) }}, and {{ else }}, {{ end }} + {{ end }} + {{$n}} + {{ end }} + created +

+ {{end}} +
+ + + + {{if .Tag}} + {{$message := .Tag.Message}} + {{if $message}} +
{{$message}}
+ {{end}} + {{else if .Commit}} + {{$body := .Commit.Message | commitBody}} + {{if $body}} +
{{$body}}
+ {{end}} + {{end}} +
+{{end}} diff --git a/pkg/web/templates/tags.html b/pkg/web/templates/tags.html index b23163dacb506b7cabe530feda3363a2accbcdcd..7813f16f9a3ab01d3df229177e36d084dbe376ac 100644 --- a/pkg/web/templates/tags.html +++ b/pkg/web/templates/tags.html @@ -4,7 +4,7 @@ {{if .Tags}} {{range .Tags}}
-

{{.Ref.Name.Short}}

+

{{.Ref.Name.Short}}

{{if .TagMessage}}
{{.TagMessage}}
{{end}} diff --git a/pkg/web/webui.go b/pkg/web/webui.go index 51eb8ea96cc28071998d37f9ff95be50c93b6644..a0d7e51918c609b53896e91545fd621f2a007283 100644 --- a/pkg/web/webui.go +++ b/pkg/web/webui.go @@ -365,6 +365,10 @@ func WebUIController(ctx context.Context, r *mux.Router) { r.Handle(basePrefix+"/tags", withRepoVars(withWebUIAccess(http.HandlerFunc(repoTags)))). Methods(http.MethodGet) + // Tag detail route + r.Handle(basePrefix+"/tag/{tag:.+}", withRepoVars(withWebUIAccess(http.HandlerFunc(repoTag)))). + Methods(http.MethodGet) + // Repository overview (catch-all, must be last) r.Handle(basePrefix, withRepoVars(withWebUIAccess(http.HandlerFunc(repoOverview)))). Methods(http.MethodGet) diff --git a/pkg/web/webui_tag.go b/pkg/web/webui_tag.go new file mode 100644 index 0000000000000000000000000000000000000000..64baad5389b3726dfe7c8dba903ca9aa39aad97e --- /dev/null +++ b/pkg/web/webui_tag.go @@ -0,0 +1,143 @@ +package web + +import ( + "net/http" + "strings" + "time" + + "github.com/charmbracelet/log/v2" + "github.com/charmbracelet/soft-serve/git" + "github.com/charmbracelet/soft-serve/pkg/config" + "github.com/charmbracelet/soft-serve/pkg/proto" + "github.com/gorilla/mux" +) + +// TagData contains data for rendering individual tag view. +type TagData struct { + RepoBaseData + Ref *git.Reference + Tag *git.Tag + Commit *git.Commit + TagDate time.Time +} + +func repoTag(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + logger := log.FromContext(ctx) + cfg := config.FromContext(ctx) + repo := proto.RepositoryFromContext(ctx) + vars := mux.Vars(r) + tagName := vars["tag"] + + gr, err := openRepository(repo) + if err != nil { + logger.Debug("failed to open repository", "repo", repo.Name(), "err", err) + renderInternalServerError(w, r) + return + } + + // Check if tag exists + if !gr.HasTag(tagName) { + logger.Debug("tag not found", "repo", repo.Name(), "tag", tagName) + renderNotFound(w, r) + return + } + + // Get all references and find our tag + refs, err := gr.References() + if err != nil { + logger.Debug("failed to get references", "repo", repo.Name(), "err", err) + renderInternalServerError(w, r) + return + } + + var ref *git.Reference + for _, r := range refs { + if r.IsTag() && r.Name().Short() == tagName { + ref = r + break + } + } + + if ref == nil { + logger.Debug("tag reference not found", "repo", repo.Name(), "tag", tagName) + renderNotFound(w, r) + return + } + + var tag *git.Tag + var commit *git.Commit + var tagDate time.Time + + // Try to get annotated tag + tag, err = gr.Tag(tagName) + if err == nil && tag != nil { + // Annotated tag - get tagger date + if tagger := tag.Tagger(); tagger != nil { + tagDate = tagger.When + } + // Get the commit the tag points to via the hash + commitHash := ref.ID + commit, err = gr.CatFileCommit(commitHash) + if err != nil { + logger.Debug("failed to get commit from tag", "repo", repo.Name(), "tag", tagName, "err", err) + renderInternalServerError(w, r) + return + } + } else { + // Lightweight tag - points directly to commit + commit, err = gr.CatFileCommit(ref.ID) + if err != nil { + logger.Debug("failed to get commit", "repo", repo.Name(), "tag", tagName, "err", err) + renderNotFound(w, r) + return + } + // Use commit author date for lightweight tags + tagDate = commit.Author.When + } + + // Fallback to commit date if tag date not available + if tagDate.IsZero() && commit != nil { + tagDate = commit.Author.When + } + + defaultBranch := getDefaultBranch(gr) + + repoDisplayName := repo.ProjectName() + if repoDisplayName == "" { + repoDisplayName = repo.Name() + } + + // Build description from tag message or commit subject + description := "" + if tag != nil && tag.Message() != "" { + description = tag.Message() + } else if commit != nil { + lines := strings.Split(commit.Message, "\n") + if len(lines) > 0 { + description = lines[0] + } + } + if len(description) > 200 { + description = description[:200] + "..." + } + + data := TagData{ + RepoBaseData: RepoBaseData{ + BaseData: BaseData{ + ServerName: cfg.Name, + ActiveTab: "tags", + Title: tagName + " | " + repoDisplayName, + Description: description, + }, + Repo: repo, + DefaultBranch: defaultBranch, + }, + Ref: ref, + Tag: tag, + Commit: commit, + TagDate: tagDate, + } + + renderHTML(w, "tag.html", data) +}