From 52cd32d1db432a058c53b5281e6d6e7668db02a1 Mon Sep 17 00:00:00 2001 From: oppiliappan Date: Wed, 28 May 2025 15:26:59 +0100 Subject: [PATCH] knotserver: git: improve performance of commit listing RepoIndex and RepoLog should no longer take longer on bigger repositories. all endpoints are still backwards compatible. Signed-off-by: oppiliappan --- knotserver/git/git.go | 54 +++++++++++++++++++++++++++++++++++++------ knotserver/routes.go | 39 ++++++++++++++----------------- 2 files changed, 64 insertions(+), 29 deletions(-) diff --git a/knotserver/git/git.go b/knotserver/git/git.go index 9fd23b24..3630a800 100644 --- a/knotserver/git/git.go +++ b/knotserver/git/git.go @@ -142,21 +142,61 @@ func PlainOpen(path string) (*GitRepo, error) { return &g, nil } -func (g *GitRepo) Commits() ([]*object.Commit, error) { - ci, err := g.r.Log(&git.LogOptions{From: g.h}) +func (g *GitRepo) Commits(offset, limit int) ([]*object.Commit, error) { + commits := []*object.Commit{} + + output, err := g.revList( + fmt.Sprintf("--skip=%d", offset), + fmt.Sprintf("--max-count=%d", limit), + ) if err != nil { return nil, fmt.Errorf("commits from ref: %w", err) } - commits := []*object.Commit{} - ci.ForEach(func(c *object.Commit) error { - commits = append(commits, c) - return nil - }) + lines := strings.Split(strings.TrimSpace(string(output)), "\n") + if len(lines) == 1 && lines[0] == "" { + return commits, nil + } + + for _, item := range lines { + obj, err := g.r.CommitObject(plumbing.NewHash(item)) + if err != nil { + continue + } + commits = append(commits, obj) + } return commits, nil } +func (g *GitRepo) TotalCommits() (int, error) { + output, err := g.revList( + fmt.Sprintf("--count"), + ) + if err != nil { + return 0, fmt.Errorf("failed to run rev-list", err) + } + + count, err := strconv.Atoi(strings.TrimSpace(string(output))) + if err != nil { + return 0, err + } + + return count, nil +} + +func (g *GitRepo) revList(extraArgs ...string) ([]byte, error) { + var args []string + args = append(args, "rev-list") + args = append(args, g.h.String()) + args = append(args, extraArgs...) + + cmd := exec.Command("git", args...) + cmd.Dir = g.path + + return cmd.Output() +} + func (g *GitRepo) Commit(h plumbing.Hash) (*object.Commit, error) { return g.r.CommitObject(h) } diff --git a/knotserver/routes.go b/knotserver/routes.go index 29dca926..068fb285 100644 --- a/knotserver/routes.go +++ b/knotserver/routes.go @@ -87,15 +87,18 @@ func (h *Handle) RepoIndex(w http.ResponseWriter, r *http.Request) { } } - commits, err := gr.Commits() - total := len(commits) + commits, err := gr.Commits(0, 60) // a good preview of commits in this repo if err != nil { writeError(w, err.Error(), http.StatusInternalServerError) l.Error("fetching commits", "error", err.Error()) return } - if len(commits) > 10 { - commits = commits[:10] + + total, err := gr.TotalCommits() + if err != nil { + writeError(w, err.Error(), http.StatusInternalServerError) + l.Error("fetching commits", "error", err.Error()) + return } branches, err := gr.Branches() @@ -349,13 +352,6 @@ func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { return } - commits, err := gr.Commits() - if err != nil { - writeError(w, err.Error(), http.StatusInternalServerError) - l.Error("fetching commits", "error", err.Error()) - return - } - // Get page parameters page := 1 pageSize := 30 @@ -372,20 +368,19 @@ func (h *Handle) Log(w http.ResponseWriter, r *http.Request) { } } - // Calculate pagination - start := (page - 1) * pageSize - end := start + pageSize - total := len(commits) + // convert to offset/limit + offset := (page - 1) * pageSize + limit := pageSize - if start >= total { - commits = []*object.Commit{} - } else { - if end > total { - end = total - } - commits = commits[start:end] + commits, err := gr.Commits(offset, limit) + if err != nil { + writeError(w, err.Error(), http.StatusInternalServerError) + l.Error("fetching commits", "error", err.Error()) + return } + total := len(commits) + resp := types.RepoLogResponse{ Commits: commits, Ref: ref, -- 2.51.2