Author | Jakob Wakeling <[email protected]> |
Date | 2025-01-11 02:40:24 |
Commit | c850c86e98d13b282f071892f73b83a609ea926c |
Parent | 37a8bcfb6e00c3dc44d0ed18a95349500b35fc00 |
Add a repo tag page with annotations
Diffstat
M | README.md | | | 2 | +- |
M | res/repo/refs.html | | | 2 | +- |
A | res/repo/tag.html | | | 19 | +++++++++++++++++++ |
M | res/res.go | | | 3 | +++ |
M | src/goit/http.go | | | 1 | + |
M | src/main.go | | | 4 | ++++ |
M | src/repo/commit.go | | | 18 | +++++++++--------- |
M | src/repo/refs.go | | | 20 | ++++++++++---------- |
A | src/repo/tag.go | | | 118 | ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
9 files changed, 166 insertions, 21 deletions
diff --git a/README.md b/README.md index 64d9832..3337143 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ Note that at present, compatibility between updates is not guaranteed. ## Features - Git Smart HTTP protocol (v2 only) -- Git SSH protocol (planned) +- Git SSH protocol (using an external SSH server) - Repository log, tree, refs, and commit viewers - File viewer with syntax highlighting - File log, blame, and raw views diff --git a/res/repo/refs.html b/res/repo/refs.html index 43d841a..baa11cf 100644 --- a/res/repo/refs.html +++ b/res/repo/refs.html @@ -42,7 +42,7 @@ <tbody> {{range .Tags}} <tr> - <td>{{.Name}}</td> + <td><a href="/{{$.Name}}/tag/{{.Name}}">{{.Name}}</a></td> <td><a href="/{{$.Name}}/commit/{{.Hash}}">{{.Message}}</a></td> <td>{{.Author}}</td> <td>{{.LastCommit}}</td> diff --git a/res/repo/tag.html b/res/repo/tag.html new file mode 100644 index 0000000..3f6693d --- /dev/null +++ b/res/repo/tag.html @@ -0,0 +1,19 @@ +<!DOCTYPE html> +<html lang="en"> + <head>{{template "base/head" .}}</head> + <body> + <header>{{template "repo/header" .}}</header><hr> + <main> + <table> + <tr><td>Tag</td><td><a href="/{{.Name}}/tag/{{.Tag}}">{{.Tag}}</a></td></tr> + <tr><td>Author</td><td>{{.Author}}</td></tr> + <tr><td>Date</td><td>{{.Date}}</td></tr> + <tr><td>Commit</td><td><a href="/{{.Name}}/commit/{{.Commit}}">{{.Commit}}</a></td></tr> + {{range $i, $h := .Parents}} + <tr><td>Parent</td><td><a href="/{{$.Name}}/commit/{{$h}}">{{$h}}</a></td></tr> + {{end}} + </table> + <p>{{.Message}}</p> + </main> + </body> +</html> diff --git a/res/res.go b/res/res.go index 23d3ace..38c57b7 100644 --- a/res/res.go +++ b/res/res.go @@ -70,6 +70,9 @@ var RepoLog string //go:embed repo/commit.html var RepoCommit string +//go:embed repo/tag.html +var RepoTag string + //go:embed repo/tree.html var RepoTree string diff --git a/src/goit/http.go b/src/goit/http.go index 2fbddc8..ff925b2 100644 --- a/src/goit/http.go +++ b/src/goit/http.go @@ -40,6 +40,7 @@ func init() { template.Must(Tmpl.New("repo/log").Parse(res.RepoLog)) template.Must(Tmpl.New("repo/commit").Parse(res.RepoCommit)) + template.Must(Tmpl.New("repo/tag").Parse(res.RepoTag)) template.Must(Tmpl.New("repo/tree").Parse(res.RepoTree)) template.Must(Tmpl.New("repo/file").Parse(res.RepoFile)) template.Must(Tmpl.New("repo/blame").Parse(res.RepoBlame)) diff --git a/src/main.go b/src/main.go index 468c8f4..1c728cf 100644 --- a/src/main.go +++ b/src/main.go @@ -224,6 +224,10 @@ found: rctx.URLParams.Add("hash", hash) protect(http.HandlerFunc(repo.HandleCommit)).ServeHTTP(w, r) + case strings.HasPrefix(spath, "/tag/"): + rctx.URLParams.Add("tag", strings.TrimPrefix(spath, "/tag/")) + protect(http.HandlerFunc(repo.HandleTag)).ServeHTTP(w, r) + case strings.HasPrefix(spath, "/tree"): rctx.URLParams.Add("*", strings.TrimLeft(strings.TrimPrefix(spath, "/tree"), "/")) protect(http.HandlerFunc(repo.HandleTree)).ServeHTTP(w, r) diff --git a/src/repo/commit.go b/src/repo/commit.go index 423791e..35000b0 100644 --- a/src/repo/commit.go +++ b/src/repo/commit.go @@ -7,7 +7,6 @@ import ( "errors" "fmt" "html/template" - "log" "net/http" "path/filepath" "strconv" @@ -15,6 +14,7 @@ import ( "time" "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" "github.com/buildkite/terminal-to-html/v3" "github.com/go-chi/chi/v5" "github.com/go-git/go-git/v5" @@ -24,7 +24,7 @@ import ( func HandleCommit(w http.ResponseWriter, r *http.Request) { auth, user, err := goit.Auth(w, r, true) if err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -53,13 +53,13 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { Summary string Diff template.HTML }{ - Title: repo.Name + " - Log", HeaderFields: GetHeaderFields(auth, user, repo, r.Host), + Title: repo.Name + " - Log", } gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true)) if err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -67,7 +67,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { ref, err := gr.Head() if err != nil { if !errors.Is(err, plumbing.ErrReferenceNotFound) { - log.Println("[/repo/log]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -85,7 +85,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { goit.HttpError(w, http.StatusNotFound) return } else if err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -102,7 +102,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { st, err := goit.DiffStats(commit) if err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -146,7 +146,7 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { c.Dir = goit.RepoPath(repo.Name, true) out, _, err := c.Run(nil, nil) if err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -154,6 +154,6 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { data.Diff = template.HTML(terminal.Render(out)) if err := goit.Tmpl.ExecuteTemplate(w, "repo/commit", data); err != nil { - log.Println("[/repo/commit]", err.Error()) + util.PrintFuncError(err) } } diff --git a/src/repo/refs.go b/src/repo/refs.go index 5f87bc5..73abad5 100644 --- a/src/repo/refs.go +++ b/src/repo/refs.go @@ -5,7 +5,6 @@ package repo import ( "errors" - "log" "net/http" "path/filepath" "slices" @@ -13,6 +12,7 @@ import ( "time" "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" "github.com/go-chi/chi/v5" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" @@ -22,7 +22,7 @@ import ( func HandleRefs(w http.ResponseWriter, r *http.Request) { auth, user, err := goit.Auth(w, r, true) if err != nil { - log.Println("[admin]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -51,7 +51,7 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true)) if err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -59,7 +59,7 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { ref, err := gr.Head() if err != nil { if !errors.Is(err, plumbing.ErrReferenceNotFound) { - log.Println("[/repo/log]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -73,7 +73,7 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { } if iter, err := gr.Branches(); err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } else if err := iter.ForEach(func(r *plumbing.Reference) error { @@ -94,13 +94,13 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { return nil }); err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } if iter, err := gr.Tags(); err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } else if err := iter.ForEach(func(r *plumbing.Reference) error { @@ -124,12 +124,12 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { data.Tags = append(data.Tags, row{ Name: r.Name().Short(), Message: strings.SplitN(c.Message, "\n", 2)[0], Author: c.Author.Name, - LastCommit: c.Author.When.UTC().Format(time.DateTime), Hash: r.Hash().String(), + LastCommit: c.Author.When.UTC().Format(time.DateTime), Hash: c.Hash.String(), }) return nil }); err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) goit.HttpError(w, http.StatusInternalServerError) return } @@ -137,6 +137,6 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { slices.Reverse(data.Tags) if err := goit.Tmpl.ExecuteTemplate(w, "repo/refs", data); err != nil { - log.Println("[/repo/refs]", err.Error()) + util.PrintFuncError(err) } } diff --git a/src/repo/tag.go b/src/repo/tag.go new file mode 100644 index 0000000..4142c19 --- /dev/null +++ b/src/repo/tag.go @@ -0,0 +1,118 @@ +// Copyright (C) 2025, Jakob Wakeling +// All rights reserved. + +package repo + +import ( + "errors" + "net/http" + "path/filepath" + "time" + + "github.com/Jamozed/Goit/src/goit" + "github.com/Jamozed/Goit/src/util" + "github.com/go-chi/chi/v5" + "github.com/go-git/go-git/v5" + "github.com/go-git/go-git/v5/plumbing" + "github.com/go-git/go-git/v5/plumbing/object" +) + +func HandleTag(w http.ResponseWriter, r *http.Request) { + auth, user, err := goit.Auth(w, r, true) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + repo, err := goit.GetRepoByName(chi.URLParam(r, "repo")) + if err != nil { + goit.HttpError(w, http.StatusInternalServerError) + return + } else if repo == nil || !goit.IsVisible(repo, auth, user) { + goit.HttpError(w, http.StatusNotFound) + return + } + + data := struct { + HeaderFields + Title string + Tag, Author, Date, Commit string + Parents []string + Message string + }{ + HeaderFields: GetHeaderFields(auth, user, repo, r.Host), + Title: repo.Name + " - Tags", + } + + gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true)) + if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + head, err := gr.Head() + if err != nil { + if !errors.Is(err, plumbing.ErrReferenceNotFound) { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + } else { + if readme, _ := findPattern(gr, head, readmePattern); readme != "" { + data.Readme = filepath.Join("/", repo.Name, "file", readme) + } + if licence, _ := findPattern(gr, head, licencePattern); licence != "" { + data.Licence = filepath.Join("/", repo.Name, "file", licence) + } + } + + ref, err := gr.Tag(chi.URLParam(r, "tag")) + if errors.Is(err, plumbing.ErrReferenceNotFound) { + goit.HttpError(w, http.StatusNotFound) + return + } else if err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + data.Tag = ref.Name().Short() + + var commit *object.Commit + if tag, err := gr.TagObject(ref.Hash()); err != nil { + if !errors.Is(err, plumbing.ErrObjectNotFound) { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + /* Tag is not annotated. */ + if commit, err = gr.CommitObject(ref.Hash()); err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + data.Author = commit.Author.String() + data.Message = commit.Message + } else { + /* Tag is annotated. */ + if commit, err = gr.CommitObject(tag.Target); err != nil { + util.PrintFuncError(err) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + data.Author = tag.Tagger.String() + data.Message = tag.Message + } + + data.Date = commit.Author.When.UTC().Format(time.DateTime) + data.Commit = commit.Hash.String() + + if err := goit.Tmpl.ExecuteTemplate(w, "repo/tag", data); err != nil { + util.PrintFuncError(err) + } +}