parent:
04789f728342d5429f9f308edb4f0bc670338b70
nmyk <nick@nmyk.io>
2026-04-21T16:37:35-04:00
make log traversal faster shell out to git binary for commit history
diff --git a/internal/core/repo.go b/internal/core/repo.go
index 5d9e44bcd6bb04f7c3fd00bb666480dc49004065..5a947efa56094282502ac9e815ebe5b4724d9c0f 100644
--- a/internal/core/repo.go
+++ b/internal/core/repo.go
@@ -58,6 +58,10 @@ }
return &Repo{repo, repoName}, nil
}
+func (repo *Repo) Path() string {
+ return repo.Name + dotGit
+}
+
func (repo *Repo) Load(entry *object.TreeEntry) (string, error) {
file, err := repo.BlobObject(entry.Hash)
if err != nil {
diff --git a/internal/handlers/handlers.go b/internal/handlers/handlers.go
index 42ffcef08fe69a5cd48062bc594eed4a65a83b1b..5b6180c9f368e86454f109a4aa8d81e90228ad97 100644
--- a/internal/handlers/handlers.go
+++ b/internal/handlers/handlers.go
@@ -1,14 +1,17 @@
package handlers
import (
+ "bufio"
"bytes"
"compress/gzip"
+ "context"
"embed"
"errors"
"fmt"
"html/template"
"io"
"net/http"
+ "os/exec"
"path"
"regexp"
"slices"
@@ -21,7 +24,6 @@ "github.com/go-git/go-git/v6"
"github.com/go-git/go-git/v6/plumbing"
"github.com/go-git/go-git/v6/plumbing/filemode"
"github.com/go-git/go-git/v6/plumbing/object"
- "github.com/go-git/go-git/v6/plumbing/storer"
"github.com/go-git/go-git/v6/plumbing/transport"
"nmyk.io/bidet/internal/core"
)
@@ -270,57 +272,6 @@ PrevPage *Cursor
NextPage *Cursor
}
-func commitChangesDir(c *object.Commit, dir string) (bool, error) {
- tree, err := c.Tree()
- if err != nil {
- return false, err
- }
- parentIter := c.Parents()
- parentCount := 0
- hasChange := false
- err = parentIter.ForEach(func(p *object.Commit) error {
- parentCount++
- pt, err := p.Tree()
- if err != nil {
- return err
- }
- // Diff only the subtree for the directory
- changes, err := object.DiffTree(pt, tree)
- if err != nil {
- return err
- }
- for _, ch := range changes {
- if strings.HasPrefix(ch.From.Name, dir+"/") || strings.HasPrefix(ch.To.Name, dir+"/") {
- hasChange = true
- return storer.ErrStop // stop iteration: we found a relevant change
- }
- }
- return nil
- })
- if err == storer.ErrStop {
- return true, nil
- }
- if err != nil {
- return false, err
- }
- // root commit
- if parentCount == 0 {
- return true, nil
- }
- return hasChange, nil
-}
-
-func pathIsFile(t *object.Tree, path string) (bool, error) {
- if path == "" {
- return false, nil
- }
- entry, err := t.FindEntry(path)
- if err != nil {
- return false, err
- }
- return entry.Mode.IsFile(), nil
-}
-
const commitsPageSize = 35
var page = regexp.MustCompile(`^([0-9a-fA-F]+) ([0-9]+)$`)
@@ -363,8 +314,8 @@ beforeParam := r.URL.Query().Get("before")
afterParam := r.URL.Query().Get("after")
if beforeParam != "" && afterParam != "" {
http.NotFound(w, r)
+ return
}
-
beforeCursor := parseCursor(beforeParam)
afterCursor := parseCursor(afterParam)
isBefore := beforeCursor != nil
@@ -373,40 +324,40 @@
var cursor *Cursor
if isBefore && beforeCursor.Offset < commitsPageSize {
http.NotFound(w, r)
+ return
} else if isBefore {
cursor = beforeCursor
} else if isAfter {
cursor = afterCursor
}
- logOpts := git.LogOptions{From: commit.Hash}
- var dir string
+ args := []string{
+ "log",
+ "--pretty=format:%H",
+ commit.Hash.String(),
+ }
if filePath != "" {
- tree, err := commit.Tree()
- if err != nil {
- s.error(w, err)
- return
- }
- isFile, err := pathIsFile(tree, filePath)
- if err != nil {
- http.NotFound(w, r)
- return
- }
- if isFile {
- logOpts.FileName = &filePath
- } else {
- dir = filePath
- }
+ args = append(args, "--", filePath)
}
+ ctx, cancel := context.WithCancel(r.Context())
+ defer cancel()
- iter, err := repo.Log(&logOpts)
+ cmd := exec.CommandContext(ctx, "git", args...)
+ cmd.Dir = path.Join(s.Dir, repo.Path())
+ stdout, err := cmd.StdoutPipe()
if err != nil {
s.error(w, err)
return
}
+ if err := cmd.Start(); err != nil {
+ s.error(w, err)
+ return
+ }
+
+ scanner := bufio.NewScanner(stdout)
var commits []CommitMeta
- var foundAnchor bool
+ var stopEarly bool
var start, offset, end int
if isBefore {
start = cursor.Offset - commitsPageSize
@@ -417,25 +368,21 @@ end = cursor.Offset + commitsPageSize
} else {
start = 0
end = commitsPageSize - 1
- foundAnchor = true
}
- err = iter.ForEach(func(c *object.Commit) error {
+
+ for scanner.Scan() {
if offset >= end {
- return io.EOF
+ stopEarly = true
+ cancel()
+ break
}
- if dir != "" {
- ok, err := commitChangesDir(c, dir)
- if err != nil {
- return err
- }
- if !ok {
- return nil
- }
- }
- if !foundAnchor {
- foundAnchor = c.Hash.String() == cursor.AnchorHash
+ hashStr := scanner.Text()
+ c, err := repo.CommitObject(plumbing.NewHash(hashStr))
+ if err != nil {
+ s.error(w, err)
+ return
}
- if foundAnchor && offset >= start && offset < end {
+ if offset >= start && offset < end {
commits = append(commits, CommitMeta{
Hash: c.Hash.String(),
Author: fmtAuthor(c.Author.Name, c.Author.Email),
@@ -443,16 +390,14 @@ When: c.Author.When,
Message: c.Message,
})
}
- if foundAnchor {
- offset++
- }
- return nil
- })
-
- if err != nil && err != io.EOF {
+ offset++
+ }
+ if err := scanner.Err(); err != nil && !errors.Is(err, context.Canceled) {
s.error(w, err)
return
}
+
+ _ = cmd.Wait()
var next, prev *Cursor
if len(commits) > 0 {
@@ -485,7 +430,7 @@ }
if prev != nil && prev.Offset < commitsPageSize {
prev = nil
}
- if !errors.Is(err, io.EOF) {
+ if !stopEarly {
next = nil
}
data := CommitsData{