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
2 changes: 1 addition & 1 deletion Sources/DependencyCalculator/PackageMetadata.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ struct PackageTargetMetadata: Sendable {
flags.append("--ignore-lock")
}

let manifest = try Shell.execOrFail("(cd \(path) && swift package dump-package \(flags.joined(separator: " ")))")
let manifest = try Shell.execOrFail("(cd \(path.string.shellQuoted) && swift package dump-package \(flags.joined(separator: " ")))")
.trimmingCharacters(in: .newlines)
guard let manifestData = manifest.data(using: .utf8),
let manifestJson = try JSONSerialization.jsonObject(with: manifestData, options: []) as? [String: Any],
Expand Down
6 changes: 3 additions & 3 deletions Sources/Git/Git+Changeset.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public extension Git {
func changeset(baseBranch: String, verbose: Bool = false) throws -> Set<Path> {
let gitRoot = try repoRoot()

var currentBranch = try Shell.execOrFail("(cd \(gitRoot) && git branch --show-current)").trimmingCharacters(in: .newlines)
var currentBranch = try Shell.execOrFail("(cd \(gitRoot.string.shellQuoted) && git branch --show-current)").trimmingCharacters(in: .newlines)
if verbose {
logger.info("Current branch: \(currentBranch)")
logger.info("Base branch: \(baseBranch)")
Expand All @@ -25,7 +25,7 @@ public extension Git {
currentBranch = "HEAD"
}

let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff '\(baseBranch)'..'\(currentBranch)' --name-only)")
let changes = try Shell.execOrFail("(cd \(gitRoot.string.shellQuoted) && git diff \(baseBranch.shellQuoted)..\(currentBranch.shellQuoted) --name-only)")
let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines)

guard !changesTrimmed.isEmpty else {
Expand All @@ -38,7 +38,7 @@ public extension Git {
func localChangeset() throws -> Set<Path> {
let gitRoot = try repoRoot()

let changes = try Shell.execOrFail("(cd \(gitRoot) && git diff HEAD --name-only)")
let changes = try Shell.execOrFail("(cd \(gitRoot.string.shellQuoted) && git diff HEAD --name-only)")

let changesTrimmed = changes.trimmingCharacters(in: .whitespacesAndNewlines)

Expand Down
4 changes: 2 additions & 2 deletions Sources/Git/Git.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,15 +14,15 @@ public struct Git {
}

public func repoRoot() throws -> Path {
let gitPath = try Shell.execOrFail("(cd \(path) && git rev-parse --show-toplevel)").trimmingCharacters(in: .newlines)
let gitPath = try Shell.execOrFail("(cd \(path.string.shellQuoted) && git rev-parse --show-toplevel)").trimmingCharacters(in: .newlines)

return Path(gitPath).absolute()
}

public func find(pattern: String) throws -> Set<Path> {
let gitRoot = try repoRoot()

let result = try Shell.exec("(cd \(gitRoot) && git ls-files | grep \(pattern))").0.trimmingCharacters(in: .newlines)
let result = try Shell.exec("(cd \(gitRoot.string.shellQuoted) && git ls-files | grep \(pattern.shellQuoted))").0.trimmingCharacters(in: .newlines)

guard !result.isEmpty else {
return Set()
Expand Down
13 changes: 13 additions & 0 deletions Sources/SelectiveTestShell/String+Shell.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// Created by Mike Gerasymenko <mike@gera.cx>
//

import Foundation

public extension String {
/// Wrap string in single quotes for safe usage in shell commands.
var shellQuoted: String {
guard !isEmpty else { return "''" }
return "'" + self.replacingOccurrences(of: "'", with: "'\"'\"'") + "'"
}
}
35 changes: 35 additions & 0 deletions Tests/SelectiveTestingTests/SelectiveTestingProjectTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Created by Mike Gerasymenko <mike@gera.cx>
//

import Foundation
import PathKit
@testable import SelectiveTestingCore
import SelectiveTestShell
Expand Down Expand Up @@ -33,6 +34,40 @@ struct SelectiveTestingProjectTests {
]))
}

@Test
func projectBasePathWithSpaces() async throws {
// given
let testTool = try IntegrationTestTool()
defer { try? testTool.tearDown() }

let projectRoot = testTool.projectPath.string.shellQuoted
let originalName = "ExampleProject.xcodeproj"
let spacedName = "Example Project.xcodeproj"
try Shell.execOrFail("(cd \(projectRoot) && git checkout main)")
try Shell.execOrFail("(cd \(projectRoot) && git mv \(originalName.shellQuoted) \(spacedName.shellQuoted))")
try Shell.execOrFail("(cd \(projectRoot) && git commit -m 'Rename project with spaces')")
try Shell.execOrFail("(cd \(projectRoot) && git checkout feature)")
try Shell.execOrFail("(cd \(projectRoot) && git merge main)")

let renamedProject = testTool.projectPath + spacedName
let tool = try testTool.createSUT(config: nil,
basePath: renamedProject)

// when
try testTool.changeFile(at: renamedProject + "project.pbxproj")

// then
let result = try await tool.run()
let expectedTargets: Set<TargetIdentity> = Set([
TargetIdentity.project(path: renamedProject, targetName: "ExampleProject", testTarget: false),
TargetIdentity.project(path: renamedProject, targetName: "ExampleProjectTests", testTarget: true),
TargetIdentity.project(path: renamedProject, targetName: "ExampleProjectUITests", testTarget: true),
TargetIdentity.project(path: renamedProject, targetName: "ExmapleTargetLibrary", testTarget: false),
TargetIdentity.project(path: renamedProject, targetName: "ExmapleTargetLibraryTests", testTarget: true),
])
#expect(result == expectedTargets)
}

@Test
func projectDeepGroupPathChange_turbo() async throws {
// given
Expand Down