diff options
Diffstat (limited to 'cmd/explain-pack/entry.go')
| -rw-r--r-- | cmd/explain-pack/entry.go | 257 |
1 files changed, 257 insertions, 0 deletions
diff --git a/cmd/explain-pack/entry.go b/cmd/explain-pack/entry.go new file mode 100644 index 00000000..0b796ef0 --- /dev/null +++ b/cmd/explain-pack/entry.go @@ -0,0 +1,257 @@ +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) + } +} |
