Author | Jakob Wakeling <[email protected]> |
Date | 2023-12-23 04:05:01 |
Commit | b4b291ece361bb22761f24eac29936f15f83a2c5 |
Parent | db3692db87cf30f7d5914d7d7b94c5ef8778510f |
Paginate repository log page
Diffstat
M | res/repo/log.html | | | 15 | +++++++++++++++ |
M | res/style.css | | | 2 | ++ |
M | src/main.go | | | 10 | ++++++++++ |
M | src/repo/commit.go | | | 4 | ++-- |
M | src/repo/edit.go | | | 4 | ++-- |
M | src/repo/file.go | | | 4 | ++-- |
M | src/repo/log.go | | | 105 | ++++++++++++++++++++++++++++++++++++++++++++++--------------------------------- |
M | src/repo/refs.go | | | 4 | ++-- |
M | src/repo/repo.go | | | 49 | ++++++++----------------------------------------- |
M | src/repo/tree.go | | | 4 | ++-- |
M | src/util/util.go | | | 18 | ++++++++++++++++-- |
11 files changed, 122 insertions, 97 deletions
diff --git a/res/repo/log.html b/res/repo/log.html index 539beeb..24ea16c 100644 --- a/res/repo/log.html +++ b/res/repo/log.html @@ -31,5 +31,20 @@ {{end}} </tbody> </table> + <footer> + {{if gt .PrevOffset 0}} + <a href="/{{$.Name}}/log?o={{.PrevOffset}}">[prev]</a> + {{else if eq .PrevOffset 0}} + <a href="/{{$.Name}}/log">[prev]</a> + {{else}} + <span>[prev]</span> + {{end}} + <span>{{.Page}}</span> + {{if .NextOffset}} + <a href="/{{$.Name}}/log?o={{.NextOffset}}">[next]</a> + {{else}} + <span>[next]</span> + {{end}} + </footer> </main> </body> diff --git a/res/style.css b/res/style.css index d84a048..ddf0589 100644 --- a/res/style.css +++ b/res/style.css @@ -6,6 +6,8 @@ a:hover { text-decoration: underline; } h1, h2 { font-size: 1em; margin: 0; } hr { border: 0; height: 1rem; margin: 0; } +footer { padding: 0.4rem 0.4rem 1rem; } + table td { padding: 0 0.4rem; } table td:empty::after { content: "\00a0"; } table td pre { margin: 0; } diff --git a/src/main.go b/src/main.go index 7087994..1933a86 100644 --- a/src/main.go +++ b/src/main.go @@ -86,6 +86,16 @@ func main() { h.Use(middleware.RedirectSlashes) h.Use(logHttp) + h.Use(func(h http.Handler) http.Handler { + return http.TimeoutHandler(h, 90*time.Second, + `<!DOCTYPE html><head lang="en-GB"> + <meta charset="UTF-8"><title>503 Service Unavailable</title> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <link rel="stylesheet" type="text/css" href="/static/style.css"> + <link rel="icon" type="image/png" href="/static/favicon.png"> + </head><body><b>503 Service Unavailable</b></body>`) + }) + protect = csrf.Protect( []byte(goit.Conf.CsrfSecret), csrf.FieldName("csrf.Token"), csrf.CookieName("csrf"), csrf.Secure(util.If(goit.Conf.UsesHttps, true, false)), diff --git a/src/repo/commit.go b/src/repo/commit.go index d4f5986..4c31712 100644 --- a/src/repo/commit.go +++ b/src/repo/commit.go @@ -75,10 +75,10 @@ func HandleCommit(w http.ResponseWriter, r *http.Request) { return } } else { - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = filepath.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = filepath.Join("/", repo.Name, "file", licence) } } diff --git a/src/repo/edit.go b/src/repo/edit.go index 3a6f34d..e007c9d 100644 --- a/src/repo/edit.go +++ b/src/repo/edit.go @@ -105,10 +105,10 @@ func HandleEdit(w http.ResponseWriter, r *http.Request) { } if ref != nil { - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = filepath.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = filepath.Join("/", repo.Name, "file", licence) } } diff --git a/src/repo/file.go b/src/repo/file.go index 38b0a78..0ec7a1b 100644 --- a/src/repo/file.go +++ b/src/repo/file.go @@ -72,10 +72,10 @@ func HandleFile(w http.ResponseWriter, r *http.Request) { return } - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = path.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = path.Join("/", repo.Name, "file", licence) } diff --git a/src/repo/log.go b/src/repo/log.go index 720b6f4..f36200e 100644 --- a/src/repo/log.go +++ b/src/repo/log.go @@ -6,9 +6,11 @@ package repo import ( "errors" "fmt" + "io" "log" "net/http" "path/filepath" + "strconv" "strings" "time" @@ -17,9 +19,10 @@ import ( "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" ) +const PAGE = 100 + func HandleLog(w http.ResponseWriter, r *http.Request) { auth, user, err := goit.Auth(w, r, true) if err != nil { @@ -39,15 +42,15 @@ func HandleLog(w http.ResponseWriter, r *http.Request) { return } - // var offset uint64 = 0 - // if o := r.URL.Query().Get("o"); o != "" { - // if i, err := strconv.ParseUint(o, 10, 64); err != nil { - // goit.HttpError(w, http.StatusBadRequest) - // return - // } else { - // offset = i - // } - // } + offset := int64(0) + if o := r.URL.Query().Get("o"); o != "" { + if i, err := strconv.ParseInt(o, 10, 64); err != nil { + goit.HttpError(w, http.StatusBadRequest) + return + } else { + offset = i + } + } type row struct{ Hash, Date, Message, Author, Files, Additions, Deletions string } data := struct { @@ -55,11 +58,15 @@ func HandleLog(w http.ResponseWriter, r *http.Request) { Readme, Licence string Commits []row Editable, IsMirror bool + Page, PrevOffset, NextOffset int64 }{ Title: repo.Name + " - Log", Name: repo.Name, Description: repo.Description, - Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, - Editable: (auth && repo.OwnerId == user.Id), - IsMirror: repo.IsMirror, + Url: util.If(goit.Conf.UsesHttps, "https://", "http://") + r.Host + "/" + repo.Name, + Editable: (auth && repo.OwnerId == user.Id), + IsMirror: repo.IsMirror, + Page: offset/PAGE + 1, + PrevOffset: util.Max(offset-PAGE, -1), + NextOffset: offset + PAGE, } gr, err := git.PlainOpen(goit.RepoPath(repo.Name, true)) @@ -78,53 +85,63 @@ func HandleLog(w http.ResponseWriter, r *http.Request) { return } - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = filepath.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = filepath.Join("/", repo.Name, "file", licence) } - if iter, err := gr.Log(&git.LogOptions{From: ref.Hash()}); err != nil { + if iter, err := gr.Log(&git.LogOptions{ + From: ref.Hash(), Order: git.LogOrderCommitterTime, PathFilter: func(s string) bool { + return tpath == "" || s == tpath || strings.HasPrefix(s, tpath+"/") + }, + }); err != nil { log.Println("[/repo/log]", err.Error()) goit.HttpError(w, http.StatusInternalServerError) return - } else if err := iter.ForEach(func(c *object.Commit) error { - var files, additions, deletions int - - if stats, err := goit.DiffStats(c); err != nil { - log.Println("[/repo/log]", err.Error()) - } else if tpath != "" { - for _, s := range stats { - if s.Name == tpath || strings.HasPrefix(s.Name, tpath+"/") { - files += 1 + } else { + for i := int64(0); i < offset; i += 1 { + if _, err := iter.Next(); err != nil && !errors.Is(err, io.EOF) { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + } + + for i := 0; i < PAGE; i += 1 { + c, err := iter.Next() + if errors.Is(err, io.EOF) { + data.NextOffset = 0 + break + } else if err != nil { + log.Println("[/repo/log]", err.Error()) + goit.HttpError(w, http.StatusInternalServerError) + return + } + + var files, additions, deletions int + + if stats, err := goit.DiffStats(c); err != nil { + log.Println("[/repo/log]", err.Error()) + } else { + files = len(stats) + for _, s := range stats { additions += s.Addition deletions += s.Deletion } } - if files == 0 { - return nil - } - } else { - files = len(stats) - for _, s := range stats { - additions += s.Addition - deletions += s.Deletion - } + data.Commits = append(data.Commits, row{ + Hash: c.Hash.String(), Date: c.Author.When.UTC().Format(time.DateTime), + Message: strings.SplitN(c.Message, "\n", 2)[0], Author: c.Author.Name, Files: fmt.Sprint(files), + Additions: "+" + fmt.Sprint(additions), Deletions: "-" + fmt.Sprint(deletions), + }) } - data.Commits = append(data.Commits, row{ - Hash: c.Hash.String(), Date: c.Author.When.UTC().Format(time.DateTime), - Message: strings.SplitN(c.Message, "\n", 2)[0], Author: c.Author.Name, Files: fmt.Sprint(files), - Additions: "+" + fmt.Sprint(additions), Deletions: "-" + fmt.Sprint(deletions), - }) - - return nil - }); err != nil { - log.Println("[/repo/log]", err.Error()) - goit.HttpError(w, http.StatusInternalServerError) - return + if _, err := iter.Next(); errors.Is(err, io.EOF) { + data.NextOffset = 0 + } } execute: diff --git a/src/repo/refs.go b/src/repo/refs.go index 2674034..af7b88e 100644 --- a/src/repo/refs.go +++ b/src/repo/refs.go @@ -62,10 +62,10 @@ func HandleRefs(w http.ResponseWriter, r *http.Request) { return } } else { - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = filepath.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = filepath.Join("/", repo.Name, "file", licence) } } diff --git a/src/repo/repo.go b/src/repo/repo.go index 6cc40a9..aa799a7 100644 --- a/src/repo/repo.go +++ b/src/repo/repo.go @@ -8,8 +8,6 @@ import ( "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" - "github.com/go-git/go-git/v5/plumbing/object" - "github.com/go-git/go-git/v5/plumbing/storer" ) type HeaderFields struct { @@ -20,54 +18,23 @@ type HeaderFields struct { var readmePattern = regexp.MustCompile(`(?i)^readme(?:\.?(?:md|txt))?$`) var licencePattern = regexp.MustCompile(`(?i)^licence(?:\.?(?:md|txt))?$`) -func findReadme(gr *git.Repository, ref *plumbing.Reference) (string, error) { - commit, err := gr.CommitObject(ref.Hash()) +/* Find a file that matches a regular expression in the root level of a reference. */ +func findPattern(gr *git.Repository, ref *plumbing.Reference, re *regexp.Regexp) (string, error) { + c, err := gr.CommitObject(ref.Hash()) if err != nil { return "", err } - iter, err := commit.Files() + t, err := c.Tree() if err != nil { return "", err } - var filename string - if err := iter.ForEach(func(f *object.File) error { - if readmePattern.MatchString(f.Name) { - filename = f.Name - return storer.ErrStop + for _, e := range t.Entries { + if re.MatchString(e.Name) { + return e.Name, nil } - - return nil - }); err != nil { - return "", err - } - - return filename, nil -} - -func findLicence(gr *git.Repository, ref *plumbing.Reference) (string, error) { - commit, err := gr.CommitObject(ref.Hash()) - if err != nil { - return "", err - } - - iter, err := commit.Files() - if err != nil { - return "", err - } - - var filename string - if err := iter.ForEach(func(f *object.File) error { - if licencePattern.MatchString(f.Name) { - filename = f.Name - return storer.ErrStop - } - - return nil - }); err != nil { - return "", err } - return filename, nil + return "", nil } diff --git a/src/repo/tree.go b/src/repo/tree.go index 26455e3..598e3bd 100644 --- a/src/repo/tree.go +++ b/src/repo/tree.go @@ -83,10 +83,10 @@ func HandleTree(w http.ResponseWriter, r *http.Request) { return } } else { - if readme, _ := findReadme(gr, ref); readme != "" { + if readme, _ := findPattern(gr, ref, readmePattern); readme != "" { data.Readme = path.Join("/", repo.Name, "file", readme) } - if licence, _ := findLicence(gr, ref); licence != "" { + if licence, _ := findPattern(gr, ref, licencePattern); licence != "" { data.Licence = path.Join("/", repo.Name, "file", licence) } diff --git a/src/util/util.go b/src/util/util.go index c4866a2..4447a8d 100644 --- a/src/util/util.go +++ b/src/util/util.go @@ -4,6 +4,7 @@ package util import ( + "cmp" "errors" "io/fs" "net/http" @@ -17,9 +18,22 @@ const ModeNotRegular = os.ModeSymlink | os.ModeDevice | os.ModeNamedPipe | os.Mo func If[T any](cond bool, a, b T) T { if cond { return a - } else { - return b } + return b +} + +func Min[T cmp.Ordered](a, b T) T { + if a < b { + return a + } + return b +} + +func Max[T cmp.Ordered](a, b T) T { + if a > b { + return a + } + return b } /* Return the named cookie or nil if not found or invalid. */