diff options
Diffstat (limited to 'object/tree')
| -rw-r--r-- | object/tree/append.go | 2 | ||||
| -rw-r--r-- | object/tree/append_test.go | 2 | ||||
| -rw-r--r-- | object/tree/clone.go | 24 | ||||
| -rw-r--r-- | object/tree/compare.go | 2 | ||||
| -rw-r--r-- | object/tree/helpers_test.go | 52 | ||||
| -rw-r--r-- | object/tree/insert.go | 10 | ||||
| -rw-r--r-- | object/tree/insert_test.go | 41 | ||||
| -rw-r--r-- | object/tree/lookup.go | 14 | ||||
| -rw-r--r-- | object/tree/lookup_test.go | 5 | ||||
| -rw-r--r-- | object/tree/malformed_test.go | 1 | ||||
| -rw-r--r-- | object/tree/mode/details.go | 10 | ||||
| -rw-r--r-- | object/tree/mode/mode_test.go | 12 | ||||
| -rw-r--r-- | object/tree/mode/ops.go | 2 | ||||
| -rw-r--r-- | object/tree/parse.go | 52 | ||||
| -rw-r--r-- | object/tree/parse_test.go | 2 | ||||
| -rw-r--r-- | object/tree/roundtrip_test.go | 6 | ||||
| -rw-r--r-- | object/tree/tree.go | 16 | ||||
| -rw-r--r-- | object/tree/type.go | 2 |
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 } |
