Undo reported "nothing left to undo" after a successful undo (#72)
Build image / build-and-push (push) Successful in 19s

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #72.
This commit is contained in:
2026-07-21 12:25:34 +00:00
committed by steve
parent 8dbbc5439d
commit 4ea0d0b262
5 changed files with 162 additions and 7 deletions
+40
View File
@@ -6,6 +6,7 @@ import (
"errors"
"fmt"
"log/slog"
"sort"
"sync"
"gitea.stevedudenhoeffer.com/steve/pansy/internal/domain"
@@ -334,9 +335,48 @@ func (s *Service) RevertChangeSet(ctx context.Context, actorID, changeSetID int6
if err != nil {
return nil, nil, err
}
// Fill in the per-op counts. commitScope returns the freshly inserted row,
// which carries no tally — and a caller receiving a change set with an empty
// Counts cannot tell "nothing was reverted" from "the tally wasn't loaded".
// That ambiguity is not theoretical: it made the chat panel report a
// successful undo as "nothing left to undo".
if cs != nil {
cs.Counts = countRevisions(sc.taken())
}
return cs, conflicts, nil
}
// countRevisions tallies revisions by (entity type, op).
//
// This deliberately reproduces in Go what ListChangeSets does in SQL, because a
// change set that has just been written has no rows to GROUP BY yet — the
// alternative is a second round trip to count what we are already holding.
// The parity is a real cross-layer contract, and TestRevertResultCarriesItsCounts
// compares the two breakdowns row for row so it can't drift silently.
func countRevisions(revs []domain.Revision) []domain.ChangeCount {
type key struct{ entityType, op string }
seen := map[key]int{}
order := []key{}
for _, r := range revs {
k := key{r.EntityType, r.Op}
if _, ok := seen[k]; !ok {
order = append(order, k)
}
seen[k]++
}
sort.Slice(order, func(i, j int) bool {
if order[i].entityType != order[j].entityType {
return order[i].entityType < order[j].entityType
}
return order[i].op < order[j].op
})
counts := make([]domain.ChangeCount, 0, len(order))
for _, k := range order {
counts = append(counts, domain.ChangeCount{EntityType: k.entityType, Op: k.op, N: seen[k]})
}
return counts
}
// entityKey identifies a row across the entity types a revision can name.
type entityKey struct {
entityType string