aboutsummaryrefslogtreecommitdiff
path: root/object/tree
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-13 16:30:31 +0000
committerGravatar Runxi Yu2026-06-13 16:30:31 +0000
commita6d0b99fd8dc28a47cee36f16890c66ea3acb746 (patch)
tree9f33b7385f20ccc90f5b899aefcf0f715ba2db00 /object/tree
parentdeps: Update lgo (diff)
object/tree: Parse, entries, etc don't clone.
Diffstat (limited to 'object/tree')
-rw-r--r--object/tree/compare.go2
-rw-r--r--object/tree/insert.go10
-rw-r--r--object/tree/lookup.go14
-rw-r--r--object/tree/parse.go15
-rw-r--r--object/tree/tree.go16
5 files changed, 37 insertions, 20 deletions
diff --git a/object/tree/compare.go b/object/tree/compare.go
index 78bf56a4..9bf16f90 100644
--- a/object/tree/compare.go
+++ b/object/tree/compare.go
@@ -6,7 +6,7 @@ package tree
// treating directory names as if they carried a trailing '/'.
// entryIsTree and searchIsTree indicate
// whether the respective names belong to subtree entries.
-func nameCompare(entryName string, entryIsTree bool, searchName string, searchIsTree bool) int {
+func nameCompare(entryName []byte, entryIsTree bool, searchName []byte, searchIsTree bool) int {
entryLen := len(entryName)
if entryIsTree {
entryLen++
diff --git a/object/tree/insert.go b/object/tree/insert.go
index 5e519069..b6c52400 100644
--- a/object/tree/insert.go
+++ b/object/tree/insert.go
@@ -1,10 +1,10 @@
package tree
import (
+ "bytes"
"errors"
"fmt"
"slices"
- "strings"
"lindenii.org/go/furgit/object/tree/mode"
)
@@ -42,16 +42,16 @@ func (tree *Tree) Insert(entry Entry) error {
}
// validateName checks that name is a structurally valid tree entry name.
-func validateName(name string) error {
- if name == "" {
+func validateName(name []byte) error {
+ if len(name) == 0 {
return fmt.Errorf("%w: empty entry name", ErrInvalidTree)
}
- if strings.IndexByte(name, 0) >= 0 {
+ if bytes.IndexByte(name, 0) >= 0 {
return fmt.Errorf("%w: entry name %q contains NUL", ErrInvalidTree, name)
}
- if strings.IndexByte(name, '/') >= 0 {
+ if bytes.IndexByte(name, '/') >= 0 {
return fmt.Errorf("%w: entry name %q contains '/'", ErrInvalidTree, name)
}
diff --git a/object/tree/lookup.go b/object/tree/lookup.go
index 34a01748..2ff6ce76 100644
--- a/object/tree/lookup.go
+++ b/object/tree/lookup.go
@@ -1,6 +1,7 @@
package tree
import (
+ "bytes"
"slices"
"lindenii.org/go/furgit/object/tree/mode"
@@ -10,13 +11,18 @@ import (
//
// A name matches whether stored as a blob-like or as a subtree entry,
// so both orderings are searched.
-// The returned entry is a copy; mutating it does not affect the tree.
-func (tree *Tree) Find(name string) (Entry, bool) {
+//
+// The returned entry is a shallow copy:
+// its Name aliases the tree's internal storage,
+// so it must not be mutated and shares the tree's lifetime.
+//
+// Labels: Life-Parent, Mut-No.
+func (tree *Tree) Find(name []byte) (Entry, bool) {
for _, searchIsTree := range [...]bool{true, false} {
- index, ok := slices.BinarySearchFunc(tree.entries, name, func(existing Entry, target string) int {
+ index, ok := slices.BinarySearchFunc(tree.entries, name, func(existing Entry, target []byte) int {
return nameCompare(existing.Name, existing.Mode == mode.Directory, target, searchIsTree)
})
- if ok && tree.entries[index].Name == name {
+ if ok && bytes.Equal(tree.entries[index].Name, name) {
return tree.entries[index], true
}
}
diff --git a/object/tree/parse.go b/object/tree/parse.go
index 5b01fa05..e89ce0fa 100644
--- a/object/tree/parse.go
+++ b/object/tree/parse.go
@@ -6,6 +6,7 @@ import (
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/furgit/object/tree/mode"
+ "lindenii.org/go/lgo/unsafe"
)
// Parse decodes a tree object body into a fully materialized Tree.
@@ -14,6 +15,14 @@ import (
// correctly sized object IDs, and strictly increasing Git tree order.
// It does not enforce fsck-level name policy
// (for example ".", "..", ".git", or platform-specific aliases).
+//
+// The returned tree aliases body:
+// each entry's Name shares body's backing array.
+// The tree inherits body's lifetime
+// and must not be mutated unless body may be.
+// Use [Tree.Clone] for an independent copy.
+//
+// Labels: Life-Parent, Mut-No.
func Parse(body []byte, objectFormat id.ObjectFormat) (*Tree, error) {
tree := new(Tree)
idSize := objectFormat.Size()
@@ -38,7 +47,7 @@ func Parse(body []byte, objectFormat id.ObjectFormat) (*Tree, error) {
return nil, fmt.Errorf("%w: missing name terminator at offset %d", ErrInvalidTree, i)
}
- name := string(body[i : i+nul])
+ name := body[i : i+nul]
i += nul + 1
err = validateName(name)
@@ -67,11 +76,11 @@ func Parse(body []byte, objectFormat id.ObjectFormat) (*Tree, error) {
}
}
- if _, dup := seen[entry.Name]; dup {
+ if _, dup := seen[unsafe.String(entry.Name)]; dup {
return nil, fmt.Errorf("%w: duplicate entry name %q", ErrInvalidTree, entry.Name)
}
- seen[entry.Name] = struct{}{}
+ seen[unsafe.String(entry.Name)] = struct{}{}
tree.entries = append(tree.entries, entry)
}
diff --git a/object/tree/tree.go b/object/tree/tree.go
index 431df649..f40bb165 100644
--- a/object/tree/tree.go
+++ b/object/tree/tree.go
@@ -1,8 +1,6 @@
package tree
import (
- "slices"
-
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/furgit/object/tree/mode"
)
@@ -21,15 +19,19 @@ type Tree struct {
// Entry represents a single entry in a tree.
type Entry struct {
Mode mode.Mode
- Name string
+ Name []byte
ID id.ObjectID
}
-// Entries returns a copy of the tree's entries in Git tree order.
+// Entries returns the tree's entries in Git tree order.
//
-// Mutating the returned slice does not affect the tree.
+// The returned slice aliases the tree's internal storage,
+// so it must not be mutated,
+// and it is invalidated by any subsequent call that mutates the tree,
+// such as [Tree.Insert].
+// Use [Tree.Clone] for an independent tree.
//
-// Labels: Life-Independent.
+// Labels: Life-Parent, Mut-No.
func (tree *Tree) Entries() []Entry {
- return slices.Clone(tree.entries)
+ return tree.entries
}