···44// Blob represents a Git blob object.
55//
66// This Blob object is fully materialized in memory.
77-// Consider using objectstorer/Store.ReadReaderContent,
77+// Consider using objectstore/Store.ReadReaderContent,
88// or appropriate streaming write APIs.
99type Blob struct {
1010 Data []byte
+3-3
object/fetch/fetcher.go
···11package fetch
2233-import objectstorer "codeberg.org/lindenii/furgit/object/storer"
33+import objectstore "codeberg.org/lindenii/furgit/object/store"
4455// Fetcher resolves parsed and streamed objects from an object store.
66//
77// A Fetcher does not take ownership of the store and does not close it.
88type Fetcher struct {
99- store objectstorer.Store
99+ store objectstore.Store
1010}
11111212// New returns a Fetcher that reads objects from store.
1313//
1414// The returned Fetcher does not take ownership of store.
1515-func New(store objectstorer.Store) *Fetcher {
1515+func New(store objectstore.Store) *Fetcher {
1616 return &Fetcher{store: store}
1717}
-46
object/storer/chain/bytes.go
···11-package chain
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99- objecttype "codeberg.org/lindenii/furgit/object/type"
1010-)
1111-1212-// ReadBytesFull reads a full serialized object from the first backend that has it.
1313-func (chain *Chain) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
1414- for i, backend := range chain.backends {
1515- full, err := backend.ReadBytesFull(id)
1616- if err == nil {
1717- return full, nil
1818- }
1919-2020- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2121- continue
2222- }
2323-2424- return nil, fmt.Errorf("objectstorer: backend %d read bytes full: %w", i, err)
2525- }
2626-2727- return nil, objectstorer.ErrObjectNotFound
2828-}
2929-3030-// ReadBytesContent reads an object's type and content bytes from the first backend that has it.
3131-func (chain *Chain) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
3232- for i, backend := range chain.backends {
3333- ty, content, err := backend.ReadBytesContent(id)
3434- if err == nil {
3535- return ty, content, nil
3636- }
3737-3838- if errors.Is(err, objectstorer.ErrObjectNotFound) {
3939- continue
4040- }
4141-4242- return objecttype.TypeInvalid, nil, fmt.Errorf("objectstorer: backend %d read bytes content: %w", i, err)
4343- }
4444-4545- return objecttype.TypeInvalid, nil, objectstorer.ErrObjectNotFound
4646-}
-12
object/storer/chain/chain.go
···11-// Package chain provides a wrapper object storage backend to query a chain of
22-// backends.
33-package chain
44-55-import objectstorer "codeberg.org/lindenii/furgit/object/storer"
66-77-// Chain queries multiple object databases in order.
88-//
99-// Chain borrows its backend stores.
1010-type Chain struct {
1111- backends []objectstorer.Store
1212-}
-8
object/storer/chain/close.go
···11-package chain
22-33-// Close releases wrapper-local resources.
44-//
55-// Chain borrows its backends, so Close does not close them.
66-//
77-// Repeated calls to Close are undefined behavior.
88-func (chain *Chain) Close() error { return nil }
-28
object/storer/chain/header.go
···11-package chain
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99- objecttype "codeberg.org/lindenii/furgit/object/type"
1010-)
1111-1212-// ReadHeader reads object header data from the first backend that has it.
1313-func (chain *Chain) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
1414- for i, backend := range chain.backends {
1515- ty, size, err := backend.ReadHeader(id)
1616- if err == nil {
1717- return ty, size, nil
1818- }
1919-2020- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2121- continue
2222- }
2323-2424- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer: backend %d read header: %w", i, err)
2525- }
2626-2727- return objecttype.TypeInvalid, 0, objectstorer.ErrObjectNotFound
2828-}
-13
object/storer/chain/new.go
···11-package chain
22-33-import objectstorer "codeberg.org/lindenii/furgit/object/storer"
44-55-// New creates an ordered object database chain.
66-//
77-// The provided backends must be non-nil and distinct.
88-// Chain borrows the provided backends and does not close them in Close.
99-func New(backends ...objectstorer.Store) *Chain {
1010- return &Chain{
1111- backends: append([]objectstorer.Store(nil), backends...),
1212- }
1313-}
-47
object/storer/chain/reader.go
···11-package chain
22-33-import (
44- "errors"
55- "fmt"
66- "io"
77-88- objectid "codeberg.org/lindenii/furgit/object/id"
99- objectstorer "codeberg.org/lindenii/furgit/object/storer"
1010- objecttype "codeberg.org/lindenii/furgit/object/type"
1111-)
1212-1313-// ReadReaderFull reads a full serialized object stream from the first backend that has it.
1414-func (chain *Chain) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
1515- for i, backend := range chain.backends {
1616- reader, err := backend.ReadReaderFull(id)
1717- if err == nil {
1818- return reader, nil
1919- }
2020-2121- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2222- continue
2323- }
2424-2525- return nil, fmt.Errorf("objectstorer: backend %d read reader full: %w", i, err)
2626- }
2727-2828- return nil, objectstorer.ErrObjectNotFound
2929-}
3030-3131-// ReadReaderContent reads an object's type, declared content length, and content stream from the first backend that has it.
3232-func (chain *Chain) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
3333- for i, backend := range chain.backends {
3434- ty, size, reader, err := backend.ReadReaderContent(id)
3535- if err == nil {
3636- return ty, size, reader, nil
3737- }
3838-3939- if errors.Is(err, objectstorer.ErrObjectNotFound) {
4040- continue
4141- }
4242-4343- return objecttype.TypeInvalid, 0, nil, fmt.Errorf("objectstorer: backend %d read reader content: %w", i, err)
4444- }
4545-4646- return objecttype.TypeInvalid, 0, nil, objectstorer.ErrObjectNotFound
4747-}
-17
object/storer/chain/refresh.go
···11-package chain
22-33-import "errors"
44-55-// Refresh forwards refresh calls to all backends.
66-func (chain *Chain) Refresh() error {
77- var errs []error
88-99- for _, backend := range chain.backends {
1010- err := backend.Refresh()
1111- if err != nil {
1212- errs = append(errs, err)
1313- }
1414- }
1515-1616- return errors.Join(errs...)
1717-}
-27
object/storer/chain/size.go
···11-package chain
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99-)
1010-1111-// ReadSize reads object content length from the first backend that has it.
1212-func (chain *Chain) ReadSize(id objectid.ObjectID) (int64, error) {
1313- for i, backend := range chain.backends {
1414- size, err := backend.ReadSize(id)
1515- if err == nil {
1616- return size, nil
1717- }
1818-1919- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2020- continue
2121- }
2222-2323- return 0, fmt.Errorf("objectstorer: backend %d read size: %w", i, err)
2424- }
2525-2626- return 0, objectstorer.ErrObjectNotFound
2727-}
···11-package loose
22-33-import (
44- objectid "codeberg.org/lindenii/furgit/object/id"
55- objecttype "codeberg.org/lindenii/furgit/object/type"
66-)
77-88-// readBytesParsed reads, inflates, and parses a loose object in one pass.
99-// It returns the full raw payload and its parsed type and content.
1010-func (store *Store) readBytesParsed(id objectid.ObjectID) ([]byte, objecttype.Type, []byte, error) {
1111- file, err := store.openObject(id)
1212- if err != nil {
1313- return nil, objecttype.TypeInvalid, nil, err
1414- }
1515-1616- defer func() { _ = file.Close() }()
1717-1818- raw, err := decodeAll(file)
1919- if err != nil {
2020- return nil, objecttype.TypeInvalid, nil, err
2121- }
2222-2323- ty, content, err := parseRaw(raw)
2424- if err != nil {
2525- return nil, objecttype.TypeInvalid, nil, err
2626- }
2727-2828- return raw, ty, content, nil
2929-}
3030-3131-// ReadBytesFull reads a full serialized object as "type size\0content".
3232-func (store *Store) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
3333- raw, _, _, err := store.readBytesParsed(id)
3434- if err != nil {
3535- return nil, err
3636- }
3737-3838- return raw, nil
3939-}
4040-4141-// ReadBytesContent reads an object's type and content bytes.
4242-func (store *Store) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
4343- _, ty, content, err := store.readBytesParsed(id)
4444- if err != nil {
4545- return objecttype.TypeInvalid, nil, err
4646- }
4747-4848- return ty, content, nil
4949-}
-37
object/storer/loose/read_header.go
···11-package loose
22-33-import (
44- "bufio"
55-66- "codeberg.org/lindenii/furgit/internal/compress/zlib"
77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objecttype "codeberg.org/lindenii/furgit/object/type"
99-)
1010-1111-// ReadHeader reads an object's type and declared content length.
1212-//
1313-// It parses only enough of the zlib-decoded object to recover the object
1414-// header. It does not verify that the remaining object content is readable and
1515-// does not verify the zlib Adler-32 trailer.
1616-func (store *Store) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
1717- file, err := store.openObject(id)
1818- if err != nil {
1919- return objecttype.TypeInvalid, 0, err
2020- }
2121-2222- defer func() { _ = file.Close() }()
2323-2424- zr, err := zlib.NewReader(file)
2525- if err != nil {
2626- return objecttype.TypeInvalid, 0, err
2727- }
2828-2929- defer func() { _ = zr.Close() }()
3030-3131- _, ty, size, err := readHeader(bufio.NewReader(zr))
3232- if err != nil {
3333- return objecttype.TypeInvalid, 0, err
3434- }
3535-3636- return ty, size, nil
3737-}
-118
object/storer/loose/read_reader.go
···11-package loose
22-33-import (
44- "bufio"
55- "bytes"
66- "errors"
77- "io"
88- "os"
99-1010- "codeberg.org/lindenii/furgit/internal/compress/zlib"
1111- "codeberg.org/lindenii/furgit/internal/iolimit"
1212- objectid "codeberg.org/lindenii/furgit/object/id"
1313- objecttype "codeberg.org/lindenii/furgit/object/type"
1414-)
1515-1616-type objectReader struct {
1717- // reader is the stream exposed by Read.
1818- reader io.Reader
1919- // file is the underlying loose object file and is closed by Close.
2020- file *os.File
2121- // zr is the zlib decoder and is closed by Close.
2222- zr io.ReadCloser
2323-}
2424-2525-func (reader *objectReader) Read(dst []byte) (int, error) {
2626- return reader.reader.Read(dst)
2727-}
2828-2929-func (reader *objectReader) Close() error {
3030- errZlib := reader.zr.Close()
3131- errFile := reader.file.Close()
3232-3333- return errors.Join(errZlib, errFile)
3434-}
3535-3636-// openInflated opens and zlib-decodes a loose object file.
3737-// The caller owns both returned closers and must close them.
3838-func (store *Store) openInflated(id objectid.ObjectID) (*os.File, io.ReadCloser, error) {
3939- file, err := store.openObject(id)
4040- if err != nil {
4141- return nil, nil, err
4242- }
4343-4444- zr, err := zlib.NewReader(file)
4545- if err != nil {
4646- _ = file.Close()
4747-4848- return nil, nil, err
4949- }
5050-5151- return file, zr, nil
5252-}
5353-5454-// ReadReaderFull reads a full serialized object stream as "type size\0content".
5555-//
5656-// The caller must close the returned reader.
5757-//
5858-// Close releases resources only. It does not drain unread data for additional
5959-// validation. In particular, malformed trailing compressed data, trailing bytes
6060-// past the declared object size, and the zlib Adler-32 trailer may go
6161-// unverified unless the caller reads to io.EOF.
6262-func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
6363- file, zr, err := store.openInflated(id)
6464- if err != nil {
6565- return nil, err
6666- }
6767-6868- br := bufio.NewReader(zr)
6969-7070- header, _, size, err := readHeader(br)
7171- if err != nil {
7272- _ = zr.Close()
7373- _ = file.Close()
7474-7575- return nil, err
7676- }
7777-7878- return &objectReader{
7979- reader: io.MultiReader(
8080- bytes.NewReader(header),
8181- iolimit.ExpectLengthReader(br, size),
8282- ),
8383- file: file,
8484- zr: zr,
8585- }, nil
8686-}
8787-8888-// ReadReaderContent reads an object's type, declared content length, and
8989-// content stream.
9090-//
9191-// The caller must close the returned reader.
9292-//
9393-// Close releases resources only. It does not drain unread data for additional
9494-// validation. In particular, malformed trailing compressed data, trailing bytes
9595-// past the declared object size, and the zlib Adler-32 trailer may go
9696-// unverified unless the caller reads to io.EOF.
9797-func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
9898- file, zr, err := store.openInflated(id)
9999- if err != nil {
100100- return objecttype.TypeInvalid, 0, nil, err
101101- }
102102-103103- br := bufio.NewReader(zr)
104104-105105- _, ty, size, err := readHeader(br)
106106- if err != nil {
107107- _ = zr.Close()
108108- _ = file.Close()
109109-110110- return objecttype.TypeInvalid, 0, nil, err
111111- }
112112-113113- return ty, size, &objectReader{
114114- reader: iolimit.ExpectLengthReader(br, size),
115115- file: file,
116116- zr: zr,
117117- }, nil
118118-}
-13
object/storer/loose/read_size.go
···11-package loose
22-33-import objectid "codeberg.org/lindenii/furgit/object/id"
44-55-// ReadSize reads an object's declared content length.
66-//
77-// Like ReadHeader, it parses only enough of the zlib-decoded object to recover
88-// the header and does not verify the zlib Adler-32 trailer.
99-func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {
1010- _, size, err := store.ReadHeader(id)
1111-1212- return size, err
1313-}
···11-package loose
22-33-// Refresh is a no-op for loose object stores.
44-func (store *Store) Refresh() error {
55- return nil
66-}
-41
object/storer/loose/store.go
···11-// Package loose provides a loose object backend (objects/XX/YYYYY..).
22-package loose
33-44-import (
55- "os"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88-)
99-1010-// Store reads loose Git objects from an objects directory root.
1111-//
1212-// Loose objects are zlib streams whose trailer uses Adler-32. Which reads
1313-// consume enough of the stream to reach and verify that trailer is documented
1414-// on the individual methods.
1515-type Store struct {
1616- // root is the objects directory capability used for all object file access.
1717- // Object files are opened by relative paths like "<first2>/<rest>".
1818- // Store borrows this root.
1919- root *os.Root
2020- // algo is the expected object ID algorithm for lookups.
2121- algo objectid.Algorithm
2222-}
2323-2424-// New creates a loose-object store rooted at an objects directory for algo.
2525-func New(root *os.Root, algo objectid.Algorithm) (*Store, error) {
2626- if algo.Size() == 0 {
2727- return nil, objectid.ErrInvalidAlgorithm
2828- }
2929-3030- return &Store{
3131- root: root,
3232- algo: algo,
3333- }, nil
3434-}
3535-3636-// Close releases resources associated with the backend.
3737-//
3838-// Store borrows its root, so Close does not close it.
3939-//
4040-// Repeated calls to Close are undefined behavior.
4141-func (store *Store) Close() error { return nil }
···11-package memory
22-33-// Refresh is a no-op for in-memory object stores.
44-func (store *Store) Refresh() error {
55- return nil
66-}
-24
object/storer/memory/store.go
···11-package memory
22-33-import (
44- objectid "codeberg.org/lindenii/furgit/object/id"
55-)
66-77-// Store is one in-memory object store.
88-type Store struct {
99- algo objectid.Algorithm
1010- objects map[objectid.ObjectID]storedObject
1111-}
1212-1313-// New builds one empty in-memory store for one object format.
1414-func New(algo objectid.Algorithm) *Store {
1515- return &Store{
1616- algo: algo,
1717- objects: make(map[objectid.ObjectID]storedObject),
1818- }
1919-}
2020-2121-// Close closes the in-memory store.
2222-func (store *Store) Close() error {
2323- return nil
2424-}
-51
object/storer/mix/bytes.go
···11-package mix
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99- objecttype "codeberg.org/lindenii/furgit/object/type"
1010-)
1111-1212-// ReadBytesFull reads a full serialized object from one backend that has it.
1313-func (mix *Mix) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
1414- for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) {
1515- full, err := backend.ReadBytesFull(id)
1616- if err == nil {
1717- mix.touchBackend(backend)
1818-1919- return full, nil
2020- }
2121-2222- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2323- continue
2424- }
2525-2626- return nil, fmt.Errorf("objectstorer: backend %d read bytes full: %w", i, err)
2727- }
2828-2929- return nil, objectstorer.ErrObjectNotFound
3030-}
3131-3232-// ReadBytesContent reads an object's type and content bytes from one backend
3333-// that has it.
3434-func (mix *Mix) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
3535- for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) {
3636- ty, content, err := backend.ReadBytesContent(id)
3737- if err == nil {
3838- mix.touchBackend(backend)
3939-4040- return ty, content, nil
4141- }
4242-4343- if errors.Is(err, objectstorer.ErrObjectNotFound) {
4444- continue
4545- }
4646-4747- return objecttype.TypeInvalid, nil, fmt.Errorf("objectstorer: backend %d read bytes content: %w", i, err)
4848- }
4949-5050- return objecttype.TypeInvalid, nil, objectstorer.ErrObjectNotFound
5151-}
-8
object/storer/mix/close.go
···11-package mix
22-33-// Close releases wrapper-local resources.
44-//
55-// Mix borrows its backends, so Close does not close them.
66-//
77-// Repeated calls to Close are undefined behavior.
88-func (mix *Mix) Close() error { return nil }
-30
object/storer/mix/header.go
···11-package mix
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99- objecttype "codeberg.org/lindenii/furgit/object/type"
1010-)
1111-1212-// ReadHeader reads object header data from one backend that has it.
1313-func (mix *Mix) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
1414- for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) {
1515- ty, size, err := backend.ReadHeader(id)
1616- if err == nil {
1717- mix.touchBackend(backend)
1818-1919- return ty, size, nil
2020- }
2121-2222- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2323- continue
2424- }
2525-2626- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer: backend %d read header: %w", i, err)
2727- }
2828-2929- return objecttype.TypeInvalid, 0, objectstorer.ErrObjectNotFound
3030-}
-20
object/storer/mix/mix.go
···11-// Package mix provides an adaptive wrapper over multiple object storage
22-// backends.
33-package mix
44-55-import (
66- "sync"
77-88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99-)
1010-1111-// Mix queries multiple object databases with an MRU backend preference.
1212-//
1313-// Mix borrows its backend stores.
1414-type Mix struct {
1515- mu sync.RWMutex
1616-1717- backendHead *backendNode
1818- backendTail *backendNode
1919- backendNodeByStore map[objectstorer.Store]*backendNode
2020-}
···11-package mix
22-33-import (
44- "errors"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objectstorer "codeberg.org/lindenii/furgit/object/storer"
99-)
1010-1111-// ReadSize reads object content length from one backend that has it.
1212-func (mix *Mix) ReadSize(id objectid.ObjectID) (int64, error) {
1313- for i, backend := 0, mix.firstBackend(); backend != nil; i, backend = i+1, mix.nextBackend(backend) {
1414- size, err := backend.ReadSize(id)
1515- if err == nil {
1616- mix.touchBackend(backend)
1717-1818- return size, nil
1919- }
2020-2121- if errors.Is(err, objectstorer.ErrObjectNotFound) {
2222- continue
2323- }
2424-2525- return 0, fmt.Errorf("objectstorer: backend %d read size: %w", i, err)
2626- }
2727-2828- return 0, objectstorer.ErrObjectNotFound
2929-}
-92
object/storer/objectstore.go
···11-// Package objectstorer provides interfaces for object storage backends.
22-package objectstorer
33-44-import (
55- "errors"
66- "io"
77-88- objectid "codeberg.org/lindenii/furgit/object/id"
99- objecttype "codeberg.org/lindenii/furgit/object/type"
1010-)
1111-1212-// ErrObjectNotFound indicates that an object does not exist in a backend.
1313-// This error MUST only be used in situations where the object store has
1414-// no specified object ID, but no other unexpected conditions were
1515-// encountered. In particular, it is not suitable for situations where one
1616-// object references another (such as a tree referencing a blob) but
1717-// the latter does not exist; these situations should use a separate
1818-// error (TODO).
1919-var ErrObjectNotFound = errors.New("objectstorer: object not found")
2020-2121-// Store reads Git objects by object ID.
2222-//
2323-// Unless an implementation explicitly documents otherwise, values returned by
2424-// Store methods are only valid until the store is closed.
2525-type Store interface {
2626- // ReadBytesFull reads a full serialized object as "type size\0content".
2727- //
2828- // In a valid repository, hashing this payload with the same algorithm yields
2929- // the requested object ID. Readers should treat this as a repository
3030- // invariant and should not re-verify it on every read.
3131- //
3232- // Any read-time integrity verification beyond producing this payload is
3333- // implementation-defined.
3434- ReadBytesFull(id objectid.ObjectID) ([]byte, error)
3535-3636- // ReadBytesContent reads an object's type and content bytes.
3737- //
3838- // Any read-time integrity verification beyond producing this payload is
3939- // implementation-defined.
4040- ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error)
4141-4242- // ReadReaderFull reads a full serialized object stream as "type size\0content".
4343- //
4444- // Caller must close the returned reader.
4545- // The returned reader is only valid until the store is closed.
4646- //
4747- // Any read-time integrity verification performed while producing the stream
4848- // is implementation-defined.
4949- ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error)
5050-5151- // ReadReaderContent reads an object's type, declared content length,
5252- // and content stream.
5353- //
5454- // Caller must close the returned reader.
5555- // The returned reader is only valid until the store is closed.
5656- //
5757- // Any read-time integrity verification performed while producing the stream
5858- // is implementation-defined.
5959- ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error)
6060-6161- // ReadSize reads an object's declared content length.
6262- //
6363- // This is equivalent to ReadHeader(...).size and may be cheaper than
6464- // ReadHeader when callers do not need object type.
6565- //
6666- // Any read-time integrity verification performed to produce the size is
6767- // implementation-defined.
6868- ReadSize(id objectid.ObjectID) (int64, error)
6969-7070- // ReadHeader reads an object's type and declared content length.
7171- //
7272- // Any read-time integrity verification performed to produce the header is
7373- // implementation-defined.
7474- ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error)
7575-7676- // Refresh updates any backend-local discovery/cache view of on-disk objects.
7777- //
7878- // Backends without dynamic discovery should return nil.
7979- Refresh() error
8080-8181- // Close releases resources associated with the backend.
8282- //
8383- // Repeated calls to Close are undefined behavior unless the implementation
8484- // explicitly documents otherwise.
8585- Close() error
8686-}
8787-8888-// type Cursor any
8989-//
9090-// Then make all read functions accept and provide a Cursor
9191-// nil must always be accepted and would exhibit the same behavior as right now
9292-// Non-nil behavior is implementation-defined: e.g., pack selection
-3
object/storer/packed/TODO
···11-* Per delta-plan memo map
22-* Internal handle/request context (might expose it externally later and add to global interface)
33-* Audit on mutex
-38
object/storer/packed/close.go
···11-package packed
22-33-// Close releases mapped pack/index resources associated with the store.
44-//
55-// Store borrows its root, so Close does not close it.
66-// Close releases cached pack/index mappings retained by the store.
77-//
88-// Repeated calls to Close are undefined behavior.
99-func (store *Store) Close() error {
1010- store.stateMu.Lock()
1111- packs := store.packs
1212- store.stateMu.Unlock()
1313- store.idxMu.RLock()
1414- indexes := store.idxByPack
1515- store.idxMu.RUnlock()
1616-1717- var closeErr error
1818-1919- for _, pack := range packs {
2020- err := pack.close()
2121- if err != nil && closeErr == nil {
2222- closeErr = err
2323- }
2424- }
2525-2626- for _, index := range indexes {
2727- err := index.close()
2828- if err != nil && closeErr == nil {
2929- closeErr = err
3030- }
3131- }
3232-3333- store.cacheMu.Lock()
3434- store.deltaCache.clear()
3535- store.cacheMu.Unlock()
3636-3737- return closeErr
3838-}
-66
object/storer/packed/delta_build_chain.go
···11-package packed
22-33-import (
44- "fmt"
55-66- packfmt "codeberg.org/lindenii/furgit/format/packfile"
77- objecttype "codeberg.org/lindenii/furgit/object/type"
88-)
99-1010-// deltaBuildChain walks one object's chain and builds a reconstruction chain.
1111-func (store *Store) deltaBuildChain(start location) (deltaChain, error) {
1212- visited := make(map[location]struct{})
1313- current := start
1414-1515- var chain deltaChain
1616-1717- for {
1818- if _, ok := visited[current]; ok {
1919- return deltaChain{}, fmt.Errorf("objectstorer/packed: delta cycle while resolving object")
2020- }
2121-2222- visited[current] = struct{}{}
2323-2424- _, meta, err := store.entryMetaAt(current)
2525- if err != nil {
2626- return deltaChain{}, err
2727- }
2828-2929- if packfmt.IsBaseObjectType(meta.ty) {
3030- chain.baseLoc = current
3131- chain.baseType = meta.ty
3232-3333- return chain, nil
3434- }
3535-3636- switch meta.ty {
3737- case objecttype.TypeRefDelta:
3838- chain.deltas = append(chain.deltas, deltaNode{
3939- loc: current,
4040- dataOffset: meta.dataOffset,
4141- })
4242-4343- next, err := store.lookup(meta.baseRefID)
4444- if err != nil {
4545- return deltaChain{}, err
4646- }
4747-4848- current = next
4949- case objecttype.TypeOfsDelta:
5050- chain.deltas = append(chain.deltas, deltaNode{
5151- loc: current,
5252- dataOffset: meta.dataOffset,
5353- })
5454- current = location{
5555- packName: current.packName,
5656- offset: meta.baseOfs,
5757- }
5858- case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
5959- return deltaChain{}, fmt.Errorf("objectstorer/packed: internal invariant violation for base type %d", meta.ty)
6060- case objecttype.TypeInvalid, objecttype.TypeFuture:
6161- return deltaChain{}, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
6262- default:
6363- return deltaChain{}, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
6464- }
6565- }
6666-}
-61
object/storer/packed/delta_cache.go
···11-package packed
22-33-import (
44- "codeberg.org/lindenii/furgit/internal/lru"
55- objecttype "codeberg.org/lindenii/furgit/object/type"
66-)
77-88-const defaultDeltaCacheMaxBytes = 32 << 20
99-1010-// deltaBaseKey identifies one base object by pack location.
1111-type deltaBaseKey struct {
1212- packName string
1313- offset uint64
1414-}
1515-1616-// deltaBaseValue stores one cached base object body.
1717-type deltaBaseValue struct {
1818- ty objecttype.Type
1919- content []byte
2020-}
2121-2222-// deltaCache wraps a weighted LRU for resolved delta bases.
2323-type deltaCache struct {
2424- lru *lru.Cache[deltaBaseKey, deltaBaseValue]
2525-}
2626-2727-// newDeltaCache creates a delta base cache with a byte budget.
2828-func newDeltaCache(maxBytes int64) *deltaCache {
2929- return &deltaCache{
3030- lru: lru.New(
3131- maxBytes,
3232- func(_ deltaBaseKey, value deltaBaseValue) int64 {
3333- return int64(len(value.content))
3434- },
3535- nil,
3636- ),
3737- }
3838-}
3939-4040-// get returns a cloned cached base object value.
4141-func (cache *deltaCache) get(key deltaBaseKey) (objecttype.Type, []byte, bool) {
4242- value, ok := cache.lru.Get(key)
4343- if !ok {
4444- return objecttype.TypeInvalid, nil, false
4545- }
4646-4747- return value.ty, append([]byte(nil), value.content...), true
4848-}
4949-5050-// add stores a cloned base object value.
5151-func (cache *deltaCache) add(key deltaBaseKey, ty objecttype.Type, content []byte) {
5252- cache.lru.Add(key, deltaBaseValue{
5353- ty: ty,
5454- content: append([]byte(nil), content...),
5555- })
5656-}
5757-5858-// clear removes all cached entries.
5959-func (cache *deltaCache) clear() {
6060- cache.lru.Clear()
6161-}
-13
object/storer/packed/delta_chain.go
···11-package packed
22-33-import objecttype "codeberg.org/lindenii/furgit/object/type"
44-55-// deltaChain describes how to reconstruct one requested object.
66-type deltaChain struct {
77- // baseLoc points to the innermost base object.
88- baseLoc location
99- // baseType is the canonical object type resolved from baseLoc.
1010- baseType objecttype.Type
1111- // deltas contains delta objects from target down toward base.
1212- deltas []deltaNode
1313-}
-9
object/storer/packed/delta_node.go
···11-package packed
22-33-// deltaNode describes one delta object in a reconstruction chain.
44-type deltaNode struct {
55- // loc identifies the delta object's pack location.
66- loc location
77- // dataOffset points to the start of the delta zlib payload in pack.
88- dataOffset int
99-}
···11-package packed
22-33-import (
44- "os"
55-66- objectid "codeberg.org/lindenii/furgit/object/id"
77-)
88-99-// idxFile stores one mapped and validated idx v2 file.
1010-type idxFile struct {
1111- // idxName is the basename of this .idx file.
1212- idxName string
1313- // packName is the matching .pack basename.
1414- packName string
1515- // algo is the hash algorithm encoded by the index.
1616- algo objectid.Algorithm
1717-1818- // file is the opened index file descriptor.
1919- file *os.File
2020- // data is the mapped index bytes.
2121- data []byte
2222-2323- // fanout stores fanout table values.
2424- fanout [256]uint32
2525- // numObjects equals fanout[255].
2626- numObjects int
2727-2828- // namesOffset starts the sorted object-id table.
2929- namesOffset int
3030- // offset32Offset starts the 32-bit offset table.
3131- offset32Offset int
3232- // offset64Offset starts the 64-bit offset table.
3333- offset64Offset int
3434- // offset64Count is the number of 64-bit offset entries.
3535- offset64Count int
3636-}
-136
object/storer/packed/idx_candidates_mru.go
···11-package packed
22-33-// packCandidateNode is one node in the candidate MRU order list.
44-type packCandidateNode struct {
55- packName string
66- prev *packCandidateNode
77- next *packCandidateNode
88-}
99-1010-func (store *Store) reconcileMRU(candidates []packCandidate) {
1111- store.mruMu.Lock()
1212- defer store.mruMu.Unlock()
1313-1414- if store.mruNodeByPack == nil {
1515- store.mruNodeByPack = make(map[string]*packCandidateNode, len(candidates))
1616- }
1717-1818- present := make(map[string]struct{}, len(candidates))
1919- for _, candidate := range candidates {
2020- present[candidate.packName] = struct{}{}
2121- }
2222-2323- ordered := make([]string, 0, len(candidates))
2424-2525- for node := store.mruHead; node != nil; node = node.next {
2626- if _, ok := present[node.packName]; !ok {
2727- continue
2828- }
2929-3030- ordered = append(ordered, node.packName)
3131- delete(present, node.packName)
3232- }
3333-3434- for _, candidate := range candidates {
3535- if _, ok := present[candidate.packName]; !ok {
3636- continue
3737- }
3838-3939- ordered = append(ordered, candidate.packName)
4040- delete(present, candidate.packName)
4141- }
4242-4343- store.mruHead = nil
4444- store.mruTail = nil
4545- store.mruNodeByPack = make(map[string]*packCandidateNode, len(ordered))
4646-4747- for _, packName := range ordered {
4848- node := &packCandidateNode{
4949- packName: packName,
5050- prev: store.mruTail,
5151- }
5252- if store.mruTail != nil {
5353- store.mruTail.next = node
5454- }
5555-5656- if store.mruHead == nil {
5757- store.mruHead = node
5858- }
5959-6060- store.mruTail = node
6161- store.mruNodeByPack[packName] = node
6262- }
6363-}
6464-6565-// touchCandidate moves one candidate to the front of the lookup order.
6666-// This is done on a best-effort basis.
6767-func (store *Store) touchCandidate(packName string) {
6868- if !store.mruMu.TryLock() {
6969- return
7070- }
7171- defer store.mruMu.Unlock()
7272-7373- node := store.mruNodeByPack[packName]
7474- if node == nil || node == store.mruHead {
7575- return
7676- }
7777-7878- if node.prev != nil {
7979- node.prev.next = node.next
8080- }
8181-8282- if node.next != nil {
8383- node.next.prev = node.prev
8484- }
8585-8686- if store.mruTail == node {
8787- store.mruTail = node.prev
8888- }
8989-9090- node.prev = nil
9191-9292- node.next = store.mruHead
9393- if store.mruHead != nil {
9494- store.mruHead.prev = node
9595- }
9696-9797- store.mruHead = node
9898- if store.mruTail == nil {
9999- store.mruTail = node
100100- }
101101-}
102102-103103-// firstCandidatePackName returns the current head pack name, or "" when none
104104-// are available.
105105-func (store *Store) firstCandidatePackName(snapshot *candidateSnapshot) string {
106106- store.mruMu.RLock()
107107- defer store.mruMu.RUnlock()
108108-109109- for node := store.mruHead; node != nil; node = node.next {
110110- if _, ok := snapshot.candidateByPack[node.packName]; ok {
111111- return node.packName
112112- }
113113- }
114114-115115- return ""
116116-}
117117-118118-// nextCandidatePackName returns the pack name after currentPack in current MRU
119119-// order, or "" at end / when currentPack is not present.
120120-func (store *Store) nextCandidatePackName(currentPack string, snapshot *candidateSnapshot) string {
121121- store.mruMu.RLock()
122122- defer store.mruMu.RUnlock()
123123-124124- node := store.mruNodeByPack[currentPack]
125125- if node == nil {
126126- return ""
127127- }
128128-129129- for node = node.next; node != nil; node = node.next {
130130- if _, ok := snapshot.candidateByPack[node.packName]; ok {
131131- return node.packName
132132- }
133133- }
134134-135135- return ""
136136-}
···11-package packed
22-33-// RefreshPolicy configures when candidate pack/index discovery refreshes.
44-type RefreshPolicy uint8
55-66-const (
77- // RefreshPolicyOnMissing refreshes candidates once after a lookup miss.
88- RefreshPolicyOnMissing RefreshPolicy = iota
99- // RefreshPolicyNever disables automatic refresh after lookup misses.
1010- RefreshPolicyNever
1111-)
1212-1313-// Options configures a packed object store.
1414-type Options struct {
1515- RefreshPolicy RefreshPolicy
1616-}
-82
object/storer/packed/pack.go
···11-package packed
22-33-import (
44- "encoding/binary"
55- "fmt"
66- "os"
77- "syscall"
88-99- packfmt "codeberg.org/lindenii/furgit/format/packfile"
1010- "codeberg.org/lindenii/furgit/internal/intconv"
1111-)
1212-1313-// packFile stores one mapped and validated .pack file.
1414-type packFile struct {
1515- // name is the .pack basename.
1616- name string
1717- // file is the opened pack file descriptor.
1818- file *os.File
1919- // data is the mapped pack bytes.
2020- data []byte
2121-}
2222-2323-// openPackFile maps and validates one pack file.
2424-func openPackFile(name string, file *os.File, size int64) (*packFile, error) {
2525- if size < 12 {
2626- return nil, fmt.Errorf("objectstorer/packed: pack %q too short", name)
2727- }
2828-2929- if size > int64(int(^uint(0)>>1)) {
3030- return nil, fmt.Errorf("objectstorer/packed: pack %q has unsupported size", name)
3131- }
3232-3333- fd, err := intconv.UintptrToInt(file.Fd())
3434- if err != nil {
3535- return nil, err
3636- }
3737-3838- data, err := syscall.Mmap(fd, 0, int(size), syscall.PROT_READ, syscall.MAP_PRIVATE)
3939- if err != nil {
4040- return nil, err
4141- }
4242-4343- if binary.BigEndian.Uint32(data[:4]) != packfmt.Signature {
4444- _ = syscall.Munmap(data)
4545-4646- return nil, fmt.Errorf("objectstorer/packed: pack %q invalid signature", name)
4747- }
4848-4949- version := binary.BigEndian.Uint32(data[4:8])
5050- if !packfmt.VersionSupported(version) {
5151- _ = syscall.Munmap(data)
5252-5353- return nil, fmt.Errorf("objectstorer/packed: pack %q unsupported version %d", name, version)
5454- }
5555-5656- return &packFile{name: name, file: file, data: data}, nil
5757-}
5858-5959-// close unmaps and closes one pack handle.
6060-func (pack *packFile) close() error {
6161- var closeErr error
6262-6363- if pack.data != nil {
6464- err := syscall.Munmap(pack.data)
6565- if err != nil && closeErr == nil {
6666- closeErr = err
6767- }
6868-6969- pack.data = nil
7070- }
7171-7272- if pack.file != nil {
7373- err := pack.file.Close()
7474- if err != nil && closeErr == nil {
7575- closeErr = err
7676- }
7777-7878- pack.file = nil
7979- }
8080-8181- return closeErr
8282-}
-34
object/storer/packed/pack_idx_checksum.go
···11-package packed
22-33-import (
44- "bytes"
55- "fmt"
66-77- objectid "codeberg.org/lindenii/furgit/object/id"
88-)
99-1010-// verifyMappedPackMatchesMappedIdx compares one mapped pack trailer hash with
1111-// the pack hash recorded in one mapped idx trailer.
1212-func verifyMappedPackMatchesMappedIdx(packData, idxData []byte, algo objectid.Algorithm) error {
1313- hashSize := algo.Size()
1414- if hashSize <= 0 {
1515- return objectid.ErrInvalidAlgorithm
1616- }
1717-1818- if len(packData) < hashSize {
1919- return fmt.Errorf("objectstorer/packed: pack too short for trailer hash")
2020- }
2121-2222- if len(idxData) < hashSize*2 {
2323- return fmt.Errorf("objectstorer/packed: idx too short for trailer hashes")
2424- }
2525-2626- packTrailerHash := packData[len(packData)-hashSize:]
2727-2828- idxPackHash := idxData[len(idxData)-hashSize*2 : len(idxData)-hashSize]
2929- if !bytes.Equal(packTrailerHash, idxPackHash) {
3030- return fmt.Errorf("objectstorer/packed: pack hash does not match idx")
3131- }
3232-3333- return nil
3434-}
-38
object/storer/packed/read_bytes.go
···11-package packed
22-33-import (
44- "fmt"
55-66- objectheader "codeberg.org/lindenii/furgit/object/header"
77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objecttype "codeberg.org/lindenii/furgit/object/type"
99-)
1010-1111-// ReadBytesContent reads an object's type and content bytes.
1212-func (store *Store) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
1313- loc, err := store.lookup(id)
1414- if err != nil {
1515- return objecttype.TypeInvalid, nil, err
1616- }
1717-1818- return store.deltaResolveContent(loc)
1919-}
2020-2121-// ReadBytesFull reads a full serialized object as "type size\0content".
2222-func (store *Store) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
2323- ty, content, err := store.ReadBytesContent(id)
2424- if err != nil {
2525- return nil, err
2626- }
2727-2828- header, ok := objectheader.Encode(ty, int64(len(content)))
2929- if !ok {
3030- return nil, fmt.Errorf("objectstorer/packed: failed to encode object header for type %d", ty)
3131- }
3232-3333- out := make([]byte, len(header)+len(content))
3434- copy(out, header)
3535- copy(out[len(header):], content)
3636-3737- return out, nil
3838-}
-19
object/storer/packed/read_closer.go
···11-package packed
22-33-import "io"
44-55-// readCloser proxies reads and closes one underlying closer.
66-type readCloser struct {
77- reader io.Reader
88- closer io.Closer
99-}
1010-1111-// Read proxies reads to the underlying reader.
1212-func (reader *readCloser) Read(dst []byte) (int, error) {
1313- return reader.reader.Read(dst)
1414-}
1515-1616-// Close closes the underlying closer.
1717-func (reader *readCloser) Close() error {
1818- return reader.closer.Close()
1919-}
-20
object/storer/packed/read_header.go
···11-package packed
22-33-import (
44- objectid "codeberg.org/lindenii/furgit/object/id"
55- objecttype "codeberg.org/lindenii/furgit/object/type"
66-)
77-88-// ReadHeader reads an object's type and declared content size.
99-//
1010-// It resolves header metadata only. It does not verify that the full pack entry
1111-// payload is readable and does not verify any zlib Adler-32 trailer for
1212-// compressed entry data.
1313-func (store *Store) ReadHeader(id objectid.ObjectID) (objecttype.Type, int64, error) {
1414- loc, err := store.lookup(id)
1515- if err != nil {
1616- return objecttype.TypeInvalid, 0, err
1717- }
1818-1919- return store.resolveHeaderAt(loc)
2020-}
-66
object/storer/packed/read_header_resolve.go
···11-package packed
22-33-import (
44- "fmt"
55-66- packfmt "codeberg.org/lindenii/furgit/format/packfile"
77- objecttype "codeberg.org/lindenii/furgit/object/type"
88-)
99-1010-// resolveHeaderAt resolves one object's canonical type and declared content size.
1111-func (store *Store) resolveHeaderAt(start location) (objecttype.Type, int64, error) {
1212- visited := make(map[location]struct{})
1313- current := start
1414- declaredSize := int64(-1)
1515-1616- for {
1717- if _, ok := visited[current]; ok {
1818- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer/packed: delta cycle while resolving object header")
1919- }
2020-2121- visited[current] = struct{}{}
2222-2323- pack, meta, err := store.entryMetaAt(current)
2424- if err != nil {
2525- return objecttype.TypeInvalid, 0, err
2626- }
2727-2828- if declaredSize < 0 {
2929- if packfmt.IsBaseObjectType(meta.ty) {
3030- declaredSize = meta.size
3131- } else {
3232- size, err := deltaDeclaredSizeAt(pack, meta.dataOffset)
3333- if err != nil {
3434- return objecttype.TypeInvalid, 0, err
3535- }
3636-3737- declaredSize = size
3838- }
3939- }
4040-4141- if packfmt.IsBaseObjectType(meta.ty) {
4242- return meta.ty, declaredSize, nil
4343- }
4444-4545- switch meta.ty {
4646- case objecttype.TypeRefDelta:
4747- next, err := store.lookup(meta.baseRefID)
4848- if err != nil {
4949- return objecttype.TypeInvalid, 0, err
5050- }
5151-5252- current = next
5353- case objecttype.TypeOfsDelta:
5454- current = location{
5555- packName: current.packName,
5656- offset: meta.baseOfs,
5757- }
5858- case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
5959- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer/packed: internal invariant violation for base type %d", meta.ty)
6060- case objecttype.TypeInvalid, objecttype.TypeFuture:
6161- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
6262- default:
6363- return objecttype.TypeInvalid, 0, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
6464- }
6565- }
6666-}
-103
object/storer/packed/read_reader.go
···11-package packed
22-33-import (
44- "bytes"
55- "fmt"
66- "io"
77-88- packfmt "codeberg.org/lindenii/furgit/format/packfile"
99- "codeberg.org/lindenii/furgit/internal/iolimit"
1010- objectheader "codeberg.org/lindenii/furgit/object/header"
1111- objectid "codeberg.org/lindenii/furgit/object/id"
1212- objecttype "codeberg.org/lindenii/furgit/object/type"
1313-)
1414-1515-// ReadReaderContent reads an object's type, declared content size, and content
1616-// stream.
1717-//
1818-// The caller must close the returned reader.
1919-//
2020-// For base pack entries, the returned reader borrows store-owned mapped pack
2121-// data and is only valid until the store is closed.
2222-//
2323-// Close releases reader-local resources only. It does not drain unread data for
2424-// additional validation. In particular, malformed trailing compressed data,
2525-// trailing bytes past the declared object size, and the zlib Adler-32 trailer
2626-// may go unverified unless the caller reads to io.EOF.
2727-func (store *Store) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
2828- loc, err := store.lookup(id)
2929- if err != nil {
3030- return objecttype.TypeInvalid, 0, nil, err
3131- }
3232-3333- pack, meta, err := store.entryMetaAt(loc)
3434- if err != nil {
3535- return objecttype.TypeInvalid, 0, nil, err
3636- }
3737-3838- if packfmt.IsBaseObjectType(meta.ty) {
3939- zr, err := zlibReaderAt(pack, meta.dataOffset)
4040- if err != nil {
4141- return objecttype.TypeInvalid, 0, nil, err
4242- }
4343-4444- return meta.ty, meta.size, &readCloser{
4545- reader: iolimit.ExpectLengthReader(zr, meta.size),
4646- closer: zr,
4747- }, nil
4848- }
4949-5050- ty, content, err := store.deltaResolveContent(loc)
5151- if err != nil {
5252- return objecttype.TypeInvalid, 0, nil, err
5353- }
5454-5555- return ty, int64(len(content)), io.NopCloser(bytes.NewReader(content)), nil
5656-}
5757-5858-// ReadReaderFull reads a full serialized object stream as "type size\0content".
5959-//
6060-// The caller must close the returned reader.
6161-//
6262-// For base pack entries, the returned reader borrows store-owned mapped pack
6363-// data and is only valid until the store is closed.
6464-//
6565-// Close releases reader-local resources only. It does not drain unread data for
6666-// additional validation. In particular, malformed trailing compressed data,
6767-// trailing bytes past the declared object size, and the zlib Adler-32 trailer
6868-// may go unverified unless the caller reads to io.EOF.
6969-func (store *Store) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
7070- loc, err := store.lookup(id)
7171- if err != nil {
7272- return nil, err
7373- }
7474-7575- pack, meta, err := store.entryMetaAt(loc)
7676- if err != nil {
7777- return nil, err
7878- }
7979-8080- if packfmt.IsBaseObjectType(meta.ty) {
8181- header, ok := objectheader.Encode(meta.ty, meta.size)
8282- if !ok {
8383- return nil, fmt.Errorf("objectstorer/packed: failed to encode object header for type %d", meta.ty)
8484- }
8585-8686- zr, err := zlibReaderAt(pack, meta.dataOffset)
8787- if err != nil {
8888- return nil, err
8989- }
9090-9191- return &readCloser{
9292- reader: io.MultiReader(bytes.NewReader(header), iolimit.ExpectLengthReader(zr, meta.size)),
9393- closer: zr,
9494- }, nil
9595- }
9696-9797- raw, err := store.ReadBytesFull(id)
9898- if err != nil {
9999- return nil, err
100100- }
101101-102102- return io.NopCloser(bytes.NewReader(raw)), nil
103103-}
-46
object/storer/packed/read_size.go
···11-package packed
22-33-import (
44- "fmt"
55-66- packfmt "codeberg.org/lindenii/furgit/format/packfile"
77- objectid "codeberg.org/lindenii/furgit/object/id"
88- objecttype "codeberg.org/lindenii/furgit/object/type"
99-)
1010-1111-// ReadSize reads an object's declared content size.
1212-//
1313-// Like ReadHeader, it resolves header metadata only. It does not verify that
1414-// the full pack entry payload is readable and does not verify any zlib
1515-// Adler-32 trailer for compressed entry data.
1616-func (store *Store) ReadSize(id objectid.ObjectID) (int64, error) {
1717- loc, err := store.lookup(id)
1818- if err != nil {
1919- return 0, err
2020- }
2121-2222- return store.resolveSizeAt(loc)
2323-}
2424-2525-// resolveSizeAt resolves one object's declared content size from location.
2626-func (store *Store) resolveSizeAt(start location) (int64, error) {
2727- pack, meta, err := store.entryMetaAt(start)
2828- if err != nil {
2929- return 0, err
3030- }
3131-3232- if packfmt.IsBaseObjectType(meta.ty) {
3333- return meta.size, nil
3434- }
3535-3636- switch meta.ty {
3737- case objecttype.TypeRefDelta, objecttype.TypeOfsDelta:
3838- return deltaDeclaredSizeAt(pack, meta.dataOffset)
3939- case objecttype.TypeInvalid, objecttype.TypeFuture:
4040- return 0, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
4141- case objecttype.TypeCommit, objecttype.TypeTree, objecttype.TypeBlob, objecttype.TypeTag:
4242- return 0, fmt.Errorf("objectstorer/packed: internal invariant violation for base type %d", meta.ty)
4343- default:
4444- return 0, fmt.Errorf("objectstorer/packed: unsupported pack type %d", meta.ty)
4545- }
4646-}
···33import "errors"
4455// ErrReferenceNotFound indicates that a reference does not exist in a backend.
66-// TODO: Interface error? Just like object not found in objectstorer.
66+// TODO: Interface error? Just like object not found in objectstore.
77var ErrReferenceNotFound = errors.New("refstore: reference not found")