shithub: furgit

ref: 040b572d95e4ca27e1ada6113c405b8a1eb4a669
dir: /packfile/ingest/record_resolve.go/

View raw version
package ingest

import (
	"fmt"

	"codeberg.org/lindenii/furgit/objecttype"
	packfmt "codeberg.org/lindenii/furgit/packfile"
)

// resolveRecord resolves one record and returns canonical type/content.
func resolveRecord(state *ingestState, idx int, visiting map[int]struct{}) (objecttype.Type, []byte, error) {
	if idx < 0 || idx >= len(state.records) {
		return objecttype.TypeInvalid, nil, fmt.Errorf("packfile/ingest: record index out of bounds")
	}

	if _, ok := visiting[idx]; ok {
		return objecttype.TypeInvalid, nil, &DeltaCycleError{Offset: state.records[idx].offset}
	}

	visiting[idx] = struct{}{}
	defer delete(visiting, idx)

	record := &state.records[idx]
	if ty, content, ok := state.baseCache.get(idx); ok {
		return ty, content, nil
	}

	if packfmt.IsBaseObjectType(record.packedType) {
		ty, content, err := readBaseRecordContent(state, idx)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}

		if record.resolved {
			state.baseCache.add(idx, record.realType, content)

			return record.realType, content, nil
		}

		id, err := hashCanonicalObject(state.algo, ty, content)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}

		record.objectID = id
		record.realType = ty
		record.resolved = true
		state.objectToRecord[id] = idx
		state.baseCache.add(idx, ty, content)

		return ty, content, nil
	}

	var (
		baseType    objecttype.Type
		baseContent []byte
		err         error
	)
	switch record.packedType {
	case objecttype.TypeOfsDelta:
		baseIdx, ok := state.offsetToRecord[record.baseOffset]
		if !ok {
			return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
				Offset: record.offset,
				Reason: "missing ofs-delta base entry",
			}
		}

		baseType, baseContent, err = resolveRecord(state, baseIdx, visiting)
		if err != nil {
			return objecttype.TypeInvalid, nil, err
		}
	case objecttype.TypeRefDelta:
		baseIdx, ok := state.objectToRecord[record.baseObject]
		if ok {
			baseType, baseContent, err = resolveRecord(state, baseIdx, visiting)
			if err != nil {
				return objecttype.TypeInvalid, nil, err
			}
		} else {
			return objecttype.TypeInvalid, nil, errExternalThinBase
		}
	case objecttype.TypeInvalid,
		objecttype.TypeCommit,
		objecttype.TypeTree,
		objecttype.TypeBlob,
		objecttype.TypeTag,
		objecttype.TypeFuture:
		return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
			Offset: record.offset,
			Reason: "unsupported delta type",
		}
	default:
		return objecttype.TypeInvalid, nil, &MalformedPackEntryError{
			Offset: record.offset,
			Reason: "unsupported delta type",
		}
	}

	ty, content, err := applyDeltaRecord(state, idx, baseType, baseContent)
	if err != nil {
		return objecttype.TypeInvalid, nil, err
	}

	id, err := hashCanonicalObject(state.algo, ty, content)
	if err != nil {
		return objecttype.TypeInvalid, nil, err
	}

	record.objectID = id
	record.realType = ty
	record.resolved = true
	state.objectToRecord[id] = idx
	state.baseCache.add(idx, ty, content)

	return ty, content, nil
}