From ecfaeabf2a92e26c6a521d5fac404a0fff6263b6 Mon Sep 17 00:00:00 2001 From: Lukasz Kasprzak Date: Wed, 16 Sep 2026 01:34:45 +0200 Subject: milestone 1 review: claims span the run, explain's chain is opt-in and its own, overrides keyed by clean path, splice and enum guards --- internal/engine/session.go | 33 +++++++++++++++++++++------------ 1 file changed, 21 insertions(+), 12 deletions(-) (limited to 'internal/engine/session.go') diff --git a/internal/engine/session.go b/internal/engine/session.go index 33b6a76..d79c364 100644 --- a/internal/engine/session.go +++ b/internal/engine/session.go @@ -25,6 +25,7 @@ type Session struct { j *journal.Writer run string claims *plan.Claims + landed []string // where this run's applied files ended up, in order dry bool } @@ -90,26 +91,34 @@ func (s *Session) Plan(ctx context.Context, d *Dir) (*DirPlan, error) { return s.e.Plan(ctx, d, s.claims) } -// Apply carries out the approved files of dp and logs the run's steps. A -// real run applies each directory before the next is planned, so afterwards -// the disk is the truth for the next one: only the paths this directory's -// files ended up at stay claimed, which keeps a later (on-conflict -// overwrite) from displacing this run's own result (spec §7.4). A dry -// session keeps every claim, since it applies nothing. +// Apply carries out the approved files of dp and logs the run's steps, +// remembering where they ended up for FinishDirectory. func (s *Session) Apply(ctx context.Context, dp *DirPlan, approved map[string]bool) (*ApplyResult, error) { if err := s.OpenLog(); err != nil { return nil, err } res, err := s.e.Apply(ctx, dp, approved, s.j, s.run) - if !s.dry { - s.claims = plan.NewClaims() - for _, p := range landedAt(res) { - s.claims.Claim(p) - } - } + s.landed = append(s.landed, landedAt(res)...) return res, err } +// FinishDirectory ends one directory of a real run: the disk is now the +// truth for the next one, so the claims start again from where this run's +// files have actually ended up - every directory's, not just this one's +// (spec §7.4, plan 13 review F3). A path this directory planned but did not +// apply is free again; a path it did apply stays protected from a later +// (on-conflict overwrite) for the rest of the run, even across a directory +// that applies nothing. A dry run applies nothing and keeps every claim. +func (s *Session) FinishDirectory() { + if s.dry { + return + } + s.claims = plan.NewClaims() + for _, p := range s.landed { + s.claims.Claim(p) + } +} + // landedAt is where res's files ended up: each copy, and the last place a // move or rename put a file - not a path it passed through and left, and // nothing at all for a file deleted for good. -- cgit v1.3