Help us shape the future of Go! Take the 2026 Go Developer Survey

workspace

package
v1.54.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Sep 17, 2026 License: AGPL-3.0 Imports: 11 Imported by: 0

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

Constants

This section is empty.

Variables

View Source
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

View Source
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

func (*LockError) Error

func (e *LockError) Error() string

func (*LockError) Unwrap

func (e *LockError) Unwrap() error

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 NewNoOpLock

func NewNoOpLock() *NoOpLock

NewNoOpLock creates a new no-operation lock.

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) Close

func (l *NoOpLock) Close() error

Close releases all locks.

func (*NoOpLock) TryAcquire

func (l *NoOpLock) TryAcquire(ctx context.Context, resource string) (LockHandle, bool, error)

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.

func (*NoOpNotifier) Close

func (n *NoOpNotifier) Close() error

Close is a no-op.

func (*NoOpNotifier) Publish

func (n *NoOpNotifier) Publish(ctx context.Context, workspacePath string) error

Publish is a no-op.

func (*NoOpNotifier) Subscribe

func (n *NoOpNotifier) Subscribe(ctx context.Context, handler func(workspacePath string)) error

Subscribe is a no-op.

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.

func (*PollingNotifier) Publish

func (n *PollingNotifier) Publish(ctx context.Context, workspacePath string) error

Publish broadcasts an invalidation event to all local subscribers. For polling notifier, this only notifies local subscribers.

func (*PollingNotifier) Subscribe

func (n *PollingNotifier) Subscribe(ctx context.Context, handler func(workspacePath string)) error

Subscribe starts listening for invalidation events. With polling notifier, this is primarily for local invalidation within the same process.

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

Close stops listening and releases resources.

func (*PostgresListenNotifyNotifier) Publish

func (n *PostgresListenNotifyNotifier) Publish(ctx context.Context, workspacePath string) error

Publish sends a notification to all listening pods via PostgreSQL NOTIFY.

func (*PostgresListenNotifyNotifier) Subscribe

func (n *PostgresListenNotifyNotifier) Subscribe(ctx context.Context, handler func(workspacePath string)) error

Subscribe starts listening for invalidation events on the PostgreSQL channel. This spawns a goroutine that listens for notifications.

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

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

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

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

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL