diff options
| author | 2026-02-20 19:06:13 +0800 | |
|---|---|---|
| committer | 2026-02-20 19:07:14 +0800 | |
| commit | aa513c069c1418734aea894dc944e27c6a78a3bb (patch) | |
| tree | 687f0a11bb550fa088fd82a98ceb8979bbc35f69 /obj_tree.go | |
| parent | Comment on prior reverts removing the pack writing API (diff) | |
| signature | No 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.go | 310 |
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 -} |
