aboutsummaryrefslogtreecommitdiff
path: root/ref/store/memory/batch.go
diff options
context:
space:
mode:
authorGravatar Runxi Yu2026-05-24 13:41:34 +0000
committerGravatar Runxi Yu2026-05-24 14:12:35 +0000
commit947bf81a33c6e4e5d21c8b36f9317fe00b84f6ae (patch)
tree67824655ef9dbf2d941dae06d59ea29a1e32d458 /ref/store/memory/batch.go
parentREADME: Update (diff)
signatureNo signature
ref/store/memory: Simple memory-backed ref store v0.1.175
Diffstat (limited to 'ref/store/memory/batch.go')
-rw-r--r--ref/store/memory/batch.go195
1 files changed, 195 insertions, 0 deletions
diff --git a/ref/store/memory/batch.go b/ref/store/memory/batch.go
new file mode 100644
index 00000000..df3d554d
--- /dev/null
+++ b/ref/store/memory/batch.go
@@ -0,0 +1,195 @@
+package memory
+
+import (
+ objectid "codeberg.org/lindenii/furgit/object/id"
+ refstore "codeberg.org/lindenii/furgit/ref/store"
+)
+
+// Batch stages in-memory updates for one subset commit.
+type Batch struct {
+ store *Store
+ ops []queuedUpdate
+}
+
+var _ refstore.Batch = (*Batch)(nil)
+
+// BeginBatch creates one new in-memory batch.
+//
+//nolint:ireturn
+func (store *Store) BeginBatch() (refstore.Batch, error) {
+ return &Batch{
+ store: store,
+ ops: make([]queuedUpdate, 0, 8),
+ }, nil
+}
+
+// Create queues a detached reference creation.
+func (batch *Batch) Create(name string, newID objectid.ObjectID) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateCreate, newID: newID})
+}
+
+// Update queues a detached reference update.
+func (batch *Batch) Update(name string, newID, oldID objectid.ObjectID) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateReplace, newID: newID, oldID: oldID})
+}
+
+// Delete queues a detached reference deletion.
+func (batch *Batch) Delete(name string, oldID objectid.ObjectID) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateDelete, oldID: oldID})
+}
+
+// Verify queues a detached reference verification.
+func (batch *Batch) Verify(name string, oldID objectid.ObjectID) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateVerify, oldID: oldID})
+}
+
+// CreateSymbolic queues a symbolic reference creation.
+func (batch *Batch) CreateSymbolic(name, newTarget string) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateCreateSymbolic, newTarget: newTarget})
+}
+
+// UpdateSymbolic queues a symbolic reference update.
+func (batch *Batch) UpdateSymbolic(name, newTarget, oldTarget string) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateReplaceSymbolic, newTarget: newTarget, oldTarget: oldTarget})
+}
+
+// DeleteSymbolic queues a symbolic reference deletion.
+func (batch *Batch) DeleteSymbolic(name, oldTarget string) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateDeleteSymbolic, oldTarget: oldTarget})
+}
+
+// VerifySymbolic queues a symbolic reference verification.
+func (batch *Batch) VerifySymbolic(name, oldTarget string) error {
+ return batch.queue(queuedUpdate{name: name, kind: updateVerifySymbolic, oldTarget: oldTarget})
+}
+
+// Apply validates queued operations,
+// drops rejected operations,
+// and applies the remaining compatible set.
+// Concurrent readers observe
+// either the pre-Apply state
+// or the post-Apply state.
+func (batch *Batch) Apply() ([]refstore.BatchResult, error) {
+ results := make([]refstore.BatchResult, len(batch.ops))
+ remainingIdx := make([]int, 0, len(batch.ops))
+ remainingOps := make([]queuedUpdate, 0, len(batch.ops))
+ seenTargets := make(map[string]struct{}, len(batch.ops))
+
+ batch.store.mu.Lock()
+ defer batch.store.mu.Unlock()
+
+ for i, op := range batch.ops {
+ results[i].Name = op.name
+
+ target, err := resolveQueuedUpdateTarget(batch.store.refs, op)
+ if err != nil {
+ if isBatchRejected(err) {
+ results[i].Status = refstore.BatchStatusRejected
+ results[i].Error = batchResultError(err)
+
+ continue
+ }
+
+ results[i].Status = refstore.BatchStatusFatal
+ results[i].Error = batchResultError(err)
+
+ for j := i + 1; j < len(results); j++ {
+ results[j].Name = batch.ops[j].name
+ results[j].Status = refstore.BatchStatusNotAttempted
+ results[j].Error = batchResultError(err)
+ }
+
+ return results, err
+ }
+
+ if _, exists := seenTargets[target.name]; exists {
+ results[i].Status = refstore.BatchStatusRejected
+ results[i].Error = &refstore.DuplicateUpdateError{}
+
+ continue
+ }
+
+ seenTargets[target.name] = struct{}{}
+
+ remainingIdx = append(remainingIdx, i)
+ remainingOps = append(remainingOps, op)
+ }
+
+ for len(remainingOps) > 0 {
+ prepared, err := prepareUpdates(batch.store.refs, remainingOps)
+ if err == nil {
+ next := cloneRefs(batch.store.refs)
+ applyPreparedUpdates(next, prepared)
+ batch.store.refs = next
+
+ for _, idx := range remainingIdx {
+ results[idx].Status = refstore.BatchStatusApplied
+ }
+
+ return results, nil
+ }
+
+ if !isBatchRejected(err) {
+ fatalName := batchResultName(err)
+ fatalMarked := false
+
+ for i, idx := range remainingIdx {
+ if !fatalMarked && remainingOps[i].name == fatalName && fatalName != "" {
+ results[idx].Status = refstore.BatchStatusFatal
+ results[idx].Error = batchResultError(err)
+ fatalMarked = true
+
+ continue
+ }
+
+ results[idx].Status = refstore.BatchStatusNotAttempted
+ results[idx].Error = batchResultError(err)
+ }
+
+ return results, err
+ }
+
+ name := batchResultName(err)
+ rejectedAt := -1
+
+ for i, op := range remainingOps {
+ if op.name == name {
+ rejectedAt = i
+
+ break
+ }
+ }
+
+ if rejectedAt < 0 {
+ for _, idx := range remainingIdx {
+ results[idx].Status = refstore.BatchStatusNotAttempted
+ results[idx].Error = batchResultError(err)
+ }
+
+ return results, err
+ }
+
+ results[remainingIdx[rejectedAt]].Status = refstore.BatchStatusRejected
+ results[remainingIdx[rejectedAt]].Error = batchResultError(err)
+ remainingIdx = append(remainingIdx[:rejectedAt], remainingIdx[rejectedAt+1:]...)
+ remainingOps = append(remainingOps[:rejectedAt], remainingOps[rejectedAt+1:]...)
+ }
+
+ return results, nil
+}
+
+// Abort abandons the batch.
+func (batch *Batch) Abort() error {
+ return nil
+}
+
+func (batch *Batch) queue(op queuedUpdate) error {
+ err := validateQueuedUpdate(op)
+ if err != nil {
+ return err
+ }
+
+ batch.ops = append(batch.ops, op)
+
+ return nil
+}