aboutsummaryrefslogtreecommitdiff
path: root/object/store/loose/quarantine_promote.go
diff options
context:
space:
mode:
Diffstat (limited to 'object/store/loose/quarantine_promote.go')
-rw-r--r--object/store/loose/quarantine_promote.go116
1 files changed, 116 insertions, 0 deletions
diff --git a/object/store/loose/quarantine_promote.go b/object/store/loose/quarantine_promote.go
new file mode 100644
index 00000000..79759eb9
--- /dev/null
+++ b/object/store/loose/quarantine_promote.go
@@ -0,0 +1,116 @@
+package loose
+
+import (
+ "errors"
+ "fmt"
+ "io/fs"
+ "os"
+ "path/filepath"
+)
+
+// Promote publishes all quarantined loose objects into the parent loose store
+// and invalidates the receiver.
+func (quarantine *objectQuarantine) Promote() error {
+ closeErr := quarantine.Close()
+ promoteErr := promoteLooseQuarantine(quarantine.parent, quarantine.tempName, quarantine.tempRoot)
+ tempRootErr := quarantine.tempRoot.Close()
+ removeErr := quarantine.parent.root.RemoveAll(quarantine.tempName)
+
+ if closeErr != nil {
+ return closeErr
+ }
+
+ if promoteErr != nil {
+ return promoteErr
+ }
+
+ if tempRootErr != nil {
+ return tempRootErr
+ }
+
+ return removeErr
+}
+
+func promoteLooseQuarantine(parent *Store, tempName string, tempRoot *os.Root) error {
+ entries, err := fs.ReadDir(tempRoot.FS(), ".")
+ if err != nil && !errors.Is(err, fs.ErrNotExist) {
+ return err
+ }
+
+ for _, entry := range entries {
+ if !entry.IsDir() {
+ return fmt.Errorf("objectstore/loose: quarantine contains unexpected file %q", entry.Name())
+ }
+
+ if len(entry.Name()) == 2 && isHexString(entry.Name()) {
+ return fmt.Errorf("objectstore/loose: quarantine contains invalid shard %q", entry.Name())
+ }
+
+ err := promoteLooseQuarantineShard(parent, tempName, tempRoot, entry.Name())
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func promoteLooseQuarantineShard(parent *Store, tempName string, tempRoot *os.Root, shard string) error {
+ entries, err := fs.ReadDir(tempRoot.FS(), shard)
+ if err != nil {
+ return err
+ }
+
+ err = parent.root.MkdirAll(shard, 0o755)
+ if err != nil {
+ return err
+ }
+
+ wantNameLen := parent.algo.HexLen() - 2
+
+ for _, entry := range entries {
+ if entry.IsDir() {
+ return fmt.Errorf("objectstore/loose: quarantine shard %q contains unexpected directory %q", shard, entry.Name())
+ }
+
+ if len(entry.Name()) != wantNameLen || !isHexString(entry.Name()) {
+ return fmt.Errorf("objectstore/loose: quarantine shard %q contains invalid object path %q", shard, entry.Name())
+ }
+
+ err := promoteLooseQuarantineObject(parent.root, filepath.Join(tempName, shard, entry.Name()), filepath.Join(shard, entry.Name()))
+ if err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+func promoteLooseQuarantineObject(root *os.Root, src, dst string) error {
+ err := root.Link(src, dst)
+ if err == nil {
+ _ = root.Remove(src)
+
+ return nil
+ }
+
+ if errors.Is(err, fs.ErrExist) {
+ _ = root.Remove(src)
+
+ return nil
+ }
+
+ return fmt.Errorf("objectstore/loose: promote quarantine %q -> %q: %w", src, dst, err)
+}
+
+func isHexString(s string) bool {
+ for _, ch := range s {
+ if ('0' <= ch && ch <= '9') || ('a' <= ch && ch <= 'f') || ('A' <= ch && ch <= 'F') {
+ continue
+ }
+
+ return false
+ }
+
+ return true
+}