Files
David Gageot 785a1042aa refactor: extract SnapshotController so the runtime no longer brokers /undo
Drops WithSnapshots in favour of builtins.RegisterSnapshot returning a
controller the embedder threads into both the runtime (as an
AutoInjector) and the App. LocalRuntime no longer carries snapshot
methods, and pkg/runtime/snapshot.go is gone.

Fixes #2701
2026-05-09 10:13:06 +02:00

82 lines
2.7 KiB
Go

package app
import (
"context"
"errors"
"fmt"
"os"
)
var ErrNothingToUndo = errors.New("nothing to undo")
type UndoSnapshotResult struct {
RestoredFiles int
}
// SnapshotsEnabled reports whether automatic shadow-git snapshots are
// active. The answer is a controller-level capability check and does
// not depend on having an active session attached.
func (a *App) SnapshotsEnabled() bool {
return a.snapshotController != nil && a.snapshotController.Enabled()
}
// UndoLastSnapshot restores the files captured in the most recent
// snapshot checkpoint for the current session.
func (a *App) UndoLastSnapshot(ctx context.Context) (UndoSnapshotResult, error) {
if a.snapshotController == nil || a.session == nil {
return UndoSnapshotResult{}, ErrNothingToUndo
}
return snapshotResult(a.snapshotController.UndoLast(ctx, a.session.ID, a.snapshotCwd()))
}
// ListSnapshots returns the file count of every snapshot captured during
// the current session, oldest first. Returns nil when no snapshots exist
// or when no controller is configured.
func (a *App) ListSnapshots() []int {
if a.snapshotController == nil || a.session == nil {
return nil
}
infos := a.snapshotController.List(a.session.ID)
counts := make([]int, len(infos))
for i, info := range infos {
counts[i] = info.Files
}
return counts
}
// ResetSnapshot reverts every checkpoint past index keep so the workspace
// returns to the state captured at that snapshot. keep == 0 resets to
// the original pre-agent state.
func (a *App) ResetSnapshot(ctx context.Context, keep int) (UndoSnapshotResult, error) {
if a.snapshotController == nil || a.session == nil {
return UndoSnapshotResult{}, ErrNothingToUndo
}
return snapshotResult(a.snapshotController.Reset(ctx, a.session.ID, a.snapshotCwd(), keep))
}
// snapshotCwd resolves the working directory the snapshot operations
// should run against. Sessions carry their own WorkingDir (set by the
// embedder when the session is constructed); if it's empty we fall
// back to os.Getwd so snapshot commands keep working in setups that
// don't propagate a working dir on the session.
func (a *App) snapshotCwd() string {
if a.session != nil && a.session.WorkingDir != "" {
return a.session.WorkingDir
}
cwd, _ := os.Getwd()
return cwd
}
// snapshotResult adapts the (files, ok, err) tuple returned by snapshot
// operations into the UndoSnapshotResult / ErrNothingToUndo shape callers
// expect.
func snapshotResult(files int, ok bool, err error) (UndoSnapshotResult, error) {
if err != nil {
return UndoSnapshotResult{}, fmt.Errorf("restoring snapshot: %w", err)
}
if !ok {
return UndoSnapshotResult{}, ErrNothingToUndo
}
return UndoSnapshotResult{RestoredFiles: files}, nil
}