aboutsummaryrefslogtreecommitdiff
path: root/obj_tree.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2025-11-11 00:00:00 +0000
committerGravatar Runxi Yu2025-11-13 00:00:00 +0000
commit15855e3249754ab7dc07183c9383f8a8e8c26af2 (patch)
tree83b32bdd63f7e672152f07d89268e9b268d1f3f5 /obj_tree.go
signature
Initial commit
Diffstat (limited to 'obj_tree.go')
-rw-r--r--obj_tree.go110
1 files changed, 110 insertions, 0 deletions
diff --git a/obj_tree.go b/obj_tree.go
new file mode 100644
index 00000000..c78fd375
--- /dev/null
+++ b/obj_tree.go
@@ -0,0 +1,110 @@
+package furgit
+
+import (
+ "bytes"
+ "errors"
+ "fmt"
+ "strconv"
+)
+
+// Tree represents a Git tree object.
+type Tree struct {
+ objectBase
+
+ Entries []TreeEntry
+}
+
+// TreeEntry represents a single entry in a Git tree.
+type TreeEntry struct {
+ Mode uint32
+ Name []byte
+ ID Hash
+}
+
+// ObjType allows Tree to satisfy the Object interface.
+func (*Tree) ObjType() ObjType {
+ return ObjTree
+}
+
+// parseTree decodes a tree body.
+func parseTree(id Hash, body []byte) (*Tree, 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+HashSize > len(body) {
+ return nil, errors.New("furgit: tree: truncated child hash")
+ }
+ var child Hash
+ copy(child[:], body[i:i+HashSize])
+ i += HashSize
+
+ mode, err := strconv.ParseUint(string(modeBytes), 8, 32)
+ if err != nil {
+ return nil, fmt.Errorf("furgit: tree: parse mode: %w", err)
+ }
+
+ entry := TreeEntry{
+ Mode: uint32(mode),
+ Name: append([]byte(nil), nameBytes...),
+ ID: child,
+ }
+ entries = append(entries, entry)
+ }
+
+ return &Tree{
+ objectBase: objectBase{Hash: id},
+ Entries: entries,
+ }, nil
+}
+
+// treeBody builds the entry list for a tree without the Git header.
+func treeBody(t *Tree) []byte {
+ var bodyLen int
+ for _, e := range t.Entries {
+ mode := strconv.FormatUint(uint64(e.Mode), 8)
+ bodyLen += len(mode) + 1 + len(e.Name) + 1 + HashSize
+ }
+
+ body := make([]byte, bodyLen)
+ pos := 0
+ for _, e := range t.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++
+ pos += copy(body[pos:], e.ID[:])
+ }
+
+ return body
+}
+
+// Serialize renders a Tree into canonical Git format.
+func (t *Tree) Serialize() ([]byte, error) {
+ body := treeBody(t)
+ header, err := headerForType(ObjTree, 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
+}