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
67 changes: 67 additions & 0 deletions apps/mac/Sources/SurgeCodeMac/Model/AppModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2171,6 +2171,73 @@ public final class AppModel {
}
}

/// Runs repository setup first, then registers the resulting folder.
/// Returning the concrete project lets New Session proceed without
/// guessing when a shell snapshot will arrive.
public func onboardProject(_ request: ProjectOnboardingRequest) async -> Project? {
let requestedPath = request.projectPath.trimmingCharacters(in: .whitespacesAndNewlines)
guard !requestedPath.isEmpty else { return nil }

do {
let registered: Project
switch request {
case .importFolder:
registered = try await registerOnboardedProject(
path: requestedPath, createWorkspaceRootIfMissing: false)

case .cloneRepository(
let provider, let repository, let remoteURL, _, let cloneProtocol):
let result = try await backend.cloneRepository(
provider: provider,
repository: repository,
remoteURL: remoteURL,
destinationPath: requestedPath,
protocol: cloneProtocol)
registered = try await registerOnboardedProject(
path: result.cwd, createWorkspaceRootIfMissing: false)

case .initializeRepository:
registered = try await registerOnboardedProject(
path: requestedPath, createWorkspaceRootIfMissing: true)
try await backend.initializeRepository(path: registered.path)

case .publishFolder(
_, let provider, let repository, let visibility, let cloneProtocol):
// `git init` is idempotent for an existing repository and makes
// publishing an ordinary local folder work as advertised.
try await backend.initializeRepository(path: requestedPath)
_ = try await backend.publishRepository(
path: requestedPath,
provider: provider,
repository: repository,
visibility: visibility,
protocol: cloneProtocol)
registered = try await registerOnboardedProject(
path: requestedPath, createWorkspaceRootIfMissing: false)
}

await refreshAll()
return projects.first(where: {
GeneralWorkspace.pathsMatch($0.path, registered.path)
}) ?? registered
} catch {
report(error)
return nil
}
}

private func registerOnboardedProject(
path: String, createWorkspaceRootIfMissing: Bool
) async throws -> Project {
if let existing = projects.first(where: {
GeneralWorkspace.pathsMatch($0.path, path)
}) {
return existing
}
return try await backend.addProject(
path: path, createWorkspaceRootIfMissing: createWorkspaceRootIfMissing)
}

