Documentation
¶
Overview ¶
Package workspace provides workspace tracking and status management for stapler-squad sessions. It supports multi-pod deployments via distributed locking and cache invalidation.
Index ¶
- Variables
- type BatchStatusResult
- type BatchVCSProvider
- type CacheInvalidationNotifier
- type ChangesSummary
- type DistributedLock
- type LockError
- type LockHandle
- type NoOpLock
- type NoOpNotifier
- type PollingNotifier
- type PostgresAdvisoryLock
- type PostgresListenNotifyNotifier
- type Registry
- type RegistryConfig
- type SQLiteTransactionLock
- type StatusRefreshOptions
- type TrackedWorkspace
- type WorkspaceFilter
- type WorkspaceRegistry
- func (r *WorkspaceRegistry) Close() error
- func (r *WorkspaceRegistry) Get(ctx context.Context, path string) (*TrackedWorkspace, error)
- func (r *WorkspaceRegistry) GetAllStatuses(ctx context.Context, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
- func (r *WorkspaceRegistry) GetStatus(ctx context.Context, path string, opts StatusRefreshOptions) (*WorkspaceStatus, error)
- func (r *WorkspaceRegistry) GetSummary(ctx context.Context) (*ChangesSummary, error)
- func (r *WorkspaceRegistry) List(ctx context.Context, filter *WorkspaceFilter) ([]*TrackedWorkspace, error)
- func (r *WorkspaceRegistry) ListByRepository(ctx context.Context, repoRoot string) ([]*TrackedWorkspace, error)
- func (r *WorkspaceRegistry) MarkOrphaned(ctx context.Context, path string) error
- func (r *WorkspaceRegistry) RefreshStatuses(ctx context.Context, paths []string, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
- func (r *WorkspaceRegistry) Register(ctx context.Context, workspace *TrackedWorkspace) error
- func (r *WorkspaceRegistry) Unregister(ctx context.Context, path string) error
- type WorkspaceStatus
Constants ¶
This section is empty.
Variables ¶
var ( // ErrLockTimeout indicates lock acquisition timed out ErrLockTimeout = &LockError{Op: "acquire", Err: context.DeadlineExceeded} // ErrLockNotHeld indicates trying to release a lock that isn't held ErrLockNotHeld = &LockError{Op: "release", Err: context.Canceled} // ErrNotSupported indicates the operation isn't supported by this implementation ErrNotSupported = &LockError{Op: "extend", Err: context.Canceled} )
Common lock errors
var ( // ErrNotFound is returned when a workspace is not found. ErrNotFound = fmt.Errorf("workspace not found") )
Functions ¶
This section is empty.
Types ¶
type BatchStatusResult ¶
type BatchStatusResult struct {
Path string // Workspace path
Status *vc.VCSStatus // VCS status (nil if error)
Error error // Error (nil if success)
Duration time.Duration // How long the operation took
IsPartial bool // True if status is incomplete
}
BatchStatusResult contains the result of a batch status operation
type BatchVCSProvider ¶
type BatchVCSProvider struct {
// contains filtered or unexported fields
}
BatchVCSProvider collects VCS status for multiple workspaces concurrently. It provides efficient batch operations with timeout handling and graceful degradation.
func NewBatchVCSProvider ¶
func NewBatchVCSProvider(maxConcurrent int, timeout time.Duration) *BatchVCSProvider
NewBatchVCSProvider creates a new batch VCS provider.
func (*BatchVCSProvider) GetStatuses ¶
func (b *BatchVCSProvider) GetStatuses(ctx context.Context, paths []string) []BatchStatusResult
GetStatuses collects VCS status for multiple paths concurrently. It returns results for all paths, including errors for failed operations.
func (*BatchVCSProvider) GetStatusesByRepository ¶
func (b *BatchVCSProvider) GetStatusesByRepository(ctx context.Context, workspaces []*TrackedWorkspace) map[string][]BatchStatusResult
GetStatusesByRepository groups workspaces by repository root and collects status. This is more efficient when multiple workspaces share the same repository.
type CacheInvalidationNotifier ¶
type CacheInvalidationNotifier interface {
// Subscribe starts listening for invalidation events.
// The handler is called for each invalidation with the workspace path.
// Runs until context is cancelled.
Subscribe(ctx context.Context, handler func(workspacePath string)) error
// Publish broadcasts an invalidation event to all subscribers.
// workspacePath can be empty string to invalidate all caches.
Publish(ctx context.Context, workspacePath string) error
// Close stops listening and releases resources.
Close() error
}
CacheInvalidationNotifier handles cross-pod cache invalidation. Implementations: - PostgresListenNotify: Uses LISTEN/NOTIFY for real-time notifications - PollingNotifier: Poll-based invalidation for SQLite (fallback)
type ChangesSummary ¶
type ChangesSummary struct {
TotalRepositories int // Distinct repository roots
TotalWorkspaces int // All tracked workspaces
TotalUncommitted int // Files with uncommitted changes
TotalUntracked int // Untracked files
TotalStaged int // Staged files
TotalConflicts int // Conflicted files
WorkspacesWithWork int // Workspaces with any pending changes
OrphanedWorkspaces int // Workspaces without active sessions
}
ChangesSummary provides aggregated statistics across all workspaces
func SummaryFromResults ¶
func SummaryFromResults(results []BatchStatusResult) *ChangesSummary
SummaryFromResults creates a ChangesSummary from batch results.
type DistributedLock ¶
type DistributedLock interface {
// Acquire obtains an exclusive lock for the given resource.
// Blocks until the lock is acquired or context is cancelled/times out.
// Returns a handle that must be released when done.
Acquire(ctx context.Context, resource string, timeout time.Duration) (LockHandle, error)
// TryAcquire attempts to acquire lock without blocking.
// Returns (handle, true) if acquired, (nil, false) if unavailable.
TryAcquire(ctx context.Context, resource string) (LockHandle, bool, error)
// Close releases any resources held by the lock implementation
Close() error
}
DistributedLock provides coordination across multiple pods/processes. Implementations: - PostgresAdvisoryLock: Uses pg_advisory_lock() for multi-pod deployments - SQLiteTransactionLock: Uses BEGIN IMMEDIATE for single-pod deployments - NoOpLock: For testing or single-instance deployments without contention
type LockError ¶
type LockError struct {
Op string // Operation: "acquire", "release", "extend"
Resource string // Resource being locked
Err error // Underlying error
}
LockError represents errors from distributed lock operations
type LockHandle ¶
type LockHandle interface {
// Release releases the lock. Must be called when done.
// Safe to call multiple times (subsequent calls are no-ops).
Release() error
// Extend extends the lock TTL for long-running operations.
// Not all implementations support this (may return ErrNotSupported).
Extend(duration time.Duration) error
// IsValid checks if the lock is still held.
// Returns false if lock was released or expired.
IsValid() bool
// Resource returns the resource name this lock is for.
Resource() string
}
LockHandle represents an acquired distributed lock. The lock is held until Release() is called or the context is cancelled.
type NoOpLock ¶
type NoOpLock struct {
// contains filtered or unexported fields
}
NoOpLock is a no-operation lock for single-instance deployments or testing where distributed coordination is not needed. It still provides in-memory locking for thread safety.
func (*NoOpLock) Acquire ¶
func (l *NoOpLock) Acquire(ctx context.Context, resource string, timeout time.Duration) (LockHandle, error)
Acquire obtains a lock for the given resource. For NoOpLock, this provides in-memory thread safety only.
func (*NoOpLock) TryAcquire ¶
TryAcquire attempts to acquire lock without blocking.
type NoOpNotifier ¶
type NoOpNotifier struct{}
NoOpNotifier is a notifier that does nothing. Used when cache invalidation is not needed (single-pod deployments).
func NewNoOpNotifier ¶
func NewNoOpNotifier() *NoOpNotifier
NewNoOpNotifier creates a new no-operation notifier.
type PollingNotifier ¶
type PollingNotifier struct {
// contains filtered or unexported fields
}
PollingNotifier implements CacheInvalidationNotifier using polling. This is used for SQLite deployments where LISTEN/NOTIFY is not available. It's less efficient but works with any database backend.
func NewPollingNotifier ¶
func NewPollingNotifier(pollInterval time.Duration) *PollingNotifier
NewPollingNotifier creates a new polling-based cache invalidation notifier. Default poll interval is 5 seconds.
func (*PollingNotifier) Close ¶
func (n *PollingNotifier) Close() error
Close stops the polling notifier.
type PostgresAdvisoryLock ¶
type PostgresAdvisoryLock struct {
// contains filtered or unexported fields
}
PostgresAdvisoryLock implements distributed locking using PostgreSQL advisory locks. Advisory locks are application-level locks that don't conflict with row-level locks. They are ideal for distributed coordination across multiple application instances.
Key characteristics: - Reentrant within the same database connection - Automatically released when the connection closes - No risk of deadlock with row-level operations - Efficient: no disk I/O, fully in-memory on the PostgreSQL server
func NewPostgresAdvisoryLock ¶
func NewPostgresAdvisoryLock(db *sql.DB, namespace int64) *PostgresAdvisoryLock
NewPostgresAdvisoryLock creates a new PostgreSQL advisory lock manager. The namespace should be unique to this application to avoid conflicts. Recommended: use a consistent hash of the application name.
func (*PostgresAdvisoryLock) Acquire ¶
func (l *PostgresAdvisoryLock) Acquire(ctx context.Context, resource string, timeout time.Duration) (LockHandle, error)
Acquire obtains an exclusive advisory lock for the given resource. The lock is held on a dedicated connection and released when Release() is called or when the connection closes.
func (*PostgresAdvisoryLock) Close ¶
func (l *PostgresAdvisoryLock) Close() error
Close releases all locks.
func (*PostgresAdvisoryLock) TryAcquire ¶
func (l *PostgresAdvisoryLock) TryAcquire(ctx context.Context, resource string) (LockHandle, bool, error)
TryAcquire attempts to acquire lock without blocking. Uses pg_try_advisory_lock which returns immediately.
type PostgresListenNotifyNotifier ¶
type PostgresListenNotifyNotifier struct {
// contains filtered or unexported fields
}
PostgresListenNotifyNotifier implements CacheInvalidationNotifier using PostgreSQL's LISTEN/NOTIFY mechanism for real-time cross-pod cache invalidation.
Note: This is a simplified implementation. For production use with high-throughput requirements, consider using github.com/jackc/pgx directly for native LISTEN/NOTIFY support.
func NewPostgresListenNotifyNotifier ¶
func NewPostgresListenNotifyNotifier(db *sql.DB, channel string) *PostgresListenNotifyNotifier
NewPostgresListenNotifyNotifier creates a new PostgreSQL LISTEN/NOTIFY notifier. The channel name should be unique to this application's workspace invalidation.
func (*PostgresListenNotifyNotifier) Close ¶
func (n *PostgresListenNotifyNotifier) Close() error
Close stops listening and releases resources.
type Registry ¶
type Registry interface {
// Registration
Register(ctx context.Context, workspace *TrackedWorkspace) error
Unregister(ctx context.Context, path string) error
MarkOrphaned(ctx context.Context, path string) error
// Query
Get(ctx context.Context, path string) (*TrackedWorkspace, error)
List(ctx context.Context, filter *WorkspaceFilter) ([]*TrackedWorkspace, error)
ListByRepository(ctx context.Context, repoRoot string) ([]*TrackedWorkspace, error)
// Status
GetStatus(ctx context.Context, path string, opts StatusRefreshOptions) (*WorkspaceStatus, error)
GetAllStatuses(ctx context.Context, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
GetSummary(ctx context.Context) (*ChangesSummary, error)
// Batch operations
RefreshStatuses(ctx context.Context, paths []string, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
// Lifecycle
Close() error
}
Registry tracks all known workspaces and their status. It provides a central point for workspace discovery, status caching, and coordination across multiple pods via distributed locking.
type RegistryConfig ¶
type RegistryConfig struct {
// Distributed lock for multi-pod coordination
Lock DistributedLock
// Cache invalidation for cross-pod coordination
Notifier CacheInvalidationNotifier
// Cache settings
CacheTTL time.Duration // How long to cache status (default: 30s)
MaxCacheSize int // Maximum cached entries (default: 1000)
RefreshBatchSize int // Max concurrent status refreshes (default: 10)
// Timeouts
LockTimeout time.Duration // Lock acquisition timeout (default: 10s)
OperationTimeout time.Duration // Individual operation timeout (default: 5s)
}
RegistryConfig configures the workspace registry
func DefaultRegistryConfig ¶
func DefaultRegistryConfig() RegistryConfig
DefaultRegistryConfig returns sensible defaults
type SQLiteTransactionLock ¶
type SQLiteTransactionLock struct {
// contains filtered or unexported fields
}
SQLiteTransactionLock implements distributed locking using SQLite transactions. It uses BEGIN IMMEDIATE to acquire an exclusive lock on the database. This is suitable for single-pod deployments or when using a shared filesystem.
Note: SQLite locks are database-wide, not per-resource. For fine-grained locking, use the in-memory lock map to simulate per-resource locks within a transaction.
func NewSQLiteTransactionLock ¶
func NewSQLiteTransactionLock(db *sql.DB) *SQLiteTransactionLock
NewSQLiteTransactionLock creates a new SQLite-based distributed lock. The db connection should already be configured with WAL mode and appropriate timeouts.
func (*SQLiteTransactionLock) Acquire ¶
func (l *SQLiteTransactionLock) Acquire(ctx context.Context, resource string, timeout time.Duration) (LockHandle, error)
Acquire obtains an exclusive lock for the given resource. This starts a SQLite transaction with BEGIN IMMEDIATE to acquire the write lock.
func (*SQLiteTransactionLock) Close ¶
func (l *SQLiteTransactionLock) Close() error
Close releases all locks and closes resources.
func (*SQLiteTransactionLock) TryAcquire ¶
func (l *SQLiteTransactionLock) TryAcquire(ctx context.Context, resource string) (LockHandle, bool, error)
TryAcquire attempts to acquire lock without blocking.
type StatusRefreshOptions ¶
type StatusRefreshOptions struct {
// Force refresh even if cache is fresh
Force bool
// Maximum age for cached status to be considered fresh
MaxAge time.Duration
// Timeout for individual git operations
Timeout time.Duration
// Continue on errors (return partial results)
ContinueOnError bool
}
StatusRefreshOptions controls how status refresh behaves
func DefaultStatusRefreshOptions ¶
func DefaultStatusRefreshOptions() StatusRefreshOptions
DefaultStatusRefreshOptions returns sensible defaults
type TrackedWorkspace ¶
type TrackedWorkspace struct {
// Identity
Path string // Absolute path to workspace
RepositoryRoot string // Root of the git/jj repository
WorktreePath string // Path if this is a worktree (empty if not)
MainRepoPath string // Main repo path if this is a worktree
// Session association
SessionTitle string // Title of associated session (empty if orphaned)
SessionStatus session.Status // Status of associated session
IsOrphaned bool // True if no active session owns this workspace
// VCS information
VCSType vc.VCSType // Git or Jujutsu
// Timestamps
LastChecked time.Time // When VCS status was last refreshed
LastActivity time.Time // Last file modification detected
// Attention flags
NeedsAttention bool // Has issues requiring action
AttentionReason string // Why attention is needed
}
TrackedWorkspace represents a directory tracked by stapler-squad
type WorkspaceFilter ¶
type WorkspaceFilter struct {
// Path filters
RepositoryRoot string // Filter by repository root
PathPrefix string // Filter by path prefix
// Status filters
IncludeOrphaned bool // Include orphaned workspaces
OnlyWithChanges bool // Only workspaces with uncommitted changes
OnlyWithConflict bool // Only workspaces with conflicts
// Session filters
SessionStatus *session.Status // Filter by session status
}
WorkspaceFilter defines criteria for filtering workspace queries
type WorkspaceRegistry ¶
type WorkspaceRegistry struct {
// contains filtered or unexported fields
}
WorkspaceRegistry is the default implementation of Registry. It uses in-memory caching with optional distributed locking.
func NewRegistry ¶
func NewRegistry(config RegistryConfig) *WorkspaceRegistry
NewRegistry creates a new workspace registry with the given configuration.
func (*WorkspaceRegistry) Close ¶
func (r *WorkspaceRegistry) Close() error
Close stops background processes and releases resources.
func (*WorkspaceRegistry) Get ¶
func (r *WorkspaceRegistry) Get(ctx context.Context, path string) (*TrackedWorkspace, error)
Get retrieves a tracked workspace by path.
func (*WorkspaceRegistry) GetAllStatuses ¶
func (r *WorkspaceRegistry) GetAllStatuses(ctx context.Context, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
GetAllStatuses retrieves VCS status for all tracked workspaces.
func (*WorkspaceRegistry) GetStatus ¶
func (r *WorkspaceRegistry) GetStatus(ctx context.Context, path string, opts StatusRefreshOptions) (*WorkspaceStatus, error)
GetStatus retrieves the VCS status for a workspace.
func (*WorkspaceRegistry) GetSummary ¶
func (r *WorkspaceRegistry) GetSummary(ctx context.Context) (*ChangesSummary, error)
GetSummary returns aggregated statistics across all workspaces.
func (*WorkspaceRegistry) List ¶
func (r *WorkspaceRegistry) List(ctx context.Context, filter *WorkspaceFilter) ([]*TrackedWorkspace, error)
List returns all tracked workspaces matching the filter.
func (*WorkspaceRegistry) ListByRepository ¶
func (r *WorkspaceRegistry) ListByRepository(ctx context.Context, repoRoot string) ([]*TrackedWorkspace, error)
ListByRepository returns all workspaces for a given repository root.
func (*WorkspaceRegistry) MarkOrphaned ¶
func (r *WorkspaceRegistry) MarkOrphaned(ctx context.Context, path string) error
MarkOrphaned marks a workspace as orphaned (no active session).
func (*WorkspaceRegistry) RefreshStatuses ¶
func (r *WorkspaceRegistry) RefreshStatuses(ctx context.Context, paths []string, opts StatusRefreshOptions) ([]*WorkspaceStatus, error)
RefreshStatuses refreshes VCS status for the given workspace paths.
func (*WorkspaceRegistry) Register ¶
func (r *WorkspaceRegistry) Register(ctx context.Context, workspace *TrackedWorkspace) error
Register adds a workspace to the registry.
func (*WorkspaceRegistry) Unregister ¶
func (r *WorkspaceRegistry) Unregister(ctx context.Context, path string) error
Unregister removes a workspace from the registry.
type WorkspaceStatus ¶
type WorkspaceStatus struct {
// Embedded VCS status
VCSStatus *vc.VCSStatus
// Workspace context
WorkspacePath string // Path to this workspace
SessionTitle string // Associated session (if any)
SessionStatus session.Status // Session state
IsOrphaned bool // No active session
IsWorktree bool // Is a git worktree
// Activity tracking
LastActivity time.Time // Last file modification
LastChecked time.Time // When status was collected
// Attention flags
NeedsAttention bool // Has issues requiring action
AttentionReason string // Why attention is needed
// Error state
Error error // Error during status collection (nil if success)
ErrorMsg string // Human-readable error message
IsPartial bool // True if status is incomplete due to errors
}
WorkspaceStatus combines VCS status with workspace-specific information