aboutsummaryrefslogtreecommitdiff
path: root/object/store/packed/reader.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-12 12:43:30 +0000
committerGravatar Runxi Yu2026-06-12 12:43:30 +0000
commited1d706ee1cf3e3ac34bce8b295525bebff8bcb9 (patch)
tree721ac907565df0ad91d766d4c6b16b2eb029a55a /object/store/packed/reader.go
parentobject/store/loose: Use SeedHistory (diff)
object/store/packed: Basic reading functionality
Diffstat (limited to 'object/store/packed/reader.go')
-rw-r--r--object/store/packed/reader.go228
1 files changed, 204 insertions, 24 deletions
diff --git a/object/store/packed/reader.go b/object/store/packed/reader.go
index a7c38d61..9c183575 100644
--- a/object/store/packed/reader.go
+++ b/object/store/packed/reader.go
@@ -1,30 +1,210 @@
package packed
-// import (
-// "io"
-//
-// "lindenii.org/go/furgit/object/id"
-// "lindenii.org/go/furgit/object/typ"
-// )
-//
-// // ReadBytesFull reads a full serialized object as "type size\x00content".
-// func (packed *Packed) ReadBytesFull(objectID id.ObjectID) ([]byte, error)
-//
-// // ReadBytesContent reads an object's type and content bytes.
-// func (packed *Packed) ReadBytesContent(objectID id.ObjectID) (typ.Type, []byte, error)
-//
-// // ReadReaderFull reads a full serialized object stream as "type size\x00content".
-// func (packed *Packed) ReadReaderFull(objectID id.ObjectID) (io.ReadCloser, error)
+import (
+ "bytes"
+ "fmt"
+ "io"
+
+ "lindenii.org/go/furgit/internal/compress/zlib"
+ "lindenii.org/go/furgit/internal/format/packfile"
+ "lindenii.org/go/furgit/internal/iolimit"
+ "lindenii.org/go/furgit/object/header"
+ "lindenii.org/go/furgit/object/id"
+ "lindenii.org/go/furgit/object/typ"
+)
+
+// ReadBytesContent reads an object's type and content bytes,
+// fully resolving delta chains.
+func (packed *Packed) ReadBytesContent(objectID id.ObjectID) (typ.Type, []byte, error) {
+ p, offset, err := packed.lookup(objectID)
+ if err != nil {
+ return typ.Unknown, nil, err
+ }
+
+ entryType, content, err := packed.unpackEntry(p, offset)
+ if err != nil {
+ return typ.Unknown, nil, err
+ }
+
+ ty, err := objectTypeOf(entryType)
+ if err != nil {
+ return typ.Unknown, nil, err
+ }
+
+ return ty, content, nil
+}
+
+// ReadBytesFull reads a full serialized object as "type size\x00content",
+// fully resolving delta chains.
+func (packed *Packed) ReadBytesFull(objectID id.ObjectID) ([]byte, error) {
+ ty, content, err := packed.ReadBytesContent(objectID)
+ if err != nil {
+ return nil, err
+ }
+
+ raw := header.Append(make([]byte, 0, len(content)+32), ty, uint64(len(content)))
+
+ return append(raw, content...), nil
+}
+
+// ReadHeader reads an object's type and declared content length.
//
-// // ReadReaderContent reads an object's type, declared content length,
-// // and content stream.
-// func (packed *Packed) ReadReaderContent(objectID id.ObjectID) (typ.Type, uint64, io.ReadCloser, error)
+// For delta entries this resolves the chained base type
+// and the declared delta result size,
+// without reconstructing content.
+func (packed *Packed) ReadHeader(objectID id.ObjectID) (typ.Type, uint64, error) {
+ p, offset, err := packed.lookup(objectID)
+ if err != nil {
+ return typ.Unknown, 0, err
+ }
+
+ entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
+ if err != nil {
+ return typ.Unknown, 0, err
+ }
+
+ size := entryHeader.Size
+
+ if entryHeader.Type.IsDelta() {
+ size, err = deltaResultSize(payload, entryHeader.Size)
+ if err != nil {
+ return typ.Unknown, 0, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
+ }
+ }
+
+ entryType, err := packed.resolveType(p, offset, entryHeader)
+ if err != nil {
+ return typ.Unknown, 0, err
+ }
+
+ ty, err := objectTypeOf(entryType)
+ if err != nil {
+ return typ.Unknown, 0, err
+ }
+
+ return ty, size, nil
+}
+
+// ReadSize reads an object's declared content length.
//
-// // ReadSize reads an object's declared content length.
-// func (packed *Packed) ReadSize(objectID id.ObjectID) (uint64, error)
+// Unlike ReadHeader,
+// this never walks delta chains.
+func (packed *Packed) ReadSize(objectID id.ObjectID) (uint64, error) {
+ p, offset, err := packed.lookup(objectID)
+ if err != nil {
+ return 0, err
+ }
+
+ entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
+ if err != nil {
+ return 0, err
+ }
+
+ if !entryHeader.Type.IsDelta() {
+ return entryHeader.Size, nil
+ }
+
+ size, err := deltaResultSize(payload, entryHeader.Size)
+ if err != nil {
+ return 0, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
+ }
+
+ return size, nil
+}
+
+// ReadReaderContent reads an object's type,
+// declared content length, and content stream.
//
-// // ReadHeader reads an object's type and declared content length.
-// func (packed *Packed) ReadHeader(objectID id.ObjectID) (typ.Type, uint64, error)
+// Non-delta entries stream directly from the pack;
+// delta entries are fully resolved in memory first.
+// Close releases resources only
+// and does not drain unread data for additional validation.
+func (packed *Packed) ReadReaderContent(objectID id.ObjectID) (typ.Type, uint64, io.ReadCloser, error) {
+ p, offset, err := packed.lookup(objectID)
+ if err != nil {
+ return typ.Unknown, 0, nil, err
+ }
+
+ entryHeader, payload, err := p.entryHeaderAt(offset, packed.objectFormat)
+ if err != nil {
+ return typ.Unknown, 0, nil, err
+ }
+
+ if !entryHeader.Type.IsBase() {
+ entryType, content, err := packed.unpackEntry(p, offset)
+ if err != nil {
+ return typ.Unknown, 0, nil, err
+ }
+
+ ty, err := objectTypeOf(entryType)
+ if err != nil {
+ return typ.Unknown, 0, nil, err
+ }
+
+ return ty, uint64(len(content)), io.NopCloser(bytes.NewReader(content)), nil
+ }
+
+ ty, err := objectTypeOf(entryHeader.Type)
+ if err != nil {
+ return typ.Unknown, 0, nil, err
+ }
+
+ zr, err := zlib.NewReader(bytes.NewReader(payload))
+ if err != nil {
+ return typ.Unknown, 0, nil, fmt.Errorf("%w: pack %q: %w", ErrMalformedPackedStore, p.name, err)
+ }
+
+ return ty, entryHeader.Size, &objectReader{
+ reader: iolimit.ExpectLengthReader(zr, entryHeader.Size),
+ zr: zr,
+ }, nil
+}
+
+// ReadReaderFull reads a full serialized object stream
+// as "type size\x00content".
//
-// // Refresh updates the packed-store view of on-disk pack/index candidates.
-// func (packed *Packed) Refresh() error
+// Non-delta entries stream directly from the pack;
+// delta entries are fully resolved in memory first.
+// Close releases resources only
+// and does not drain unread data for additional validation.
+func (packed *Packed) ReadReaderFull(objectID id.ObjectID) (io.ReadCloser, error) {
+ ty, size, reader, err := packed.ReadReaderContent(objectID)
+ if err != nil {
+ return nil, err
+ }
+
+ headerBytes := header.Append(nil, ty, size)
+
+ return &objectReader{
+ reader: io.MultiReader(bytes.NewReader(headerBytes), reader),
+ zr: reader,
+ }, nil
+}
+
+// objectTypeOf converts one packfile entry type
+// into an ordinary object type.
+func objectTypeOf(entryType packfile.EntryType) (typ.Type, error) {
+ ty, err := entryType.ObjectType()
+ if err != nil {
+ return typ.Unknown, fmt.Errorf("%w: %w", ErrMalformedPackedStore, err)
+ }
+
+ return ty, nil
+}
+
+// objectReader streams one packed object payload
+// and owns the underlying decompressor.
+type objectReader struct {
+ // reader is the stream exposed by Read.
+ reader io.Reader
+ // zr is closed by Close.
+ zr io.Closer
+}
+
+func (reader *objectReader) Read(dst []byte) (int, error) {
+ return reader.reader.Read(dst)
+}
+
+func (reader *objectReader) Close() error {
+ return reader.zr.Close()
+}