aboutsummaryrefslogtreecommitdiff
path: root/object/tree
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-03-30 05:40:58 +0000
committerGravatar Runxi Yu2026-03-30 05:44:56 +0000
commitaa7e2873aa4da53fbaa4774fdd33f5af20c1afb0 (patch)
tree0d1367f693324ea8287ccef52ba524f8515ca146 /object/tree
parentREADME, etc.: Use proper unicode hyphens :P (diff)
signatureNo signature
object/tree: Add helpers and cleanup v0.1.152
Diffstat (limited to 'object/tree')
-rw-r--r--object/tree/entry.go38
-rw-r--r--object/tree/insert.go14
-rw-r--r--object/tree/mode.go12
-rw-r--r--object/tree/mode_details.go9
-rw-r--r--object/tree/mode_is_blob_like.go8
-rw-r--r--object/tree/mode_table.go19
-rw-r--r--object/tree/path_append.go14
-rw-r--r--object/tree/path_clone.go16
-rw-r--r--object/tree/path_prefix.go19
-rw-r--r--object/tree/path_split.go19
-rw-r--r--object/tree/remove.go14
11 files changed, 137 insertions, 45 deletions
diff --git a/object/tree/entry.go b/object/tree/entry.go
index 06b8d112..70f1dab7 100644
--- a/object/tree/entry.go
+++ b/object/tree/entry.go
@@ -2,6 +2,7 @@ package tree
import (
"bytes"
+ "slices"
objectid "codeberg.org/lindenii/furgit/object/id"
)
@@ -16,26 +17,25 @@ type TreeEntry struct {
}
func (tree *Tree) entry(name []byte, searchIsTree bool) *TreeEntry {
- low, high := 0, len(tree.Entries)-1
- for low <= high {
- mid := low + (high-low)/2
- entry := &tree.Entries[mid]
-
- cmp := TreeEntryNameCompare(entry.Name, entry.Mode, name, searchIsTree)
- if cmp == 0 {
- if bytes.Equal(entry.Name, name) {
- return entry
- }
-
- return nil
- }
+ index, ok := slices.BinarySearchFunc(tree.Entries, treeEntrySearch{
+ name: name,
+ isTree: searchIsTree,
+ }, func(entry TreeEntry, search treeEntrySearch) int {
+ return TreeEntryNameCompare(entry.Name, entry.Mode, search.name, search.isTree)
+ })
+ if !ok {
+ return nil
+ }
- if cmp < 0 {
- low = mid + 1
- } else {
- high = mid - 1
- }
+ entry := &tree.Entries[index]
+ if !bytes.Equal(entry.Name, name) {
+ return nil
}
- return nil
+ return entry
+}
+
+type treeEntrySearch struct {
+ name []byte
+ isTree bool
}
diff --git a/object/tree/insert.go b/object/tree/insert.go
index 2da86514..683af4a7 100644
--- a/object/tree/insert.go
+++ b/object/tree/insert.go
@@ -2,7 +2,7 @@ package tree
import (
"fmt"
- "sort"
+ "slices"
)
// InsertEntry inserts a tree entry while preserving Git ordering.
@@ -15,13 +15,13 @@ func (tree *Tree) InsertEntry(newEntry TreeEntry) error {
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
+ insertAt, _ := slices.BinarySearchFunc(tree.Entries, treeEntrySearch{
+ name: newEntry.Name,
+ isTree: newEntry.Mode == FileModeDir,
+ }, func(entry TreeEntry, search treeEntrySearch) int {
+ return TreeEntryNameCompare(entry.Name, entry.Mode, search.name, search.isTree)
})
- tree.Entries = append(tree.Entries, TreeEntry{})
- copy(tree.Entries[insertAt+1:], tree.Entries[insertAt:])
- tree.Entries[insertAt] = newEntry
+ tree.Entries = slices.Insert(tree.Entries, insertAt, newEntry)
return nil
}
diff --git a/object/tree/mode.go b/object/tree/mode.go
index c0bb4d75..b1cbc6bc 100644
--- a/object/tree/mode.go
+++ b/object/tree/mode.go
@@ -10,15 +10,3 @@ const (
FileModeSymlink FileMode = 0o120000
FileModeGitlink FileMode = 0o160000
)
-
-// IsBlobLike reports whether mode names one blob-like tree entry kind.
-//
-// Blob-like entries store blob object IDs as their targets.
-func (mode FileMode) IsBlobLike() bool {
- switch mode {
- case FileModeRegular, FileModeExecutable, FileModeSymlink:
- return true
- default:
- return false
- }
-}
diff --git a/object/tree/mode_details.go b/object/tree/mode_details.go
new file mode 100644
index 00000000..02d22620
--- /dev/null
+++ b/object/tree/mode_details.go
@@ -0,0 +1,9 @@
+package tree
+
+type fileModeDetails struct {
+ isBlobLike bool
+}
+
+func (mode FileMode) details() fileModeDetails {
+ return fileModeTable[mode]
+}
diff --git a/object/tree/mode_is_blob_like.go b/object/tree/mode_is_blob_like.go
new file mode 100644
index 00000000..3ec3a308
--- /dev/null
+++ b/object/tree/mode_is_blob_like.go
@@ -0,0 +1,8 @@
+package tree
+
+// IsBlobLike reports whether mode names one blob-like tree entry kind.
+//
+// Blob-like entries store blob object IDs as their targets.
+func (mode FileMode) IsBlobLike() bool {
+ return mode.details().isBlobLike
+}
diff --git a/object/tree/mode_table.go b/object/tree/mode_table.go
new file mode 100644
index 00000000..e180c38f
--- /dev/null
+++ b/object/tree/mode_table.go
@@ -0,0 +1,19 @@
+package tree
+
+var fileModeTable = map[FileMode]fileModeDetails{ //nolint:gochecknoglobals
+ FileModeDir: {
+ isBlobLike: false,
+ },
+ FileModeRegular: {
+ isBlobLike: true,
+ },
+ FileModeExecutable: {
+ isBlobLike: true,
+ },
+ FileModeSymlink: {
+ isBlobLike: true,
+ },
+ FileModeGitlink: {
+ isBlobLike: false,
+ },
+}
diff --git a/object/tree/path_append.go b/object/tree/path_append.go
new file mode 100644
index 00000000..609d5279
--- /dev/null
+++ b/object/tree/path_append.go
@@ -0,0 +1,14 @@
+package tree
+
+// AppendPath appends path to dst as one slash-separated byte path.
+func AppendPath(dst []byte, path [][]byte) []byte {
+ for i := range path {
+ if i > 0 {
+ dst = append(dst, '/')
+ }
+
+ dst = append(dst, path[i]...)
+ }
+
+ return dst
+}
diff --git a/object/tree/path_clone.go b/object/tree/path_clone.go
new file mode 100644
index 00000000..a4668add
--- /dev/null
+++ b/object/tree/path_clone.go
@@ -0,0 +1,16 @@
+package tree
+
+import (
+ "bytes"
+ "slices"
+)
+
+// ClonePath returns one deep copy of path.
+func ClonePath(path [][]byte) [][]byte {
+ cloned := slices.Clone(path)
+ for i := range cloned {
+ cloned[i] = bytes.Clone(cloned[i])
+ }
+
+ return cloned
+}
diff --git a/object/tree/path_prefix.go b/object/tree/path_prefix.go
new file mode 100644
index 00000000..ed658cee
--- /dev/null
+++ b/object/tree/path_prefix.go
@@ -0,0 +1,19 @@
+package tree
+
+import (
+ "bytes"
+ "slices"
+)
+
+// HasPathPrefix reports whether path begins with prefix as whole components.
+func HasPathPrefix(path, prefix [][]byte) bool {
+ if len(prefix) == 0 {
+ return true
+ }
+
+ if len(path) < len(prefix) {
+ return false
+ }
+
+ return slices.EqualFunc(path[:len(prefix)], prefix, bytes.Equal)
+}
diff --git a/object/tree/path_split.go b/object/tree/path_split.go
new file mode 100644
index 00000000..c147dd25
--- /dev/null
+++ b/object/tree/path_split.go
@@ -0,0 +1,19 @@
+package tree
+
+import (
+ "bytes"
+)
+
+// SplitPath splits one slash-separated tree path into components.
+func SplitPath(path []byte) [][]byte {
+ if len(path) == 0 {
+ return nil
+ }
+
+ parts := bytes.Split(path, []byte{'/'})
+ for i := range parts {
+ parts[i] = bytes.Clone(parts[i])
+ }
+
+ return parts
+}
diff --git a/object/tree/remove.go b/object/tree/remove.go
index 9eb42028..fe4fda32 100644
--- a/object/tree/remove.go
+++ b/object/tree/remove.go
@@ -3,6 +3,7 @@ package tree
import (
"bytes"
"fmt"
+ "slices"
)
// RemoveEntry removes a tree entry by name.
@@ -11,13 +12,12 @@ func (tree *Tree) RemoveEntry(name []byte) error {
return fmt.Errorf("object: tree: entry %q not found", name)
}
- for i := range tree.Entries {
- if bytes.Equal(tree.Entries[i].Name, name) {
- copy(tree.Entries[i:], tree.Entries[i+1:])
- tree.Entries = tree.Entries[:len(tree.Entries)-1]
-
- return nil
- }
+ index := slices.IndexFunc(tree.Entries, func(entry TreeEntry) bool {
+ return bytes.Equal(entry.Name, name)
+ })
+ if index >= 0 {
+ tree.Entries = slices.Delete(tree.Entries, index, index+1)
+ return nil
}
return fmt.Errorf("object: tree: entry %q not found", name)