/// Prefer the provider of the most recently updated active thread when it
/// is still runnable; otherwise the first runnable provider.
public var preferredQuickChatProvider: ProviderKind? {
Expand Down
33 changes: 33 additions & 0 deletions apps/mac/Sources/SurgeCodeMac/Model/BackendService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,18 @@ public protocol BackendService: Sendable {
func renameProject(id: String, name: String) async throws
/// Deletes a project and all of its sessions (force-cascades server-side).
func deleteProject(id: String) async throws
/// Clone a provider repository name or direct Git URL into a host-local destination.
func cloneRepository(
provider: SourceControlProviderKind?, repository: String?, remoteURL: String?,
destinationPath: String, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlCloneRepositoryResult
/// Initialize a Git repository at an existing host-local folder.
func initializeRepository(path: String) async throws
/// Create a provider repository, attach it as a remote, and push when commits exist.
func publishRepository(
path: String, provider: SourceControlProviderKind, repository: String,
visibility: SourceControlRepositoryVisibility, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlPublishRepositoryResult

/// Start (or keep) a live VCS status subscription for a thread's
/// workspace; status arrives via `.vcsStatusChanged` events.
Expand Down Expand Up @@ -231,6 +243,27 @@ public extension BackendService {
/// Default for conformers without one-shot VCS refresh (test fakes).
func refreshVcsStatus(threadID: String) async throws {}

func cloneRepository(
provider: SourceControlProviderKind?, repository: String?, remoteURL: String?,
destinationPath: String, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlCloneRepositoryResult {
throw BackendServiceError.unsupportedOperation(
"Repository cloning is unavailable on this connection.")
}

func initializeRepository(path: String) async throws {
throw BackendServiceError.unsupportedOperation(
"Repository initialization is unavailable on this connection.")
}

func publishRepository(
path: String, provider: SourceControlProviderKind, repository: String,
visibility: SourceControlRepositoryVisibility, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlPublishRepositoryResult {
throw BackendServiceError.unsupportedOperation(
"Repository publishing is unavailable on this connection.")
}

}

/// One page of archived threads as returned by `archivedThreadsPage`.
Expand Down
31 changes: 31 additions & 0 deletions apps/mac/Sources/SurgeCodeMac/Model/LiveBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2922,6 +2922,37 @@ public actor LiveBackend: BackendService {
}
}

public func cloneRepository(
provider: SourceControlProviderKind?, repository: String?, remoteURL: String?,
destinationPath: String, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlCloneRepositoryResult {
guard let client = currentClient else { throw LiveBackendError.notConnected }
return try await client.cloneRepository(
provider: provider,
repository: repository,
remoteURL: remoteURL,
destinationPath: destinationPath,
protocol: cloneProtocol)
}

public func initializeRepository(path: String) async throws {
guard let client = currentClient else { throw LiveBackendError.notConnected }
try await client.initializeRepository(cwd: path)
}

public func publishRepository(
path: String, provider: SourceControlProviderKind, repository: String,
visibility: SourceControlRepositoryVisibility, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlPublishRepositoryResult {
guard let client = currentClient else { throw LiveBackendError.notConnected }
return try await client.publishRepository(
cwd: path,
provider: provider,
repository: repository,
visibility: visibility,
protocol: cloneProtocol)
}

// MARK: - BackendService: git / VCS

/// Live status subscriptions keyed by threadID; re-established on demand
Expand Down
41 changes: 41 additions & 0 deletions apps/mac/Sources/SurgeCodeMac/Model/MockBackend.swift
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,47 @@ public final class MockBackend: BackendService, @unchecked Sendable {
public func deleteProject(id: String) async throws {
await state.deleteProject(id: id)
}

public func cloneRepository(
provider: SourceControlProviderKind?, repository: String?, remoteURL: String?,
destinationPath: String, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlCloneRepositoryResult {
let resolvedURL =
remoteURL
?? "https://example.com/\(repository ?? "repository").git"
return SourceControlCloneRepositoryResult(
cwd: destinationPath,
remoteUrl: resolvedURL,
repository: repository.map {
SourceControlRepositoryInfo(
provider: provider?.rawValue ?? "unknown",
nameWithOwner: $0,
url: resolvedURL,
sshUrl: "git@example.com:\($0).git")
})
}

public func initializeRepository(path: String) async throws {
_ = path
}

public func publishRepository(
path: String, provider: SourceControlProviderKind, repository: String,
visibility: SourceControlRepositoryVisibility, protocol cloneProtocol: SourceControlCloneProtocol?
) async throws -> SourceControlPublishRepositoryResult {
_ = (path, visibility, cloneProtocol)
let url = "https://example.com/\(repository)"
return SourceControlPublishRepositoryResult(
repository: SourceControlRepositoryInfo(
provider: provider.rawValue,
nameWithOwner: repository,
url: url,
sshUrl: "git@example.com:\(repository).git"),
remoteName: "origin",
remoteUrl: url,
branch: "main",
status: "remote_added")
}
}

// MARK: - Actor-isolated mutable state + demo data
Expand Down
32 changes: 32 additions & 0 deletions apps/mac/Sources/SurgeCodeMac/Model/ProjectOnboarding.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import Foundation
import T3Kit

public enum ProjectOnboardingRequest: Sendable, Equatable {
case importFolder(path: String)
case cloneRepository(
provider: SourceControlProviderKind?,
repository: String?,
remoteURL: String?,
destinationPath: String,
protocol: SourceControlCloneProtocol
)
case initializeRepository(path: String)
case publishFolder(
path: String,
provider: SourceControlProviderKind,
repository: String,
visibility: SourceControlRepositoryVisibility,
protocol: SourceControlCloneProtocol
)

public var projectPath: String {
switch self {
case .importFolder(let path), .initializeRepository(let path):
path
case .cloneRepository(_, _, _, let destinationPath, _):
destinationPath
case .publishFolder(let path, _, _, _, _):
path
}
}
}
Loading
Loading