ref: 777db8470909b16411cb54a6c0bbf927be778952
parent: 52be24ec3be4290ad3be421a448907b0edcb368b
author: Runxi Yu <me@runxiyu.org>
date: Sat Feb 21 17:55:15 EST 2026
objectstore/*, repository: Add ReadSize For cases where knowing the type is unnecessary and incurs extra overhead.
--- a/objectstore/chain/chain.go
+++ b/objectstore/chain/chain.go
@@ -96,6 +96,24 @@
return objecttype.TypeInvalid, 0, nil, objectstore.ErrObjectNotFound
}
+// ReadSize reads object content length from the first backend that has it.
+func (chain *Chain) ReadSize(id objectid.ObjectID) (int64, error) {+ for i, backend := range chain.backends {+ if backend == nil {+ continue
+ }
+ size, err := backend.ReadSize(id)
+ if err == nil {+ return size, nil
+ }
+ if errors.Is(err, objectstore.ErrObjectNotFound) {+ continue
+ }
+ return 0, fmt.Errorf("objectstore: backend %d read size: %w", i, err)+ }
+ return 0, objectstore.ErrObjectNotFound
+}
+
// ReadHeader reads object header data from the first backend that has it.
func (chain *Chain) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) { for i, backend := range chain.backends {--- /dev/null
+++ b/objectstore/loose/read_size.go
@@ -1,0 +1,9 @@
+package loose
+
+import "codeberg.org/lindenii/furgit/objectid"
+
+// ReadSize reads an object's declared content length.
+func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {+ _, size, err := store.ReadHeader(id)
+ return size, err
+}
--- a/objectstore/objectstore.go
+++ b/objectstore/objectstore.go
@@ -30,6 +30,11 @@
// and content stream.
// Caller must close the returned reader.
ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error)
+ // ReadSize reads an object's declared content length.
+ //
+ // This is equivalent to ReadHeader(...).size and may be cheaper than
+ // ReadHeader when callers do not need object type.
+ ReadSize(id objectid.ObjectID) (int64, error)
// ReadHeader reads an object's type and declared content length.
ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error)
// Close releases resources associated with the backend.
--- /dev/null
+++ b/objectstore/packed/read_size.go
@@ -1,0 +1,39 @@
+package packed
+
+import (
+ "fmt"
+
+ packfmt "codeberg.org/lindenii/furgit/format/pack"
+ "codeberg.org/lindenii/furgit/objectid"
+ "codeberg.org/lindenii/furgit/objecttype"
+)
+
+// ReadSize reads an object's declared content size.
+func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {+ loc, err := store.lookup(id)
+ if err != nil {+ return 0, err
+ }
+ return store.resolveSizeAt(loc)
+}
+
+// resolveSizeAt resolves one object's declared content size from location.
+func (store *Store) resolveSizeAt(start location) (int64, error) {+ pack, meta, err := store.entryMetaAt(start)
+ if err != nil {+ return 0, err
+ }
+ if packfmt.IsBaseObjectType(meta.ty) {+ return meta.size, nil
+ }
+ switch meta.ty {+ case objecttype.TypeRefDelta, objecttype.TypeOfsDelta:
+ return deltaDeclaredSizeAt(pack, meta.dataOffset)
+ case objecttype.TypeInvalid, objecttype.TypeFuture:
+ return 0, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty)+ case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
+ return 0, fmt.Errorf("objectstore/packed: internal invariant violation for base type %d", meta.ty)+ default:
+ return 0, fmt.Errorf("objectstore/packed: unsupported pack type %d", meta.ty)+ }
+}
--- a/objectstore/packed/read_test.go
+++ b/objectstore/packed/read_test.go
@@ -36,6 +36,13 @@
if gotHeaderSize != int64(len(wantBody)) { t.Fatalf("ReadHeader size = %d, want %d", gotHeaderSize, len(wantBody))}
+ gotSize, err := store.ReadSize(id)
+ if err != nil {+ t.Fatalf("ReadSize: %v", err)+ }
+ if gotSize != int64(len(wantBody)) {+ t.Fatalf("ReadSize = %d, want %d", gotSize, len(wantBody))+ }
gotRaw, err := store.ReadBytesFull(id)
if err != nil {@@ -108,6 +115,9 @@
if _, _, err := store.ReadHeader(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) { t.Fatalf("ReadHeader not-found error = %v", err)}
+ if _, err := store.ReadSize(notFoundID); !errors.Is(err, objectstore.ErrObjectNotFound) {+ t.Fatalf("ReadSize not-found error = %v", err)+ }
var otherAlgo objectid.Algorithm
for _, candidate := range objectid.SupportedAlgorithms() {@@ -181,6 +191,13 @@
}
if gotSize != wantResolvedSize { t.Fatalf("ReadHeader(%s) size = %d, want resolved size %d", deltaID, gotSize, wantResolvedSize)+ }
+ gotReadSize, err := store.ReadSize(deltaID)
+ if err != nil {+ t.Fatalf("ReadSize(%s): %v", deltaID, err)+ }
+ if gotReadSize != wantResolvedSize {+ t.Fatalf("ReadSize(%s) = %d, want resolved size %d", deltaID, gotReadSize, wantResolvedSize)}
})
}
--- a/repository/read_stored_passthrough.go
+++ b/repository/read_stored_passthrough.go
@@ -12,6 +12,11 @@
return repo.objects.ReadHeader(id)
}
+// ReadStoredSize reads an object's declared content length.
+func (repo *Repository) ReadStoredSize(id objectid.ObjectID) (int64, error) {+ return repo.objects.ReadSize(id)
+}
+
// ReadStoredBytesFull reads a full serialized object as "type size\\x00content".
func (repo *Repository) ReadStoredBytesFull(id objectid.ObjectID) ([]byte, error) {return repo.objects.ReadBytesFull(id)
--
⑨