aboutsummaryrefslogtreecommitdiff
path: root/obj_tree.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-02-20 19:06:13 +0800
committerGravatar Runxi Yu2026-02-20 19:07:14 +0800
commitaa513c069c1418734aea894dc944e27c6a78a3bb (patch)
tree687f0a11bb550fa088fd82a98ceb8979bbc35f69 /obj_tree.go
parentComment on prior reverts removing the pack writing API (diff)
signatureNo signature
Delete everything, I'm redesigning this.
I'll stop using a flat package and make things much more modular. And also experiment with streaming APIs so large blobs don't OOM us.
Diffstat (limited to 'obj_tree.go')
-rw-r--r--obj_tree.go310
1 files changed, 0 insertions, 310 deletions
diff --git a/obj_tree.go b/obj_tree.go
deleted file mode 100644
index 01b0651f..00000000
--- a/obj_tree.go
+++ /dev/null
@@ -1,310 +0,0 @@
-package furgit
-
-import (
- "bytes"
- "errors"
- "fmt"
- "sort"
- "strconv"
-)
-
-// Tree represents a Git tree object.
-type Tree struct {
- // Entries represents the entries in the tree.
- Entries []TreeEntry
-}
-
-// StoredTree represents a tree stored in the object database.
-type StoredTree struct {
- Tree
- hash Hash
-}
-
-// Hash returns the hash of the stored tree.
-func (sTree *StoredTree) Hash() Hash {
- return sTree.hash
-}
-
-// FileMode represents the mode of a file in a Git tree.
-type FileMode uint32
-
-const (
- // FileModeDir represents a directory (tree) in a Git tree.
- FileModeDir FileMode = 0o40000
- // FileModeRegular represents a regular file (blob) in a Git tree.
- FileModeRegular FileMode = 0o100644
- // FileModeExecutable represents an executable file (blob) in a Git tree.
- FileModeExecutable FileMode = 0o100755
- // FileModeSymlink represents a symbolic link (blob) in a Git tree.
- FileModeSymlink FileMode = 0o120000
- // FileModeGitlink represents a Git link (submodule) in a Git tree.
- FileModeGitlink FileMode = 0o160000
-)
-
-// TreeEntry represents a single entry in a Git tree.
-type TreeEntry struct {
- // Mode represents the file mode of the entry.
- Mode FileMode
- // Name represents the name of the entry.
- Name []byte
- // ID represents the hash of the entry. This is typically
- // either a blob or a tree.
- ID Hash
-}
-
-// ObjectType returns the object type of the tree.
-//
-// It always returns ObjectTypeTree.
-func (tree *Tree) ObjectType() ObjectType {
- _ = tree
- return ObjectTypeTree
-}
-
-// parseTree decodes a tree body.
-func parseTree(id Hash, body []byte, repo *Repository) (*StoredTree, error) {
- var entries []TreeEntry
- i := 0
- for i < len(body) {
- space := bytes.IndexByte(body[i:], ' ')
- if space < 0 {
- return nil, errors.New("furgit: tree: missing mode terminator")
- }
- modeBytes := body[i : i+space]
- i += space + 1
-
- nul := bytes.IndexByte(body[i:], 0)
- if nul < 0 {
- return nil, errors.New("furgit: tree: missing name terminator")
- }
- nameBytes := body[i : i+nul]
- i += nul + 1
-
- if i+repo.hashAlgo.Size() > len(body) {
- return nil, errors.New("furgit: tree: truncated child hash")
- }
- var child Hash
- copy(child.data[:], body[i:i+repo.hashAlgo.Size()])
- child.algo = repo.hashAlgo
- i += repo.hashAlgo.Size()
-
- mode, err := strconv.ParseUint(string(modeBytes), 8, 32)
- if err != nil {
- return nil, fmt.Errorf("furgit: tree: parse mode: %w", err)
- }
-
- entry := TreeEntry{
- Mode: FileMode(mode),
- Name: append([]byte(nil), nameBytes...),
- ID: child,
- }
- entries = append(entries, entry)
- }
-
- return &StoredTree{
- hash: id,
- Tree: Tree{
- Entries: entries,
- },
- }, nil
-}
-
-// treeBody builds the entry list for a tree without the Git header.
-func (tree *Tree) serialize() []byte {
- var bodyLen int
- for _, e := range tree.Entries {
- mode := strconv.FormatUint(uint64(e.Mode), 8)
- bodyLen += len(mode) + 1 + len(e.Name) + 1 + e.ID.Size()
- }
-
- body := make([]byte, bodyLen)
- pos := 0
- for _, e := range tree.Entries {
- mode := strconv.FormatUint(uint64(e.Mode), 8)
- pos += copy(body[pos:], []byte(mode))
- body[pos] = ' '
- pos++
- pos += copy(body[pos:], e.Name)
- body[pos] = 0
- pos++
- size := e.ID.Size()
- pos += copy(body[pos:], e.ID.data[:size])
- }
-
- return body
-}
-
-// Serialize renders the tree into its raw byte representation,
-// including the header (i.e., "type size\0").
-func (tree *Tree) Serialize() ([]byte, error) {
- body := tree.serialize()
- header, err := headerForType(ObjectTypeTree, body)
- if err != nil {
- return nil, err
- }
-
- raw := make([]byte, len(header)+len(body))
- copy(raw, header)
- copy(raw[len(header):], body)
- return raw, nil
-}
-
-// Entry looks up a tree entry by name.
-//
-// Lookups are not recursive.
-// It returns nil if no such entry exists.
-func (tree *Tree) Entry(name []byte) *TreeEntry {
- if len(tree.Entries) == 0 {
- return nil
- }
-
- if e := tree.entry(name, true); e != nil {
- return e
- }
-
- return tree.entry(name, false)
-}
-
-// EntryRecursive looks up a tree entry by path.
-//
-// Lookups are recursive.
-func (sTree *StoredTree) EntryRecursive(repo *Repository, path [][]byte) (*TreeEntry, error) {
- if len(path) == 0 {
- return nil, errors.New("furgit: tree: empty path")
- }
-
- currentTree := sTree
- for i, part := range path {
- entry := currentTree.Entry(part)
- if entry == nil {
- return nil, ErrNotFound
- }
- if i == len(path)-1 {
- return entry, nil
- }
- obj, err := repo.ReadObject(entry.ID)
- if err != nil {
- return nil, err
- }
- nextTree, ok := obj.(*StoredTree)
- if !ok {
- return nil, fmt.Errorf("furgit: tree: expected tree object at %s, got %T", part, obj)
- // TODO: It may be useful to check the mode instead of reporting
- // an object type error.
- }
- currentTree = nextTree
- }
-
- return nil, ErrNotFound
-}
-
-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
- }
- if cmp < 0 {
- low = mid + 1
- } else {
- high = mid - 1
- }
- }
- return nil
-}
-
-// InsertEntry inserts a tree entry while preserving Git's name ordering.
-// It returns an error if an entry with the same name already exists.
-func (tree *Tree) InsertEntry(newEntry TreeEntry) error {
- if tree == nil {
- return ErrInvalidObject
- }
- for _, entry := range tree.Entries {
- if bytes.Equal(entry.Name, newEntry.Name) {
- return fmt.Errorf("furgit: tree: entry %q already exists", 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
- })
- tree.Entries = append(tree.Entries, TreeEntry{})
- copy(tree.Entries[insertAt+1:], tree.Entries[insertAt:])
- tree.Entries[insertAt] = newEntry
- return nil
-}
-
-// RemoveEntry removes a tree entry by name.
-// It returns ErrNotFound if no matching entry exists.
-func (tree *Tree) RemoveEntry(name []byte) error {
- if tree == nil {
- return ErrInvalidObject
- }
- if len(tree.Entries) == 0 {
- return ErrNotFound
- }
- 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
- }
- }
- return ErrNotFound
-}
-
-// TreeEntryNameCompare compares names using Git's tree ordering rules.
-func TreeEntryNameCompare(entryName []byte, entryMode FileMode, searchName []byte, searchIsTree bool) int {
- isEntryTree := entryMode == FileModeDir
-
- entryLen := len(entryName)
- if isEntryTree {
- entryLen++
- }
- searchLen := len(searchName)
- if searchIsTree {
- searchLen++
- }
-
- n := entryLen
- if searchLen < n {
- n = searchLen
- }
-
- for i := 0; i < n; i++ {
- var ec, sc byte
-
- if i < len(entryName) {
- ec = entryName[i]
- } else {
- ec = '/'
- }
-
- if i < len(searchName) {
- sc = searchName[i]
- } else {
- sc = '/'
- }
-
- if ec < sc {
- return -1
- }
- if ec > sc {
- return 1
- }
- }
-
- if entryLen < searchLen {
- return -1
- }
- if entryLen > searchLen {
- return 1
- }
- return 0
-}