shithub: furgit

Download patch

ref: 7ddaf1eb2fde11a9e07df0215646c1dca08ccc50
parent: 803089a76171db1dd5b891fb6dfa1b7e7f3d50d2
author: Runxi Yu <runxiyu@umich.edu>
date: Sun Mar 22 18:07:39 EDT 2026

refstore, repository: Ownership/lifetimes fix

--- a/refstore/batch.go
+++ b/refstore/batch.go
@@ -34,8 +34,12 @@
 
 	// Apply validates and applies queued operations, returning one result per
 	// queued operation in order. Fatal backend failures are returned separately.
+	//
+	// Apply is terminal. Further use of the batch is undefined behavior.
 	Apply() ([]BatchResult, error)
 	// Abort abandons the batch and releases any resources it holds.
+	//
+	// Abort is terminal. Further use of the batch is undefined behavior.
 	Abort() error
 }
 
--- a/refstore/files/close.go
+++ b/refstore/files/close.go
@@ -1,13 +1,10 @@
 package files
 
 // Close releases resources associated with the store.
+//
+// Store borrows gitRoot, so Close does not close it.
+//
+// Repeated calls to Close are undefined behavior.
 func (store *Store) Close() error {
-	err := store.gitRoot.Close()
-	commonErr := store.commonRoot.Close()
-
-	if err != nil {
-		return err
-	}
-
-	return commonErr
+	return store.commonRoot.Close()
 }
--- a/refstore/files/store.go
+++ b/refstore/files/store.go
@@ -14,7 +14,8 @@
 // Store reads and writes one Git files ref namespace rooted at one repository
 // gitdir plus its commondir.
 //
-// Store owns both roots and closes them in Close.
+// Store borrows gitRoot and owns commonRoot. Close releases only resources
+// opened by the store itself.
 type Store struct {
 	gitRoot    *os.Root
 	commonRoot *os.Root
--- a/refstore/reading.go
+++ b/refstore/reading.go
@@ -25,5 +25,8 @@
 	// The exact pattern language is backend-defined.
 	List(pattern string) ([]ref.Ref, error)
 	// Close releases resources associated with the store.
+	//
+	// Repeated calls to Close are undefined behavior unless the implementation
+	// explicitly documents otherwise.
 	Close() error
 }
--- a/refstore/transaction.go
+++ b/refstore/transaction.go
@@ -37,7 +37,11 @@
 	VerifySymbolic(name, oldTarget string) error
 
 	// Commit validates and applies all queued operations atomically.
+	//
+	// Commit is terminal. Further use of the transaction is undefined behavior.
 	Commit() error
 	// Abort abandons the transaction and releases any resources it holds.
+	//
+	// Abort is terminal. Further use of the transaction is undefined behavior.
 	Abort() error
 }
--- a/repository/close.go
+++ b/repository/close.go
@@ -49,5 +49,12 @@
 		}
 	}
 
+	if repo.refRoot != nil {
+		err := repo.refRoot.Close()
+		if err != nil {
+			errs = append(errs, err)
+		}
+	}
+
 	return errors.Join(errs...)
 }
--- a/repository/open.go
+++ b/repository/open.go
@@ -1,7 +1,12 @@
 package repository
 
-import "os"
+import (
+	"fmt"
+	"os"
 
+	reffiles "codeberg.org/lindenii/furgit/refstore/files"
+)
+
 // Open opens a repository and wires object/ref stores from its on-disk format.
 //
 // Open borrows root during construction and does not close it.
@@ -39,12 +44,20 @@
 	repo.objectsLooseForWritingOnly = objectsLooseForWritingOnly
 	repo.objectsWriteRoot = objectsWriteRoot
 
-	refs, err := openRefStore(root, algo, detectPackedRefsTimeout(cfg))
+	refRoot, err := root.OpenRoot(".")
 	if err != nil {
+		return nil, fmt.Errorf("repository: open root for refs: %w", err)
+	}
+
+	refs, err := reffiles.New(refRoot, algo, detectPackedRefsTimeout(cfg))
+	if err != nil {
+		_ = refRoot.Close()
+
 		return nil, err
 	}
 
 	repo.refs = refs
+	repo.refRoot = refRoot
 
 	return repo, nil
 }
--- a/repository/refs.go
+++ b/repository/refs.go
@@ -1,31 +1,6 @@
 package repository
 
-import (
-	"fmt"
-	"os"
-	"time"
-
-	"codeberg.org/lindenii/furgit/objectid"
-	"codeberg.org/lindenii/furgit/refstore"
-	reffiles "codeberg.org/lindenii/furgit/refstore/files"
-)
-
-//nolint:ireturn
-func openRefStore(root *os.Root, algo objectid.Algorithm, packedRefsTimeout time.Duration) (out refstore.ReadWriteStore, err error) {
-	refRoot, err := root.OpenRoot(".")
-	if err != nil {
-		return nil, fmt.Errorf("repository: open root for refs: %w", err)
-	}
-
-	store, err := reffiles.New(refRoot, algo, packedRefsTimeout)
-	if err != nil {
-		_ = refRoot.Close()
-
-		return nil, err
-	}
-
-	return store, nil
-}
+import "codeberg.org/lindenii/furgit/refstore"
 
 // Refs returns the configured ref store.
 //
--- a/repository/repository.go
+++ b/repository/repository.go
@@ -24,5 +24,6 @@
 	objectsPackRoot            *os.Root
 	objectsLooseForWritingOnly *objectloose.Store
 	objectsWriteRoot           *os.Root
+	refRoot                    *os.Root
 	refs                       refstore.ReadWriteStore
 }
--