native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
6.3 kB · 153 lines
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154import ComposableArchitectureimport Foundation
/// A workspace child repository resolved to its on-disk working directory./// `id` is the working-directory path string (the key for all child maps).struct ResolvedWorkspaceChild: Equatable, Sendable, Identifiable { let id: String let workspaceID: Repository.ID let repositoryName: String let metadataBranch: String? let workingDirectory: URL /// The child's canonical repository root: the recorded local source for /// linked and worktree checkouts, else the working directory itself (remote /// clones, metadata without a source location). Keys `repositorySettings` /// and fills `{repoPath}`, matching how `Worktree.repositoryRootURL` points /// at the registered repository rather than the checkout. let repositoryRootURL: URL}
extension RepositoriesFeature { /// Refresh live status (current branch + uncommitted diff) for every /// workspace child. Driven from `repositoriesLoaded`, which fires on initial /// load, explicit reloads, and the periodic scene-active refresh — so child /// rows update on the same cadence without watching their files directly. /// /// Workspace children are deliberately NOT fed through the worktree info /// watcher: that pipeline bails on any worktree not tracked in /// `repository.worktrees`, and children are metadata entries, not tracked /// worktrees. func refreshWorkspaceChildrenEffect(state: State) -> Effect<Action> { let children = state.allResolvedWorkspaceChildren() guard !children.isEmpty else { // A reload that removed the last child must also stop an in-flight // refresh, or its late result would repopulate the just-pruned maps. return .cancel(id: CancelID.workspaceChildrenRefresh) } let gitClient = self.gitClient let githubCLI = self.githubCLI // PR fetch only when GitHub integration is globally available; each child is // its own repo, so its remote is resolved from the child's own git root. let fetchesPullRequests = state.githubIntegrationAvailability == .available return .run { send in let updates = await withTaskGroup(of: WorkspaceChildInfoUpdate.self) { group in for child in children { group.addTask { async let branchTask = gitClient.branchName(child.workingDirectory) async let changesTask = gitClient.lineChanges(child.workingDirectory) let changes = await changesTask let branch = await branchTask let pullRequest = await Self.fetchWorkspaceChildPullRequest( workingDirectory: child.workingDirectory, branch: branch, enabled: fetchesPullRequests, gitClient: gitClient, githubCLI: githubCLI ) return WorkspaceChildInfoUpdate( id: child.id, branch: branch, lineChanges: changes, pullRequest: pullRequest ) } } var results: [WorkspaceChildInfoUpdate] = [] for await update in group { results.append(update) } return results } await send(.workspaceChildrenInfoLoaded(updates)) } .cancellable(id: CancelID.workspaceChildrenRefresh, cancelInFlight: true) }
/// Resolve the child repo's GitHub remote (local-only, cheap) and fetch the /// PR for the current branch. Returns nil on any failure — children are /// best-effort status, never blocking. nonisolated private static func fetchWorkspaceChildPullRequest( workingDirectory: URL, branch: String?, enabled: Bool, gitClient: GitClientDependency, githubCLI: GithubCLIClient ) async -> GithubPullRequest? { guard enabled, let branch = branch?.trimmingCharacters(in: .whitespacesAndNewlines), !branch.isEmpty, let remote = await gitClient.remoteInfo(workingDirectory) else { return nil } let pullRequestsByBranch = try? await githubCLI.batchPullRequests( remote.host, remote.owner, remote.repo, [branch], nil ) return pullRequestsByBranch?[branch] }}
/// Merge a batch of child refresh results into the child maps.func applyWorkspaceChildrenInfo( _ updates: [WorkspaceChildInfoUpdate], state: inout RepositoriesFeature.State) { for update in updates { if let branch = update.branch?.trimmingCharacters(in: .whitespacesAndNewlines), !branch.isEmpty { state.workspaceChildBranchByID[update.id] = branch } else { state.workspaceChildBranchByID.removeValue(forKey: update.id) }
var entry = state.workspaceChildInfoByID[update.id] ?? WorktreeInfoEntry() if let changes = update.lineChanges, !changes.isEmpty { entry.addedLines = changes.added entry.removedLines = changes.removed } else { entry.addedLines = nil entry.removedLines = nil } entry.skippedUntrackedFileCount = update.lineChanges?.skippedUntrackedFileCount ?? 0 entry.pullRequest = update.pullRequest if entry.isEmpty { state.workspaceChildInfoByID.removeValue(forKey: update.id) } else { state.workspaceChildInfoByID[update.id] = entry } }}
/// Drop child map entries that no longer belong to any current workspace./// Called from `applyRepositories` on every reload.func pruneWorkspaceChildInfo(state: inout RepositoriesFeature.State) { let validIDs = Set(state.allResolvedWorkspaceChildren().map(\.id)) state.workspaceChildInfoByID = state.workspaceChildInfoByID.filter { validIDs.contains($0.key) } state.workspaceChildBranchByID = state.workspaceChildBranchByID.filter { validIDs.contains($0.key) } // The selection is validated against the selected workspace's own children, // not the global path set: another workspace referencing the same path must // not keep a removed child selected here. if let selectedWorkspaceChildID = state.selectedWorkspaceChildID { let selectionIsValid = state.selectedRepository.map { repository in repository.isWorkspace && state.resolvedWorkspaceChildren(in: repository) .contains { $0.id == selectedWorkspaceChildID } } ?? false if !selectionIsValid { state.selectedWorkspaceChildID = nil } }}