diff options
| author | 2025-11-11 00:00:00 +0000 | |
|---|---|---|
| committer | 2025-11-13 00:00:00 +0000 | |
| commit | 15855e3249754ab7dc07183c9383f8a8e8c26af2 (patch) | |
| tree | 83b32bdd63f7e672152f07d89268e9b268d1f3f5 /obj_tree.go | |
| signature | ||
Initial commit
Diffstat (limited to 'obj_tree.go')
| -rw-r--r-- | obj_tree.go | 110 |
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 +} |
