From 8a1c175d2c903ac27eecd5ba07c59e59005f589a Mon Sep 17 00:00:00 2001 From: Runxi Yu Date: Sun, 7 Jun 2026 12:36:30 +0000 Subject: object/tree: Add Find --- object/tree/insert.go | 19 +------------------ object/tree/lookup.go | 25 +++++++++++++++++++++++++ object/tree/lookup_test.go | 41 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 67 insertions(+), 18 deletions(-) create mode 100644 object/tree/lookup.go create mode 100644 object/tree/lookup_test.go diff --git a/object/tree/insert.go b/object/tree/insert.go index b5227938..5e519069 100644 --- a/object/tree/insert.go +++ b/object/tree/insert.go @@ -26,7 +26,7 @@ func (tree *Tree) Insert(entry Entry) error { return fmt.Errorf("%w: entry %q has invalid mode", ErrInvalidTree, entry.Name) } - if _, found := tree.find(entry.Name); found { + if _, found := tree.Find(entry.Name); found { return fmt.Errorf("%w: entry %q already exists", ErrInvalidTree, entry.Name) } @@ -41,23 +41,6 @@ func (tree *Tree) Insert(entry Entry) error { return nil } -// find returns the index of the entry with the given name, if present. -// -// A name conflicts whether stored as a blob-like or as a subtree entry, -// so both orderings are searched. -func (tree *Tree) find(name string) (int, bool) { - for _, searchIsTree := range [...]bool{true, false} { - index, ok := slices.BinarySearchFunc(tree.entries, name, func(existing Entry, target string) int { - return nameCompare(existing.Name, existing.Mode == mode.Directory, target, searchIsTree) - }) - if ok && tree.entries[index].Name == name { - return index, true - } - } - - return 0, false -} - // validateName checks that name is a structurally valid tree entry name. func validateName(name string) error { if name == "" { diff --git a/object/tree/lookup.go b/object/tree/lookup.go new file mode 100644 index 00000000..34a01748 --- /dev/null +++ b/object/tree/lookup.go @@ -0,0 +1,25 @@ +package tree + +import ( + "slices" + + "lindenii.org/go/furgit/object/tree/mode" +) + +// Find returns the entry with the given name, if present. +// +// 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) { + for _, searchIsTree := range [...]bool{true, false} { + index, ok := slices.BinarySearchFunc(tree.entries, name, func(existing Entry, target string) int { + return nameCompare(existing.Name, existing.Mode == mode.Directory, target, searchIsTree) + }) + if ok && tree.entries[index].Name == name { + return tree.entries[index], true + } + } + + return Entry{}, false +} diff --git a/object/tree/lookup_test.go b/object/tree/lookup_test.go new file mode 100644 index 00000000..22d73615 --- /dev/null +++ b/object/tree/lookup_test.go @@ -0,0 +1,41 @@ +package tree_test + +import ( + "testing" + + "lindenii.org/go/furgit/internal/testgit" + "lindenii.org/go/furgit/object/id" +) + +func TestFind(t *testing.T) { + t.Parallel() + + for _, objectFormat := range id.SupportedObjectFormats() { + t.Run(objectFormat.String(), func(t *testing.T) { + t.Parallel() + + repo, err := testgit.NewRepo(t, testgit.RepoOptions{ObjectFormat: objectFormat}) + if err != nil { + t.Fatalf("NewRepo: %v", err) + } + + entries := mixedEntries(t, repo) + tr := buildTree(t, entries) + + for _, want := range entries { + got, ok := tr.Find(want.Name) + if !ok { + t.Fatalf("Find(%q) not found", want.Name) + } + + if got.Mode != want.Mode || 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 { + t.Fatalf("Find(does-not-exist) = true, want false") + } + }) + } +} -- cgit v1.3.1-10-gc9f91