diff --git a/Sources/GitKit/Git.swift b/Sources/GitKit/Git.swift index bb9d22a..25f5e26 100644 --- a/Sources/GitKit/Git.swift +++ b/Sources/GitKit/Git.swift @@ -76,7 +76,7 @@ public final class Git: Shell { case .clone(let url, let dirname): params = [Command.clone.rawValue, url] if let dirName = dirname { - params.append(dirName) + params.append("\"\(dirName)\"") } case .checkout(let branch, let create, let tracking): params = [Command.checkout.rawValue] @@ -281,14 +281,15 @@ public final class Git: Shell { var cmd: [String] = [] // if there is a path let's change directory first if let path = self.path { + let quotedPath = "\"\(path)\"" // try to create work dir at given path for init or clone commands if alias.rawValue.hasPrefix(Command.initialize.rawValue) || alias.rawValue.hasPrefix(Command.clone.rawValue) { - cmd += ["mkdir", "-p", path, "&&"] + cmd += ["mkdir", "-p", quotedPath, "&&"] } - cmd += ["cd", path, "&&"] + cmd += ["cd", quotedPath, "&&"] } cmd += ["git", alias.rawValue] diff --git a/Tests/GitKitTests/GitKitTests.swift b/Tests/GitKitTests/GitKitTests.swift index b066e90..88877e0 100644 --- a/Tests/GitKitTests/GitKitTests.swift +++ b/Tests/GitKitTests/GitKitTests.swift @@ -38,6 +38,8 @@ final class GitKitTests: XCTestCase { ("testCommitVariations", testCommitVariations), ("testLogVariations", testLogVariations), ("testLogWithRevisions", testLogWithRevisions), + ("testPathsWithSpaces", testPathsWithSpaces), + ("testCloneWithSpacesInDirectoryName", testCloneWithSpacesInDirectoryName), ] // MARK: - helpers @@ -586,6 +588,82 @@ final class GitKitTests: XCTestCase { try self.clean(path: path) } + func testPathsWithSpaces() throws { + let basePath = self.currentPath() + let pathWithSpaces = "\(basePath) with spaces" + + try self.clean(path: pathWithSpaces) + + let git = Git(path: pathWithSpaces) + + // Test basic operations in a path with spaces + try git.run(.raw("init")) + try git.run(.raw("config user.name 'Test User'")) + try git.run(.raw("config user.email 'test@example.com'")) + + // Create a file and test git operations + FileManager.default.createFile(atPath: "\(pathWithSpaces)/test file.txt", contents: "test content".data(using: .utf8)) + + try git.run(.addAll) + let statusOutput = try git.run(.status(short: true)) + XCTAssertTrue(statusOutput.contains("A"), "File should be staged") + + try git.run(.commit(message: "test commit with spaces in path", allowEmpty: false)) + + let logOutput = try git.run(.log(numberOfCommits: 1, options: ["--oneline"])) + XCTAssertTrue(logOutput.contains("test commit with spaces in path"), "Commit should be successful") + + // Test status after commit + let cleanStatus = try git.run(.status(short: true)) + XCTAssertTrue(cleanStatus.isEmpty, "Working directory should be clean after commit") + + try self.clean(path: pathWithSpaces) + } + + func testCloneWithSpacesInDirectoryName() throws { + let basePath = self.currentPath() + let currentDirectory = FileManager.default.currentDirectoryPath + let sourcePath = "\(currentDirectory)/\(basePath)-source" + let clonePath = "\(currentDirectory)/\(basePath) clone path" + let targetDirName = "repo with spaces" + + try self.clean(path: sourcePath) + try self.clean(path: clonePath) + + // Create source repository + let sourceGit = Git(path: sourcePath) + try sourceGit.run(.raw("init")) + try sourceGit.run(.raw("config user.name 'Test User'")) + try sourceGit.run(.raw("config user.email 'test@example.com'")) + try sourceGit.run(.raw("commit -m 'initial commit' --allow-empty --no-gpg-sign")) + + // Test cloning into a path with spaces + let git = Git(path: clonePath) + try git.run(.clone(url: sourcePath, dirName: targetDirName)) + + // Verify the clone was successful + let clonedRepoPath = "\(clonePath)/\(targetDirName)" + XCTAssertTrue(FileManager.default.fileExists(atPath: clonedRepoPath), "Cloned repository should exist") + XCTAssertTrue(FileManager.default.fileExists(atPath: "\(clonedRepoPath)/.git"), "Cloned repository should have .git directory") + + // Test git operations in the cloned repo with spaces in path + let clonedGit = Git(path: clonedRepoPath) + let statusOutput = try clonedGit.run(.status()) + XCTAssertTrue(statusOutput.contains("On branch main"), "Should be on main branch") + XCTAssertTrue(statusOutput.contains("nothing to commit"), "Should be clean working directory") + + // Test creating and committing a file with spaces in the repo path + FileManager.default.createFile(atPath: "\(clonedRepoPath)/file with spaces.txt", contents: "content".data(using: .utf8)) + try clonedGit.run(.addAll) + try clonedGit.run(.commit(message: "add file with spaces")) + + let logOutput = try clonedGit.run(.log(numberOfCommits: 1, options: ["--oneline"])) + XCTAssertTrue(logOutput.contains("add file with spaces"), "New commit should exist") + + try self.clean(path: sourcePath) + try self.clean(path: clonePath) + } + #if os(macOS) func testAsyncRun() throws { let path = self.currentPath()