package main
import (
"bytes"
"fmt"
"io"
"lindenii.org/go/furgit/internal/compress/zlib"
"lindenii.org/go/furgit/internal/format/packfile"
"lindenii.org/go/furgit/internal/format/packfile/delta"
"lindenii.org/go/furgit/object/tree"
"lindenii.org/go/lgo/intconv"
)
func (explainer *explainer) explainEntry(num, count, cursor int) (int, error) {
hashSize := explainer.objectFormat.Size()
header, err := packfile.ParseEntryHeader(explainer.data[cursor:], hashSize)
if err != nil {
return 0, fmt.Errorf("entry %d at offset %d: %w", num, cursor, err)
}
payloadStart := cursor + header.HeaderLen
if payloadStart > len(explainer.data) {
return 0, fmt.Errorf("entry %d at offset %d: header runs past the end of the pack", num, cursor)
}
payload, consumed, err := inflateAt(explainer.data[payloadStart:])
if err != nil {
return 0, fmt.Errorf("entry %d at offset %d: %w", num, cursor, err)
}
next := payloadStart + consumed
explainer.printf("object %d of %d\n", num, count)
explainer.printf("\tty\t%s\n", entryTypeLabel(header.Type))
explainer.printf("\tofs\t%d\n", cursor)
explainer.printf("\thdrsz\t%d\n", header.HeaderLen)
explainer.printf("\tsz\t%d\n", header.Size)
if uint64(len(payload)) != header.Size {
explainer.printf("\tnote\tdeclared %d byte(s) but inflated to %d\n", header.Size, len(payload))
}
if header.Type.IsBase() {
err = explainer.renderBase(cursor, header.Type, payload, consumed)
} else {
err = explainer.renderDelta(cursor, header, payload, consumed)
}
if err != nil {
return 0, fmt.Errorf("entry %d at offset %d: %w", num, cursor, err)
}
explainer.printf("\n")
return next, nil
}
func (explainer *explainer) renderBase(cursor int, entryType packfile.EntryType, content []byte, consumed int) error {
explainer.renderContent(entryType, content)
explainer.printf("\tzlib\t%d\n", consumed)
oid, err := explainer.recomputeOID(entryType, content)
if err != nil {
return err
}
explainer.printf("\toid\t%s\n", oid)
explainer.oidIndex[oid] = cursor
explainer.cache.Add(cursor, resolvedBase{entryType: entryType, content: content})
return nil
}
func (explainer *explainer) renderDelta(cursor int, header packfile.EntryHeader, payload []byte, consumed int) error {
baseSize, resultSize, pos, err := delta.ParseHeaderSizes(payload)
if err != nil {
return fmt.Errorf("delta header: %w", err)
}
err = explainer.renderBaseRef(cursor, header)
if err != nil {
return err
}
explainer.printf("\tbasesz\t%d\n", baseSize)
explainer.printf("\tnewsz\t%d\n", resultSize)
baseOffset, located, err := explainer.baseOffset(cursor, header)
if err != nil {
return err
}
var (
baseType packfile.EntryType
baseContent []byte
baseResolved bool
)
if located {
baseType, baseContent, baseResolved, err = explainer.reconstruct(baseOffset, 0)
if err != nil {
return err
}
}
var walkBase []byte
if baseResolved {
walkBase = baseContent
}
result, complete, err := explainer.walkDelta(walkBase, payload, pos)
if err != nil {
return err
}
explainer.printf("\tzlib\t%d\n", consumed)
switch {
case baseResolved && complete:
if uint64(len(result)) != resultSize {
explainer.printf("\tnote\tdelta produced %d byte(s) but declared %d\n", len(result), resultSize)
}
explainer.renderContent(baseType, result)
newOID, err := explainer.recomputeOID(baseType, result)
if err != nil {
return err
}
explainer.printf("\tnewoid\t%s\n", newOID)
explainer.oidIndex[newOID] = cursor
explainer.cache.Add(cursor, resolvedBase{entryType: baseType, content: result})
case !baseResolved:
explainer.printf("\tnote\tbase not available in this pack; cannot reconstruct\n")
default:
explainer.printf("\tnote\tdelta decode incomplete; cannot reconstruct\n")
}
return nil
}
func (explainer *explainer) renderBaseRef(cursor int, header packfile.EntryHeader) error {
switch header.Type {
case packfile.EntryTypeOfsDelta:
dist, err := intconv.Uint64ToInt(header.OfsDistance)
if err != nil {
return fmt.Errorf("ofs-delta distance overflows int: %w", err)
}
explainer.printf("\tbaseofs\t-%d = %d\n", dist, cursor-dist)
case packfile.EntryTypeRefDelta:
baseID, err := explainer.objectFormat.FromBytes(header.RefBase[:explainer.objectFormat.Size()])
if err != nil {
return fmt.Errorf("ref-delta base ID: %w", err)
}
explainer.printf("\tbaseoid\t%s\n", baseID)
case packfile.EntryTypeInvalid,
packfile.EntryTypeCommit,
packfile.EntryTypeTree,
packfile.EntryTypeBlob,
packfile.EntryTypeTag,
packfile.EntryTypeFuture:
}
return nil
}
func (explainer *explainer) renderContent(entryType packfile.EntryType, content []byte) {
switch entryType {
case packfile.EntryTypeCommit, packfile.EntryTypeTag:
explainer.printf("\tcontent\n")
indentBlock(explainer.out, "\t\t", content)
case packfile.EntryTypeTree:
explainer.renderTree(content)
case packfile.EntryTypeBlob,
packfile.EntryTypeOfsDelta,
packfile.EntryTypeRefDelta,
packfile.EntryTypeInvalid,
packfile.EntryTypeFuture:
explainer.printf("\thexdump\n")
hexBlock(explainer.out, "\t\t", content)
}
}
func (explainer *explainer) renderTree(content []byte) {
parsed, err := tree.Parse(content, explainer.objectFormat)
if err != nil {
explainer.printf("\thexdump\t(not a valid tree: %v)\n", err)
hexBlock(explainer.out, "\t\t", content)
return
}
explainer.printf("\ttree\n")
for _, entry := range parsed.Entries() {
mode := string(entry.Mode.Append(nil))
explainer.printf(
"\t\t%s %s %s\t%s\n",
mode, entry.Mode.ObjectType().Name(), entry.ID, entry.Name,
)
}
}
func inflateAt(data []byte) ([]byte, int, error) {
reader := bytes.NewReader(data)
zr, err := zlib.NewReader(reader)
if err != nil {
return nil, 0, fmt.Errorf("opening zlib stream: %w", err)
}
content, err := io.ReadAll(zr)
closeErr := zr.Close()
if err != nil {
return nil, 0, fmt.Errorf("inflating payload: %w", err)
}
if closeErr != nil {
return nil, 0, fmt.Errorf("closing zlib stream: %w", closeErr)
}
consumed := len(data) - reader.Len()
return content, consumed, nil
}
func entryTypeLabel(entryType packfile.EntryType) string {
switch entryType {
case packfile.EntryTypeCommit:
return "commit"
case packfile.EntryTypeTree:
return "tree"
case packfile.EntryTypeBlob:
return "blob"
case packfile.EntryTypeTag:
return "tag"
case packfile.EntryTypeOfsDelta:
return "ofs-delta"
case packfile.EntryTypeRefDelta:
return "ref-delta"
case packfile.EntryTypeInvalid:
return "invalid"
case packfile.EntryTypeFuture:
return "future"
default:
return fmt.Sprintf("unknown (%d)", entryType)
}
}