Skip to content

Commit c4de1e7

Browse files
authored
Don't use matcher from a .gitignore against includePatterns (#1297)
* Don't use matcher from a .gitignore against includePatterns When attempting to determine whether a .gitignore should be respected, we previously tried to see, whether any of the patterns in the .gitignore match against the includePatterns. As @fsoikin noticed [here](https://github.com/purescript/spago/pull/1296/files/b84981dd086219bc1157a440667aca92ea23efb4#r1815996597) this does not really make any sense in case the includePatterns really are actual patterns and not just paths. Therefore, instead of matching the .gitignore patterns against the includePatterns directly, we now only match them against the base of each includePattern. The base of a pattern is always a path. However, some includePatterns may not have a base, for example when searching for `**/foo/bar`. What should one do here? I think disrespecting .gitignores is not really feasible in such a case, because how can we tell if something that the .gitignore excludes could match the baseless pattern `**/foo/bar` without actually walking the excluded directories? So for example, if there was a file: `./abc/foo` and a .gitignore with the pattern `abc` and we glob for `**/foo`, then we do not find `./abc/foo`, because the .gitignore was respected. I think, this is the behaviour we would want anyway, we only ignore a .gitignore, when it is obvious that the glob target is necessarily within an ignored directory. Like globbing for `.spago/p/arrays-7.3.0/src/**/*.purs` when `.spago` is gitignored. This was already the behaviour previously. In the above example, we would have previously matched `abc` against `**/foo`, which would be false and therefore no conflict would have been dectected, so the .gitignore would also have been respected. This PR only makes this behaviour explicit and removes the, incidentally working, match of a pattern against another pattern. The new test also passes without this change, but i think it's a nice kind of documentation. I wonder, whether the globbing code should even attempt to disrespect .gitignore files, doesn't the calling code always know, whether it should limit its search to the non-ignored files? For example, when globbing for the source files of a package in .spago, we know beforehand, that a nonGitignoringGlob (otherwise known as `glob` :^D) would suffice. * style: rearrange let-bound vals, use guard, don't `or` twice, use plural
1 parent b13857d commit c4de1e7

File tree

2 files changed

+27
-12
lines changed

2 files changed

+27
-12
lines changed

src/Spago/Glob.purs

Lines changed: 22 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import Control.Monad.Maybe.Trans (runMaybeT)
1212
import Control.Monad.Trans.Class (lift)
1313
import Data.Array as Array
1414
import Data.Filterable (filter)
15-
import Data.Foldable (any, traverse_)
15+
import Data.Foldable (all, any, traverse_)
1616
import Data.String as String
1717
import Data.String as String.CodePoint
1818
import Effect.Aff as Aff
@@ -101,6 +101,15 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do
101101
canceled <- Ref.new false
102102

103103
let
104+
-- The base of every includePattern
105+
-- The base of a pattern is its longest non-glob prefix.
106+
-- For example: foo/bar/*/*.purs => foo/bar
107+
-- **/spago.yaml => ""
108+
includePatternBases :: Array String
109+
includePatternBases = map (_.base <<< scanPattern) includePatterns
110+
111+
allIncludePatternsHaveBase = all (not <<< String.null) includePatternBases
112+
104113
-- Update the ignoreMatcherRef with the patterns from a .gitignore file
105114
updateIgnoreMatcherWithGitignore :: Entry -> Effect Unit
106115
updateIgnoreMatcherWithGitignore entry = do
@@ -119,9 +128,17 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do
119128
-- ex. if `includePatterns` is [".spago/p/aff-1.0.0/**/*.purs"],
120129
-- and `gitignored` is ["node_modules", ".spago"],
121130
-- then add "node_modules" to `ignoreMatcher` but not ".spago"
122-
wouldConflictWithSearch matcher = any matcher includePatterns
123-
124-
newMatchers = or $ filter (not <<< wouldConflictWithSearch) gitignored
131+
wouldConflictWithSearch matcher = any matcher includePatternBases
132+
133+
newMatchers :: Array (String -> Boolean)
134+
newMatchers | allIncludePatternsHaveBase = filter (not <<< wouldConflictWithSearch) gitignored
135+
newMatchers = do
136+
-- Some of the include patterns don't have a base,
137+
-- e.g. there is an include pattern like "*/foo/bar" or "**/.spago".
138+
-- In this case, do not attempt to determine whether the gitignore
139+
-- file would exclude some of the target paths. Instead always respect
140+
-- the .gitignore.
141+
gitignored
125142

126143
-- Another possible approach could be to keep a growing array of patterns and
127144
-- regenerate the matcher on every gitignore. We have tried that (see #1234),
@@ -133,17 +150,10 @@ fsWalk cwd ignorePatterns includePatterns = Aff.makeAff \cb -> do
133150
-- new matchers together, then the whole thing with the previous matcher.
134151
-- This is still prone to stack issues, but we now have a tree so it should
135152
-- not be as dramatic.
136-
addMatcher currentMatcher = or [ currentMatcher, newMatchers ]
153+
addMatcher currentMatcher = or $ Array.cons currentMatcher newMatchers
137154

138155
Ref.modify_ addMatcher ignoreMatcherRef
139156

140-
-- The base of every includePattern
141-
-- The base of a pattern is its longest non-glob prefix.
142-
-- For example: foo/bar/*/*.purs => foo/bar
143-
-- **/spago.yaml => ""
144-
includePatternBases :: Array String
145-
includePatternBases = map (_.base <<< scanPattern) includePatterns
146-
147157
matchesAnyPatternBase :: String -> Boolean
148158
matchesAnyPatternBase relDirPath = any matchesPatternBase includePatternBases
149159
where

test/Spago/Glob.purs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -103,3 +103,8 @@ spec = Spec.around globTmpDir do
103103
FS.writeTextFile (Path.concat [ p, "fruits", "right", ".gitignore" ]) hugeGitignore
104104
a <- Glob.gitignoringGlob p [ "fruits/**/apple" ]
105105
Array.sort a `Assert.shouldEqual` [ "fruits/left/apple", "fruits/right/apple" ]
106+
107+
Spec.it "does respect .gitignore even though it might conflict with a search path without base" $ \p -> do
108+
FS.writeTextFile (Path.concat [ p, ".gitignore" ]) "fruits"
109+
a <- Glob.gitignoringGlob p [ "**/apple" ]
110+
Array.sort a `Assert.shouldEqual` []

0 commit comments

Comments
 (0)