knotserver: add generic git repo walker interface #269

merged
opened by oppi.li targeting master from push-zvkrywwskknq
Changed files
+79
knotserver
git
+79
knotserver/git/tree.go
···
import (
"context"
"fmt"
"path"
"time"
···
return nts
}
···
import (
"context"
+
"errors"
"fmt"
"path"
"time"
···
return nts
}
+
+
var (
+
TerminateWalk error = errors.New("terminate walk")
+
)
+
+
type callback = func(node object.TreeEntry, parent *object.Tree, fullPath string) error
+
+
func (g *GitRepo) Walk(
+
ctx context.Context,
+
root string,
+
cb callback,
+
) error {
+
c, err := g.r.CommitObject(g.h)
+
if err != nil {
+
return fmt.Errorf("commit object: %w", err)
+
}
+
+
tree, err := c.Tree()
+
if err != nil {
+
return fmt.Errorf("file tree: %w", err)
+
}
+
+
subtree := tree
+
if root != "" {
+
subtree, err = tree.Tree(root)
+
if err != nil {
+
return fmt.Errorf("sub tree: %w", err)
+
}
+
}
+
+
return g.walkHelper(ctx, root, subtree, cb)
+
}
+
+
func (g *GitRepo) walkHelper(
+
ctx context.Context,
+
root string,
+
currentTree *object.Tree,
+
cb callback,
+
) error {
+
for _, e := range currentTree.Entries {
+
// check if context hits deadline before processing
+
select {
+
case <-ctx.Done():
+
return ctx.Err()
+
default:
+
}
+
+
mode, err := e.Mode.ToOSFileMode()
+
if err != nil {
+
// TODO: log this
+
continue
+
}
+
+
if e.Mode.IsFile() {
+
err = cb(e, currentTree, root)
+
if errors.Is(err, TerminateWalk) {
+
return err
+
}
+
}
+
+
// e is a directory
+
if mode.IsDir() {
+
subtree, err := currentTree.Tree(e.Name)
+
if err != nil {
+
return fmt.Errorf("sub tree %s: %w", e.Name, err)
+
}
+
+
fullPath := path.Join(root, e.Name)
+
+
err = g.walkHelper(ctx, fullPath, subtree, cb)
+
if err != nil {
+
return err
+
}
+
}
+
}
+
+
return nil
+
}