package main
import (
"fmt"
"lindenii.org/go/furgit/internal/cache/clock"
"lindenii.org/go/furgit/internal/format/packfile"
"lindenii.org/go/furgit/internal/format/packfile/delta"
"lindenii.org/go/furgit/object/header"
"lindenii.org/go/furgit/object/id"
"lindenii.org/go/lgo/intconv"
)
const baseCacheMaxWeight = 64 << 20
type resolvedBase struct {
entryType packfile.EntryType
content []byte
}
type baseCache = clock.Clock[int, resolvedBase]
func newBaseCache() *baseCache {
return clock.New(baseCacheMaxWeight, func(_ int, base resolvedBase) uint64 {
return uint64(len(base.content)) + 32
})
}
func (explainer *explainer) reconstruct(offset, depth int) (packfile.EntryType, []byte, bool, error) {
var zero packfile.EntryType
if depth > delta.MaxChainDepth {
return zero, nil, false, fmt.Errorf("delta chain too deep at offset %d", offset)
}
if cached, ok := explainer.cache.Get(offset); ok {
return cached.entryType, cached.content, true, nil
}
header, err := packfile.ParseEntryHeader(explainer.data[offset:], explainer.objectFormat.Size())
if err != nil {
return zero, nil, false, fmt.Errorf("entry at offset %d: %w", offset, err)
}
payloadStart := offset + header.HeaderLen
if payloadStart > len(explainer.data) {
return zero, nil, false, fmt.Errorf("entry at offset %d: header runs past end of pack", offset)
}
if header.Type.IsBase() {
content, _, err := inflateAt(explainer.data[payloadStart:])
if err != nil {
return zero, nil, false, fmt.Errorf("entry at offset %d: %w", offset, err)
}
explainer.cache.Add(offset, resolvedBase{entryType: header.Type, content: content})
return header.Type, content, true, nil
}
baseOffset, ok, err := explainer.baseOffset(offset, header)
if err != nil {
return zero, nil, false, err
}
if !ok {
return zero, nil, false, nil
}
baseType, baseContent, ok, err := explainer.reconstruct(baseOffset, depth+1)
if err != nil || !ok {
return zero, nil, ok, err
}
payload, _, err := inflateAt(explainer.data[payloadStart:])
if err != nil {
return zero, nil, false, fmt.Errorf("entry at offset %d: %w", offset, err)
}
content, err := delta.Apply(baseContent, payload)
if err != nil {
return zero, nil, false, fmt.Errorf("entry at offset %d: %w", offset, err)
}
explainer.cache.Add(offset, resolvedBase{entryType: baseType, content: content})
return baseType, content, true, nil
}
func (explainer *explainer) baseOffset(offset int, header packfile.EntryHeader) (int, bool, error) {
switch header.Type {
case packfile.EntryTypeOfsDelta:
dist, err := intconv.Uint64ToInt(header.OfsDistance)
if err != nil || dist <= 0 || dist > offset {
return 0, false, fmt.Errorf("entry at offset %d: ofs-delta base out of bounds", offset)
}
return offset - dist, true, nil
case packfile.EntryTypeRefDelta:
refBytes := header.RefBase[:explainer.objectFormat.Size()]
if explainer.idx != nil {
baseOffsetU, found, err := explainer.idx.Lookup(refBytes)
if err != nil {
return 0, false, fmt.Errorf("entry at offset %d: index lookup: %w", offset, err)
}
if found {
baseOffset, err := intconv.Uint64ToInt(baseOffsetU)
if err != nil {
return 0, false, fmt.Errorf("entry at offset %d: index base offset overflows int: %w", offset, err)
}
return baseOffset, true, nil
}
}
baseID, err := explainer.objectFormat.FromBytes(refBytes)
if err != nil {
return 0, false, fmt.Errorf("entry at offset %d: %w", offset, err)
}
if baseOffset, found := explainer.oidIndex[baseID]; found {
return baseOffset, true, nil
}
return 0, false, nil
case packfile.EntryTypeInvalid,
packfile.EntryTypeCommit,
packfile.EntryTypeTree,
packfile.EntryTypeBlob,
packfile.EntryTypeTag,
packfile.EntryTypeFuture:
}
return 0, false, fmt.Errorf("entry at offset %d: not a delta entry", offset)
}
func (explainer *explainer) recomputeOID(entryType packfile.EntryType, content []byte) (id.ObjectID, error) {
var zero id.ObjectID
objectType, err := entryType.ObjectType()
if err != nil {
return zero, err
}
hashImpl, err := explainer.objectFormat.New()
if err != nil {
return zero, err
}
_, _ = hashImpl.Write(header.Append(nil, objectType, len(content)))
_, _ = hashImpl.Write(content)
oid, err := explainer.objectFormat.FromBytes(hashImpl.Sum(nil))
if err != nil {
return zero, err
}
return oid, nil
}