Skip to content
Draft
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
3 changes: 2 additions & 1 deletion CodeApp/Containers/SourceControlContainer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ struct SourceControlContainer: View {
guard let serviceProvider = App.workSpaceStorage.gitServiceProvider else {
throw SourceControlError.gitServiceProviderUnavailable
}
guard let gitURL = URL(string: urlString) else {
let normalizedURLString = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)
guard let gitURL = URL(string: normalizedURLString) else {
App.notificationManager.showErrorMessage("errors.source_control.invalid_url")
throw SourceControlError.invalidURL
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -204,12 +204,43 @@ final class LocalGitCredentialsHelper {
}

func credentialsForRemote(remote: Remote) throws -> Credentials {
guard let url = URL(string: remote.URL) else {
let normalizedURL = LocalGitCredentialsHelper.normalizeRemoteURL(remote.URL)
guard let url = URL(string: normalizedURL) else {
throw HelperError.UnsupportedRemoteURL
}
return try credentialsForRemoteURL(url: url)
}

/// Normalizes a Git remote URL string to ensure proper parsing
/// Handles bare IP:port and hostname:port formats by adding http:// scheme
/// Preserves scp-like syntax (git@host:path) and fully-qualified URLs
static func normalizeRemoteURL(_ urlString: String) -> String {
// If URL already has a valid scheme and parses correctly, return as-is
if let url = URL(string: urlString),
let scheme = url.scheme,
["http", "https", "ssh", "git", "file", "ftp", "ftps"].contains(scheme),
url.host != nil {
return urlString
}

// Check if it's an scp-like URL (has @ but no ://)
// e.g., git@github.com:user/repo.git
if urlString.contains("@") && !urlString.contains("://") {
return urlString // Let parseRemoteURL handle it
}

// Check if it looks like bare IP:port or hostname:port
// Pattern matches: 192.1.1.1:3000/path or forgejo.local:3000/path
let pattern = #"^([a-zA-Z0-9\.\-]+):(\d+)(/.*)?$"#
if let regex = try? NSRegularExpression(pattern: pattern),
regex.firstMatch(in: urlString, range: NSRange(urlString.startIndex..., in: urlString)) != nil {
// Prepend http:// as default scheme
return "http://\(urlString)"
}

return urlString
}

static func parseRemoteURL(url: URL) -> URL? {
if url.scheme == nil {
// Handle scp-like syntax urls.
Expand Down
44 changes: 44 additions & 0 deletions CodeUITests/CodeUITests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -80,4 +80,48 @@ final class CodeUITests: XCTestCase {
XCTAssertEqual(parsed!.scheme, "ssh")
XCTAssertEqual(parsed!.path, "/codeapp.git")
}

func testNormalizeRemoteURL_bareIPWithPort() throws {
let urlString = "192.1.1.1:3000/repo.git"
let normalized = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)

XCTAssertEqual(normalized, "http://192.1.1.1:3000/repo.git")
XCTAssertNotNil(URL(string: normalized))
XCTAssertEqual(URL(string: normalized)!.host, "192.1.1.1")
XCTAssertEqual(URL(string: normalized)!.port, 3000)
}

func testNormalizeRemoteURL_hostnameWithPort() throws {
let urlString = "forgejo.local:3000/user/repo.git"
let normalized = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)

XCTAssertEqual(normalized, "http://forgejo.local:3000/user/repo.git")
XCTAssertNotNil(URL(string: normalized))
XCTAssertEqual(URL(string: normalized)!.host, "forgejo.local")
XCTAssertEqual(URL(string: normalized)!.port, 3000)
}

func testNormalizeRemoteURL_scpLikeSyntax() throws {
let urlString = "git@github.com:user/repo.git"
let normalized = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)

// Should not be modified - let parseRemoteURL handle it
XCTAssertEqual(normalized, urlString)
}

func testNormalizeRemoteURL_fullyQualifiedHTTPS() throws {
let urlString = "https://github.com/user/repo.git"
let normalized = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)

// Should not be modified - already valid
XCTAssertEqual(normalized, urlString)
}

func testNormalizeRemoteURL_fullyQualifiedHTTP() throws {
let urlString = "http://192.1.1.1:3000/repo.git"
let normalized = LocalGitCredentialsHelper.normalizeRemoteURL(urlString)

// Should not be modified - already valid
XCTAssertEqual(normalized, urlString)
}
}