From 060e5b7063ec282627dde96ac028f0d40cf05c60 Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sat, 28 Mar 2026 16:33:14 +0000 Subject: object/tree: Update docs --- object/tree/entry.go | 2 ++ object/tree/insert.go | 4 ++++ object/tree/lookup.go | 3 +++ object/tree/parse.go | 2 +- object/tree/parse_test.go | 31 +++++++++++++++++++++++++++++++ object/tree/tree.go | 8 +++++--- 6 files changed, 46 insertions(+), 4 deletions(-) diff --git a/object/tree/entry.go b/object/tree/entry.go index cddcde73..06b8d112 100644 --- a/object/tree/entry.go +++ b/object/tree/entry.go @@ -9,6 +9,8 @@ import ( // TreeEntry represents a single entry in a tree. type TreeEntry struct { Mode FileMode + // Name is part of the tree ordering. Mutating it after insertion may break + // Tree ordering and lookup behavior. Name []byte ID objectid.ObjectID } diff --git a/object/tree/insert.go b/object/tree/insert.go index bca4aa49..2da86514 100644 --- a/object/tree/insert.go +++ b/object/tree/insert.go @@ -6,11 +6,15 @@ import ( ) // InsertEntry inserts a tree entry while preserving Git ordering. +// +// InsertEntry copies newEntry.Name. func (tree *Tree) InsertEntry(newEntry TreeEntry) error { if tree.entry(newEntry.Name, true) != nil || tree.entry(newEntry.Name, false) != nil { return fmt.Errorf("object: tree: entry %q already exists", newEntry.Name) } + newEntry.Name = append([]byte(nil), newEntry.Name...) + newIsTree := newEntry.Mode == FileModeDir insertAt := sort.Search(len(tree.Entries), func(i int) bool { return TreeEntryNameCompare(tree.Entries[i].Name, tree.Entries[i].Mode, newEntry.Name, newIsTree) >= 0 diff --git a/object/tree/lookup.go b/object/tree/lookup.go index 957b31c4..ac8865b4 100644 --- a/object/tree/lookup.go +++ b/object/tree/lookup.go @@ -1,6 +1,9 @@ package tree // Entry looks up a tree entry by name. +// +// The returned pointer refers to storage within tree.Entries and must not be +// retained across InsertEntry or RemoveEntry calls. func (tree *Tree) Entry(name []byte) *TreeEntry { if len(tree.Entries) == 0 { return nil diff --git a/object/tree/parse.go b/object/tree/parse.go index 10bef968..bb874828 100644 --- a/object/tree/parse.go +++ b/object/tree/parse.go @@ -8,7 +8,7 @@ import ( objectid "codeberg.org/lindenii/furgit/object/id" ) -// Parse decodes a tree object body. +// Parse decodes a tree object body into a fully materialized Tree. func Parse(body []byte, algo objectid.Algorithm) (*Tree, error) { var entries []TreeEntry diff --git a/object/tree/parse_test.go b/object/tree/parse_test.go index 6f00220e..bf1c2fd0 100644 --- a/object/tree/parse_test.go +++ b/object/tree/parse_test.go @@ -74,3 +74,34 @@ func TestTreeParseFromGit(t *testing.T) { } }) } + +func TestTreeInsertEntryCopiesName(t *testing.T) { + t.Parallel() + + var tr tree.Tree + name := []byte("alpha") + entry := tree.TreeEntry{ + Mode: tree.FileModeRegular, + Name: name, + ID: objectid.ObjectID{}, + } + + if err := tr.InsertEntry(entry); err != nil { + t.Fatalf("InsertEntry: %v", err) + } + + name[0] = 'b' + + got := tr.Entry([]byte("alpha")) + if got == nil { + t.Fatalf("Entry(alpha) returned nil") + } + + if !bytes.Equal(got.Name, []byte("alpha")) { + t.Fatalf("stored name = %q, want %q", got.Name, []byte("alpha")) + } + + if tr.Entry([]byte("blpha")) != nil { + t.Fatalf("mutating caller name should not affect stored entry") + } +} diff --git a/object/tree/tree.go b/object/tree/tree.go index 31cabd93..d0c7f4f0 100644 --- a/object/tree/tree.go +++ b/object/tree/tree.go @@ -1,10 +1,12 @@ // Package tree provides representations, parsers, and serializers for tree objects. package tree -// Tree represents a Git tree object. +// Tree represents a fully materialized Git tree object. +// +// Labels: MT-Unsafe. type Tree struct { // Entries must be sorted by TreeEntryNameCompare. - // You are strongly advised to use the methods for manipulation - // rather than modifying the slice yourself. + // Use the Tree methods to preserve ordering and copy semantics rather than + // modifying the slice directly. Entries []TreeEntry } -- cgit v1.3.1-10-gc9f91