diff options
| author | 2026-05-24 13:41:34 +0000 | |
|---|---|---|
| committer | 2026-05-24 14:12:35 +0000 | |
| commit | 947bf81a33c6e4e5d21c8b36f9317fe00b84f6ae (patch) | |
| tree | 67824655ef9dbf2d941dae06d59ea29a1e32d458 /ref/store/memory/batch.go | |
| parent | README: Update (diff) | |
| signature | No 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.go | 195 |
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 +} |
