diff --git a/Marksman/GitIgnore.fs b/Marksman/GitIgnore.fs index d0672de3..a3e76a00 100644 --- a/Marksman/GitIgnore.fs +++ b/Marksman/GitIgnore.fs @@ -40,7 +40,17 @@ let mkGlobPattern (pat: string) : array = [||] else if pat.StartsWith("!") then let pat = pat.Substring(1) - patternToGlob pat |> Array.map Include + let isDir = pat.EndsWith("/") + if isDir then + let dirPat = if pat.StartsWith("/") then pat.Substring(1, pat.Length - 2) else pat.Substring(0, pat.Length - 1) + let dirPat = if pat.IndexOf('/') = pat.Length - 1 then "**/" + dirPat else dirPat + try + [| Include (GlobExpressions.Glob(dirPat, GlobExpressions.GlobOptions.Compiled)) |] + with :? GlobExpressions.GlobPatternException -> + logger.warn (Log.setMessage "Unsupported glob pattern" >> Log.addContext "pat" pat) + [||] + else + patternToGlob pat |> Array.map Include else patternToGlob pat |> Array.map Exclude @@ -57,17 +67,14 @@ module GlobMatcher = let ignores (matcher: GlobMatcher) (path: string) : bool = let relPath = Path.GetRelativePath(matcher.root, path) - - let checkGlob g = - match g with - | Include glob -> if glob.IsMatch(relPath) then Some false else None - | Exclude glob -> if glob.IsMatch(relPath) then Some true else None - - - match matcher.patterns |> Seq.map checkGlob |> Seq.tryFind Option.isSome with + let mutable lastMatch : option = None + for pat in matcher.patterns do + match pat with + | Include glob -> if glob.IsMatch(relPath) then lastMatch <- Some false + | Exclude glob -> if glob.IsMatch(relPath) then lastMatch <- Some true + match lastMatch with + | Some r -> r | None -> false - | Some(Some r) -> r - | Some None -> failwith "Unreachable: GlobMatcher.ignores" let ignoresAny (matchers: seq) (path: string) : bool = Seq.exists (fun m -> ignores m path) matchers diff --git a/Tests/GitIgnoreTest.fs b/Tests/GitIgnoreTest.fs index d937681f..5fd387ba 100644 --- a/Tests/GitIgnoreTest.fs +++ b/Tests/GitIgnoreTest.fs @@ -134,3 +134,24 @@ let issue_218 () = GlobMatcher.ignores glob "zap.fooo" |> Assert.False // TN GlobMatcher.ignores glob "zap.foos" |> Assert.False + +[] +let issue_428 () = + let root = "/Users/john/notes" + let patterns = + [| "a/**" + "!a/b/" + "!a/b/c/" + "!a/b/c/**" + "d/**" |] + let glob = GlobMatcher.mk root patterns + let ignored_1 = "/Users/john/notes/a/private/file.md" + let ignored_2 = "/Users/john/notes/a/file.md" + let ignored_3 = "/Users/john/notes/a/b/file.md" + let ignored_4 = "/Users/john/notes/d/file.md" + let notIgnored = "/Users/john/notes/a/b/c/file.md" + Assert.True(GlobMatcher.ignores glob ignored_1) + Assert.True(GlobMatcher.ignores glob ignored_2) + Assert.True(GlobMatcher.ignores glob ignored_3) + Assert.True(GlobMatcher.ignores glob ignored_4) + Assert.False(GlobMatcher.ignores glob notIgnored)