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) } }