aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-28 16:33:14 +0000
committerGravatar Runxi Yu2026-03-28 16:33:14 +0000
commit060e5b7063ec282627dde96ac028f0d40cf05c60 (patch)
tree6a80e635a6c2f2955dfd97ab104103d6acdcf930
parentobject/store/{loose,packed}: TIghten language around Adler-32 (diff)
signatureNo signature
object/tree: Update docs
-rw-r--r--object/tree/entry.go2
-rw-r--r--object/tree/insert.go4
-rw-r--r--object/tree/lookup.go3
-rw-r--r--object/tree/parse.go2
-rw-r--r--object/tree/parse_test.go31
-rw-r--r--object/tree/tree.go8
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
}