Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 34 additions & 6 deletions Sources/RemoteTerminalWorkingDirectoryResolver.swift
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import Foundation

enum RemoteTerminalWorkingDirectoryResolver {
static func normalized(_ workingDirectory: String?, preserveExact: Bool) -> String? {
guard let workingDirectory else { return nil }
Expand All @@ -16,11 +18,20 @@ enum RemoteTerminalWorkingDirectoryResolver {
remoteInitialDirectory: String?,
currentDirectory: String?
) -> String? {
if let requested = normalized(requested, preserveExact: preserveExact) {
return requested
}
if let rescued {
return rescued
if preserveExact {
if let requested = normalized(requested, preserveExact: true) { return requested }
if let rescued = normalized(rescued, preserveExact: true) { return rescued }
} else {
// A deleted local worktree must never fall through to the
// selected workspace's directory. Keep the restore in the saved
// path's ancestry, where a resumed agent cannot silently target a
// different repository.
if let requested = normalized(requested, preserveExact: false),
nearestExistingDirectory(requested) == requested {
return requested
}
if let rescued = nearestExistingDirectory(rescued) { return rescued }
if let requested = nearestExistingDirectory(requested) { return requested }
}
let candidates = [
panelDirectory,
Expand All @@ -31,6 +42,23 @@ enum RemoteTerminalWorkingDirectoryResolver {
if preserveExact {
return candidates.lazy.compactMap { normalized($0, preserveExact: true) }.first
}
return TerminalWorkingDirectoryResolver.firstAvailable(candidates)
if let candidate = candidates.lazy.compactMap(nearestExistingDirectory).first {
return candidate
}
return FileManager.default.homeDirectoryForCurrentUser.path
}

private static func nearestExistingDirectory(_ value: String?) -> String? {
guard let normalized = normalized(value, preserveExact: false) else { return nil }
var url = URL(fileURLWithPath: normalized, isDirectory: true)
while true {
var isDirectory: ObjCBool = false
if FileManager.default.fileExists(atPath: url.path, isDirectory: &isDirectory), isDirectory.boolValue {
return url.path
}
let parent = url.deletingLastPathComponent()
guard parent.path != url.path else { return nil }
url = parent
}
}
}
56 changes: 56 additions & 0 deletions cmuxTests/WorkspaceTerminalWorkingDirectoryFallbackTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,62 @@ import Testing

@MainActor
@Suite struct WorkspaceTerminalWorkingDirectoryFallbackTests {
@Test func missingLocalRestoreDirectoryUsesItsNearestExistingParent() throws {
let root = FileManager.default.temporaryDirectory
.appendingPathComponent("cmux-missing-cwd-\(UUID().uuidString)")
try FileManager.default.createDirectory(at: root, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: root) }

let missingSavedDirectory = root
.appendingPathComponent("deleted-worktree", isDirectory: true)
.appendingPathComponent("project", isDirectory: true)
.path
let selectedWorkspaceDirectory = root
.appendingPathComponent("selected-workspace", isDirectory: true)
try FileManager.default.createDirectory(at: selectedWorkspaceDirectory, withIntermediateDirectories: true)

let resolved = RemoteTerminalWorkingDirectoryResolver.resolve(
requested: missingSavedDirectory,
preserveExact: false,
rescued: nil,
panelDirectory: missingSavedDirectory,
requestedPanelDirectory: nil,
remoteInitialDirectory: nil,
currentDirectory: selectedWorkspaceDirectory.path
)

#expect(resolved == root.path)
#expect(resolved != selectedWorkspaceDirectory.path)

let rescuedDirectory = root.appendingPathComponent("rescued-agent", isDirectory: true)
try FileManager.default.createDirectory(at: rescuedDirectory, withIntermediateDirectories: true)
let rescued = RemoteTerminalWorkingDirectoryResolver.resolve(
requested: missingSavedDirectory,
preserveExact: false,
rescued: rescuedDirectory.path,
panelDirectory: missingSavedDirectory,
requestedPanelDirectory: nil,
remoteInitialDirectory: nil,
currentDirectory: selectedWorkspaceDirectory.path
)
#expect(rescued == rescuedDirectory.path)
}

@Test func remoteRestorePreservesAnExactMissingDirectory() {
let missingRemoteDirectory = "/home/cmux/deleted-worktree/project"
let resolved = RemoteTerminalWorkingDirectoryResolver.resolve(
requested: missingRemoteDirectory,
preserveExact: true,
rescued: nil,
panelDirectory: nil,
requestedPanelDirectory: nil,
remoteInitialDirectory: nil,
currentDirectory: "/Users/selected"
)

#expect(resolved == missingRemoteDirectory)
}

@Test func newTerminalSurfaceFallsBackToRequestedWorkingDirectoryWhenReportedDirectoryIsStale() throws {
let workspace = Workspace()
let sourcePaneId = try #require(
Expand Down
Loading