Monorepo for Tangled tangled.org

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 <me@oppi.li>

authored by oppi.li and committed by Tangled 52cd32d1 c4e83c26

Changed files
+64 -29
knotserver
+47 -7
knotserver/git/git.go
··· 142 142 return &g, nil 143 143 } 144 144 145 - func (g *GitRepo) Commits() ([]*object.Commit, error) { 146 - ci, err := g.r.Log(&git.LogOptions{From: g.h}) 145 + func (g *GitRepo) Commits(offset, limit int) ([]*object.Commit, error) { 146 + commits := []*object.Commit{} 147 + 148 + output, err := g.revList( 149 + fmt.Sprintf("--skip=%d", offset), 150 + fmt.Sprintf("--max-count=%d", limit), 151 + ) 147 152 if err != nil { 148 153 return nil, fmt.Errorf("commits from ref: %w", err) 149 154 } 150 155 151 - commits := []*object.Commit{} 152 - ci.ForEach(func(c *object.Commit) error { 153 - commits = append(commits, c) 154 - return nil 155 - }) 156 + lines := strings.Split(strings.TrimSpace(string(output)), "\n") 157 + if len(lines) == 1 && lines[0] == "" { 158 + return commits, nil 159 + } 160 + 161 + for _, item := range lines { 162 + obj, err := g.r.CommitObject(plumbing.NewHash(item)) 163 + if err != nil { 164 + continue 165 + } 166 + commits = append(commits, obj) 167 + } 156 168 157 169 return commits, nil 170 + } 171 + 172 + func (g *GitRepo) TotalCommits() (int, error) { 173 + output, err := g.revList( 174 + fmt.Sprintf("--count"), 175 + ) 176 + if err != nil { 177 + return 0, fmt.Errorf("failed to run rev-list", err) 178 + } 179 + 180 + count, err := strconv.Atoi(strings.TrimSpace(string(output))) 181 + if err != nil { 182 + return 0, err 183 + } 184 + 185 + return count, nil 186 + } 187 + 188 + func (g *GitRepo) revList(extraArgs ...string) ([]byte, error) { 189 + var args []string 190 + args = append(args, "rev-list") 191 + args = append(args, g.h.String()) 192 + args = append(args, extraArgs...) 193 + 194 + cmd := exec.Command("git", args...) 195 + cmd.Dir = g.path 196 + 197 + return cmd.Output() 158 198 } 159 199 160 200 func (g *GitRepo) Commit(h plumbing.Hash) (*object.Commit, error) {
+17 -22
knotserver/routes.go
··· 87 87 } 88 88 } 89 89 90 - commits, err := gr.Commits() 91 - total := len(commits) 90 + commits, err := gr.Commits(0, 60) // a good preview of commits in this repo 92 91 if err != nil { 93 92 writeError(w, err.Error(), http.StatusInternalServerError) 94 93 l.Error("fetching commits", "error", err.Error()) 95 94 return 96 95 } 97 - if len(commits) > 10 { 98 - commits = commits[:10] 96 + 97 + total, err := gr.TotalCommits() 98 + if err != nil { 99 + writeError(w, err.Error(), http.StatusInternalServerError) 100 + l.Error("fetching commits", "error", err.Error()) 101 + return 99 102 } 100 103 101 104 branches, err := gr.Branches() ··· 349 352 return 350 353 } 351 354 352 - commits, err := gr.Commits() 353 - if err != nil { 354 - writeError(w, err.Error(), http.StatusInternalServerError) 355 - l.Error("fetching commits", "error", err.Error()) 356 - return 357 - } 358 - 359 355 // Get page parameters 360 356 page := 1 361 357 pageSize := 30 ··· 372 368 } 373 369 } 374 370 375 - // Calculate pagination 376 - start := (page - 1) * pageSize 377 - end := start + pageSize 378 - total := len(commits) 371 + // convert to offset/limit 372 + offset := (page - 1) * pageSize 373 + limit := pageSize 379 374 380 - if start >= total { 381 - commits = []*object.Commit{} 382 - } else { 383 - if end > total { 384 - end = total 385 - } 386 - commits = commits[start:end] 375 + commits, err := gr.Commits(offset, limit) 376 + if err != nil { 377 + writeError(w, err.Error(), http.StatusInternalServerError) 378 + l.Error("fetching commits", "error", err.Error()) 379 + return 387 380 } 381 + 382 + total := len(commits) 388 383 389 384 resp := types.RepoLogResponse{ 390 385 Commits: commits,