aboutsummaryrefslogtreecommitdiff
path: root/object/tree
diff options
context:
space:
mode:
Diffstat (limited to 'object/tree')
-rw-r--r--object/tree/append.go2
-rw-r--r--object/tree/append_test.go2
-rw-r--r--object/tree/clone.go24
-rw-r--r--object/tree/compare.go2
-rw-r--r--object/tree/helpers_test.go52
-rw-r--r--object/tree/insert.go10
-rw-r--r--object/tree/insert_test.go41
-rw-r--r--object/tree/lookup.go14
-rw-r--r--object/tree/lookup_test.go5
-rw-r--r--object/tree/malformed_test.go1
-rw-r--r--object/tree/mode/details.go10
-rw-r--r--object/tree/mode/mode_test.go12
-rw-r--r--object/tree/mode/ops.go2
-rw-r--r--object/tree/parse.go52
-rw-r--r--object/tree/parse_test.go2
-rw-r--r--object/tree/roundtrip_test.go6
-rw-r--r--object/tree/tree.go16
-rw-r--r--object/tree/type.go2
18 files changed, 155 insertions, 100 deletions
diff --git a/object/tree/append.go b/object/tree/append.go
index 9985e668..edabf714 100644
--- a/object/tree/append.go
+++ b/object/tree/append.go
@@ -39,7 +39,7 @@ func (tree *Tree) AppendWithHeader(dst []byte) ([]byte, error) {
return dst, err
}
- dst = header.Append(dst, typ.TypeTree, uint64(len(body)))
+ dst = header.Append(dst, typ.Tree, len(body))
return append(dst, body...), nil
}
diff --git a/object/tree/append_test.go b/object/tree/append_test.go
index babcd03b..174f50bf 100644
--- a/object/tree/append_test.go
+++ b/object/tree/append_test.go
@@ -29,7 +29,7 @@ func TestAppend(t *testing.T) {
t.Fatalf("AppendWithoutHeader: %v", err)
}
- treeID, err := repo.HashObject(t, typ.TypeTree, bytes.NewReader(rawBody))
+ treeID, err := repo.HashObject(t, typ.Tree, bytes.NewReader(rawBody))
if err != nil {
t.Fatalf("HashObject(tree): %v", err)
}
diff --git a/object/tree/clone.go b/object/tree/clone.go
new file mode 100644
index 00000000..d00c62f2
--- /dev/null
+++ b/object/tree/clone.go
@@ -0,0 +1,24 @@
+package tree
+
+import "bytes"
+
+// Clone returns a deep copy of the tree
+// whose entry names are independent of any memory the original may alias.
+//
+// Labels: Life-Independent.
+func (tree *Tree) Clone() *Tree {
+ if tree.entries == nil {
+ return &Tree{}
+ }
+
+ clone := &Tree{entries: make([]Entry, len(tree.entries))}
+ for i, entry := range tree.entries {
+ clone.entries[i] = Entry{
+ Mode: entry.Mode,
+ Name: bytes.Clone(entry.Name),
+ ID: entry.ID,
+ }
+ }
+
+ return clone
+}
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/helpers_test.go b/object/tree/helpers_test.go
index c71f0117..3e5eddd4 100644
--- a/object/tree/helpers_test.go
+++ b/object/tree/helpers_test.go
@@ -15,23 +15,23 @@ import (
func mixedEntries(tb testing.TB, repo *testgit.Repo) []tree.Entry {
tb.Helper()
- blobA, err := repo.HashObject(tb, typ.TypeBlob, strings.NewReader("blob-A\n"))
+ blobA, err := repo.HashObject(tb, typ.Blob, strings.NewReader("blob-A\n"))
if err != nil {
tb.Fatalf("HashObject(blob-A): %v", err)
}
- blobB, err := repo.HashObject(tb, typ.TypeBlob, strings.NewReader("blob-B\n"))
+ blobB, err := repo.HashObject(tb, typ.Blob, strings.NewReader("blob-B\n"))
if err != nil {
tb.Fatalf("HashObject(blob-B): %v", err)
}
- blobC, err := repo.HashObject(tb, typ.TypeBlob, strings.NewReader("blob-C\n"))
+ blobC, err := repo.HashObject(tb, typ.Blob, strings.NewReader("blob-C\n"))
if err != nil {
tb.Fatalf("HashObject(blob-C): %v", err)
}
subTree, err := repo.MkTree(tb, []testgit.TreeEntry{
- {Mode: "100644", Type: typ.TypeBlob, OID: blobA, Name: "leaf"},
+ {Mode: "100644", Type: typ.Blob, OID: blobA, Name: "leaf"},
})
if err != nil {
tb.Fatalf("MkTree(subtree): %v", err)
@@ -43,26 +43,26 @@ func mixedEntries(tb testing.TB, repo *testgit.Repo) []tree.Entry {
}
return []tree.Entry{
- {Mode: mode.Regular, Name: "z", ID: blobA},
- {Mode: mode.Regular, Name: "A", ID: blobB},
- {Mode: mode.Regular, Name: "aa", ID: blobC},
- {Mode: mode.Regular, Name: "a0", ID: blobA},
- {Mode: mode.Regular, Name: "a.", ID: blobC},
- {Mode: mode.Regular, Name: "Z", ID: blobB},
- {Mode: mode.Regular, Name: "0", ID: blobA},
- {Mode: mode.Regular, Name: "CAPS", ID: blobB},
- {Mode: mode.Regular, Name: "caps", ID: blobC},
- {Mode: mode.Regular, Name: "name with space", ID: blobB},
- {Mode: mode.Regular, Name: "name.with.dot", ID: blobA},
- {Mode: mode.Regular, Name: "这是一些非 ASCII 的字符", ID: blobC},
- {Mode: mode.Regular, Name: "Emoji 👀", ID: blobC},
- {Mode: mode.Regular, Name: ".hidden", ID: blobA},
- {Mode: mode.Executable, Name: "exec.sh", ID: blobB},
- {Mode: mode.Symlink, Name: "sym.link", ID: blobC},
- {Mode: mode.Gitlink, Name: "submodule", ID: submodule},
- {Mode: mode.Regular, Name: "dir-", ID: blobA},
- {Mode: mode.Directory, Name: "dir", ID: subTree},
- {Mode: mode.Regular, Name: "dir0", ID: blobB},
+ {Mode: mode.Regular, Name: []byte("z"), ID: blobA},
+ {Mode: mode.Regular, Name: []byte("A"), ID: blobB},
+ {Mode: mode.Regular, Name: []byte("aa"), ID: blobC},
+ {Mode: mode.Regular, Name: []byte("a0"), ID: blobA},
+ {Mode: mode.Regular, Name: []byte("a."), ID: blobC},
+ {Mode: mode.Regular, Name: []byte("Z"), ID: blobB},
+ {Mode: mode.Regular, Name: []byte("0"), ID: blobA},
+ {Mode: mode.Regular, Name: []byte("CAPS"), ID: blobB},
+ {Mode: mode.Regular, Name: []byte("caps"), ID: blobC},
+ {Mode: mode.Regular, Name: []byte("name with space"), ID: blobB},
+ {Mode: mode.Regular, Name: []byte("name.with.dot"), ID: blobA},
+ {Mode: mode.Regular, Name: []byte("这是一些非 ASCII 的字符"), ID: blobC},
+ {Mode: mode.Regular, Name: []byte("Emoji 👀"), ID: blobC},
+ {Mode: mode.Regular, Name: []byte(".hidden"), ID: blobA},
+ {Mode: mode.Executable, Name: []byte("exec.sh"), ID: blobB},
+ {Mode: mode.Symlink, Name: []byte("sym.link"), ID: blobC},
+ {Mode: mode.Gitlink, Name: []byte("submodule"), ID: submodule},
+ {Mode: mode.Regular, Name: []byte("dir-"), ID: blobA},
+ {Mode: mode.Directory, Name: []byte("dir"), ID: subTree},
+ {Mode: mode.Regular, Name: []byte("dir0"), ID: blobB},
}
}
@@ -73,7 +73,7 @@ func mkTreeEntries(entries []tree.Entry) []testgit.TreeEntry {
Mode: strconv.FormatUint(uint64(entry.Mode), 8),
Type: entry.Mode.ObjectType(),
OID: entry.ID,
- Name: entry.Name,
+ Name: string(entry.Name),
}
}
@@ -124,7 +124,7 @@ func assertGitDecode(tb testing.TB, repo *testgit.Repo, treeID id.ObjectID, got
tb.Fatalf("entry[%d] id = %s, want %s", i, got[i].ID, want[i].OID)
}
- if got[i].Name != want[i].Name {
+ if string(got[i].Name) != want[i].Name {
tb.Fatalf("entry[%d] name = %q, want %q", i, got[i].Name, want[i].Name)
}
}
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/insert_test.go b/object/tree/insert_test.go
index fbf65b84..1dd406d5 100644
--- a/object/tree/insert_test.go
+++ b/object/tree/insert_test.go
@@ -18,10 +18,10 @@ func TestInsertRejects(t *testing.T) {
name string
entry tree.Entry
}{
- {name: "empty-name", entry: tree.Entry{Mode: mode.Regular, Name: "", ID: zero}},
- {name: "slash-name", entry: tree.Entry{Mode: mode.Regular, Name: "a/b", ID: zero}},
- {name: "nul-name", entry: tree.Entry{Mode: mode.Regular, Name: "a\x00b", ID: zero}},
- {name: "invalid-mode", entry: tree.Entry{Mode: mode.Mode(0o100640), Name: "file", ID: zero}},
+ {name: "empty-name", entry: tree.Entry{Mode: mode.Regular, Name: []byte(""), ID: zero}},
+ {name: "slash-name", entry: tree.Entry{Mode: mode.Regular, Name: []byte("a/b"), ID: zero}},
+ {name: "nul-name", entry: tree.Entry{Mode: mode.Regular, Name: []byte("a\x00b"), ID: zero}},
+ {name: "invalid-mode", entry: tree.Entry{Mode: mode.Mode(0o100640), Name: []byte("file"), ID: zero}},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
@@ -48,18 +48,18 @@ func TestInsertRejectsConflict(t *testing.T) {
}{
{
name: "same-mode",
- first: tree.Entry{Mode: mode.Regular, Name: "file", ID: zero},
- second: tree.Entry{Mode: mode.Regular, Name: "file", ID: zero},
+ first: tree.Entry{Mode: mode.Regular, Name: []byte("file"), ID: zero},
+ second: tree.Entry{Mode: mode.Regular, Name: []byte("file"), ID: zero},
},
{
name: "blob-then-tree",
- first: tree.Entry{Mode: mode.Regular, Name: "name", ID: zero},
- second: tree.Entry{Mode: mode.Directory, Name: "name", ID: zero},
+ first: tree.Entry{Mode: mode.Regular, Name: []byte("name"), ID: zero},
+ second: tree.Entry{Mode: mode.Directory, Name: []byte("name"), ID: zero},
},
{
name: "tree-then-blob",
- first: tree.Entry{Mode: mode.Directory, Name: "name", ID: zero},
- second: tree.Entry{Mode: mode.Regular, Name: "name", ID: zero},
+ first: tree.Entry{Mode: mode.Directory, Name: []byte("name"), ID: zero},
+ second: tree.Entry{Mode: mode.Regular, Name: []byte("name"), ID: zero},
},
} {
t.Run(tc.name, func(t *testing.T) {
@@ -79,24 +79,3 @@ func TestInsertRejectsConflict(t *testing.T) {
})
}
}
-
-func TestEntriesIsCopy(t *testing.T) {
- t.Parallel()
-
- zero := id.SupportedObjectFormats()[0].Zero()
-
- var tr tree.Tree
-
- err := tr.Insert(tree.Entry{Mode: mode.Regular, Name: "file", ID: zero})
- if err != nil {
- t.Fatalf("Insert: %v", err)
- }
-
- entries := tr.Entries()
- entries[0].Name = "mutated"
-
- again := tr.Entries()
- if again[0].Name != "file" {
- t.Fatalf("Entries()[0].Name = %q, want %q", again[0].Name, "file")
- }
-}
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/lookup_test.go b/object/tree/lookup_test.go
index 22d73615..706c1cd2 100644
--- a/object/tree/lookup_test.go
+++ b/object/tree/lookup_test.go
@@ -1,6 +1,7 @@
package tree_test
import (
+ "bytes"
"testing"
"lindenii.org/go/furgit/internal/testgit"
@@ -28,12 +29,12 @@ func TestFind(t *testing.T) {
t.Fatalf("Find(%q) not found", want.Name)
}
- if got.Mode != want.Mode || got.Name != want.Name || got.ID != want.ID {
+ if got.Mode != want.Mode || !bytes.Equal(got.Name, want.Name) || got.ID != want.ID {
t.Fatalf("Find(%q) = %+v, want %+v", want.Name, got, want)
}
}
- if _, ok := tr.Find("does-not-exist"); ok {
+ if _, ok := tr.Find([]byte("does-not-exist")); ok {
t.Fatalf("Find(does-not-exist) = true, want false")
}
})
diff --git a/object/tree/malformed_test.go b/object/tree/malformed_test.go
index ca00ea94..8a22b90f 100644
--- a/object/tree/malformed_test.go
+++ b/object/tree/malformed_test.go
@@ -44,6 +44,7 @@ func TestParseMalformed(t *testing.T) {
{name: "unsorted", body: append(record("100644", "b", size), record("100644", "a", size)...)},
{name: "duplicate", body: append(record("100644", "a", size), record("100644", "a", size)...)},
{name: "conflicting-tree-blob", body: append(record("100644", "foo", size), record("40000", "foo", size)...)},
+ {name: "conflicting-tree-blob-nonadjacent", body: append(append(record("100644", "foo", size), record("100644", "foo.c", size)...), record("40000", "foo", size)...)},
} {
t.Run(tc.name, func(t *testing.T) {
t.Parallel()
diff --git a/object/tree/mode/details.go b/object/tree/mode/details.go
index 90ead2ed..32ea6768 100644
--- a/object/tree/mode/details.go
+++ b/object/tree/mode/details.go
@@ -19,30 +19,30 @@ var modeTable = map[Mode]modeDetails{
valid: true,
isBlobLike: false,
isRegularFile: false,
- objectType: typ.TypeTree,
+ objectType: typ.Tree,
},
Regular: {
valid: true,
isBlobLike: true,
isRegularFile: true,
- objectType: typ.TypeBlob,
+ objectType: typ.Blob,
},
Executable: {
valid: true,
isBlobLike: true,
isRegularFile: true,
- objectType: typ.TypeBlob,
+ objectType: typ.Blob,
},
Symlink: {
valid: true,
isBlobLike: true,
isRegularFile: false,
- objectType: typ.TypeBlob,
+ objectType: typ.Blob,
},
Gitlink: {
valid: true,
isBlobLike: false,
isRegularFile: false,
- objectType: typ.TypeCommit,
+ objectType: typ.Commit,
},
}
diff --git a/object/tree/mode/mode_test.go b/object/tree/mode/mode_test.go
index 0e2cc2aa..2ce6c8ea 100644
--- a/object/tree/mode/mode_test.go
+++ b/object/tree/mode/mode_test.go
@@ -14,12 +14,12 @@ func TestObjectType(t *testing.T) {
mode mode.Mode
want typ.Type
}{
- {mode: mode.Directory, want: typ.TypeTree},
- {mode: mode.Regular, want: typ.TypeBlob},
- {mode: mode.Executable, want: typ.TypeBlob},
- {mode: mode.Symlink, want: typ.TypeBlob},
- {mode: mode.Gitlink, want: typ.TypeCommit},
- {mode: mode.Mode(0), want: typ.TypeUnknown},
+ {mode: mode.Directory, want: typ.Tree},
+ {mode: mode.Regular, want: typ.Blob},
+ {mode: mode.Executable, want: typ.Blob},
+ {mode: mode.Symlink, want: typ.Blob},
+ {mode: mode.Gitlink, want: typ.Commit},
+ {mode: mode.Mode(0), want: typ.Unknown},
} {
if got := tc.mode.ObjectType(); got != tc.want {
t.Fatalf("Mode(%o).ObjectType() = %v, want %v", tc.mode, got, tc.want)
diff --git a/object/tree/mode/ops.go b/object/tree/mode/ops.go
index 149d95db..5f161d0b 100644
--- a/object/tree/mode/ops.go
+++ b/object/tree/mode/ops.go
@@ -32,7 +32,7 @@ func (mode Mode) HasSameType(other Mode) bool {
// ObjectType returns the type of object that an entry with this mode targets.
//
-// It returns [typ.TypeUnknown] for invalid modes.
+// It returns [typ.Unknown] for invalid modes.
func (mode Mode) ObjectType() typ.Type {
return mode.details().objectType
}
diff --git a/object/tree/parse.go b/object/tree/parse.go
index 5b01fa05..bd6ed3b0 100644
--- a/object/tree/parse.go
+++ b/object/tree/parse.go
@@ -14,10 +14,22 @@ 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()
- seen := make(map[string]struct{})
+
+ const minEntryOverhead = 5 + 1 + 1 + 1 // mode, space, name, NUL
+ if estimate := len(body) / (minEntryOverhead + idSize); estimate > 0 {
+ tree.entries = make([]Entry, 0, estimate)
+ }
i := 0
for i < len(body) {
@@ -38,7 +50,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,14 +79,44 @@ func Parse(body []byte, objectFormat id.ObjectFormat) (*Tree, error) {
}
}
- if _, dup := seen[entry.Name]; dup {
+ if entryMode == mode.Directory && hasNonDirNamed(tree.entries, entry.Name) {
return nil, fmt.Errorf("%w: duplicate entry name %q", ErrInvalidTree, entry.Name)
}
- seen[entry.Name] = struct{}{}
-
tree.entries = append(tree.entries, entry)
}
return tree, nil
}
+
+// hasNonDirNamed reports whether entries, sorted in Git tree order,
+// holds a non-directory entry whose name equals name.
+//
+// The match sorts immediately below a directory of the same name,
+// so the search gallops from the back before binary searching the bracket.
+func hasNonDirNamed(entries []Entry, name []byte) bool {
+ lo, hi := 0, len(entries)
+
+ for stride := 1; stride < hi-lo; stride *= 2 {
+ mid := hi - stride
+ if nameCompare(entries[mid].Name, entries[mid].Mode == mode.Directory, name, false) < 0 {
+ lo = mid + 1
+
+ break
+ }
+
+ hi = mid
+ }
+
+ for lo < hi {
+ mid := lo + (hi-lo)/2
+ if nameCompare(entries[mid].Name, entries[mid].Mode == mode.Directory, name, false) < 0 {
+ lo = mid + 1
+ } else {
+ hi = mid
+ }
+ }
+
+ return lo < len(entries) &&
+ nameCompare(entries[lo].Name, entries[lo].Mode == mode.Directory, name, false) == 0
+}
diff --git a/object/tree/parse_test.go b/object/tree/parse_test.go
index 8d32b136..0f5a8b30 100644
--- a/object/tree/parse_test.go
+++ b/object/tree/parse_test.go
@@ -28,7 +28,7 @@ func TestParse(t *testing.T) {
t.Fatalf("MkTree: %v", err)
}
- rawBody, err := repo.CatFile(t, typ.TypeTree, treeID)
+ rawBody, err := repo.CatFile(t, typ.Tree, treeID)
if err != nil {
t.Fatalf("CatFile: %v", err)
}
diff --git a/object/tree/roundtrip_test.go b/object/tree/roundtrip_test.go
index 7fdc4140..a9d5f40f 100644
--- a/object/tree/roundtrip_test.go
+++ b/object/tree/roundtrip_test.go
@@ -30,7 +30,7 @@ func TestRoundTrip(t *testing.T) {
t.Fatalf("AppendWithoutHeader: %v", err)
}
- treeID, err := repo.HashObject(t, typ.TypeTree, bytes.NewReader(rawBody))
+ treeID, err := repo.HashObject(t, typ.Tree, bytes.NewReader(rawBody))
if err != nil {
t.Fatalf("HashObject(tree): %v", err)
}
@@ -43,7 +43,7 @@ func TestRoundTrip(t *testing.T) {
t.Fatalf("Fsck: %v", err)
}
- gitBody, err := repo.CatFile(t, typ.TypeTree, treeID)
+ gitBody, err := repo.CatFile(t, typ.Tree, treeID)
if err != nil {
t.Fatalf("CatFile: %v", err)
}
@@ -70,7 +70,7 @@ func assertEntriesEqual(t *testing.T, got []tree.Entry, want []tree.Entry) {
t.Fatalf("entry[%d] mode = %o, want %o", i, got[i].Mode, want[i].Mode)
}
- if got[i].Name != want[i].Name {
+ if !bytes.Equal(got[i].Name, want[i].Name) {
t.Fatalf("entry[%d] name = %q, want %q", i, got[i].Name, want[i].Name)
}
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
}
diff --git a/object/tree/type.go b/object/tree/type.go
index 125e0bcc..aa433e82 100644
--- a/object/tree/type.go
+++ b/object/tree/type.go
@@ -6,5 +6,5 @@ import "lindenii.org/go/furgit/object/typ"
func (tree *Tree) ObjectType() typ.Type {
_ = tree
- return typ.TypeTree
+ return typ.Tree
}