diff --git a/Sources/Basic/FileSystem.swift b/Sources/Basic/FileSystem.swift index 5c1810ae58f..acf237d701c 100644 --- a/Sources/Basic/FileSystem.swift +++ b/Sources/Basic/FileSystem.swift @@ -115,7 +115,7 @@ public enum FileMode { // FIXME: Design an asynchronous story? public protocol FileSystem: class { /// Check whether the given path exists and is accessible. - func exists(_ path: AbsolutePath) -> Bool + func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool /// Check whether the given path is accessible and a directory. func isDirectory(_ path: AbsolutePath) -> Bool @@ -168,6 +168,11 @@ public protocol FileSystem: class { /// Convenience implementations (default arguments aren't permitted in protocol /// methods). public extension FileSystem { + /// exists override with default value. + func exists(_ path: AbsolutePath) -> Bool { + return exists(path, followSymlink: true) + } + /// Default implementation of createDirectory(_:) func createDirectory(_ path: AbsolutePath) throws { try createDirectory(path, recursive: false) @@ -197,8 +202,8 @@ private class LocalFileSystem: FileSystem { return filestat.st_mode & libc.S_IXUSR != 0 } - func exists(_ path: AbsolutePath) -> Bool { - return Basic.exists(path) + func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool { + return Basic.exists(path, followSymlink: followSymlink) } func isDirectory(_ path: AbsolutePath) -> Bool { @@ -332,7 +337,7 @@ private class LocalFileSystem: FileSystem { } func removeFileTree(_ path: AbsolutePath) throws { - if self.exists(path) { + if self.exists(path, followSymlink: false) { try Basic.removeFileTree(path) } } @@ -509,7 +514,7 @@ public class InMemoryFileSystem: FileSystem { // MARK: FileSystem Implementation - public func exists(_ path: AbsolutePath) -> Bool { + public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool { do { return try getNode(path) != nil } catch { @@ -706,8 +711,8 @@ public class RerootedFileSystemView: FileSystem { // MARK: FileSystem Implementation - public func exists(_ path: AbsolutePath) -> Bool { - return underlyingFileSystem.exists(formUnderlyingPath(path)) + public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool { + return underlyingFileSystem.exists(formUnderlyingPath(path), followSymlink: followSymlink) } public func isDirectory(_ path: AbsolutePath) -> Bool { diff --git a/Sources/Basic/PathShims.swift b/Sources/Basic/PathShims.swift index cbbe6f3314a..a5902be157b 100644 --- a/Sources/Basic/PathShims.swift +++ b/Sources/Basic/PathShims.swift @@ -89,7 +89,7 @@ public func makeDirectories(_ path: AbsolutePath) throws { } /// Recursively deletes the file system entity at `path`. If there is no file system entity at `path`, this function -/// does nothing (in particular, this is not considered to be an error). +/// throws an error. public func removeFileTree(_ path: AbsolutePath) throws { try FileManager.default.removeItem(atPath: path.asString) } diff --git a/Sources/SourceControl/GitRepository.swift b/Sources/SourceControl/GitRepository.swift index d98a06d63d6..d96998771f3 100644 --- a/Sources/SourceControl/GitRepository.swift +++ b/Sources/SourceControl/GitRepository.swift @@ -561,7 +561,7 @@ private class GitFileSystemView: FileSystem { return tree } - func exists(_ path: AbsolutePath) -> Bool { + func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool { do { return try getEntry(path) != nil } catch { diff --git a/Sources/SourceControl/InMemoryGitRepository.swift b/Sources/SourceControl/InMemoryGitRepository.swift index 8c87994101d..a0493e5e3a8 100644 --- a/Sources/SourceControl/InMemoryGitRepository.swift +++ b/Sources/SourceControl/InMemoryGitRepository.swift @@ -183,8 +183,8 @@ public final class InMemoryGitRepository { extension InMemoryGitRepository: FileSystem { - public func exists(_ path: AbsolutePath) -> Bool { - return head.fileSystem.exists(path) + public func exists(_ path: AbsolutePath, followSymlink: Bool) -> Bool { + return head.fileSystem.exists(path, followSymlink: followSymlink) } public func isDirectory(_ path: AbsolutePath) -> Bool { diff --git a/Tests/BasicTests/FileSystemTests.swift b/Tests/BasicTests/FileSystemTests.swift index 64246b1fa45..b0c06191a41 100644 --- a/Tests/BasicTests/FileSystemTests.swift +++ b/Tests/BasicTests/FileSystemTests.swift @@ -71,6 +71,37 @@ class FileSystemTests: XCTestCase { XCTAssertTrue(thisDirectoryContents.contains(where: { $0 == AbsolutePath(#file).basename })) } + func testLocalExistsSymlink() throws { + mktmpdir { path in + let fs = Basic.localFileSystem + + let source = path.appending(component: "source") + let target = path.appending(component: "target") + try fs.writeFileContents(target, bytes: "source") + + // Source and target exist. + + try createSymlink(source, pointingAt: target) + XCTAssertEqual(fs.exists(source), true) + XCTAssertEqual(fs.exists(source, followSymlink: true), true) + XCTAssertEqual(fs.exists(source, followSymlink: false), true) + + // Source only exists. + + try fs.removeFileTree(target) + XCTAssertEqual(fs.exists(source), false) + XCTAssertEqual(fs.exists(source, followSymlink: true), false) + XCTAssertEqual(fs.exists(source, followSymlink: false), true) + + // None exist. + + try fs.removeFileTree(source) + XCTAssertEqual(fs.exists(source), false) + XCTAssertEqual(fs.exists(source, followSymlink: true), false) + XCTAssertEqual(fs.exists(source, followSymlink: false), false) + } + } + func testLocalCreateDirectory() throws { let fs = Basic.localFileSystem @@ -378,6 +409,7 @@ class FileSystemTests: XCTestCase { ("testLocalBasics", testLocalBasics), ("testLocalCreateDirectory", testLocalCreateDirectory), ("testLocalReadWriteFile", testLocalReadWriteFile), + ("testLocalExistsSymlink", testLocalExistsSymlink), ("testInMemoryBasics", testInMemoryBasics), ("testInMemoryCreateDirectory", testInMemoryCreateDirectory), ("testInMemoryFsCopy", testInMemoryFsCopy),