aboutsummaryrefslogtreecommitdiff
path: root/object/store/packed/quarantine.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-06-12 18:41:58 +0000
committerGravatar Runxi Yu2026-06-12 18:41:58 +0000
commit7faa841b581dbbacf563a6ca3167efbfd697d37c (patch)
treeab54845bcf708b1099f88a339d18bdf1cdb6f23f /object/store/packed/quarantine.go
parentobject/store/packed: Add missing t.Helper (diff)
object/store/packed: Add basic ingestion
Diffstat (limited to 'object/store/packed/quarantine.go')
-rw-r--r--object/store/packed/quarantine.go215
1 files changed, 161 insertions, 54 deletions
diff --git a/object/store/packed/quarantine.go b/object/store/packed/quarantine.go
index fac64402..5e0b85cb 100644
--- a/object/store/packed/quarantine.go
+++ b/object/store/packed/quarantine.go
@@ -1,57 +1,164 @@
package packed
-// import (
-// "os"
-//
-// "lindenii.org/go/furgit/object/store"
-// )
-//
-// var (
-// _ store.PackQuarantiner = (*Packed)(nil)
-// _ store.PackQuarantine = (*packQuarantine)(nil)
-// )
-//
-// // packQuarantine is one quarantined packed store
-// // rooted privately beneath a destination pack root.
-// type packQuarantine struct {
-// *Packed
-//
-// parent *Packed
-// tempName string
-// tempRoot *os.Root
-// }
-//
-// // BeginPackQuarantine creates one quarantined packed store
-// // rooted privately beneath the destination pack root.
-// //
-// // Labels: Deps-Borrowed, Life-Parent, Close-No.
-// func (packed *Packed) BeginPackQuarantine(opts store.PackQuarantineOptions) (store.PackQuarantine, error)
-//
-// // Discard removes the quarantine
-// // and invalidates the receiver.
-// func (quarantine *packQuarantine) Discard() error
-//
-// // Promote publishes all finalized pack artifacts in the quarantine
-// // into the parent packed store,
-// // and invalidates the receiver.
-// func (quarantine *packQuarantine) Promote() error
-//
-// // promoteAll links every pack artifact in the quarantine
-// // into the parent packed store,
-// // in pack/rev/idx dependency order.
-// func (quarantine *packQuarantine) promoteAll() error
-//
-// // promoteFile links one quarantined pack artifact
-// // into the parent packed store,
-// // treating an already-present destination as success.
-// func (quarantine *packQuarantine) promoteFile(name string) error
-//
-// // createPackQuarantineRoot creates a private quarantine directory
-// // beneath parent,
-// // and returns its name and an os.Root over it.
-// func createPackQuarantineRoot(parent *os.Root) (string, *os.Root, error)
+import (
+ "crypto/rand"
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "slices"
+ "strings"
+
+ "lindenii.org/go/furgit/object/store"
+)
+
+var (
+ _ store.PackQuarantiner = (*Packed)(nil)
+ _ store.PackQuarantine = (*packQuarantine)(nil)
+)
+
+var errQuarantineNamesExhausted = errors.New("object/store/packed: exhausted quarantine directory names")
+
+// packQuarantine is one quarantined packed store
+// rooted privately beneath a destination pack root.
+type packQuarantine struct {
+ *Packed
+
+ parent *Packed
+
+ tempName string
+ tempRoot *os.Root
+}
+
+// BeginPackQuarantine creates one quarantined packed store
+// rooted privately beneath the destination pack root.
//
-// // packPromotionPriority orders pack artifacts
-// // so that data files are linked
-// // before the index that publishes them.
-// func packPromotionPriority(name string) int
+// Labels: Deps-Borrowed, Life-Parent.
+func (packed *Packed) BeginPackQuarantine(_ store.PackQuarantineOptions) (store.PackQuarantine, error) { //nolint:ireturn
+ tempName, tempRoot, err := createPackQuarantineRoot(packed.root)
+ if err != nil {
+ return nil, err
+ }
+
+ quarantineStore, err := New(tempRoot, packed.objectFormat)
+ if err != nil {
+ _ = tempRoot.Close()
+ _ = packed.root.RemoveAll(tempName)
+
+ return nil, err
+ }
+
+ return &packQuarantine{
+ Packed: quarantineStore,
+ parent: packed,
+ tempName: tempName,
+ tempRoot: tempRoot,
+ }, nil
+}
+
+// Promote publishes the quarantined pack artifacts into the parent store,
+// refreshes the parent so the objects become available,
+// and invalidates the receiver.
+func (quarantine *packQuarantine) Promote() error {
+ closeErr := quarantine.Close()
+ promoteErr := quarantine.promoteAll()
+
+ var refreshErr error
+ if promoteErr == nil {
+ refreshErr = quarantine.parent.Refresh()
+ }
+
+ tempRootErr := quarantine.tempRoot.Close()
+ removeErr := quarantine.parent.root.RemoveAll(quarantine.tempName)
+
+ return errors.Join(closeErr, promoteErr, refreshErr, tempRootErr, removeErr)
+}
+
+// Discard removes the quarantine and invalidates the receiver.
+func (quarantine *packQuarantine) Discard() error {
+ closeErr := quarantine.Close()
+ tempRootErr := quarantine.tempRoot.Close()
+ removeErr := quarantine.parent.root.RemoveAll(quarantine.tempName)
+
+ return errors.Join(closeErr, tempRootErr, removeErr)
+}
+
+// promoteAll links every pack artifact in the quarantine into the parent store,
+// in pack/rev/idx dependency order.
+func (quarantine *packQuarantine) promoteAll() error {
+ entries, err := fs.ReadDir(quarantine.tempRoot.FS(), ".")
+ if err != nil {
+ return fmt.Errorf("object/store/packed: %w", err)
+ }
+
+ slices.SortFunc(entries, func(left, right fs.DirEntry) int {
+ return packPromotionPriority(left.Name()) - packPromotionPriority(right.Name())
+ })
+
+ for _, entry := range entries {
+ err := quarantine.promoteFile(entry.Name())
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// promoteFile links one quarantined artifact into the parent store,
+// treating an already-present destination as success.
+func (quarantine *packQuarantine) promoteFile(name string) error {
+ src := quarantine.tempName + "/" + name
+
+ err := quarantine.parent.root.Link(src, name)
+ if err != nil && !errors.Is(err, fs.ErrExist) {
+ return fmt.Errorf("object/store/packed: promoting %q: %w", name, err)
+ }
+
+ _ = quarantine.parent.root.Remove(src)
+
+ return nil
+}
+
+// createPackQuarantineRoot creates a private quarantine directory beneath parent
+// and returns its name and an os.Root over it.
+func createPackQuarantineRoot(parent *os.Root) (string, *os.Root, error) {
+ for range 32 {
+ name := "tmp_packq_" + rand.Text()
+
+ err := parent.Mkdir(name, 0o700)
+ if err != nil {
+ if errors.Is(err, fs.ErrExist) {
+ continue
+ }
+
+ return "", nil, fmt.Errorf("object/store/packed: %w", err)
+ }
+
+ root, err := parent.OpenRoot(name)
+ if err != nil {
+ _ = parent.RemoveAll(name)
+
+ return "", nil, fmt.Errorf("object/store/packed: %w", err)
+ }
+
+ return name, root, nil
+ }
+
+ return "", nil, errQuarantineNamesExhausted
+}
+
+// packPromotionPriority orders pack artifacts
+// so that data files are linked before the index that publishes them.
+func packPromotionPriority(name string) int {
+ switch {
+ case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".pack"):
+ return 1
+ case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".rev"):
+ return 2
+ case strings.HasPrefix(name, "pack-") && strings.HasSuffix(name, ".idx"):
+ return 3
+ default:
+ return 0
+ }
+}