vibetunnel/mac/VibeTunnel/Core/Services/WorktreeService.swift
Peter Steinberger 3f86ed365c Optimize GitRepositoryMonitor to reduce redundant checks
- Implement repository deduplication in SessionMonitor to check each unique directory only once
- Add smart parent directory detection for common development paths (Projects, Development, etc.)
- Extend cache duration for common parent directories from 30s to 5 minutes
- Add negative caching for non-Git directories (10 minute cache)
- Group sessions by repository before Git checks to avoid duplicate lookups

This reduces Git repository checks from ~100/minute to significantly fewer by:
1. Deduplicating checks across multiple sessions in the same repository
2. Caching parent directory results longer
3. Remembering non-Git paths to avoid repeated filesystem traversals
2025-07-27 14:00:22 +02:00

155 lines
4.7 KiB
Swift

import Foundation
import Observation
import OSLog
/// Service for managing Git worktrees through the VibeTunnel server API
@MainActor
@Observable
final class WorktreeService {
private let logger = Logger(subsystem: BundleIdentifiers.loggerSubsystem, category: "WorktreeService")
private let serverManager: ServerManager
private(set) var worktrees: [Worktree] = []
private(set) var branches: [GitBranch] = []
private(set) var stats: WorktreeStats?
private(set) var followMode: FollowModeStatus?
private(set) var isLoading = false
private(set) var isLoadingBranches = false
private(set) var error: Error?
init(serverManager: ServerManager) {
self.serverManager = serverManager
}
/// Fetch the list of worktrees for a Git repository
func fetchWorktrees(for gitRepoPath: String) async {
isLoading = true
error = nil
do {
let worktreeResponse = try await serverManager.performRequest(
endpoint: "/api/worktrees",
method: "GET",
queryItems: [URLQueryItem(name: "repoPath", value: gitRepoPath)],
responseType: WorktreeListResponse.self
)
self.worktrees = worktreeResponse.worktrees
// Stats and followMode are not part of the current API response
// They could be fetched separately if needed
} catch {
self.error = error
logger.error("Failed to fetch worktrees: \(error.localizedDescription)")
}
isLoading = false
}
/// Create a new worktree
func createWorktree(
gitRepoPath: String,
branch: String,
worktreePath: String,
baseBranch: String? = nil
)
async throws
{
let request = CreateWorktreeRequest(
repoPath: gitRepoPath,
branch: branch,
path: worktreePath,
baseBranch: baseBranch
)
try await serverManager.performVoidRequest(
endpoint: "/api/worktrees",
method: "POST",
body: request
)
// Refresh the worktree list
await fetchWorktrees(for: gitRepoPath)
}
/// Delete a worktree
func deleteWorktree(gitRepoPath: String, branch: String, force: Bool = false) async throws {
try await serverManager.performVoidRequest(
endpoint: "/api/worktrees/\(branch)",
method: "DELETE",
queryItems: [
URLQueryItem(name: "repoPath", value: gitRepoPath),
URLQueryItem(name: "force", value: String(force))
]
)
// Refresh the worktree list
await fetchWorktrees(for: gitRepoPath)
}
/// Switch to a different branch
func switchBranch(gitRepoPath: String, branch: String) async throws {
let request = SwitchBranchRequest(repoPath: gitRepoPath, branch: branch)
try await serverManager.performVoidRequest(
endpoint: "/api/worktrees/switch",
method: "POST",
body: request
)
// Refresh the worktree list
await fetchWorktrees(for: gitRepoPath)
}
/// Toggle follow mode
func toggleFollowMode(gitRepoPath: String, enabled: Bool, targetBranch: String? = nil) async throws {
let request = FollowModeRequest(repoPath: gitRepoPath, branch: targetBranch, enable: enabled)
try await serverManager.performVoidRequest(
endpoint: "/api/worktrees/follow",
method: "POST",
body: request
)
// Refresh the worktree list
await fetchWorktrees(for: gitRepoPath)
}
/// Fetch the list of branches for a Git repository
func fetchBranches(for gitRepoPath: String) async {
isLoadingBranches = true
do {
self.branches = try await serverManager.performRequest(
endpoint: "/api/repositories/branches",
method: "GET",
queryItems: [URLQueryItem(name: "path", value: gitRepoPath)],
responseType: [GitBranch].self
)
} catch {
self.error = error
logger.error("Failed to fetch branches: \(error.localizedDescription)")
}
isLoadingBranches = false
}
}
// MARK: - Error Types
enum WorktreeError: LocalizedError {
case invalidURL
case invalidResponse
case serverError(String)
case invalidConfiguration
var errorDescription: String? {
switch self {
case .invalidURL:
"Invalid URL"
case .invalidResponse:
"Invalid server response"
case .serverError(let message):
message
case .invalidConfiguration:
"Invalid configuration"
}
}
}
// MARK: - Helper Types