diff options
| author | 2026-03-23 03:25:44 +0000 | |
|---|---|---|
| committer | 2026-03-23 03:27:52 +0000 | |
| commit | 4a796e64ac576d6a3e3f2fe6174c4aa476ea0c5c (patch) | |
| tree | 44d72a20076ceab0981d0b553693d26ca36cc0be /refstore/files/batch_apply.go | |
| parent | receivepack: Lifecycle/ownership docs (diff) | |
| signature | No signature | |
refstore: Improve interfaces, errors, and make batch work v0.1.92
Diffstat (limited to 'refstore/files/batch_apply.go')
| -rw-r--r-- | refstore/files/batch_apply.go | 142 |
1 files changed, 102 insertions, 40 deletions
diff --git a/refstore/files/batch_apply.go b/refstore/files/batch_apply.go index 0c217c56..55224b36 100644 --- a/refstore/files/batch_apply.go +++ b/refstore/files/batch_apply.go @@ -1,73 +1,135 @@ package files -import ( - "errors" - - "codeberg.org/lindenii/furgit/refstore" -) +import "codeberg.org/lindenii/furgit/refstore" func (batch *Batch) Apply() ([]refstore.BatchResult, error) { - if batch.closed { - return nil, errors.New("refstore/files: batch already closed") - } - results := make([]refstore.BatchResult, len(batch.ops)) - seen := make(map[string]struct{}, 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)) + executor := &refUpdateExecutor{store: batch.store} for i, op := range batch.ops { results[i].Name = op.name - if _, exists := seen[op.name]; exists { - batch.closed = true + err := executor.validateQueuedUpdate(op) + if err != nil { + results[i].Status = refstore.BatchStatusRejected + results[i].Error = batchResultError(err) + + continue + } + + target, err := executor.resolveQueuedUpdateTarget(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) - err := errors.New("refstore/files: duplicate batch operation for " + `"` + op.name + `"`) - for j := i; j < len(results); j++ { + for j := i + 1; j < len(results); j++ { results[j].Name = batch.ops[j].name - results[j].Error = err + results[j].Status = refstore.BatchStatusNotAttempted + results[j].Error = batchResultError(err) } return results, err } - seen[op.name] = struct{}{} - } + targetKey := updateTargetKey(target.loc) + if _, exists := seenTargets[targetKey]; exists { + results[i].Status = refstore.BatchStatusRejected + results[i].Error = &refstore.DuplicateUpdateError{} - for i, op := range batch.ops { - tx := &Transaction{ - store: batch.store, - ops: []txOp{op}, + continue } - err := tx.validateOp(op) + seenTargets[targetKey] = struct{}{} + remainingIdx = append(remainingIdx, i) + remainingOps = append(remainingOps, op) + } + + for len(remainingOps) > 0 { + prepared, err := executor.prepareUpdates(remainingOps) if err != nil { - results[i].Error = err + if isBatchRejected(err) { + name := batchResultName(err) + rejectedAt := -1 - continue - } + for i, op := range remainingOps { + if op.name == name { + rejectedAt = i - err = tx.Commit() - if err == nil { - continue - } + break + } + } - if isBatchRejected(err) { - results[i].Error = err + if rejectedAt < 0 { + for _, idx := range remainingIdx { + results[idx].Status = refstore.BatchStatusNotAttempted + results[idx].Error = batchResultError(err) + } - continue + 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:]...) + + continue + } + + 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 } - batch.closed = true - results[i].Error = err + err = executor.commitPreparedUpdates(prepared) + if err != nil { + 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) + } - for j := i + 1; j < len(results); j++ { - results[j].Name = batch.ops[j].name - results[j].Error = err + return results, err } - return results, err - } + for _, idx := range remainingIdx { + results[idx].Status = refstore.BatchStatusApplied + } - batch.closed = true + return results, nil + } return results, nil } |
