shithub: furgit

ref: 0587025b7d48bae29b0843c2c4ab691b99f82752
dir: /obj_tree.go/

View raw version
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
}