native macOS codings agent orchestrator prowl.onev.cat
Something went wrong. Try again.
Swift
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246import ConcurrencyExtrasimport Foundationimport Testing
@testable import Prowl
nonisolated final class GitWorktreeDiscoveryRecorder: @unchecked Sendable { struct Invocation: Equatable { let executablePath: String let arguments: [String] let currentDirectoryPath: String? }
private let lock = NSLock() private var runInvocationsValue: [Invocation] = [] private var loginInvocationsValue: [Invocation] = []
func recordRun(executableURL: URL, arguments: [String], currentDirectoryURL: URL?) { lock.lock() runInvocationsValue.append( Invocation( executablePath: executableURL.path(percentEncoded: false), arguments: arguments, currentDirectoryPath: currentDirectoryURL?.path(percentEncoded: false) ) ) lock.unlock() }
func recordLogin(executableURL: URL, arguments: [String], currentDirectoryURL: URL?) { lock.lock() loginInvocationsValue.append( Invocation( executablePath: executableURL.path(percentEncoded: false), arguments: arguments, currentDirectoryPath: currentDirectoryURL?.path(percentEncoded: false) ) ) lock.unlock() }
func runInvocations() -> [Invocation] { lock.lock() let value = runInvocationsValue lock.unlock() return value }
func loginInvocations() -> [Invocation] { lock.lock() let value = loginInvocationsValue lock.unlock() return value }}
struct GitClientWorktreeDiscoveryTests { @Test func repoRootUsesDirectBundledWtExecution() async throws { let recorder = GitWorktreeDiscoveryRecorder() let shell = ShellClient( run: { executableURL, arguments, currentDirectoryURL in recorder.recordRun( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) return ShellOutput(stdout: "/tmp/repo\n", stderr: "", exitCode: 0) }, runLoginImpl: { executableURL, arguments, currentDirectoryURL, _ in recorder.recordLogin( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) Issue.record("repoRoot should not use runLogin when direct execution succeeds") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable }) let worktreeURL = URL(fileURLWithPath: "/tmp/repo/worktree")
let root = try await client.repoRoot(for: worktreeURL)
#expect(root.standardizedFileURL.path(percentEncoded: false).hasSuffix("/tmp/repo")) let runs = recorder.runInvocations() #expect(runs.count == 2) #expect(runs.first?.arguments.suffix(2) == ["rev-parse", "--git-dir"]) if let invocation = runs.last { #expect(invocation.arguments.last == "root") #expect(invocation.arguments.contains("PATH=/usr/bin:/bin")) let normalizedPath = URL(fileURLWithPath: invocation.currentDirectoryPath ?? "") .standardizedFileURL .path(percentEncoded: false) .trimmingCharacters(in: CharacterSet(charactersIn: "/")) #expect(normalizedPath == "tmp/repo") } else { Issue.record("Expected one direct bundled wt invocation for repoRoot") } #expect(recorder.loginInvocations().isEmpty) }
@Test func worktreesUseDirectBundledWtExecution() async throws { let recorder = GitWorktreeDiscoveryRecorder() let output = """ [ {"branch":"main","path":"/tmp/repo","head":"abc","is_bare":false}, {"branch":"feature","path":"/tmp/repo/.worktrees/feature","head":"def","is_bare":false} ] """ let shell = ShellClient( run: { executableURL, arguments, currentDirectoryURL in recorder.recordRun( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) return ShellOutput(stdout: output, stderr: "", exitCode: 0) }, runLoginImpl: { executableURL, arguments, currentDirectoryURL, _ in recorder.recordLogin( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) Issue.record("worktrees should not use runLogin when direct execution succeeds") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable }) let repoRoot = URL(fileURLWithPath: "/tmp/repo")
let worktrees = try await client.worktrees(for: repoRoot)
#expect(worktrees.map(\.id) == ["/tmp/repo", "/tmp/repo/.worktrees/feature"]) let runs = recorder.runInvocations() #expect(runs.count == 1) if let invocation = runs.first { #expect(invocation.arguments.suffix(2) == ["ls", "--json"]) #expect(invocation.arguments.contains("PATH=/usr/bin:/bin")) #expect(invocation.currentDirectoryPath == "/tmp/repo") } else { Issue.record("Expected one direct bundled wt invocation for worktree discovery") } #expect(recorder.loginInvocations().isEmpty) }
@Test func worktreesDeduplicateStandardizedPaths() async throws { let output = """ [ {"branch":"main","path":"/tmp/repo","head":"abc","is_bare":false}, {"branch":"feature","path":"/tmp/repo/.worktrees/feature","head":"def","is_bare":false}, {"branch":"feature","path":"/tmp/repo/.worktrees/./feature","head":"def","is_bare":false} ] """ let shell = ShellClient( run: { _, _, _ in ShellOutput(stdout: output, stderr: "", exitCode: 0) }, runLoginImpl: { _, _, _, _ in Issue.record("worktrees should not use runLogin when direct execution succeeds") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable }) let repoRoot = URL(fileURLWithPath: "/tmp/repo")
let worktrees = try await client.worktrees(for: repoRoot)
#expect(worktrees.map(\.id) == ["/tmp/repo", "/tmp/repo/.worktrees/feature"]) }
@Test func repoRootDoesNotReplayDiscoveryAgainstAnotherEnvironment() async { let calls = LockIsolated(0) let shell = ShellClient( run: { _, _, _ in calls.withValue { $0 += 1 } throw ShellClientError(command: "git", stdout: "", stderr: "permission denied", exitCode: 1) }, runLoginImpl: { _, _, _, _ in Issue.record("Git selection must precede the operation, not replay it") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable }) await #expect(throws: GitClientError.self) { try await client.repoRoot(for: URL(fileURLWithPath: "/tmp/repo")) } #expect(calls.value == 1) }
@Test func worktreesDoNotFallbackToLoginShellForNonGitDirectory() async { let recorder = GitWorktreeDiscoveryRecorder() let shell = ShellClient( run: { executableURL, arguments, currentDirectoryURL in recorder.recordRun( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) throw ShellClientError( command: "wt ls --json", stdout: "", stderr: "fatal: not a git repository (or any of the parent directories): .git", exitCode: 128 ) }, runLoginImpl: { executableURL, arguments, currentDirectoryURL, _ in recorder.recordLogin( executableURL: executableURL, arguments: arguments, currentDirectoryURL: currentDirectoryURL ) Issue.record("worktrees should not fallback to runLogin for non-git directories") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable })
await #expect(throws: GitClientError.self) { _ = try await client.worktrees(for: URL(fileURLWithPath: "/tmp/not-a-repo")) }
#expect(recorder.runInvocations().count == 1) #expect(recorder.loginInvocations().isEmpty) }
@Test func worktreeListDoesNotReplayAfterAnOperationFailure() async { let calls = LockIsolated(0) let shell = ShellClient( run: { _, _, _ in calls.withValue { $0 += 1 } throw ShellClientError(command: "wt ls", stdout: "", stderr: "permission denied", exitCode: 1) }, runLoginImpl: { _, _, _, _ in Issue.record("Worktree listing must use the selected Git environment") return ShellOutput(stdout: "", stderr: "", exitCode: 0) } ) let client = GitClient(shell: shell, resolveGit: { _ in .testExecutable }) await #expect(throws: GitClientError.self) { try await client.worktrees(for: URL(fileURLWithPath: "/tmp/repo")) } #expect(calls.value == 1) }}