Skip to content

Commit 81040c9

Browse files
committed
Use git check-ignore for filtering files, check if a checkout exists
before opening it
1 parent 135d60e commit 81040c9

File tree

8 files changed

+62
-20
lines changed

8 files changed

+62
-20
lines changed

Sources/SourceControl/GitRepository.swift

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,13 @@ public class GitRepositoryProvider: RepositoryProvider {
9292
}
9393
}
9494

95+
public func checkoutExists(at path: AbsolutePath) throws -> Bool {
96+
precondition(exists(path))
97+
98+
let result = try Process.popen(args: Git.tool, "rev-parse", "--is-bare-repository")
99+
return try result.exitStatus == .terminated(code: 0) && result.utf8Output().chomp() == "false"
100+
}
101+
95102
public func openCheckout(at path: AbsolutePath) throws -> WorkingCheckout {
96103
return GitRepository(path: path)
97104
}
@@ -100,6 +107,9 @@ public class GitRepositoryProvider: RepositoryProvider {
100107
enum GitInterfaceError: Swift.Error {
101108
/// This indicates a problem communicating with the `git` tool.
102109
case malformedResponse(String)
110+
111+
/// This indicates that a fatal error was encountered
112+
case fatalError
103113
}
104114

105115
/// A basic `git` repository. This class is thread safe.
@@ -391,13 +401,19 @@ public class GitRepository: Repository, WorkingCheckout {
391401
return localFileSystem.isDirectory(AbsolutePath(firstLine))
392402
}
393403

394-
public func getIgnoredFiles() -> [AbsolutePath] {
395-
return queue.sync {
396-
let result = try? Process.checkNonZeroExit(
397-
args: Git.tool, "-C", path.asString, "ls-files", "-o", "-i", "--exclude-standard").chomp()
398-
return result?.split(separator: "\n").map(String.init).map {
399-
return path.appending(RelativePath($0))
400-
} ?? []
404+
/// Returns true if the file at `path` is ignored by `git`
405+
public func isIgnored(_ path: AbsolutePath) throws -> Bool {
406+
return try queue.sync {
407+
let result = try Process.popen(args: Git.tool, "check-ignore", path.asString)
408+
409+
switch result.exitStatus {
410+
case .terminated(code: 0):
411+
return true
412+
case .terminated(code: 1):
413+
return false
414+
default:
415+
throw GitInterfaceError.fatalError
416+
}
401417
}
402418
}
403419

Sources/SourceControl/InMemoryGitRepository.swift

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -269,8 +269,8 @@ extension InMemoryGitRepository: WorkingCheckout {
269269
return true
270270
}
271271

272-
public func getIgnoredFiles() -> [AbsolutePath] {
273-
return []
272+
public func isIgnored(_ path: AbsolutePath) throws -> Bool {
273+
return false
274274
}
275275
}
276276

@@ -323,6 +323,10 @@ public final class InMemoryGitRepositoryProvider: RepositoryProvider {
323323
try checkout.installHead()
324324
}
325325

326+
public func checkoutExists(at path: AbsolutePath) throws -> Bool {
327+
return checkoutsMap.keys.contains(path)
328+
}
329+
326330
public func openCheckout(at path: AbsolutePath) throws -> WorkingCheckout {
327331
return checkoutsMap[path]!
328332
}

Sources/SourceControl/Repository.swift

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,9 @@ public protocol RepositoryProvider {
9898
to destinationPath: AbsolutePath,
9999
editable: Bool) throws
100100

101+
/// Returns true if a working repository exists at `path`
102+
func checkoutExists(at path: AbsolutePath) throws -> Bool
103+
101104
/// Open a working repository copy.
102105
///
103106
/// - Parameters:
@@ -204,8 +207,8 @@ public protocol WorkingCheckout {
204207
/// Returns true if there is an alternative store in the checkout and it is valid.
205208
func isAlternateObjectStoreValid() -> Bool
206209

207-
/// Returns the `AbsolutePath` of the files ignored by git.
208-
func getIgnoredFiles() -> [AbsolutePath]
210+
/// Returns true if the file at `path` is ignored by `git`
211+
func isIgnored(_ path: AbsolutePath) throws -> Bool
209212
}
210213

211214
/// A single repository revision.

Sources/Xcodeproj/generate().swift

Lines changed: 17 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -74,15 +74,23 @@ public func generate(
7474
// Find non-source files in the source directories and root that should be added
7575
// as a reference to the project.
7676
var extraFiles = try findNonSourceFiles(path: srcroot)
77-
let sourcesExtraFiles = try findNonSourceFiles(path: srcroot.appending(component: "Sources"), recursively: true)
78-
extraFiles.append(contentsOf: sourcesExtraFiles)
79-
let testsExtraFiles = try findNonSourceFiles(path: srcroot.appending(component: "Tests"), recursively: true)
80-
extraFiles.append(contentsOf: testsExtraFiles)
81-
82-
// Get the ignored files and exclude them
83-
let ignoredFiles = try repositoryProvider.openCheckout(at: srcroot).getIgnoredFiles()
84-
extraFiles = extraFiles.filter {
85-
return !ignoredFiles.contains($0)
77+
78+
let sourcesDirectory = srcroot.appending(component: "Sources")
79+
if localFileSystem.isDirectory(sourcesDirectory) {
80+
let sourcesExtraFiles = try findNonSourceFiles(path: sourcesDirectory, recursively: true)
81+
extraFiles.append(contentsOf: sourcesExtraFiles)
82+
}
83+
84+
let testsDirectory = srcroot.appending(component: "Tests")
85+
if localFileSystem.isDirectory(testsDirectory) {
86+
let testsExtraFiles = try findNonSourceFiles(path: testsDirectory, recursively: true)
87+
extraFiles.append(contentsOf: testsExtraFiles)
88+
}
89+
90+
if try repositoryProvider.checkoutExists(at: srcroot) {
91+
// Get the ignored files and exclude them
92+
let repository = try repositoryProvider.openCheckout(at: srcroot)
93+
extraFiles = try extraFiles.filter { try !repository.isIgnored($0) }
8694
}
8795

8896
/// Generate the contents of project.xcodeproj (inside the .xcodeproj).

Sources/Xcodeproj/pbxproj().swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@ func xcodeProject(
297297
let name = (sourcesGroup == nil ? groupName : target.name)
298298
let group = (sourcesGroup ?? parentGroup)
299299
.addGroup(path: (path == "." ? "" : path), pathBase: .projectDir, name: name)
300+
300301
// Associate the group with the target's root path.
301302
srcPathsToGroups[target.sources.root] = group
302303
}

Tests/PackageGraphTests/RepositoryPackageContainerProviderTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,10 @@ private class MockRepositories: RepositoryProvider {
9797
// No-op.
9898
assert(repositories.index(forKey: repository.url) != nil)
9999
}
100+
101+
func checkoutExists(at path: AbsolutePath) throws -> Bool {
102+
return false
103+
}
100104

101105
func open(repository: RepositorySpecifier, at path: AbsolutePath) throws -> Repository {
102106
return repositories[repository.url]!

Tests/SourceControlTests/InMemoryGitRepositoryTests.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,7 +100,9 @@ class InMemoryGitRepositoryTests: XCTestCase {
100100
XCTAssert(fooRepo.exists(revision: try fooRepo.resolveRevision(tag: v1)))
101101

102102
let fooCheckoutPath = AbsolutePath("/fooCheckout")
103+
XCTAssertFalse(try provider.checkoutExists(at: fooCheckoutPath))
103104
try provider.cloneCheckout(repository: specifier, at: fooRepoPath, to: fooCheckoutPath, editable: false)
105+
XCTAssertTrue(try provider.checkoutExists(at: fooCheckoutPath))
104106
let fooCheckout = try provider.openCheckout(at: fooCheckoutPath)
105107

106108
XCTAssertEqual(fooCheckout.tags.sorted(), [v1, v2])

Tests/SourceControlTests/RepositoryManagerTests.swift

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,10 @@ private class DummyRepositoryProvider: RepositoryProvider {
9595
try localFileSystem.writeFileContents(destinationPath.appending(component: "README.txt"), bytes: "Hi")
9696
}
9797

98+
func checkoutExists(at path: AbsolutePath) throws -> Bool {
99+
return false
100+
}
101+
98102
func openCheckout(at path: AbsolutePath) throws -> WorkingCheckout {
99103
fatalError("unsupported")
100104
}

0 commit comments

Comments
 (0)