Skip to content

Commit ac360c5

Browse files
committed
Optimize directory listing
Lists directories without proxy value – by iterator directly
1 parent 02f4196 commit ac360c5

File tree

12 files changed

+190
-96
lines changed

12 files changed

+190
-96
lines changed

internal/iter/dirstream.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package iter
2+
3+
import (
4+
"github.com/hanwen/go-fuse/v2/fuse"
5+
"syscall"
6+
)
7+
8+
type DirStreamAdapter[T any] struct {
9+
iter Iter[T]
10+
next T
11+
err error
12+
entryCallback func(T) fuse.DirEntry
13+
}
14+
15+
func NewDirStreamAdapter[T any](iter Iter[T], entryCallback func(T) fuse.DirEntry) *DirStreamAdapter[T] {
16+
return &DirStreamAdapter[T]{
17+
iter: iter,
18+
entryCallback: entryCallback,
19+
}
20+
}
21+
22+
func (adapter *DirStreamAdapter[T]) HasNext() bool {
23+
var err error
24+
adapter.next, err = adapter.iter.Next()
25+
adapter.err = err
26+
return err == nil
27+
}
28+
29+
func (adapter *DirStreamAdapter[T]) Next() (fuse.DirEntry, syscall.Errno) {
30+
if adapter.err != nil {
31+
return fuse.DirEntry{}, syscall.ENOENT
32+
}
33+
return adapter.entryCallback(adapter.next), 0
34+
}
35+
36+
func (adapter *DirStreamAdapter[T]) Close() { adapter.iter.Close() }

internal/iter/filter.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package iter
2+
3+
type FilterIter[T any] struct {
4+
iter Iter[T]
5+
filterFunc func(T) bool
6+
}
7+
8+
func NewFilterIter[T any](iter Iter[T], filterFunc func(T) bool) *FilterIter[T] {
9+
return &FilterIter[T]{iter: iter, filterFunc: filterFunc}
10+
}
11+
12+
func (f FilterIter[T]) Next() (T, error) {
13+
for {
14+
item, err := f.iter.Next()
15+
if err != nil {
16+
return item, err
17+
}
18+
if f.filterFunc(item) {
19+
return item, nil
20+
}
21+
}
22+
}
23+
24+
func (f FilterIter[T]) ForEach(callback func(T) error) error {
25+
for {
26+
item, err := f.Next()
27+
if err != nil {
28+
return err
29+
}
30+
if err := callback(item); err != nil {
31+
return err
32+
}
33+
}
34+
}
35+
36+
func (f FilterIter[T]) Close() { f.iter.Close() }

internal/iter/iter.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
package iter
2+
3+
type Iter[T any] interface {
4+
Next() (T, error)
5+
ForEach(func(T) error) error
6+
Close()
7+
}

internal/referenceiter/referenceiter.go renamed to internal/iter/reference.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
1-
package referenceiter
1+
package iter
22

33
import (
44
"github.com/go-git/go-git/v5/plumbing"
5-
"github.com/go-git/go-git/v5/plumbing/storer"
65
"strings"
76
)
87

