ref: 0587025b7d48bae29b0843c2c4ab691b99f82752
dir: /obj_tree.go/
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
}