aboutsummaryrefslogtreecommitdiff
path: root/object/fetch
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-08 07:47:11 +0000
committerGravatar Runxi Yu2026-06-08 07:52:54 +0000
commit21aedc6a1455771edeb1190f15101e40b51b53f6 (patch)
treea1220e5dd87acedd0555ee82eddc417d5522dec9 /object/fetch
parentobject/fetch: Fix size types (diff)
object/fetch: Add simple treefs test and fix some lints
Diffstat (limited to 'object/fetch')
-rw-r--r--object/fetch/object.go2
-rw-r--r--object/fetch/treefs.go146
-rw-r--r--object/fetch/treefs_test.go172
3 files changed, 246 insertions, 74 deletions
diff --git a/object/fetch/object.go b/object/fetch/object.go
index 5dbb84ab..fdb46634 100644
--- a/object/fetch/object.go
+++ b/object/fetch/object.go
@@ -21,7 +21,7 @@ func (fetcher *Fetcher) ExactObject(id oid.ObjectID) (*stored.Stored[object.Obje
return stored.New(id, parsed), nil
}
-func (fetcher *Fetcher) parseObject(id oid.ObjectID) (object.Object, error) {
+func (fetcher *Fetcher) parseObject(id oid.ObjectID) (object.Object, error) { //nolint:ireturn
ty, content, err := fetcher.store.ReadBytesContent(id)
if err != nil {
return nil, wrapObjectReadError(id, err)
diff --git a/object/fetch/treefs.go b/object/fetch/treefs.go
index f5060a30..6a70f98f 100644
--- a/object/fetch/treefs.go
+++ b/object/fetch/treefs.go
@@ -37,33 +37,6 @@ var (
_ fs.SubFS = (*TreeFS)(nil)
)
-func (treeFS *TreeFS) resolvePath(op treeFSOp, name string) (treeEntryValue, error) {
- if !treeFSValidPath(name) {
- return treeEntryValue{}, treeFSPathError(op, name, fs.ErrInvalid)
- }
-
- if name == "." {
- return treeEntryValue{
- name: ".",
- mode: mode.Directory,
- treeID: treeFS.rootTree,
- treeEntry: treeFS.rootEntry,
- }, nil
- }
-
- entry, err := treeFS.fetcher.Path(treeFS.rootTree, splitPath(name))
- if err != nil {
- return treeEntryValue{}, treeFS.pathResolveError(op, name, err)
- }
-
- return treeEntryValue{
- name: string(entry.Name),
- mode: entry.Mode,
- objectID: entry.ID,
- treeEntry: &entry,
- }, nil
-}
-
func splitPath(path string) []string {
if len(path) == 0 {
return nil
@@ -72,22 +45,6 @@ func splitPath(path string) []string {
return strings.Split(path, "/")
}
-func (treeFS *TreeFS) pathResolveError(op treeFSOp, name string, err error) error {
- if _, ok := errors.AsType[*PathNotFoundError](err); ok {
- return treeFSPathError(op, name, fs.ErrNotExist)
- }
-
- if _, ok := errors.AsType[*PathNotTreeError](err); ok {
- return treeFSPathError(op, name, fs.ErrInvalid)
- }
-
- if errors.Is(err, ErrPathInvalid) {
- return treeFSPathError(op, name, fs.ErrInvalid)
- }
-
- return treeFSPathError(op, name, err)
-}
-
type treeEntryValue struct {
name string
mode mode.Mode
@@ -157,35 +114,6 @@ func treeFSEntryMode(mod mode.Mode) fs.FileMode {
}
}
-func (treeFS *TreeFS) statEntry(entry treeEntryValue) (*treeFSInfo, error) {
- size := int64(0)
-
- if entry.mode == mode.Regular || entry.mode == mode.Executable || entry.mode == mode.Symlink {
- sz, err := entry.blobSize(treeFS.fetcher)
- if err != nil {
- return nil, err
- }
-
- size, err = intconv.Uint64ToInt64(sz)
- if err != nil {
- return nil, err
- }
- }
-
- var sys any
- if entry.treeEntry != nil {
- sys = *entry.treeEntry
- }
-
- return &treeFSInfo{
- name: entry.name,
- mode: treeFSEntryMode(entry.mode),
- size: size,
- sys: sys,
- isDir: entry.isDir(),
- }, nil
-}
-
// TreeFS returns a new filesystem view rooted at root, which may be any
// tree-ish object accepted by PeelToTreeID.
//
@@ -258,7 +186,7 @@ func (treeFS *TreeFS) Open(name string) (fs.File, error) {
entries := make([]fs.DirEntry, 0, len(tree.Object().Entries()))
for _, child := range tree.Object().Entries() {
childEntry := treeEntryValue{
- name: string(child.Name),
+ name: child.Name,
mode: child.Mode,
objectID: child.ID,
treeEntry: &child,
@@ -436,3 +364,75 @@ func (treeFS *TreeFS) Sub(dir string) (fs.FS, error) {
rootEntry: entry.treeEntry,
}, nil
}
+
+func (treeFS *TreeFS) resolvePath(op treeFSOp, name string) (treeEntryValue, error) {
+ if !treeFSValidPath(name) {
+ return treeEntryValue{}, treeFSPathError(op, name, fs.ErrInvalid)
+ }
+
+ if name == "." {
+ return treeEntryValue{
+ name: ".",
+ mode: mode.Directory,
+ treeID: treeFS.rootTree,
+ treeEntry: treeFS.rootEntry,
+ }, nil
+ }
+
+ entry, err := treeFS.fetcher.Path(treeFS.rootTree, splitPath(name))
+ if err != nil {
+ return treeEntryValue{}, treeFS.pathResolveError(op, name, err)
+ }
+
+ return treeEntryValue{
+ name: entry.Name,
+ mode: entry.Mode,
+ objectID: entry.ID,
+ treeEntry: &entry,
+ }, nil
+}
+
+func (treeFS *TreeFS) pathResolveError(op treeFSOp, name string, err error) error {
+ if _, ok := errors.AsType[*PathNotFoundError](err); ok {
+ return treeFSPathError(op, name, fs.ErrNotExist)
+ }
+
+ if _, ok := errors.AsType[*PathNotTreeError](err); ok {
+ return treeFSPathError(op, name, fs.ErrInvalid)
+ }
+
+ if errors.Is(err, ErrPathInvalid) {
+ return treeFSPathError(op, name, fs.ErrInvalid)
+ }
+
+ return treeFSPathError(op, name, err)
+}
+
+func (treeFS *TreeFS) statEntry(entry treeEntryValue) (*treeFSInfo, error) {
+ size := int64(0)
+
+ if entry.mode == mode.Regular || entry.mode == mode.Executable || entry.mode == mode.Symlink {
+ sz, err := entry.blobSize(treeFS.fetcher)
+ if err != nil {
+ return nil, err
+ }
+
+ size, err = intconv.Uint64ToInt64(sz)
+ if err != nil {
+ return nil, err
+ }
+ }
+
+ var sys any
+ if entry.treeEntry != nil {
+ sys = *entry.treeEntry
+ }
+
+ return &treeFSInfo{
+ name: entry.name,
+ mode: treeFSEntryMode(entry.mode),
+ size: size,
+ sys: sys,
+ isDir: entry.isDir(),
+ }, nil
+}
diff --git a/object/fetch/treefs_test.go b/object/fetch/treefs_test.go
new file mode 100644
index 00000000..73f1c591
--- /dev/null
+++ b/object/fetch/treefs_test.go
@@ -0,0 +1,172 @@
+package fetch_test
+
+import (
+ "errors"
+ "io/fs"
+ "testing"
+
+ "lindenii.org/go/furgit/object/commit"
+ "lindenii.org/go/furgit/object/fetch"
+ "lindenii.org/go/furgit/object/id"
+ "lindenii.org/go/furgit/object/signature"
+ "lindenii.org/go/furgit/object/store/memory"
+ "lindenii.org/go/furgit/object/tree"
+ "lindenii.org/go/furgit/object/tree/mode"
+ "lindenii.org/go/furgit/object/typ"
+)
+
+func TestTreeFS(t *testing.T) {
+ t.Parallel()
+
+ for _, objectFormat := range id.SupportedObjectFormats() {
+ t.Run(objectFormat.String(), func(t *testing.T) {
+ t.Parallel()
+
+ store := memory.New(objectFormat)
+
+ plainID, err := store.WriteBytesContent(typ.TypeBlob, []byte("plain\n"))
+ if err != nil {
+ t.Fatalf("WriteBytesContent(plain.txt): %v", err)
+ }
+
+ execID, err := store.WriteBytesContent(typ.TypeBlob, []byte("#!/bin/sh\nexit 0\n"))
+ if err != nil {
+ t.Fatalf("WriteBytesContent(exec.sh): %v", err)
+ }
+
+ subTreeID := writeTree(t, store, []tree.Entry{
+ {Mode: mode.Executable, Name: "exec.sh", ID: execID},
+ })
+
+ rootTreeID := writeTree(t, store, []tree.Entry{
+ {Mode: mode.Regular, Name: "plain.txt", ID: plainID},
+ {Mode: mode.Directory, Name: "dir", ID: subTreeID},
+ })
+
+ commitID := writeCommit(t, store, rootTreeID)
+
+ fetcher := fetch.New(store)
+
+ treeFS, err := fetcher.TreeFS(commitID)
+ if err != nil {
+ t.Fatalf("fetcher.TreeFS: %v", err)
+ }
+
+ content, err := treeFS.ReadFile("plain.txt")
+ if err != nil {
+ t.Fatalf("ReadFile(plain.txt): %v", err)
+ }
+
+ if string(content) != "plain\n" {
+ t.Fatalf("ReadFile(plain.txt) = %q, want %q", string(content), "plain\n")
+ }
+
+ entries, err := treeFS.ReadDir(".")
+ if err != nil {
+ t.Fatalf("ReadDir(.): %v", err)
+ }
+
+ if len(entries) != 2 {
+ t.Fatalf("len(ReadDir(.)) = %d, want 2", len(entries))
+ }
+
+ info, err := treeFS.Stat("plain.txt")
+ if err != nil {
+ t.Fatalf("Stat(plain.txt): %v", err)
+ }
+
+ entry, ok := info.Sys().(tree.Entry)
+ if !ok {
+ t.Fatalf("Stat(plain.txt).Sys() type = %T, want tree.Entry", info.Sys())
+ }
+
+ if entry.Mode != mode.Regular {
+ t.Fatalf("Stat(plain.txt).Sys().Mode = %o, want %o", entry.Mode, mode.Regular)
+ }
+
+ subFS, err := treeFS.Sub("dir")
+ if err != nil {
+ t.Fatalf("Sub(dir): %v", err)
+ }
+
+ subReadFileFS, ok := subFS.(fs.ReadFileFS)
+ if !ok {
+ t.Fatalf("Sub(dir) type does not implement fs.ReadFileFS")
+ }
+
+ subContent, err := subReadFileFS.ReadFile("exec.sh")
+ if err != nil {
+ t.Fatalf("Sub(dir).ReadFile(exec.sh): %v", err)
+ }
+
+ if string(subContent) != "#!/bin/sh\nexit 0\n" {
+ t.Fatalf("Sub(dir).ReadFile(exec.sh) = %q", string(subContent))
+ }
+
+ _, err = treeFS.ReadFile("dir")
+ if err == nil {
+ t.Fatal("ReadFile(dir) unexpectedly succeeded")
+ }
+
+ if _, ok := errors.AsType[*fs.PathError](err); !ok {
+ t.Fatalf("ReadFile(dir) err type = %T, want *fs.PathError", err)
+ }
+ })
+ }
+}
+
+// writeTree builds a tree object from entries, writes it to store,
+// and returns its object ID.
+func writeTree(t *testing.T, store *memory.Memory, entries []tree.Entry) id.ObjectID {
+ t.Helper()
+
+ tr := new(tree.Tree)
+ for _, entry := range entries {
+ err := tr.Insert(entry)
+ if err != nil {
+ t.Fatalf("tree.Insert(%q): %v", entry.Name, err)
+ }
+ }
+
+ body, err := tr.AppendWithoutHeader(nil)
+ if err != nil {
+ t.Fatalf("tree.AppendWithoutHeader: %v", err)
+ }
+
+ treeID, err := store.WriteBytesContent(typ.TypeTree, body)
+ if err != nil {
+ t.Fatalf("WriteBytesContent(tree): %v", err)
+ }
+
+ return treeID
+}
+
+// writeCommit builds a commit object pointing at tree, writes it to store,
+// and returns its object ID.
+func writeCommit(t *testing.T, store *memory.Memory, tree id.ObjectID) id.ObjectID {
+ t.Helper()
+
+ who := signature.Signature{
+ Name: []byte("Test Author"),
+ Email: []byte("author@example.org"),
+ WhenUnix: 1234567890,
+ OffsetMinutes: 0,
+ }
+
+ body, err := (&commit.Commit{
+ Tree: tree,
+ Author: who,
+ Committer: who,
+ Message: []byte("treefs\n"),
+ }).AppendWithoutHeader(nil)
+ if err != nil {
+ t.Fatalf("commit.AppendWithoutHeader: %v", err)
+ }
+
+ commitID, err := store.WriteBytesContent(typ.TypeCommit, body)
+ if err != nil {
+ t.Fatalf("WriteBytesContent(commit): %v", err)
+ }
+
+ return commitID
+}