-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Add isGitIgnored method for checking if a file is ignored by git #1613
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
c1184a2
9881587
7ee2d12
c24b081
970d0c0
c75b92f
a79e038
859f58e
dd335c5
9d037b4
118b505
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -11,6 +11,7 @@ | |
import Basic | ||
import Dispatch | ||
import Utility | ||
import struct Foundation.Data | ||
|
||
public enum GitRepositoryProviderError: Swift.Error { | ||
case gitCloneFailure(errorOutput: String) | ||
|
@@ -92,6 +93,13 @@ public class GitRepositoryProvider: RepositoryProvider { | |
} | ||
} | ||
|
||
public func checkoutExists(at path: AbsolutePath) throws -> Bool { | ||
precondition(exists(path)) | ||
|
||
let result = try Process.popen(args: Git.tool, "-C", path.asString, "rev-parse", "--is-bare-repository") | ||
return try result.exitStatus == .terminated(code: 0) && result.utf8Output().chomp() == "false" | ||
} | ||
|
||
public func openCheckout(at path: AbsolutePath) throws -> WorkingCheckout { | ||
return GitRepository(path: path) | ||
} | ||
|
@@ -100,6 +108,9 @@ public class GitRepositoryProvider: RepositoryProvider { | |
enum GitInterfaceError: Swift.Error { | ||
/// This indicates a problem communicating with the `git` tool. | ||
case malformedResponse(String) | ||
|
||
/// This indicates that a fatal error was encountered | ||
case fatalError | ||
} | ||
|
||
/// A basic `git` repository. This class is thread safe. | ||
|
@@ -387,6 +398,33 @@ public class GitRepository: Repository, WorkingCheckout { | |
return localFileSystem.isDirectory(AbsolutePath(firstLine)) | ||
} | ||
|
||
/// Returns true if the file at `path` is ignored by `git` | ||
public func isGitIgnored(_ paths: [AbsolutePath]) throws -> [Bool] { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How about |
||
return try queue.sync { | ||
let stringPaths = paths.map({ $0.asString }) | ||
let pathsFileContent = stringPaths.joined(separator: "\0") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It'll be better for performance if you append to the stream directly: try localFileSystem.writeFileContents(pathsFile.path) {
for path in paths {
$0 <<< path.asString <<< "\0"
}
} |
||
|
||
let pathsFile = try TemporaryFile() | ||
let pathsData = Data(pathsFileContent.utf8) | ||
pathsFile.fileHandle.write(pathsData) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should use BufferedOutputByteStream and localFileSystem to APIs to write to the temp file. |
||
|
||
let args = [Git.tool, "-C", self.path.asString, "check-ignore", "-z", "--stdin", "<", "\(pathsFile.path.asString)"] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to shell quotes the arguments here as we'll be using There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should have a test for this. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We need to shell escape the args here, we should also have a test for it. |
||
let argsWithSh = ["sh", "-c", args.joined(separator: " ")] | ||
let result = try Process.popen(arguments: argsWithSh) | ||
let output = try result.output.dematerialize() | ||
|
||
let outputs: [String] = output.split(separator: 0).map(Array.init).map({ (bytes: [Int8]) -> String in | ||
return String(cString: bytes + [0]) | ||
}) | ||
|
||
guard result.exitStatus == .terminated(code: 0) || result.exitStatus == .terminated(code: 1) else { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The documentation says check-ignore will return 1 when "None of the provided paths are ignored". We shouldn't throw fatal in that case, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This doesn't throw on exit code 1. Only on exit codes different than 0 or 1. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. oh ya, right! |
||
throw GitInterfaceError.fatalError | ||
} | ||
|
||
return stringPaths.map(outputs.contains) | ||
} | ||
} | ||
|
||
// MARK: Git Operations | ||
|
||
/// Resolve a "treeish" to a concrete hash. | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't we return false if the path doesn't exist?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could return false, but I think that the purpose of this method is to determine if the checkout exists, not the path itself, so in my opinion, it's more like a precondition.