shithub: furgit

Download patch

ref: ab6f8dde0cdc554084c4455c76feef0099db70d9
parent: 1ac6099100ed61a1e49766f190deae8b426a1ea6
author: Runxi Yu <runxiyu@umich.edu>
date: Sun Mar 22 19:10:37 EDT 2026

*: Fixup ownership of compositional backends

--- a/objectstore/chain/bytes.go
+++ b/objectstore/chain/bytes.go
@@ -12,10 +12,6 @@
 // ReadBytesFull reads a full serialized object from the first backend that has it.
 func (chain *Chain) ReadBytesFull(id objectid.ObjectID) ([]byte, error) {
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		full, err := backend.ReadBytesFull(id)
 		if err == nil {
 			return full, nil
@@ -34,10 +30,6 @@
 // ReadBytesContent reads an object's type and content bytes from the first backend that has it.
 func (chain *Chain) ReadBytesContent(id objectid.ObjectID) (objecttype.Type, []byte, error) {
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		ty, content, err := backend.ReadBytesContent(id)
 		if err == nil {
 			return ty, content, nil
--- a/objectstore/chain/chain.go
+++ b/objectstore/chain/chain.go
@@ -1,4 +1,5 @@
-// Package chain provides a wrapper object storage backend to query a chain of backends.
+// Package chain provides a wrapper object storage backend to query a chain of
+// backends.
 package chain
 
 import (
@@ -6,6 +7,8 @@
 )
 
 // Chain queries multiple object databases in order.
+//
+// Chain borrows its backend stores.
 type Chain struct {
 	backends []objectstore.Store
 }
--- a/objectstore/chain/close.go
+++ b/objectstore/chain/close.go
@@ -1,21 +1,8 @@
 package chain
 
-import "errors"
-
-// Close closes all backends and joins close errors.
-func (chain *Chain) Close() error {
-	var errs []error
-
-	for _, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
-		err := backend.Close()
-		if err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	return errors.Join(errs...)
-}
+// Close releases wrapper-local resources.
+//
+// Chain borrows its backends, so Close does not close them.
+//
+// Repeated calls to Close are undefined behavior.
+func (chain *Chain) Close() error { return nil }
--- a/objectstore/chain/header.go
+++ b/objectstore/chain/header.go
@@ -12,10 +12,6 @@
 // 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 {
-		if backend == nil {
-			continue
-		}
-
 		ty, size, err := backend.ReadHeader(id)
 		if err == nil {
 			return ty, size, nil
--- a/objectstore/chain/new.go
+++ b/objectstore/chain/new.go
@@ -3,6 +3,9 @@
 import "codeberg.org/lindenii/furgit/objectstore"
 
 // New creates an ordered object database chain.
+//
+// The provided backends must be non-nil and distinct.
+// Chain borrows the provided backends and does not close them in Close.
 func New(backends ...objectstore.Store) *Chain {
 	return &Chain{
 		backends: append([]objectstore.Store(nil), backends...),
--- a/objectstore/chain/reader.go
+++ b/objectstore/chain/reader.go
@@ -13,10 +13,6 @@
 // ReadReaderFull reads a full serialized object stream from the first backend that has it.
 func (chain *Chain) ReadReaderFull(id objectid.ObjectID) (io.ReadCloser, error) {
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		reader, err := backend.ReadReaderFull(id)
 		if err == nil {
 			return reader, nil
@@ -35,10 +31,6 @@
 // ReadReaderContent reads an object's type, declared content length, and content stream from the first backend that has it.
 func (chain *Chain) ReadReaderContent(id objectid.ObjectID) (objecttype.Type, int64, io.ReadCloser, error) {
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		ty, size, reader, err := backend.ReadReaderContent(id)
 		if err == nil {
 			return ty, size, reader, nil
--- a/objectstore/chain/refresh.go
+++ b/objectstore/chain/refresh.go
@@ -7,10 +7,6 @@
 	var errs []error
 
 	for _, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		err := backend.Refresh()
 		if err != nil {
 			errs = append(errs, err)
--- a/objectstore/chain/size.go
+++ b/objectstore/chain/size.go
@@ -11,10 +11,6 @@
 // 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
--- a/objectstore/mix/close.go
+++ b/objectstore/mix/close.go
@@ -1,30 +1,8 @@
 package mix
 
-import (
-	"errors"
-
-	"codeberg.org/lindenii/furgit/objectstore"
-)
-
-// Close closes all backends and joins close errors.
-func (mix *Mix) Close() error {
-	mix.mu.RLock()
-
-	backends := make([]objectstore.Store, 0, len(mix.backendNodeByStore))
-	for node := mix.backendHead; node != nil; node = node.next {
-		backends = append(backends, node.backend)
-	}
-
-	mix.mu.RUnlock()
-
-	var errs []error
-
-	for _, backend := range backends {
-		err := backend.Close()
-		if err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	return errors.Join(errs...)
-}
+// Close releases wrapper-local resources.
+//
+// Mix borrows its backends, so Close does not close them.
+//
+// Repeated calls to Close are undefined behavior.
+func (mix *Mix) Close() error { return nil }
--- a/objectstore/mix/mix.go
+++ b/objectstore/mix/mix.go
@@ -9,6 +9,8 @@
 )
 
 // Mix queries multiple object databases with an MRU backend preference.
+//
+// Mix borrows its backend stores.
 type Mix struct {
 	mu sync.RWMutex
 
--- a/objectstore/mix/new.go
+++ b/objectstore/mix/new.go
@@ -3,6 +3,9 @@
 import "codeberg.org/lindenii/furgit/objectstore"
 
 // New creates a Mix from backends.
+//
+// The provided backends must be non-nil and distinct.
+// Mix borrows the provided backends and does not close them in Close.
 func New(backends ...objectstore.Store) *Mix {
 	nodeByStore := make(map[objectstore.Store]*backendNode, len(backends))
 
@@ -12,10 +15,6 @@
 	)
 
 	for _, backend := range backends {
-		if backend == nil {
-			continue
-		}
-
 		node := &backendNode{
 			backend: backend,
 			prev:    tail,
--- a/receivepack/service/run_hook.go
+++ b/receivepack/service/run_hook.go
@@ -41,6 +41,8 @@
 
 	var (
 		quarantineObjectsStore objectstore.Store
+		quarantineLooseStore   *loose.Store
+		quarantinePackedStore  *packed.Store
 		quarantineLooseRoot    *os.Root
 		quarantinePackRoot     *os.Root
 		err                    error
@@ -54,7 +56,7 @@
 			return nil, nil, nil, false, err.Error()
 		}
 
-		quarantineLooseStore, err := loose.New(quarantineLooseRoot, service.opts.Algorithm)
+		quarantineLooseStore, err = loose.New(quarantineLooseRoot, service.opts.Algorithm)
 		if err != nil {
 			_ = quarantineLooseRoot.Close()
 
@@ -68,7 +70,9 @@
 
 		quarantinePackRoot, err = quarantineLooseRoot.OpenRoot("pack")
 		if err == nil {
-			quarantinePackedStore, packedErr := packed.New(quarantinePackRoot, service.opts.Algorithm, packed.Options{})
+			var packedErr error
+
+			quarantinePackedStore, packedErr = packed.New(quarantinePackRoot, service.opts.Algorithm, packed.Options{})
 			if packedErr != nil {
 				_ = quarantineLooseStore.Close()
 				_ = quarantinePackRoot.Close()
@@ -93,6 +97,14 @@
 		defer func() {
 			if quarantineObjectsStore != nil {
 				_ = quarantineObjectsStore.Close()
+			}
+
+			if quarantinePackedStore != nil {
+				_ = quarantinePackedStore.Close()
+			}
+
+			if quarantineLooseStore != nil {
+				_ = quarantineLooseStore.Close()
 			}
 
 			if quarantinePackRoot != nil {
--- a/refstore/chain/chain.go
+++ b/refstore/chain/chain.go
@@ -5,6 +5,8 @@
 import "codeberg.org/lindenii/furgit/refstore"
 
 // Chain queries multiple reference stores in order.
+//
+// Chain borrows its backend stores.
 type Chain struct {
 	backends []refstore.ReadingStore
 }
--- a/refstore/chain/close.go
+++ b/refstore/chain/close.go
@@ -1,21 +1,8 @@
 package chain
 
-import "errors"
-
-// Close closes all backends and joins close errors.
-func (chain *Chain) Close() error {
-	var errs []error
-
-	for _, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
-		err := backend.Close()
-		if err != nil {
-			errs = append(errs, err)
-		}
-	}
-
-	return errors.Join(errs...)
-}
+// Close releases wrapper-local resources.
+//
+// Chain borrows its backends, so Close does not close them.
+//
+// Repeated calls to Close are undefined behavior.
+func (chain *Chain) Close() error { return nil }
--- a/refstore/chain/list.go
+++ b/refstore/chain/list.go
@@ -15,10 +15,6 @@
 	seen := map[string]struct{}{}
 
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		listed, err := backend.List(pattern)
 		if err != nil {
 			return nil, fmt.Errorf("refstore: backend %d list: %w", i, err)
--- a/refstore/chain/new.go
+++ b/refstore/chain/new.go
@@ -3,6 +3,9 @@
 import "codeberg.org/lindenii/furgit/refstore"
 
 // New creates an ordered reference store chain.
+//
+// The provided backends must be non-nil and distinct.
+// Chain borrows the provided backends and does not close them in Close.
 func New(backends ...refstore.ReadingStore) *Chain {
 	return &Chain{
 		backends: append([]refstore.ReadingStore(nil), backends...),
--- a/refstore/chain/resolve.go
+++ b/refstore/chain/resolve.go
@@ -13,10 +13,6 @@
 //nolint:ireturn
 func (chain *Chain) Resolve(name string) (ref.Ref, error) {
 	for i, backend := range chain.backends {
-		if backend == nil {
-			continue
-		}
-
 		resolved, err := backend.Resolve(name)
 		if err == nil {
 			return resolved, nil
--- a/repository/close.go
+++ b/repository/close.go
@@ -21,15 +21,15 @@
 		}
 	}
 
-	if repo.objectsLooseForWritingOnly != nil {
-		err := repo.objectsLooseForWritingOnly.Close()
+	if repo.objectsPacked != nil {
+		err := repo.objectsPacked.Close()
 		if err != nil {
 			errs = append(errs, err)
 		}
 	}
 
-	if repo.objectsWriteRoot != nil {
-		err := repo.objectsWriteRoot.Close()
+	if repo.objectsLoose != nil {
+		err := repo.objectsLoose.Close()
 		if err != nil {
 			errs = append(errs, err)
 		}
--- a/repository/objects.go
+++ b/repository/objects.go
@@ -20,8 +20,8 @@
 	objects objectstore.Store,
 	objectsRoot *os.Root,
 	objectsPackRoot *os.Root,
-	objectsLooseForWritingOnly *objectloose.Store,
-	objectsWriteRoot *os.Root,
+	objectsLoose *objectloose.Store,
+	objectsPacked *objectpacked.Store,
 	err error,
 ) {
 	objectsRoot, err = root.OpenRoot("objects")
@@ -29,7 +29,7 @@
 		return nil, nil, nil, nil, nil, fmt.Errorf("repository: open objects: %w", err)
 	}
 
-	looseStore, err := objectloose.New(objectsRoot, algo)
+	objectsLoose, err = objectloose.New(objectsRoot, algo)
 	if err != nil {
 		_ = objectsRoot.Close()
 
@@ -36,13 +36,11 @@
 		return nil, nil, nil, nil, nil, err
 	}
 
-	backends := []objectstore.Store{looseStore}
+	backends := []objectstore.Store{objectsLoose}
 
 	objectsPackRoot, err = objectsRoot.OpenRoot("pack")
 	if err == nil {
-		var packedStore *objectpacked.Store
-
-		packedStore, err = objectpacked.New(
+		objectsPacked, err = objectpacked.New(
 			objectsPackRoot,
 			algo,
 			objectpacked.Options{RefreshPolicy: objectpacked.RefreshPolicyNever},
@@ -49,15 +47,15 @@
 		)
 		if err != nil {
 			_ = objectsPackRoot.Close()
-			_ = looseStore.Close()
+			_ = objectsLoose.Close()
 			_ = objectsRoot.Close()
 
 			return nil, nil, nil, nil, nil, err
 		}
 
-		backends = append(backends, packedStore)
+		backends = append(backends, objectsPacked)
 	} else if !errors.Is(err, os.ErrNotExist) {
-		_ = looseStore.Close()
+		_ = objectsLoose.Close()
 		_ = objectsRoot.Close()
 
 		return nil, nil, nil, nil, nil, fmt.Errorf("repository: open objects/pack: %w", err)
@@ -65,33 +63,7 @@
 
 	objects = objectmix.New(backends...)
 
-	objectsWriteRoot, err = root.OpenRoot("objects")
-	if err != nil {
-		_ = objects.Close()
-		if objectsPackRoot != nil {
-			_ = objectsPackRoot.Close()
-		}
-
-		_ = objectsRoot.Close()
-
-		return nil, nil, nil, nil, nil, fmt.Errorf("repository: open objects for loose writing: %w", err)
-	}
-
-	objectsLooseForWritingOnly, err = objectloose.New(objectsWriteRoot, algo)
-	if err != nil {
-		_ = objects.Close()
-
-		_ = objectsWriteRoot.Close()
-		if objectsPackRoot != nil {
-			_ = objectsPackRoot.Close()
-		}
-
-		_ = objectsRoot.Close()
-
-		return nil, nil, nil, nil, nil, err
-	}
-
-	return objects, objectsRoot, objectsPackRoot, objectsLooseForWritingOnly, objectsWriteRoot, nil
+	return objects, objectsRoot, objectsPackRoot, objectsLoose, objectsPacked, nil
 }
 
 // Objects returns the configured object store.
--- a/repository/open.go
+++ b/repository/open.go
@@ -33,7 +33,7 @@
 
 	repo.algo = algo
 
-	objects, objectsRoot, objectsPackRoot, objectsLooseForWritingOnly, objectsWriteRoot, err := openObjectStore(root, algo)
+	objects, objectsRoot, objectsPackRoot, objectsLoose, objectsPacked, err := openObjectStore(root, algo)
 	if err != nil {
 		return nil, err
 	}
@@ -41,8 +41,8 @@
 	repo.objects = objects
 	repo.objectsRoot = objectsRoot
 	repo.objectsPackRoot = objectsPackRoot
-	repo.objectsLooseForWritingOnly = objectsLooseForWritingOnly
-	repo.objectsWriteRoot = objectsWriteRoot
+	repo.objectsLoose = objectsLoose
+	repo.objectsPacked = objectsPacked
 
 	refRoot, err := root.OpenRoot(".")
 	if err != nil {
--- a/repository/repository.go
+++ b/repository/repository.go
@@ -8,6 +8,7 @@
 	"codeberg.org/lindenii/furgit/objectid"
 	"codeberg.org/lindenii/furgit/objectstore"
 	objectloose "codeberg.org/lindenii/furgit/objectstore/loose"
+	objectpacked "codeberg.org/lindenii/furgit/objectstore/packed"
 	"codeberg.org/lindenii/furgit/refstore"
 )
 
@@ -19,11 +20,11 @@
 	config *config.Config
 	algo   objectid.Algorithm
 
-	objects                    objectstore.Store
-	objectsRoot                *os.Root
-	objectsPackRoot            *os.Root
-	objectsLooseForWritingOnly *objectloose.Store
-	objectsWriteRoot           *os.Root
-	refRoot                    *os.Root
-	refs                       refstore.ReadWriteStore
+	objects         objectstore.Store
+	objectsRoot     *os.Root
+	objectsPackRoot *os.Root
+	objectsLoose    *objectloose.Store
+	objectsPacked   *objectpacked.Store
+	refRoot         *os.Root
+	refs            refstore.ReadWriteStore
 }
--- a/repository/write_loose.go
+++ b/repository/write_loose.go
@@ -5,5 +5,5 @@
 )
 
 func (repo *Repository) LooseStoreForWriting() *objectloose.Store {
-	return repo.objectsLooseForWritingOnly
+	return repo.objectsLoose
 }
--