9-
func Has(iter storer.ReferenceIter, name string) (bool, bool) {
8+
func HasReference(iter Iter[*plumbing.Reference], name string) (bool, bool) {
109
has := false
1110
hasPrefix := false
1211
_ = iter.ForEach(func(reference *plumbing.Reference) error {

internal/iter/slice.go

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package iter
2+
3+
import (
4+
"io"
5+
)
6+
7+
type SliceIter[T any] struct {
8+
slice []T
9+
current int
10+
}
11+
12+
func NewSliceIter[T any](slice []T) *SliceIter[T] {
13+
return &SliceIter[T]{
14+
slice: slice,
15+
current: 0,
16+
}
17+
}
18+
19+
func (s *SliceIter[T]) Next() (T, error) {
20+
if s.current >= len(s.slice) {
21+
var empty T
22+
return empty, io.EOF
23+
}
24+
s.current++
25+
return s.slice[s.current-1], nil
26+
}
27+
28+
func (s *SliceIter[T]) ForEach(f func(T) error) error {
29+
for _, v := range s.slice {
30+
if err := f(v); err != nil {
31+
return err
32+
}
33+
}
34+
return nil
35+
}
36+
37+
func (s *SliceIter[T]) Close() {}

internal/set/set.go

Lines changed: 0 additions & 19 deletions
This file was deleted.

nodes/branch_segment.go

Lines changed: 17 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package nodes
22

33
import (
44
"context"
5-
"github.com/dsxack/gitfs/internal/referenceiter"
6-
"github.com/dsxack/gitfs/internal/set"
5+
"github.com/dsxack/gitfs/internal/iter"
76
"github.com/go-git/go-git/v5"
87
"github.com/go-git/go-git/v5/plumbing"
98
"github.com/hanwen/go-fuse/v2/fs"
@@ -49,7 +48,7 @@ func (node *BranchSegmentNode) Lookup(ctx context.Context, name string, _ *fuse.
4948
logger.Error("Error lookup branch segment", slog.String("error", err.Error()))
5049
return nil, syscall.ENOENT
5150
}
52-
ok, hasPrefix := referenceiter.Has(branches, revision)
51+
ok, hasPrefix := iter.HasReference(branches, revision)
5352
if ok {
5453
branchNode, err := NewObjectTreeNodeByRevision(node.repository, revision)
5554
if err != nil {
@@ -77,21 +76,24 @@ func (node *BranchSegmentNode) Lookup(ctx context.Context, name string, _ *fuse.
7776
// For example, if branch names are "foo/bar" and "foo/buz", then
7877
// will return "foo" directory with two children, "bar" and "buz".
7978
func (node *BranchSegmentNode) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
80-
branches, err := node.repository.Branches()
79+
var branches iter.Iter[*plumbing.Reference]
80+
var err error
81+
82+
branches, err = node.repository.Branches()
8183
if err != nil {
8284
return nil, syscall.ENOENT
8385
}
84-
dirEntries := set.New[fuse.DirEntry]()
85-
_ = branches.ForEach(func(branchRef *plumbing.Reference) error {
86+
branches = iter.NewFilterIter(branches, func(branchRef *plumbing.Reference) bool {
8687
branchName := bareBranchName(branchRef.Name().String())
87-
if !strings.HasPrefix(branchName, node.branchPrefix) {
88-
return nil
89-
}
90-
segments := strings.Split(strings.TrimPrefix(branchName, node.branchPrefix), branchNameSeparator)
91-
dirEntries.Add(fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR})
92-
return nil
88+
return strings.HasPrefix(branchName, node.branchPrefix)
9389
})
94-
slog.Default().Info("Dir of repository branch segment has been read",
95-
slog.String("branchPrefix", node.branchPrefix))
96-
return fs.NewListDirStream(dirEntries.Values()), 0
90+
91+
return iter.NewDirStreamAdapter[*plumbing.Reference](
92+
branches,
93+
func(branchRef *plumbing.Reference) fuse.DirEntry {
94+
branchName := bareBranchName(branchRef.Name().String())
95+
segments := strings.Split(strings.TrimPrefix(branchName, node.branchPrefix), branchNameSeparator)
96+
return fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR}
97+
},
98+
), 0
9799
}

nodes/branches.go

Lines changed: 10 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package nodes
22

33
import (
44
"context"
5-
"github.com/dsxack/gitfs/internal/referenceiter"
6-
"github.com/dsxack/gitfs/internal/set"
5+
"github.com/dsxack/gitfs/internal/iter"
76
"github.com/go-git/go-git/v5"
87
"github.com/go-git/go-git/v5/plumbing"
98
"github.com/hanwen/go-fuse/v2/fs"
@@ -49,7 +48,7 @@ func (node *BranchesNode) Lookup(ctx context.Context, name string, _ *fuse.Entry
4948
logger.Error("Error lookup branch", slog.String("error", err.Error()))
5049
return nil, syscall.ENOENT
5150
}
52-
ok, hasPrefix := referenceiter.Has(branches, revision)
51+
ok, hasPrefix := iter.HasReference(branches, revision)
5352
if ok {
5453
branchNode, err := NewObjectTreeNodeByRevision(node.repository, revision)
5554
if err != nil {
@@ -79,15 +78,15 @@ func (node *BranchesNode) Readdir(_ context.Context) (fs.DirStream, syscall.Errn
7978
if err != nil {
8079
return nil, syscall.ENOENT
8180
}
82-
dirEntries := set.New[fuse.DirEntry]()
83-
_ = branches.ForEach(func(branchRef *plumbing.Reference) error {
84-
name := bareBranchName(branchRef.Name().String())
85-
segments := strings.Split(name, branchNameSeparator)
86-
dirEntries.Add(fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR})
87-
return nil
88-
})
8981
slog.Default().Info("Dir of repository branches has been read")
90-
return fs.NewListDirStream(dirEntries.Values()), 0
82+
return iter.NewDirStreamAdapter[*plumbing.Reference](
83+
branches,
84+
func(branchRef *plumbing.Reference) fuse.DirEntry {
85+
name := bareBranchName(branchRef.Name().String())
86+
segments := strings.Split(name, branchNameSeparator)
87+
return fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR}
88+
},
89+
), 0
9190
}
9291

9392
const revisionBranchPrefix = "refs/heads/"

nodes/commits.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package nodes
22

33
import (
44
"context"
5-
"github.com/dsxack/gitfs/internal/set"
5+
"github.com/dsxack/gitfs/internal/iter"
66
"github.com/go-git/go-git/v5"
77
"github.com/go-git/go-git/v5/plumbing/object"
88
"github.com/hanwen/go-fuse/v2/fs"
@@ -53,12 +53,10 @@ func (node *CommitsNode) Readdir(_ context.Context) (fs.DirStream, syscall.Errno
5353
if err != nil {
5454
return nil, syscall.ENOENT
5555
}
56-
dirEntries := set.New[fuse.DirEntry]()
57-
_ = commits.ForEach(func(commit *object.Commit) error {
58-
dirEntries.Add(fuse.DirEntry{Name: commit.Hash.String(), Mode: syscall.S_IFDIR})
59-
return nil
60-
})
6156
slog.Default().Info("Dir of repository commits has been read")
62-
63-
return fs.NewListDirStream(dirEntries.Values()), 0
57+
return iter.NewDirStreamAdapter[*object.Commit](
58+
commits, func(commit *object.Commit) fuse.DirEntry {
59+
return fuse.DirEntry{Name: commit.Hash.String(), Mode: syscall.S_IFDIR}
60+
},
61+
), 0
6462
}

nodes/object.go

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package nodes
33
import (
44
"context"
55
"fmt"
6-
"github.com/dsxack/gitfs/internal/set"
6+
"github.com/dsxack/gitfs/internal/iter"
77
"github.com/go-git/go-git/v5"
88
"github.com/go-git/go-git/v5/plumbing"
99
"github.com/go-git/go-git/v5/plumbing/object"
@@ -103,20 +103,20 @@ func (node *ObjectTreeNode) Lookup(ctx context.Context, name string, _ *fuse.Ent
103103
}
104104

105105
func (node *ObjectTreeNode) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
106-
dirEntries := set.New[fuse.DirEntry]()
107-
for _, entry := range node.tree.Entries {
108-
var mode uint32 = fuse.S_IFREG
109-
if !entry.Mode.IsFile() {
110-
mode = fuse.S_IFDIR
111-
}
112-
113-
dirEntries.Add(fuse.DirEntry{
114-
Name: entry.Name,
115-
Mode: mode,
116-
})
117-
}
118106
slog.Default().Info("Dir of object tree has been read")
119-
return fs.NewListDirStream(dirEntries.Values()), 0
107+
return iter.NewDirStreamAdapter[object.TreeEntry](
108+
iter.NewSliceIter(node.tree.Entries),
109+
func(entry object.TreeEntry) fuse.DirEntry {
110+
var mode uint32 = fuse.S_IFREG
111+
if !entry.Mode.IsFile() {
112+
mode = fuse.S_IFDIR
113+
}
114+
return fuse.DirEntry{
115+
Name: entry.Name,
116+
Mode: mode,
117+
}
118+
},
119+
), 0
120120
}
121121

122122
func (node *ObjectTreeNode) Getattr(_ context.Context, _ fs.FileHandle, out *fuse.AttrOut) syscall.Errno {

nodes/tags.go

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@ package nodes
22

33
import (
44
"context"
5-
"github.com/dsxack/gitfs/internal/referenceiter"
6-
"github.com/dsxack/gitfs/internal/set"
5+
"github.com/dsxack/gitfs/internal/iter"
76
"github.com/go-git/go-git/v5"
87
"github.com/go-git/go-git/v5/plumbing"
98
"github.com/hanwen/go-fuse/v2/fs"
@@ -48,7 +47,7 @@ func (node *TagsNode) Lookup(ctx context.Context, name string, _ *fuse.EntryOut)
4847
logger.Error("Error lookup tag", slog.String("error", err.Error()))
4948
return nil, syscall.ENOENT
5049
}
51-
ok, hasPrefix := referenceiter.Has(tags, revision)
50+
ok, hasPrefix := iter.HasReference(tags, revision)
5251
if ok {
5352
branchNode, err := NewObjectTreeNodeByRevision(node.repository, revision)
5453
if err != nil {
@@ -78,16 +77,15 @@ func (node *TagsNode) Readdir(_ context.Context) (fs.DirStream, syscall.Errno) {
7877
if err != nil {
7978
return nil, syscall.ENOENT
8079
}
81-
dirEntries := set.New[fuse.DirEntry]()
82-
_ = tagRefs.ForEach(func(tagRef *plumbing.Reference) error {
83-
tagName := bareTagName(tagRef.Name().String())
84-
segments := strings.Split(tagName, tagNameSeparator)
85-
dirEntries.Add(fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR})
86-
return nil
87-
})
8880
slog.Default().Info("Dir of repository tags has been read")
89-
90-
return fs.NewListDirStream(dirEntries.Values()), 0
81+
return iter.NewDirStreamAdapter[*plumbing.Reference](
82+
tagRefs,
83+
func(tagRef *plumbing.Reference) fuse.DirEntry {
84+
tagName := bareTagName(tagRef.Name().String())
85+
segments := strings.Split(tagName, tagNameSeparator)
86+
return fuse.DirEntry{Name: segments[0], Mode: syscall.S_IFDIR}
87+
},
88+
), 0
9189
}
9290

9391
const revisionTagPrefix = "refs/tags/"

0 commit comments

Comments
 (0)